References are declared using the declarator syntax:
& cv-qualifier-listopt dname
A reference is a 16- or 32-bit quantity that holds the address of an object but behaves syntactically like an object. A reference declaration consists of an (optional) list of specifiers followed by a reference declarator.
decl-specifiers & cv-qualifier-listopt dname ;
Consider the user-defined type Date:
struct Date
{
short DayOfWeek;
short Month;
short Day;
short Year;
};
The following statements declare an object of type Date and a reference to that object:
Date Today; // Declare the object.
Date& TodayRef = Today; // Declare the reference.
Both the name of the object, Today, and the reference to the object, TodayRef, can be used identically in programs:
Today.DayOfWeek = 3; // Tuesday
TodayRef.Month = 7; // July
Reference-Type Function Arguments
Often, when passing large objects to functions, it is more efficient to pass references to these objects instead. This allows the compiler to pass the address of the object while maintaining the syntax that would have been used to access the object. Consider the following example that uses the Date structure:
// Create a Julian date of the form DDDYYYY
// from a Gregorian date.
long JulianFromGregorian( Date& GDate )
{
static int cDaysInMonth[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
long JDate;
// Add in days for months already elapsed.
for( int i = 0; i < GDate.Month - 1; ++i )
JDate += cDaysInMonth[i];
// Add in days for this month.
JDate += GDate.Day;
// Check for leap year.
if( GDate.Year % 100 != 0 && GDate.Year % 4 != 0 )
JDate++;
// Add in year.
JDate *= 10000;
JDate += GDate.Year;
return JDate;
}
The above code illustrates the fact that members of a structure passed by reference are accessed using the member-selection operator (.) instead of the pointer member-selection operator (–>).
Although arguments passed as reference types observe the syntax of nonpointer types, they retain one important characteristic of pointer types: they are modifiable unless declared as const. Because the intent of the above code is not to modify the object GDate, a more appropriate function prototype is:
long JulianFromGregorian( const Date& GDate );
This prototype guarantees that the function JulianFromGregorian will not change its argument.
Note that any function prototyped as taking a reference type can accept an object of the same type in its place because there is a standard conversion from typename to typename&.
Reference-Type Function Returns
Functions can be declared to return a reference type. There are two reasons to make such a declaration:
The information being returned is a large enough object that returning a reference is more efficient than returning a copy.
The type of the function must be an l-value.
Just as it can be more efficient to pass large objects to functions by reference, it also can be more efficient to return large objects from functions by reference. Reference return protocol eliminates the necessity of copying the object to a temporary location prior to returning.
Another case in which reference return types can be useful is when the function must evaluate to an l-value. Most overloaded operators fall into this category, particularly the assignment operator. Overloaded operators are covered in “Overloaded Operators” in Chapter 12, on topic . Consider the Point example from Chapter 4:
class Point
{
public:
// Define "accessor" functions as
// reference types.
unsigned& x();
unsigned& y();
private:
unsigned obj_x;
unsigned obj_y;
};
unsigned& Point :: x()
{
return obj_x;
}
unsigned& Point :: y()
{
return obj_y;
}
void main()
{
Point ThePoint;
// Use x() and y() as l-values.
ThePoint.x() = 7;
ThePoint.y() = 9;
// Use x() and y() as r-values.
cout << "x = " << ThePoint.x() << "\n"
<< "y = " << ThePoint.y() << "\n";
}
Notice that the functions x and y are declared as returning reference types. These functions can be used on either side of an assignment statement.
Declarations of reference types must contain initializers except in the following cases:
Explicit extern declaration
Declaration of a class member
Declaration within a class
Declaration of an argument to a function or the return type for a function
References to pointers can be declared in much the same way as references to objects. Declaring a reference to a pointer yields a modifiable value that is used like a normal pointer. The following code samples illustrate the difference between using a pointer to a pointer and a reference to a pointer:
#include <iostream.h>
#include <string.h>
// Define a binary tree structure.
struct BTree
{
char *szText;
BTree *Left;
BTree *Right;
};
// Define a pointer to the root of the tree.
BTree *btRoot = 0;
int Add1( BTree **Root, char *szToAdd );
int Add2( BTree*& Root, char *szToAdd );
void PrintTree( BTree* btRoot );
main( int argc, char *argv[] )
{
if( argc < 2 )
{
cerr << "Usage: Refptr [1 | 2]" << "\n";
cerr << "\n\twhere:\n";
cerr << "\t1 uses double indirection\n";
cerr << "\t2 uses a reference to a pointer.\n";
cerr << "\n\tInput is from stdin.\n";
return 1;
}
char *szBuf = new char[132];
// Read a text file from the standard input device and
// build a binary tree.
while( !cin.eof() )
{
cin.get( szBuf, 132, '\n' );
cin.get();
if( strlen( szBuf ) )
switch( *argv[1] )
{
// Method 1: Use double indirection.
case '1':
Add1( &btRoot, szBuf );
break;
// Method 2: Use reference to a pointer.
case '2':
Add2( btRoot, szBuf );
break;
default:
cerr << "Illegal value '" << *argv[1]
<< "' supplied for add method.\n"
<< "Choose 1 or 2.\n";
return -1;
}
}
// Display the sorted list.
PrintTree( btRoot );
return 0;
}
// PrintTree: Display the binary tree in order.
void PrintTree( BTree* btRoot )
{
// Traverse the left branch of the tree recursively.
if( btRoot->Left )
PrintTree( btRoot->Left );
// Print the current node.
cout << btRoot->szText << "\n";
// Traverse the right branch of the tree recursively.
if( btRoot->Right )
PrintTree( btRoot->Right );
}
// Add1: Add a node to the binary tree.
// Uses double indirection.
int Add1( BTree **Root, char *szToAdd )
{
if( (*Root) == 0 )
{
(*Root) = new BTree;
(*Root)->Left = 0;
(*Root)->Right = 0;
(*Root)->szText = new char[strlen( szToAdd ) + 1];
strcpy( (*Root)->szText, szToAdd );
return 1;
}
else if( strcmp( (*Root)->szText, szToAdd ) > 0 )
return Add1( &((*Root)->Left), szToAdd );
else
return Add1( &((*Root)->Right), szToAdd );
}
// Add2: Add a node to the binary tree.
// Uses reference to pointer
int Add2( BTree*& Root, char *szToAdd )
{
if( Root == 0 )
{
Root = new BTree;
Root->Left = 0;
Root->Right = 0;
Root->szText = new char[strlen( szToAdd ) + 1];
strcpy( Root->szText, szToAdd );
return 1;
}
else if( strcmp( Root->szText, szToAdd ) > 0 )
return Add2( Root->Left, szToAdd );
else
return Add2( Root->Right, szToAdd );
}
In the preceding program, functions Add1 and Add2 are functionally equivalent (although they are not called the same way). The difference is that Add1 uses double indirection whereas Add2 uses the convenience of a reference to a pointer.