Introduction to Pointers |
|
When writing a program, you declare the necessary variables that you will need in order to accomplish your work. When declaring variables, you are simply asking the computer to reserve a set amount of space in its memory for a particular object you want to use. When you declare a variable, the computer reserves an amount of space for that variable, and uses the variable's name to refer to that memory space. This will allow you to store something, namely the value of that variable, in that space. Indeed, the computer refers to that space using an address. Therefore, everything you declare has an address, just like the address of your house. You can find out what address a particular variable is using.
Here are a few things you know already about writing a program: To use a variable, you declare it first to tell the compiler what kind of variable you are planning to use and what its name is . Once you declare a variable, the compiler reserves and assigns it a portion of space in memory and locates it there so that whenever you need that variable, you just call it and then use it. To use a function, you have to define it, tell the compiler what the function is supposed to do, and whether the function is supposed to give back a result or not, after it has performed its assignment. To see a variable's address, you can use the & operator followed by the name of the variable. For example, after declaring an integer as int numberOfStudents; you can find the address where the NumberOfStudents variable is located by using: cout << &numberOfStudents; This program would give you the address of the declared variable: #include <iostream> using namespace std; int main() { int value; cout << "Value lives at " << &value; cout << "\n\n"; return 0; } After executing the program, you could get: Value lives at: 0x0065FDF4
Every time you declare a variable, the compiler puts it somewhere, which you can now refer to as an address. Once you know that address, you can use it. Like a reference, when you pass an argument to a function, the argument is passed using its address. This allows the calling function to dig into the address of the variable (the argument) and use the value directly. This transaction, like that of passing an argument by reference, allows the calling function to alter the real value of the argument. Using this ability, a pointer can allow you to return many values from a function; as opposed to a regular argument passing where the data, although changed inside of the calling function, will regain its previous value once the calling function is exited. Therefore, passing arguments as pointers allows a function to return many values, even if a function is declared as void. When you declare an array, you must specify the dimension of the array. That's already a problem: what if you don't know and don't want to know the dimension of the array? Pointers provide an ability that regular arrays do not have. Since pointers have a different and better system of managing memory, a pointer can store an array of almost any size; this is tremendous when dealing with arrays of characters or a whole text. Using this feature, when declaring a pointer in replacement of an array, you do not have to worry about the size of the array, the compiler will take care of that. Again, this feature allows you to pass pointers to a function (just like arrays) and return a value that has been altered even if the function is declared as void. This is even more dynamic with multidimensional arrays.
Pointers are not particularly useful when declared and used inside of one function. They show their capabilities when different functions (and/or objects) exchange data stored in those pointers. As you can see from the execution of the program above, the address of a variable is very difficult to read and interpret. Fortunately, we don't need to know that address and we don't need to know what it means or where the variable is located. C++ provides an alternative to this problem. DataType * PointerName; The identifier should be one of those we have learned already. This means it could be an int, a char, a double, etc. The identifier should be the same type of identifier the pointer variable will point to. Therefore, if you are declaring a pointer that will point to an integer variable, the pointer identifier should be an integer. DataType* PointerName; DataType * PointerName; DataType *pointerName; By default, it does not matter how you append the *, the compiler will know that the thing that follows is a variable. Be careful when declaring various pointers. If you declare a few of them on the same line, like this: DataType* pointer1, pointer2; Only the first variable is a pointer, the second is a regular variable. If you want to declare different variables, you use: DataType* pointer1, *pointer2; Or DataType* pointer1; DataType* pointer2; Since the name of the pointer is indeed the name of a variable, you will follow the naming rules that govern every C++ variable. #include <iostream> using namespace std; int main() { int value; int *pointer; cout << "Value lives at " << &value << "\n"; cout << "Pointer lives at " << &pointer; cout << "\n\n"; return 0; } After executing the program, you might get: Value lives at: 0x0065FDF4 Pointer lives at: 0x0065FDF0
One of the reasons you are using a pointer is to find an alternative to knowing the address
of a variable. Therefore, from now on, we are not interested in a variable's real address. Instead, we will use a pointer to point to that variable. int* Pointer; initialize it by following the assignment operator with & operator and the name of the variable, like this int* Pointer = &Variable; You can also initialize a pointer on a different line, after declaring it. This time, you should not use the asterisk on the Pointer, but the compiler should know that the pointer will point to a variable's address; therefore, the name of the variable will still use the &. |
|
#include <iostream> using namespace std; int main() { int value = 12; int *pointer = &value; cout << "Value lives at: " << value << "\n"; cout << "Pointer lives at: " << *pointer; cout << "\n\n"; return 0; } |
The program would produce:
Value lives at: 12 Pointer lives at: 12 |
This program could also have the pointer initialized as:
#include <iostream> using namespace std; int main() { int Value = 12; int *pointer; cout << "Value lives at: " << value << "\n"; Pointer = &value; cout << "Pointer lives at: " << *pointer; cout << "\n\n"; return 0; } |
And it would produce the same result. As another of the program, you can first declare both variables, then initialize them later on, when needed: |
#include <iostream> using namespace std; int main() { int value; int *pointer; pointer = &value; Value = 26; cout << "Value = " << value << "\n"; cout << "*Pointer = " << *pointer << "\n"; cout << "\n"; return 0; } |
Once you have declare a variable and assign it to a pointer, during the course of your program, the value of a variable is likely to change, you can therefore assign it a different value: |
#include <iostream> using namespace std; int main() { int value; int *pointer; Pointer = &value; Value = 26; cout << "Value = " << value << "\n"; cout << "*pointer = " << *pointer << "\n"; Value = 35; cout << "Value = " << value << "\n"; cout << "*pointer = " << *pointer << "\n"; cout << "\n"; return 0; } |
As you know now, both *pointer and Value have the same value. This allows you to change the value of the pointer directly and affect the main variable meanwhile. Therefore, you can safely change the value of the pointer and it will be assigned accordingly. To see an example, make the following change to the file: |
#include <iostream> using namespace std; int main() { int value; int *pointer; Pointer = &value; Value = 26; cout << "Value = " << value << "\n"; cout << "*pointer = " << *pointer << "\n"; Value = 35; cout << "Value = " << value << "\n"; cout << "*pointer = " << *pointer << "\n"; *pointer = 144; cout << "Value = " << value << "\n"; cout << "*pointer = " << *pointer << "\n"; cout << "\n"; return 0; } |
This would produce:
Value = 26 *pointer = 26 Value = 35 *pointer = 35 Value = 144 *pointer = 144 |
A Pointer to a Pointer |
|
Instead of pointing to a regular variable, a pointer can be made to point to another pointer. To apply this, always remember that you must specify what target a pointer is pointing to. Once again, consider the following example: #include <iostream> using namespace std; int main() { int value = 26; int *pointer; pointer = &value; cout << " Value = " << value << "\n"; cout << "*Pointer = " << *pointer << "\n"; return 0; } This would produce: Value = 26 *Pointer = 26 In this program, if necessary, you can declare a new variable that is a pointer that itself points to another pointer. When declaring such a variable, precede it with two *. After declaring the pointer, before using it, you must initialize it with a reference to a pointer, that is, a reference to a variable that was declared as a pointer. Here is an example: #include <iostream> using namespace std; int main() { int value = 26; int *pointer; int **pointerToPointer; pointer = &value; pointerToPointer = &pointer; cout << " Value = " << value << "\n"; cout << " *Pointer = " << *pointer << "\n"; cout << "**Pointer = " << **pointerToPointer << "\n"; return 0; } This would produce: Value = 26 *Pointer = 26 **Pointer = 26 Just as demonstrated earlier, after initializing a pointer, if you change the value of the variable it points to, the pointer would be updated. Consider the following program: #include <iostream> using namespace std; int main() { int value = 26; int *pointer; int **pointerToPointer; pointer = &value; pointerToPointer = &pointer; cout << " Value = " << value << "\n"; cout << " *Pointer = " << *pointer << "\n"; cout << "**Pointer = " << **pointerToPointer << "\n"; value = 4805; cout << "After changing the value of the main variable...\n"; cout << " Value = " << value << "\n"; cout << " *Pointer = " << *pointer << "\n"; cout << "**Pointer = " << **pointerToPointer << "\n"; return 0; } This would produce: Value = 26 *Pointer = 26 **Pointer = 26 After changing the value of the main variable... Value = 4805 *Pointer = 4805 **Pointer = 4805 Notice that, by changing the value of the original variable, when accessing its pointer or the pointer to its pointer, they reflect the new value. In the same way, instead of (directly) changing the value of the variable, you can change the value of its pointer. You can also change the value of the pointer to its pointer. Like a chain reaction, all variables would be updated. Consider the following program: #include <iostream> using namespace std; int main() { int value = 26; int *pointer; int **pointerToPointer; pointer = &value; pointerToPointer = &pointer; cout << " Value = " << value << "\n"; cout << " *Pointer = " << *pointer << "\n"; cout << "**Pointer = " << **pointerToPointer << "\n"; value = 4805; cout << "\nAfter changing the value of the main variable...\n"; cout << " Value = " << value << "\n"; cout << " *Pointer = " << *pointer << "\n"; cout << "**Pointer = " << **pointerToPointer << "\n"; *pointer = -728; cout << "\nAfter changing the value of the pointer...\n"; cout << " Value = " << value << "\n"; cout << " *Pointer = " << *pointer << "\n"; cout << "**Pointer = " << **pointerToPointer << "\n"; **pointerToPointer = 945580; cout << "\nAfter changing the value of the pointer to pointer...\n"; cout << " Value = " << value << "\n"; cout << " *Pointer = " << *pointer << "\n"; cout << "**Pointer = " << **pointerToPointer << "\n"; return 0; } This would produce: Value = 26 *Pointer = 26 **Pointer = 26 After changing the value of the main variable... Value = 4805 *Pointer = 4805 **Pointer = 4805 After changing the value of the pointer... Value = -728 *Pointer = -728 **Pointer = -728 After changing the value of the pointer to pointer... Value = 945580 *Pointer = 945580 **Pointer = 945580 |
Operations on Pointers |
|
Introduction |
|
Consider that, added just a few rules, a pointer is a variable like any other: it can get its value from the user (indirectly), you can apply any of the algebraic operations we have learned, it can be incremented, it can be applied on a function, etc. A variable is a value that is supposed to change some time to time. Since a pointer is a variable whose value points to another variable, the value of a pointer is affected by the variable it points to. You can use this indirection to change the value of a pointer when changing its main variable. To get a value from the user, we have already learned that you can use the cin operator. When using a pointer to get a value from the user, don't forget the * operator, otherwise, the compiler would get confused. We have already learned how to request and display the value of a regular variable from the user: #include <iostream> using namespace std; int main() { int students; cout << "Number of students: "; cin >> students; cout << "\nNumber of students: " << students; cout << "\n\n"; return 0; } Once you have gotten a value and store it in a variable, it is available: #include <iostream> using namespace std; int main() { int students; int *ptrStudents; ptrStudents = &students; cout << "Number of students: "; cin >> students; cout << "\nNumber of students: " << students << "\nThat is: " << *ptrStudents << " students."; cout << "\n\n"; return 0; } This could produce: Number of students: 24 Number of students: 24 That is: 24 students In the same way, you can request a value from the user and store it in the pointer. To see an example, make the following change to the file: #include <iostream> using namespace std; int main() { int students; int *ptrStudents; ptrStudents = &students; cout << "Number of students: "; cin >> *ptrStudents; cout << "\nNumber of students: " << students << "\nThat is: " << *ptrStudents << " students."; cout << "\n\n"; return 0; } Of course, you can use various pointers on the same program. Apply an example by making the following changes: #include <iostream> using namespace std; int main() { int boys; int girls; int *ptrBoys; int *ptrGirls; ptrBoys = &boys; ptrGirls = &girls; cout << "Number of male students: "; cin >> *ptrboys; cout << "Number of female students: "; cin >> *ptrGirls; cout << "\nNumber of students:"; cout << "\nBoys:" << "\t" << Boys << "\nThat is: " << *ptrBoys << " students."; cout << "\nGirls:" << "\t" << Girls << "\nThat is: " << *ptrGirls << " students."; cout << "\n\n"; return 0; } We have learned how to perform algebraic calculations and expressions in C++. When performing these operations on pointers, remember to use the * for each pointer involved. The calculations should be as smooth: #include <iostream> using namespace std; int main() { int boys; int girls; int total; int *ptrBoys; int *ptrGirls; int *ptrTotal; ptrBoys = &boys; ptrGirls = &girls; ptrTotal = &total; cout << "Number of male students: "; cin >> *ptrBoys; cout << "Number of female students: "; cin >> *ptrGirls; cout << "\nNumber of students:"; cout << "\nBoys:" << "\t" << Boys << "\nThat is: " << *ptrBoys << " students."; cout << "\nGirls:" << "\t" << Girls << "\nThat is: " << *ptrGirls << " students."; Total = Boys + Girls; *ptrTotal = *ptrBoys + *ptrGirls; cout << "\n\nTotal number of students: " << total; cout << "\nThere are " << *ptrTotal << " students"; cout << "\n\n"; return 0; } This would produce: Number of male students: 26 Number of female students: 24 Boys: 26 That is: 26 students Girls: 24 That is: 24 students Total number of students: 50 There are 50 students |
Passing Pointers to Functions |
|
We know that a function uses arguments in order to carry its assignment. The arguments are usually provided to the function. When necessary, a function also declares its own variable to get the desired return value. Like other variables, pointers can be provided to a function, with just a few rules. When declaring a function that takes a pointer as an argument, make sure you use the asterisk for the argument or for each argument. When calling the function, use the references to the variables. The function will perform its assignment on the referenced variable(s). After the function has performed its assignment, the changed value(s) of the argument(s) will be preserved and given to the calling function. Here is a starting file from what we have learned so far: |
#include <iostream> using namespace std; int main() { int shirts = 12; int pants = 5; cout << "Shirts = " << shirts << endl; cout << "Pants = " << pants << endl; cout << endl; return 0; } |
This would produce:
Shirts = 12 Pants = 5 |
To pass arguments to a function, you can make the following changes:
#include <iostream> using namespace std; int main() { int shirts = 3; int pants = 5; void Deposit(int s, int p); cout << "When starting, within main():\n"; cout << "\tShirts = " << shirts << endl; cout << "\tPants = " << pants << endl; Deposit(Shirts, Pants); cout << "\n\nAfter calling Deposit(), within main():\n"; cout << "\tShirts = " << shirts << endl; cout << "\tPants = " << pants << endl; cout << endl; return 0; } void Deposit(int s, int p) { s = 8; p = 12; cout << "Within Deposit()" << "\n\tShirts = " << s << "\n\tPants = " << p; } |
After executing, the program would produce:
When starting, within main(): Shirts = 3 Pants = 5 Within Deposit() Shirts = 8 Pants = 12 After calling Deposit(), Within main(): Shirts = 3 Pants = 5 |
To pass pointer arguments, use the asterisks when declaring the function, and use the ampersand & when calling the function. Here is an example: |
#include <iostream> using namespace std; int main() { int shirts = 12; int pants = 5; void Deposit(int s, int p); void Pickup(int *sht, int *pt); cout << "When starting, within main():\n"; cout << "\tShirts = " << shirts << endl; cout << "\tPants = " << pants << endl; Deposit(shirts, pants); cout << "\n\nAfter calling Deposit(), within main():\n"; cout << "\tShirts = " << shirts << endl; cout << "\tPants = " << pants << endl; Pickup(&shirts, &pants); cout << "\n\nAfter calling Pickup(), within main():\n"; cout << "\tShirts = " << shirts << endl; cout << "\tPants = " << pants << endl; cout << endl; return 0; } void Deposit(int s, int p) { s = 8; p = 5; cout << "\nWithin Deposit()" << "\n\tShirts = " << s << "\n\tPants = " << p; } void Pickup(int *sht, int *pt) { *sht = 26; *pt = 17; cout << "\nWithin Pickup()" << "\n\tShirts = " << *sht << "\n\tPants = " << *pt; } |
The result of executing the program is:
When starting, within main(): Shirts = 12 Pants = 5 Within Deposit() Shirts = 8 Pants = 5 After calling Deposit(), within main(): Shirts = 12 Pants = 5 Within Pickup() Shirts = 26 Pants = 17 After calling Pickup(), within main(): Shirts = 26 Pants = 17 |
A Function That Returns a Pointer |
If you want a function to return a pointer, when declaring the function, make sure that you specify its return type as a pointer and you can use a type of your choice. Here is an example of such a declaration: #include <iostream> using namespace std; int main() { int *GetNumber(); return 0; } In this case, we have declared a function named GetNumber that will return a pointer to int. When implementing the function, you can apply any of the techniques we have used so far inside the function. The most important rule to keep in mind is that the function must return a pointer and not a regular value. In the same way, when calling a function that returns a pointer, you can use its value only where the returned pointer is appropriate. For example, you can assign its returned value only to a pointer of the same type. Here is an example: #include <iostream> using namespace std; int main() { int *GetNumber(); int *number; number = GetNumber(); cout << "Number = " << *number << endl; return 0; } int *GetNumber() { int *a = new int(2885); return a; } This would produce: Number = 2885
|
Pointers and Memory Management |
|
By definition, the variables in your program are meant to "vary", that is, their values change regularly. When you declare a variable, such as int Shirts;
the compiler reserves an appropriate amount of memory space for that particular variable. This is done when the program is compiling but before its execution. This means of providing memory space is called static allocation, the memory space is "allocated" to that variable. When the program executes, this static memory allocation does not change; but the memory space might be empty, especially if the variable is not initialized. This is important: the fact that a variable is not initialized means its memory space is empty, it is not equal to zero; it is simply empty. Nothing is occupying it. PointerName = new DataType; The keyword new is required. The data type can be any of those we are already familiar with, but it must be appropriate to the variable it is pointing to. This means that, if it is pointing to an integer variable, the data type must be an integer. For example, our corresponding dynamic allocation would be: ptrShirts = new int; After dynamically allocating memory, you can assign a new value to the pointer for any purpose. Once the memory is not anymore in use, you should reclaim it. This is done with the delete keyword, like this: delete ptrShirts; Here is a starting point for this section : |
#include <iostream> using namespace std; int main() { int studentAge = 12; cout << "Student age = " << studentAge << endl; cout << endl; return 0; } |
Now, let's add a pointer and try to access it without initializing it:
#include <iostream> using namespace std; int main() { int studentAge = 12; int* age; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; cout << endl; return 0; } |
You will get a value that is not insignificant. Depending on the compiler, you might even get a nasty dialog box and a warning. This is because you were trying to access a pointer that has not been initialized. You can initialize the pointer like this: |
#include <iostream> using namespace std; int main() { int studentAge = 12; int* age; Age = &studentAge; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; cout << endl; return 0; } |
To illustrate that an un-initialized variable has an address (although empty), you can change the file as follows: |
#include <iostream> using namespace std; int main() { int studentAge; int* ptrAge; ptrAge = &studentAge; cout << "Student age = " << studentAge << endl; cout << "*ptrAge = " << *ptrAge << endl; cout << endl; return 0; } |
When you initialize a variable, its value gets stored in the memory space that was statically allocated by the compiler. In the same way, since its corresponding pointer points to its address, you can initialize the pointer and still access the value assigned to the variable it is pointing to. To see an example of a pointer, and not the variable itself being initialized, make the following changes to the file: |
#include <iostream> using namespace std; int main() { int studentAge; int *age; age = &studentAge; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; *Age = 15; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; cout << "\n"; return 0; } |
This results in:
Student age = -858993460 *Age = -858993460 Student age = 15 *Age = 15 |
To dynamically allocate memory, you assign the pointer with the new keyword followed by the appropriate identifier: |
#include <iostream> using namespace std; int main() { int studentAge; int *age; age = &studentAge; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; *age = 15; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; age = new int; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; cout << "\n"; return 0; } |
As you can see, since the value of the pointer has been dynamically assigned, its address is now empty. If you want to access its content, you have to reassign it another value; this is one of the mistakes that happen regularly in a program. When this happens, the program will compile fine, without any error or warning, but the result… Therefore, you should always know the value of a pointer. In our example, you can reassign a value to the empty pointer: |
age = new int; *Age = 17; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; cout << "\n"; }
After using a pointer, don't forget to clean your memory. You do this using the delete operator:
#include <iostream.h> int main() { int studentAge; int *age; age = &studentAge; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; *age = 15; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; Age = new int; *age = 17; cout << "Student age = " << studentAge << endl; cout << "*Age = " << *age << endl; delete age; cout << "\n"; return 0; } |
|
||
Previous | Copyright © 2000-2016, FunctionX, Inc. | Next |
|