Contents  1-5  6-11  12-16  17-21  22-27   28-33  34 - 38  39-46  Projects   MFC

   Contact
   C
   C++
   Visual Basic
   Java
   JavaScript
   DHTML
   Style Sheets
   About
   Normalization
   Active X
   TDC Binding
   PHP
   Perl and CGI
   Flash
   XML
   SQL
   Chat
   MCSE
   Linux
   Cabling   
 

   
 
    
    

Note: Updated!  See top of introduction page c1.html for using a C++ .Net compiler for these standard C++ tutorials.

A reference is an alias.  It operates and manipulates memory much the same as a pointer, yet its behavior differs. 
When you create a reference you initialize it with the name of another object, the "target". The reference acts as an
alternative name for the target.  Anything you do to the reference is really done to the target.  Basically, much of what
you do with a pointer can be done with a reference, only without the need of the address of operator and dereferencing.

"A computer lets you make more mistakes faster than any invention in human history - with the possible exceptions of handguns and tequila." - Mitch Radcliffe

"
To err is human, but to really foul things up you need a computer." - Paul Ehrlich

Some would say that references are, in reality, just pointers.  This is incorrect.  References are often implemented using pointers, but that only concerns what is going on behind the scenes in the compiler.  As programmers, we should keep the two ideas of pointers and references distinctly separate.  Pointers are variables that hold the address of another object.  References are aliases to another reference/object.  Pointers can be reassigned, references can not - they are "permanent" aliases to their objects.

To create a reference: Write the type of the target object followed by the reference operator "
&" followed by the name of the reference.  If you have an int variable named AnInteger, you can make a reference to it by writing: int &AReference = AnInteger;

This is read as "AReference is a reference to an integer that is initialized to refer to AnInteger."  Example:

// Demonstrating the use of References
#include <iostream.h>

int main()
{
    int intOne;
    int &AReference = intOne;

    intOne = 5;
    cout << "intOne: " << intOne << endl;
    cout << "AReference: " << AReference << endl;

    AReference = 7;
    cout << "intOne: " << intOne << endl;
    cout << "AReference: " << AReference << endl;
 
    return 0;
}

Output:
intOne: 5
AReference: 5
intOne: 7
AReference: 7


Unlike a pointer, using the address of operator "
&" on references returns the address of its target.  References are aliases for their target:

#include <iostream.h>

int main()
{
    int intOne;
    int &AReference = intOne;

    intOne = 5;
    cout << "intOne: " << intOne << endl;
    cout << "AReference: " << AReference << endl;

    cout << "&intOne: " << &intOne << endl;
    cout << "&AReference: " << &AReference << endl;

    return 0;
}

Output:
intOne: 5
AReference: 5
&intOne: 0x3500
&AReference: 0x3500


C++ gives you no way to access the address itself because it is not meaningful.  It's just an alias for intOne, and so returns intOne's address value.  If you were using a pointer or other variable, it would be different.  References are initialized when created, because they can not be reassigned. Be careful to distinguish between the first & symbol in the program which declares a reference to an int "&AReference", and the second & symbol used as the "address of operator" in "&rAReference", which returns the address of the integer reference.

Once more, references can not be reassigned and are always aliases for their target.  References have entirely different behavior patterns than pointers.  What may appear to be a reassignment will turn out to be the assignment of a new value to the target.  Example:

//Reassigning a reference
#include <iostream.h>

int main()
{
    int intOne;
    int &AReference = intOne;

    intOne = 5;
    cout << "intOne:\t" << intOne << endl;
    cout << "AReference:\t" << AReference << endl;
    cout << "&intOne:\t" << &intOne << endl;
    cout << "&AReference:\t" << &AReference << endl;

    int intTwo = 8;

    AReference = intTwo;          //not what you think!

    cout << "\nintOne:\t" << intOne << endl;
    cout << "intTwo:\t" << intTwo << endl;
    cout << "AReference:\t" << AReference << endl;
    cout << "&intOne:\t" << &intOne << endl;
    cout << "&intTwo:\t" << &intTwo << endl;
    cout << "&AReference:\t" << &AReference << endl;

    return 0;
}

Output:
intOne: 5 
AReference: 5
&intOne: 0x213e
&AReference: 0x213e

intOne: 8
intTwo: 8
AReference: 8
&intOne: 0x213e
&intTwo: 0x2130
&AReference: 0x213e


It may appear that we are  reassigning  AReference to be an alias for intTwo, but it continues to act as an alias for intOne instead.  It can't be reassigned and is equivalent to saying: intOne = intTwo;  If it were a pointer, this would have reassigned it to point to intTwo, but it is a reference, it will always be an alias for intOne and will assign the value of intOne to anything on the left side of the assignment operator.

You create a reference to an object - NOT a class. You cannot write:
int &IntRef = int; //this is wrong.
You must initialize IntRef to a particular int like:

int howBig = 200;
int &IntRef = howBig;

You cannot create a reference to a CAT:   CAT &CatRef = CAT;   //this is wrong.
You must initialize CatRef to a particular object:

CAT Frisky;
CAT &CatRef = Frisky;

References to objects are used just like the object itself. Member data and methods are accessed by the normal class member access operator "." and the dot operator. The reference acts as an alias to the object.  No need for "->" or "*" dereferencing to obtain the value at an address. 

While pointers should ALWAYS be assigned to null, references should NEVER be assigned to null.  References cannot be null.  A program with a reference to a null object is invalid.  Anything can happen, crashes, erasing files on the hard disk, etc.

Passing function arguments by reference - Standard functions have 2 limitations:  arguments are passed by value, so memory wasting copies are made, and the return statement can only return one value.  Passing values to a function by reference can overcome both these limitations.  Passing by reference is accomplished in 2 ways:  using pointers and using references.  Rather than a copy being created within the scope of a function, the original object is passed into the function. Passing an object to a function allows the function to change the object being referred to. Example:

//Demonstrates passing by value
#include <iostream.h>

void swap(int x, int y);

//---------------------------------------------------------------------------------------------------------------------

int main()
{
    int x = 5, y = 10;

    cout << "Main. Before swap, x: " << x << " y: " << y << "\n";
   
    swap(x,y);
   
    cout << "Main. After swap, x: " << x << " y: " << y << "\n";
  
    return 0;
}

//---------------------------------------------------------------------------------------------------------------------

//Function definition
void swap (int x, int y)
{
     int temp;

     cout << "Swap. Before swap, x: " << x << " y: " << y << "\n";

     temp = x;
     x = y;
     y = temp;

     cout << "Swap. After swap, x: " << x << " y: " << y << "\n";

}

Output:
Main. Before swap, x: 5 y: 10
Swap. Before swap, x: 5 y: 10
Swap. After swap, x: 10 y: 5
Main. After swap, x: 5 y: 10


The problem is that x and y are passed to swap() by value. What you really want to do is to pass x and y by reference.
You can do this 2 ways: 1-you can make the parameters of swap() pointers to the original values or 2-you can pass in references to the original values.

making swap() work with pointers - when you pass in a pointer you pass in the address of the object and thus the function can manipulate the value at that address. To make swap() change the actual values using pointers it should be declared to accept 2 int pointers. By dereferencing the pointers the values of x and y will be swapped. Example:

//Demonstrates passing by reference using pointers
#include <iostream.h>

void swap(int *x, int *y);

//---------------------------------------------------------------------------------------------------------------------

int main()
{
    int x = 5, y = 10;

    cout << "Main. Before swap, x: " << x << " y: " << y << "\n";

    swap(&x,&y);

    cout << "Main. After swap, x: " << x << " y: " << y << "\n";

    return 0;
}

//---------------------------------------------------------------------------------------------------------------------

void swap (int *px, int *py)
{
     int temp;

     cout << "Swap. Before swap, *px: " << *px << " *py: " << *py << "\n";

     temp = *px;
     *px = *py;
     *py = temp;

     cout << "Swap. After swap, *px: " << *px << " *py: " << *py << "\n";
}

Output:
Main. Before swap, x: 5 y: 10
Swap. Before swap, x: 5 y: 10
Swap. After swap, x: 10 y: 5
Main. After swap, x: 10 y: 5


There are 2 problems here:
1) The repeated need to dereference the pointers within the swap() function makes it error-prone and hard to read.
2) The need to pass the address of the variables in the calling function makes the inner workings of swap() overly apparent to its users.

It is the goal of C++ to prevent the user of a function from worrying about how it works.  It is better to do it by references, since references would appear more like the objects they alias.  In this way, passing references is more "transparent" than passing pointers.  Example:

//Demonstrates passing by reference using references
#include <iostream.h>

void swap(int &x, int &y);

//---------------------------------------------------------------------------------------------------------------------

int main()
{
    int x = 5, y = 10;
    cout << "Main. Before swap, x: " << x << " y: " << y << "\n";

    swap(x,y);
 
    cout << "Main. After swap, x: " << x << " y: " << y << "\n";

    return 0;
}

//---------------------------------------------------------------------------------------------------------------------

void swap (int &rx, int &ry)
{
     int temp;
     cout << "Swap. Before swap, rx: ";
     cout << rx << " ry: " << ry << "\n";

     temp = rx;
     rx = ry;
     ry = temp;

     cout << "Swap. After swap, rx: ";
     cout << rx << " ry: " << ry << "\n";
}

Output:
Main. Before swap, x: 5 y: 10
Swap. Before swap, x: 5 y: 10
Swap. After swap, x: 10 y: 5
Main. After swap, x: 10 y: 5


Since the parameters to swap() are declared to be references, the values from main() are passed by reference and changed in main() as tey are changed in the function.  References combine the convenience and simplicity of normal variables with the power and pass-by-reference ability of pointers.  References also provide another use for the function prototype. By examining the parameters declared in the prototype which are typically in a header file with all the other prototypes, the programmer knows the values passed into swap() are passed by reference and so will be swapped properly.  Clients of classes and functions rely on the header file to tell all that is needed.  The actual implementation is hidden from the client and this allows focus on solving the problem, rather than how to implement it.  This permits the use of a class or function without concern for how it works - following the fine OOP principles of data hiding and encapsulation.

Another interesting aspect of passing by reference is the ability to return more than one value.  By default functions only return 1 value.  You can return multiple values by passing 2 objects into a function by reference.  The function can fill the objects with the correct values.  Passing by reference allows the function to change the original objects and this allows the function to return 2 pieces of information.  This bypasses the return value of the function, so that it can be reserved for reporting errors.  The following example returns 3 values:  2 as pointer parameters and 1 as the return value of the function: 

//Returning multiple values from a function using pointers
#include <iostream.h>

typedef unsigned long ULONG;

long Factor(ULONG, ULONG*, ULONG*); 
//notice that no variable names are in the prototype!

//---------------------------------------------------------------------------------------------------------------------

int main()
{
    ULONG number, squared, cubed;
    bool error;

    cout << "Enter a number (0 - 20): ";
    cin >> number;

    error = Factor(number, &squared, &cubed);   
//address passed in

    if(!error)
    {
       cout << "number: " << number << "\n";
       cout << "square: " << squared << "\n";
       cout << "cubed: " << cubed << "\n";
    }
   
    else
       cout << "Error encountered!!\n";

       return 0;
}

//---------------------------------------------------------------------------------------------------------------------

long Factor(ULONG n, ULONG *pSquared, ULONG *pCubed)
{
     bool error = false;

     if(n > 20) {
        error = true; }

     else {
           *pSquared = n*n;
           *pCubed = n*n*n;
           error = false;
     }

     return error;
}

Output:
Enter a number (0-20): 3
number: 3
square: 9
cubed: 27


In this example "number", "squared", and "cubed" are defined as USHORTs.  The variable "number" is assigned value by user input. The variable "number" and the addresses of squared and cubed are passed to the function Factor().  Factor() examines the first parameter, "number", which is passed by value.  If it is > 20 (the maximum value the function can handle) it sets the boolean "error" to true.  The return value for Factor() is reserved for the error value or the value of 0, which indicates all went well.  The actual values - the square and cube of "number", are not returned by using the return mechanism but by changing the pointers that were passed into the function. 

We could use an enumerated constant in this case:

enum ERROR_VALUE {SUCCESS, FAILURE};

The program would return these in place of 0 or 1. This program can be made easier to read and maintain by using references instead of pointers. Example:

//Returning multiple values from a function using references
#include <iostream.h>

typedef unsigned long ULONG;
enum ERR_CODE { SUCCESS, ERROR }; 
//acts like a user-defined boolean

ERR_CODE Factor(ULONG, ULONG&, ULONG&);

//---------------------------------------------------------------------------------------------------------------------

int main()
{
    ULONG number, squared, cubed;
    ERR_CODE result;

    cout << "Enter a number (0 - 20): ";
    cin >> number;

    result = Factor(number, squared, cubed); 
//when calling the function, no &

    if(result == SUCCESS)
    {
        cout << "number: " << number << "\n";
        cout << "square: " << squared << "\n";
        cout << "cubed: " << cubed << "\n";
    }
    else {
        cout << "Error encountered!!\n";  }

    return 0;
}

//---------------------------------------------------------------------------------------------------------------------

ERR_CODE Factor(ULONG n, ULONG &rSquared, ULONG &rCubed)
{
    if(n > 20) {
        return ERROR;  }  
// simple error code

    else
    {
        rSquared = n*n;
        rCubed = n*n*n;
        return SUCCESS;
    }
}

Output:
Enter a number (0-20): 3
number: 3
square: 9
cubed: 27


Factor() is now declared to take references to squared and cubed, rather than to pointers. This makes manipulation of these parameters simpler and easier to understand.  When calling the function, the variable are simply passed in without the address of operator or any dereferencing.  This makes them more transparent to whoever is calling the function - data hiding and encapsulation. 

©2004 C. Germany