Pointers
A pointer is a variable that stores a memory address. Pointers provide the capability to manipulate computer memory directly. Since a non-generic pointer directly references another variable value it is necessary to specify which data type a pointer is pointed to. This is known as the base type.
A general pointer variable is declared as follows –
type *var-name;
The base type is important because the compiler needs to know how many bytes make up the value pointed to. Pointers can have any legal variable name however, the standard naming convention is to start each pointer name with p and capitalize the second letter, as in pAge or pHeap.
Initialising a Pointer
When created, all pointers should be initialised. A pointer not initialised is called a wild pointer and will contain a random or junk value. Wild pointers are dangerous because they cause a program to access invalid memory locations leading to unpredictable results when the pointer is used.
To initialise the pointer use the nullptr keyword. In earlier versions of C++, a pointer was assigned a null value when created by using either 0 or NULL as the value however due to ambiguities with function overloading the nullptr is now preferred. Examples of pointer initialisation are listed below -
int *pPointer = 0; int *pPointer = NULL; int *pPointer = nullptr; //Superceeds the above two methods of initialisation
Setting a Pointer to store a Variable Address
To store the address of a variable in a pointer use the referencing operator &. When placed in front of a variable the referencing operator will always return the address of that variable.
int a_value = 30; //declares int variable to value 30 int * pTr= &a_value; //declares pointer name pTr of type int and sets it to address of variable a_value
Assigning and Accessing Values
The indirection operator or dereferencing operator (*) operates on a pointer and returns the value stored in the address kept in the pointer variable. For example, if pTr is an int pointer and the address contains 30, using *pTr returns that int value. The dereferencing operators can also assign a new value to an address. Using *pTr = 101 will assign the value of 101 to the address stored at pointer pTr.
Pointer Arithmetic
There are only four arithmetic operators that can be used on pointers: ++, – –, +, and –
An increment or decrement operation on a pointer will point to the next value in the memory. The new location will represent the location of the next variable value and not the next byte of memory. For instance, using the ++ on an int pointer instructs the compiler to point to the next consecutive integer. Decrementing pointers ( -- ) has the opposite effect. The address in the pointer is incremented or decremented by the size of the type being pointed to. This ensures that the pointer points to the beginning of some valid data and not to some arbitrary memory location. In the case of character pointers, an increment or decrement will only change the pointer value by 1 since characters are one byte long. Every other type of pointer will increase or decrease by the length of its base type.
Integer values can also be added or subtracted to or from a pointer. For instance, adding 5 to a pointer value means pointing to the 6th element beyond the pointer's current address. In the code section below, a pointer is used to display the contents of an int array together with the address of each element-
#include <iostream> int main() { int myvalues[10]={1,2,3,4,5,6,7,8,9,10};//declare and initialise array of ints int *iPtr=nullptr;//declare pointer and set to null iPtr=myvalues;//set pointer to address of first element in array std::cout << "The size of an int " << sizeof(int)<< "\n"; for (int aNum : myvalues) // range based for { std::cout << "The array elements are " << *iPtr <<" address "<<std::dec << iPtr<<"\n"; iPtr++;//increase pointer } }
Pointer Comparisons
Pointers can also be compared by using relational operators, such as ==, <, and >. however, the two pointers must be of the same type. Two pointers of the same type are equal if they are both null, point to the same function, or represent the same address
Using the Const Keyword on Pointers
If the address contained in the pointer is declared constant then it cannot be changed, however, the data at that address can be changed:
int valueofint = 1; int* const pTr = &valueofint; *pTr = 10; // allowed because value pointed to can be changed int valueofint1 = 21; pTr = &valueofint1; // not allowed because pointer address cannot be changed
If the Data pointed to is declared as constant then it cannot be changed, but the address contained in the pointer can be changed
int valueofint = 1; const int* pTr = &valueofint; *pTr = 10; // not allowed because value pointed to cannot be changed int valueofint1 = 21; pTr = &valueofint1; //allowed because pointer address can be changed
If the pointer address and the value being pointed are declared constant neither can be changed.
int valueofint = 1; const int* const pTr = &valueofint; *pTr = 10;//not allowed because value pointed to cannot be changed int valueofint1 = 21; pTr=&valueofint1;//not allowed because pointer address cannot be changed
Const variables are useful when passing pointers to functions. If function parameters are declared with the most restrictive access possible then a function cannot modify the values passed to them thereby preventing unwanted changes to pointer values or data.
Pointer to a Pointer
Normally, a pointer contains the address of a variable however when we define a pointer to a pointer, the first pointer contains the address of the second pointer, which points to the location that contains the actual value. A variable that is a pointer to a pointer is declared by placing an additional asterisk in front of its name
int **var;
Accessing the target value requires that the asterisk operator be applied twice
#include <iostream>
int main() { int i=10; int *pInt=&i; int **pPtr=&pInt; int ***ppPtr=&pPtr; /*output size of pointer to pointer*/ std::cout << "the size of ppPtr " << sizeof(ppPtr)<<"\n"; /*dereference pInt and show address*/ std::cout << "The integer value is " << *pInt <<" address of pointer"<< pInt<<"\n"; /*dereference pPtr and show address*/ std::cout << "The integer value is " << **pPtr <<" address "<< pPtr<<"\n"; /*dereference ppPtr and show address*/ std::cout << "The integer value is " << ***ppPtr <<" address "<< ppPtr<<"\n"; }
Cpp will permit n-pointer variables and those variables will be represented with n number of asterisks (**********)
Void Pointers
The void or generic pointer is a special pointer with no associated data type. A void pointer is declared like a normal pointer but the void keyword is used to declare the pointer’s type.
void *pTr; // ptr is a void pointer
Since a void pointer does not know what type of object it is pointing to, it cannot be dereferenced without explicitly casting it to the base type before dereferencing. The code section below illustrates how to implement and dereference a void pointer. Note any attempt to print a value of the pVoid pointer will generate an error -
#include <iostream> int main() { int i=10; void *pVoid=&i;/*declare void pointer*/ int *pInt = static_cast<int*>(pVoid);/*cast void pointer*/ std::cout<< "The integer value is " << *pInt <<" address "<< pInt<<"\n";/*output ponter value*/ }
Pointer arithmetic is not possible on void pointers since the pointer is of an unknown base type and hence the compiler has no idea of its size in memory. In general, it is a good idea to avoid using void pointers as they bypass compiler type checking. Void pointers are used to implement generic functions.
Why use Pointers?
Pointers are useful because they allow the passing of data structures to functions more efficiently. Since there is no data duplication, passing by direct reference increases overall efficiency, especially regarding speed and memory usage. Pointers also allow the management of dynamically allocated memory.
Potential Problems with Pointers
Since the misuse of pointers can be a major source of bugs, and security vulnerabilities and can make code unnecessarily complex, pointers are best avoided in C++ where possible. Some of the potential problems with the careless use of pointers are -
- uninitialised pointers - the value stored in an uninitialised pointer could be pointing to anywhere in memory. Storing a value using an uninitialised pointer has the potential to overwrite anything in a program, including the program itself. Therefore pointers should always be initialised
- memory leaks - caused when pointers to a value allocated on the heap have been lost.
- dangling pointers - a pointer pointing to an object that has been deleted. Although the pointer still has the object address, the memory for that object will have been returned to the system.
References
A reference variable is an alias for an existing variable. A reference, like a pointer, is also implemented by storing the address of an object however a reference differs from a pointer in that it cannot be null, cannot be reassigned, does not allow arithmetic operation, does not allow redirection, and shares the same memory address with the original variable
A reference is declared using the reference operator ( & )
#include <iostream> int main() { int i=10; int &r=i;//create reference and set to address of variable i std::cout << "value of variable i " << i<<"\n";// output value of variable i std::cout << "value of reference r " << r<<"\n";//output value of ref f r=20;//changing value of i using alias std::cout << "new value of variable i " << i<<"\n";// output new value of variable i }
Using Keyword Const on References
One of the major advantages of references is that they allow a called function to work on parameters that have not been copied from the calling function. Since it's often important to ensure that the called function cannot change the original variable value, declaring a const reference parameter prevents any attempts at assigning a new value.
const int& constReference = originalvalue