|

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
|