References and Pointers This chapter describes how to define references and pointers and how to use them as parameters and/or return values of functions. In this context, passing by reference and read-only access to arguments are introduced. Defining References Sample program
#include <iostream> #include <string> using namespace std;
float x = 10.7F; // Global
int main() { float &rx=x; // Local reference to x // double &ref = x; // Error: different type!
cout << "cref = " << cref << endl; // ok! // ++cref; // Error: read-only! const string str = "I am a constant string!"; // str = "That doesn't work!"; // Error: str constant! // string& text = str; // Error: str constant! Const string& text = str; // ok! cout << text << endl; // ok! Just reading. return 0; } A reference is another name, or alias, for an object that already exists. Defining a reference does not occupy additional memory. Any operations defined for the reference are performed with the object to which it refers. References are particularly useful as parameters and return values of functions. Definition The ampersand character, &, is used to define a reference. Given that T is a type, T& denotes a reference to T. Example : float x = 10.7; float& rx = x; // or: float &rx = x; rx is thus a different way of expressing the variable x and belongs to the type "reference to float". Operations with rx, such as Example: --rx; // equivalent to --x; will automatically affect the variable x. The & character, which indicates a reference, only occurs in declarations and is not related to the address operator &! The address operator returns the address of an object. If you apply this operator to a reference, it returns the address of the referenced object. Example: &rx // Address of x, thus is equal to &x A reference must be initialized when it is declared, and cannot be modified subsequently. In other words, you cannot use the reference to address a different variable at a later stage. Read-Only References A reference that addresses a constant object must be a constant itself, that is, it must be defined using the const keyword to avoid modifying the object by reference. However, it is conversely possible to use a reference to a constant to address a non-constant object. Example: int a; const int& cref = a; // ok! The reference cref can be used for read-only access to the variable a, and is said to be a read-only identifier. A read-only identifier can be initialized by a constant, in contrast to a normal reference: Example: const double& pi = 3.1415927; Since the constant does not take up any memory space, the compiler creates a temporary object which is then referenced. References As Parameters Sample program
void putClient( const string& name, const long& nr) { // name and nr can only be read!
cout << "
-------- Client Data ---------
" << "
Name: "; cout << name << "
Number: "; cout << nr << endl; } Passing by Reference A pass by reference can be programmed using references or pointers as function parameters. It is syntactically simpler to use references, although not always permissible. A parameter of a reference type is an alias for an argument. When a function is called, a reference parameter is initialized with the object supplied as an argument. The function can thus directly manipulate the argument passed to it. Example: void test( int& a) { ++a; } Based on this definition, the statement test( var); // For an int variable var increments the variable var. Within the function, any access to the reference a automatically accesses the supplied variable, var. If an object is passed as an argument when passing by reference, the object is not copied. Instead, the address of the object is passed to the function internally, allowing the function to access the object with which it was called. Comparison to Passing by Value In contrast to a normal pass by value an expression, such as a+b, cannot be used as an argument. The argument must have an address in memory and be of the correct type. Using references as parameters offers the following benefits: arguments are not copied. In contrast to passing by value, the run time of a program should improve, especially if the arguments occupy large amounts of memory a function can use the reference parameter to return multiple values to the calling function. Passing by value allows only one result as a return value, unless you resort to using global variables. If you need to read arguments, but not copy them, you can define a read-only reference as a parameter. Example: void display( const string& str); The function display() contains a string as an argument. However, it does not generate a new string to which the argument string is copied. Instead, str is simply a reference to the argument. The caller can rest assured that the argument is not modified within the function, as str is declared as a const. Example: Operator << of class ostream Sample assignments of class string
#include <iostream> #include <string> #include <cctype> // For toupper() using namespace std; void strToUpper( string& ); // Prototype int main() { string text("Test with assignments
");
strToUpper(text); cout << text << endl;
strToUpper( text = "Flowers"); cout << text << endl;
strToUpper( text += " cheer you up!
"); cout << text << endl; return 0; }
void strToUpper( string& str) // Converts the content { // of str to uppercase. int len = str.length(); for( int i=0; i < len; ++i) str[i] = toupper( str[i]); } Every C++ expression belongs to a certain type and also has a value, if the type is not void. Reference types are also valid for expressions. The Stream Class Shift Operators The << and >> operators used for stream input and output are examples of expressions that return a reference to an object. Example: cout << " Good morning " This expression is not a void type but a reference to the object cout, that is, it represents the object cout. This allows you to repeatedly use the << on the expression: cout << "Good morning" << '!' The expression is then equivalent to (cout << " Good morning ") << '!' Expressions using the << operator are composed from left to right, as you can see from the table of precedence contained in the appendix. Similarly, the expression cin >> variable represents the stream cin. This allows repeated use of the >> operator. Example: int a; double x; cin >> a >> x; // (cin >> a) >> x; Other Reference Type Operators Other commonly used reference type operators include the simple assignment operator = and compound assignments, such as += and *=. These operators return a reference to the operand on the left. In an expression such as a = b or a += b a must therefore be an object. In turn, the expression itself represents the object a. This also applies when the operators refer to objects belonging to class types. However, the class definition stipulates the available operators. For example, the assignment operators = and += are available in the standard class string. Example: string name("Jonny "); name += "Depp"; //Reference to name Since an expression of this type represents an object, the expression can be passed as an argument to a function that is called by reference. This point is illustrated by the example on the opposite page. Defining Pointers Sample program // pointer1.cpp // Prints the values and addresses of variables. // -------------------------------------------------- #include <iostream> using namespace std;
int var, *ptr; // Definition of variables var and ptr
int main() // Outputs the values and addresses { // of the variables var and ptr. var = 100; ptr = &var;
cout << " Value of var: " << var << " Address of var: " << &var << endl; cout << " Value of ptr: " << ptr << " Address of ptr: " << &ptr << endl; return 0; } Sample screen output Value of var: 100 Address of var: 00456FD4 Value of ptr: 00456FD4 Address of ptr: 00456FD0 The variables var and ptr in memory Efficient program logic often requires access to the memory addresses used by a program's data, rather than manipulation of the data itself. Linked lists or trees whose elements are generated dynamically at runtime are typical examples. Pointers A pointer is an expression that represents both the address and type of another object. Using the address operator, &, for a given object creates a pointer to that object. Given that var is an int variable, Example: &var // Address of the object var is the address of the int object in memory and thus a pointer to var. A pointer points to a memory address and simultaneously indicates by its type how the memory address can be read or written to. Thus, depending on the type, we refer to pointers to char, pointers to int, and so on, or use an abbreviation, such as char pointer, int pointer, and so on. Pointer Variables An expression such as &var is a constant pointer; however, C++ allows you to define pointer variables, that is, variables that can store the address of another object. Example: int *ptr; // or: int* ptr; This statement defines the variable ptr, which is an int* type (in other words, a pointer to int). ptr can thus store the address of an int variable. In a declaration, the star character * always means "pointer to." Pointer types are derived types. The general form is T*, where T can be any given type. In the above example T is an int type. Objects of the same base type T can be declared together. Example: int a, *p, &r = a; // Definition of a, p, r After declaring a pointer variable, you must point the pointer at a memory address. The program on the opposite page does this using the statement ptr = &var;. References: References are similar to pointers: both refer to an object in memory. However, a pointer is not merely an alias but an individual object that has an identity separate from the object it references. A pointer has its own memory address and can be manipulated by pointing it at a new memory address and thus referencing a different object. The Indirection Operator Using the indirection operator double x, y, *px;
px = &x; // Let px point to x. *px = 12.3; // Assign the value 12.3 to x *px += 4.5; // Increment x by 4.5. y = sin(*px); // To assign sine of x to y. Notes on addresses in a program Each pointer variable occupies the same amount of space, independent of the type of object it references. That is, it occupies as much space as is necessary to store an address. On a 32-bit computer, such as a PC, this is four bytes. The addresses visible in a program are normally logic addresses that are allocated and mapped to physical addresses by the system. This allows for efficient storage management and the swapping of currently unused memory blocks to the hard disk. C++ guarantees that any valid address will not be equal to 0. Thus, the special value 0 is used to indicate an error. For pointers, the symbolic constant NULL is defined as 0 in standard header files. A pointer containing the value NULL is also called NULL pointer. Using Pointers to Access Objects The indirection operator * is used to access an object referenced by a pointer: Given a pointer, ptr, *ptr is the object referenced by ptr. As a programmer, you must always distinguish between the pointer ptr and the addressed object *ptr. Example: long a = 10, b, // Definition of a, b *ptr; // and pointer ptr. ptr = &a; // Let ptr point to a. b = *ptr; This assigns the value of a to b, since ptr points to a. The assignment b = a; would return the same result. The expression *ptr represents the object a, and can be used wherever a could be used. The star character * used for defining pointer variables is not an operator but merely imitates the later use of the pointer in expressions. Thus, the definition long *ptr; has the following meaning: ptr is a long* (pointer to long) type and *ptr is a long type. The indirection operator * has high precedence, just like the address operator &. Both operators are unary, that is, they have only one operand. This also helps distinguish the redirection operator from the binary multiplication operator *, which always takes two operands. L-values An expression that identifies an object in memory is known as an L-value in C++. The term L-value occurs commonly in compiler error messages and is derived from the assignment. The left operand of the = operator must always designate a memory address. Expressions other than an L-value are often referred to as R-values. A variable name is the simplest example of an L-value. However, a constant or an expression, such as x + 1, is an R-value. The indirection operator is one example of an operator that yields L-values. Given a pointer variable p, both p and *p are L-values, as *p designates the object to which p points.
Objects as Arguments If an object is passed as an argument to a function, two possible situations occur: the parameter in question is the same type as the object passed to it. The function that is called is then passed a copy of the object (passing by value) the parameter in question is a reference. The parameter is then an alias for the argument, that is, the function that is called manipulates the object passed by the calling function (passing by reference). In the first case, the argument passed to the function cannot be manipulated by the function. This is not true for passing by reference. However, there is a third way of passing by referencepassing pointers to the function. Pointers as Arguments How do you declare a function parameter to allow an address to be passed to the function as an argument? The answer is quite simple: The parameter must be declared as a pointer variable. If, for example, the function func() requires the address of an int value as an argument, you can use the following statement Example: long func( int *iPtr ) { // Function block } to declare the parameter iPtr as an int pointer. If a function knows the address of an object, it can of course use the indirection operator to access and manipulate the object. In the program on the opposite page, the function swap() swaps the values of the variables x and y in the calling function. The function swap() is able to access the variables since the addresses of these variables, that is &x and &y, are passed to it as arguments. The parameters p1 and p2 in swap() are thus declared as float pointers. The statement swap( &x, &y); initializes the pointers p1 and p2 with the addresses of x or y. When the function manipulates the expressions *p1 and *p2, it really accesses the variables x and y in the calling function and exchanges their values. Exercises Listing for exercise 3
temp = p1; p1 = p2; p2 = temp; } Solutions of quadratic equations Test values Quadratic Equation Solutions 2x2 - 2x - 1.5 = 0 x1 = 1.5, x2 = -0.5 x2 - 6x + 9 = 0 X1 = 3.0, x2 = 3.0 2x2 + 2 = 0 none Exercise 1 What happens if the parameter in the sample function strToUpper() is declared as a string& instead of a string?
Exercise 2 Write a void type function called circle()to calculate the circumference and area of a circle. The radius and two variables are passed to the function, which therefore has three parameters:
Parameters: A read-only reference to double for the radius and two references to double that the function uses to store the area and circumference of the circle.
Note Given a circle with radius r: Area = p * r * r and circumference = 2 * p * r where p = 3.1415926536
Test the function circle() by outputting a table containing the radius, the circumference, and the area for the radii 0.5, 1.0, 1.5, . . . , 10.0. Exercise 3 a.The version of the function swap() opposite can be compiled without producing any error messages. However, the function will not swap the values of x and y when swap(&x,&y); is called. What is wrong? b.Test the correct pointer version of the function swap() found in this chapter. Then write and test a version of the function swap() that uses references instead of pointers. Exercise 4 Create a function quadEquation() that calculates the solutions to quadratic equations. The formula for calculating quadratic equations is shown opposite. Arguments: The coefficients a, b, c and two pointers to both solutions. Returns: false, if no real solution is available, otherwise true. Test the function by outputting the quadratic equations on the opposite page and their solutions. Solutions Exercise 1 The call to function strToUpper() is left unchanged. But instead of passing by reference, a passing by value occurs, i.e., the function manipulates a local copy. Thus, only a local copy of the string is changed in the function, but the string in the calling function remains unchanged. Exercise 2 // ---------------------------------------------------- // circle.cpp // Defines and calls the function circle(). // ---------------------------------------------------- #include <iostream> #include <iomanip> #include <string> using namespace std;
temp = *p1; // Above call points p1 *p1 = *p2; // to x and p2 to y. *p2 = temp; } void swap(float& a, float& b) // Reference version { float temp; // Temporary variable
temp = a; // For above call a = b; // a equals x and b equals y b = temp; } Exercise 4 // ---------------------------------------------------- // quadEqu.cpp // Defines and calls the function quadEquation(), // which computes the solutions of quadratic equations // a*x*x + b*x + c = 0 // The equation and its solutions are printed by // the function printQuadEquation(). // ----------------------------------------------------
#include <iostream> #include <iomanip> #include <string> #include <cmath> // For the square root sqrt() using namespace std;
// Prints the equation and its solutions: void printQuadEquation( double a, double b, double c) { double x1 = 0.0, x2 = 0.0; // For solutions
cout << line << '
' << "
The quadratic equation:
" << a << "*x*x + " << b << "*x + " << c << " = 0" << endl;
if( quadEquation( a, b, c, &x1, &x2) ) { cout << "has real solutions:" << "
x1 = " << x1 << "
x2 = " << x2 << endl; } else cout << "has no real solutions!" << endl;
cout << "
Go on with return.
"; cin.get(); }
bool quadEquation( double a, double b, double c, double* x1Ptr, double* x2Ptr) // Computes the solutions of the quadratic equation: // a*x*x + b*x + c = 0 // Stores the solutions in the variables to which // x1Ptr and x2Ptr point. // Returns: true, if a solution exists, // otherwise false. { bool return_flag = false;
double help = b*b - 4*a*c;
if( help >= 0) // There are real solutions. { help = sqrt( help);