190 likes | 376 Views
Pointers and References (1). THE REFERENCE OPERATOR REFERENCES POINTERS THE DEREFERENCE OPERATOR DERIVED TYPES RETURNING A REFERENCE. THE REFERENCE OPERATOR.
E N D
Pointers and References (1) • THE REFERENCE OPERATOR • REFERENCES • POINTERS • THE DEREFERENCE OPERATOR • DERIVED TYPES • RETURNING A REFERENCE
THE REFERENCE OPERATOR • Computer memory can be imagined as a very large array of bytes. For example, a computer with 256 MB of RAM (256 megabytes of random-access memory) actually contains an array of 268,435,456 (228) bytes. As an array, these bytes are indexed from 0 to 268,435,455. The index of each byte is its memory address. So a 256 MB computer has memory addresses ranging from 0 to 268,435,455, which is 0x00000000 to 0x0fffffff in hexadecimal. The diagram at right represents that array of bytes, each with its hexadecimal address. • A variable declaration associates three fundamental attributes to the variable: its name, its type, and its memory address. For example, the declaration int n; • associates the name n, the type int, and the address of some location in memory where the value of n is stored. Suppose that address is 0x0064fdf0. Then we can visualize n like this:
EX1 Printing Pointer Values int main() { int n=44; cout << "n = " << n << endl; // prints the value of n cout << "&n = " << &n << endl; // prints the address of n }
REFERENCES • A reference is an alias or synonym for another variable. It is declared by the syntax type& ref-name = var-name; where type is the variable’s type, ref-name is the name of the reference, and var-name is the name of the variable. • For example, in the declaration int& rn=n; // r is a synonym for n • rn is declared to be a reference to the variable n, which must already have been declared. • EX2 Using References • This declares rn as a reference to n: int main() { int n=44; int& rn=n; // r is a synonym for n cout << "n = " << n << ", rn = " << rn << endl; --n; cout << "n = " << n << ", rn = " << rn << endl; rn *= 2; cout << "n = " << n << ", rn = " << rn << endl; } • The two identifiers n and rn are different names for the same variable; they always have the same value. Decrementing n changes both n and nr to 32. Doubling rn increases both n and rn to 64.
EX3 References Are Not Separate Variables int main() { int n=44; int& rn=n; // r is a synonym for n cout << " &n = " << &n << ", & rn = " << &rn << endl; int& rn2=n; // r is another synonym for n int& rn3=rn; // r is another synonym for n cout << "&rn2 = " << &rn2 << ",&rn 3 = " << &rn3 << endl; } • The first line of output shows that n and rn have the same address: 0x0064fde4. Thus they are merely different names for the same object. The second line of output shows that an object can have several references, and that a reference to a reference is the same as a reference to the object to which it refers. In this program, there is only one object: an int named n with address 0x0064fde4. The names rn, rn2, and rn3 are all references to that same object. • In C++, the reference operator & is used for two distinct purposes. When applied as a prefix to the name of an object, it forms an expression that evaluates to the address of the object. When applied as a suffix to a type T, it names the derived type “reference to T”. For example, int& is the type “reference to int”. So in this example , n is declared to have type int and rn is declared to have type reference to int.
POINTERS • The reference operator & returns the memory address of the variable to which it is applied. We used this in Ex1to print the address. We can also store the address in another variable. The type of the variable that stores an address is called a pointer. Pointer variables have the derived type “pointer to T”, where T is the type of the object to which the pointer points. As mentioned in previous section, that derived type is denoted by T*. For example, the address of an int variable can be stored in a pointer variable of type int*.
EX4 Using Pointer Variables • This program defines the int variable n and the int* variable pn: int main() { int n=44; cout << "n = " << n << ", &n = " << &n << endl; int* pn=&n; // pn holds the address of n cout << " pn = " << pn << endl; cout << "&pn = " << &pn << endl; } • The variable n is initialized to 44. Its address is 0x0064fddc. The variable pn is initialized to &n which is the address of n, so the value of pn is 0x0064fddc, as the second line of output shows. But pn is a separate object, as the third line of output shows: it has the distinct address 0x0064fde0.
EX4 Using Pointer Variables (2) • The variable pn is called a “pointer” because its value “points” to the location of another value. The value of a pointer is an address. That address depends upon the state of the individual computer on which the program is running. In most cases, the actual value of that address (here, 0x0064fddc) is not relevant to the issues that concern the programmer. So diagrams like the one above are usually drawn more simply like this. This captures the essential features of n and pn: pn is a pointer to n, and n has the value 44. A pointer can be thought of as a “locator”: it locates another object.
THE DEREFERENCE OPERATOR • If pn points to n, we can obtain the value of n directly from p; the expression *pn evaluates to the value of n. This evaluation is called “dereferencing the pointer” pn, and the symbol * is called the dereference operator. • EX5 Dereferencing a Pointer • This is the same program as in Ex4 with one more line of code: int main() { int n=44; cout << "n = " << n << ", &n = " << &n << endl; int* pn=&n; // pn holds the address of n cout << " pn = " << pn << endl; cout << "&pn = " << &pn << endl; cout << "*pn = " << *pn << endl; } • This shows that *pn is an alias for n: they both have the value 44.
EX6 Pointers to Pointers • This continues to build upon the program from Ex4: int main() { int n=44; cout << " n = " << n << endl; cout << " &n = " << &n << endl; int* pn=&n; // pn holds the address of n cout << " pn = " << pn << endl; cout << " &pn = " << &pn << endl; cout << " *pn = " << *pn << endl; int** ppn=&pn; // ppn holds the address of pn cout << " ppn = " << ppn << endl; cout << " &ppn = " << &ppn << endl; cout << " *ppn = " << *ppn << endl; cout << "**ppn = " << **ppn << endl; }
Pointers to Pointers • The variable ppn points to pn which points to n. So *ppn is an alias for pn, just as *pn is an alias for n. Therefore **ppn is also an alias for n. • Note in Ex6 that each of the three variables n, pn, and ppn, has a different type: int, int*, and int**. In general, if T1 and T2 are different types, then any of their derived types will also be different. So although pn and ppn are both pointers, they are not the same type: pn has type pointer to int, while ppn has type pointer to int*. • The reference operator & and the dereference operator * are inverses: n == *p whenever p == &n. This can also be expressed as *&n == n and &*p == p.
DERIVED TYPES • Like the reference operator &, the dereference operator * is used for two distinct purposes. • When applied as a prefix to a pointer to an object, it forms an expression that evaluates to that object’s value. • When applied as a suffix to a type T, it names the derived type “pointer to T”. • For example, int* is the type “pointer to int”.
DERIVED TYPES • Here are some declarations of derived types: • const int C = 33; // const int • int& rn = n; // reference to int • int* pn = &n; // pointer to int • int a[] = { 33,66 }; // array of int • int f() = { return 33; }; // function returning int • A derived type can derive from any other type. So many combinations are possible: • int* const Pn=44; // constant pointer to an int • const int* pN=&N; // pointer to a constant int • const int* const PN=&N; // constant pointer to a constant int • float& ar[] = { x, y }; // array of 2 references to floats • float* ap[] = { &x,&y }; // array of 2 pointers to floats • long& r() { return n; } // function returning reference to long • long* p() { return &n; } // function returning pointer to long • long (*pf)() { return 44; } // pointer to function returning long
RETURNING A REFERENCE • A function’s return type may be a reference provided that the value returned is an lvalue which is not local to the function. This restriction means that the returned value is actually a reference to an lvalue that exists after the function terminates. Consequently that returned lvalue may be used like any other lvalue; for example, on the left side of an assignment: • EX8 Returning a Reference int& max(int& m,int& n) // return type is reference to int { return (m > n ? m : n); // m and n are non-local references } int main() { int m = 44,n = 22; cout << m << "," << n << "," << max(m,n) << endl; max(m,n) = 55; // changes the value of m from 44 to 55 cout << m << "," << n << "," << max(m,n) << endl; } • The max() function returns a reference to the larger of the two variables passed to it. Since the return value is a reference, the expression max(m,n) acts like a reference to m (since m is larger than n). So assigning 55 to the expression max(m,n) is equivalent to assigning it to m itself.
Using a Function as an Array Subscript float& component(float* v,int k) { return v[k-1]; } int main() { float v[4]; for (int k = 1; k <= 4; k++) component(v,k) = 1.0/k; for (int i = 0; i < 4; i++) cout << "v[" << i << "] = " << v[i] << endl; } • The component() function allows vectors to be accessed using the scientific “1-based indexing” instead of the default “0-based indexing.” So the assignment component(v,k) = 1.0/k is really the assignment v[k+1] = 1.0/k.