A pointer is an address of memory location where data is stored. Pointers provide an indirect way of accessing data in memory. When a variable is declared, the compiler reserves and assigns it a portion of space in memory. This portion of memory is known to the computer by the address associated to it. Once we know that address, we can utilise it using the a reference or a pointer. A pointer variable is defined to point (to store the address of) the data stored at a specific memory.
Declaring and Initializing a Pointer
To declare a pointer variable, start with the data type followed by a dereference operator (*) and the name of the variable. Once a pointer has been declared, we assign it with address of the variable to store. By default, it does not matter how you append the *, as long as it is between the data type and the pointer variable. The name of a pointer variable, must comply with the rules that govern every C++ variable. The value of a pointer variable is the address to which it points. Follow the example below
int *ptr1; // pointer to an int float *ptr2; // pointer to a float int age; float height;
we can now store the address of age and height in ptr1 and ptr2 respectively as follows:
ptr1 = &age; //ptr1 points to age; ptr2=&height //ptr2 points to height;
Remember the symbol & is the address operator that takes a variable as an argument and returns the memory address of that variable. As such, by assigning &age and &height to ptr1 and ptr2 respectively we have in effect assigned the address of the two variables to the two pointers.
Pointers ptr1 and ptr2 are said to point to age and height respectively as illustrated in the figure below.
The symbol * is the dereference operator; it takes a pointer as an argument and returns the contents of the location to which it points. The type of a pointer must match the type of the data it is set to point to save for type void* which matches any type. Basing on this knowledge we can now a develop a program as follows
#include<iostream> using namespace std; int main() { int age; float height; int *AgePtr = &age; //ptr1 is assigned adress of age float* HeitPtr=&height; //ptr2 is assigned adress of height age=27; height=5.6; cout<< "His age is "<<*AgePtr<< " years \n"; cout<< "He is "<<*HeitPtr<<" tall\n\n"; cout<<"Using pointers, the adresses of 'age' and 'height' are " <<AgePtr<<" and "<<HeitPtr<<" respectively\n"; cout <<"Using references, the adresses of 'age' and 'height' are " <<&age<<" and "<<&height<<" respectively\n\n"; return 0; }
Output:
His age is 27 years He is 5.6 tall Using pointers, the adresses of 'age' and 'height' are 0xbf9c0464 and 0xbf9c0460 respectively Using references, the adresses of 'age' and 'height' are 0xbf9c0464 and 0xbf9c0460 respectively
Notice the way we printed the values of “age” and “height” using their respective pointers (i.e *AgePtr and *HeitPtr). In other words both the variable and its pointer proceeded dereference operator are the same and can be used alternately. This is because “age” and “height” have the same values with their respective dereferenced pointers. We also printed the addresses of both “age” and “height” using the pointers (AgePtr and HeitPtr) and also the reference operator (&). In both cases the addresses printed are the same implying that pointers actually do store the addresses of the variables to which they point.
Like any other variable the value of a variable is likely to change in the course of the program, a different value can be assigned to the pointer as appropriate. Because both *pointer and the variable it points to have the same value, it is possible to change the value of the pointer directly and affect the main variable meanwhile. Follow the example below to see this concept in action.
#include<iostream> using namespace std; int main() { int age =27; float height=5.6; cout << "His age is "<<age<< " years \n"; cout << "He is "<<height<<" tall\n\n"; int *AgePtr = &age; //ptr1 is assigned adress of age float* HeitPtr=&height; //ptr2 is assigned adress of height cout<<"After adjusting the values using the respective pointers,\n\n"; *AgePtr=30; *HeitPtr=5.4; cout << "His new age is "<<age<< " years \n"; cout << "He is now "<<height<<" tall\n\n"; return 0; }
Output:
His age is 27 years He is 5.6 tall After adjusting the values using the respective pointers, His new age is 30 years He is now 5.4 tall
Pointer to a Pointer
A pointer can be made to point to another pointer, instead of pointing to a regular variable. Such a pointer variable is preceded with **. Remember a pointer must always be initialized before it can be used. A pointer to a pointer is initialized with a reference to a pointer, that is, a reference to a variable that was declared as a pointer. Again, once a pointer to a pointer is initialized we can change the value of the pointer it points to and this will update that pointer and the initial variable. Follow the following example.
#include<iostream> using namespace std; int main() { int age =27; cout<<"His age is "<<age<<" years \n\n"; int *AgePtr1 = &age; //ptr1 is assigned adress of age cout<<"After adjusting the values using the AgePtr1 pointer,\n\n"; *AgePtr1=30; cout << "His new age is "<<age<< " years \n\n"; cout<<"After adjusting the values using the AgePtr2 pointer to AgePtr1,\n\n"; int**AgePtr2=&AgePtr1; **AgePtr2=35; cout << "He is now "<<age<<" years\n\n"; return 0; }
Output:
His age is 27 years After adjusting the values using the AgePtr1 pointer, His new age is 30 years After adjusting the values using the AgePtr2 pointer to AgePtr1, He is now 35 years
As can see, we changed the value of AgePtr2 which is a pointer or pointer AgePtr1, instead of directly changing the value of the variable ‘age”. As a result both pointer AgePtr1 and “age” were updated.
We can use a type void* to define pointers which may point to data of different types, including those whose type is originally unknown. It is also possible to convert a pointer from one data type to another. We use the cast (type converted to) to convert to another type. For example,
Ptr3 = (float*) ptr1; // converts ptr1 to float pointer and //before assigns it to ptr3.
A pointer assigned the value 0 is called a null pointer. A null pointer is used for initializing pointers, and for marking the end of pointer-based data structures, such as linked lists and queues as we will see later.
Dynamic Memory
In C++ data is allocated a fixed memory size when it is created. At times however the size data is not known from the beginning. In such situations, memory cannot be allocated directly, instead a program can allocate de-allocate memory dynamically at execution time. A memory space called the heap is allocated to a program to dynamically allocate and de-allocate memory block at execution time. This is referred to as dynamic memory management.
The new and deleteoperators are used to dynamically allocate and de-allocate memory on the heap. The new takes a type as an argument and allocates memory block for an object of that type. An object is allocated memory on the heap also known as the free store. An object created on the heap can be accessed through a pointer that the operator new returns.
In the examples below operator new uses arguments int and char respectively and returns a pointer in each case, i.e ptr1 to int and ptr2 to char large enough for storing an array of 10 characters.
int *ptr1 = new int; char *ptr2 = new char[15];
When a variable goes out of scope, it will be destroyed but the memory allocated to it at the heap will not automatically be released. In the example below, when AreaRec returns
int AreaRec () { int *wPtr=new int; float *hptr= new float; : : return (*wptr)*(*hptr)*2; }
local variables wptr and hptr will be destroyed, but their memory will remain allocated until it is explicitly released. The delete operator is used to release memory allocated by new. The delete takes a pointer as argument and releases the memory block to which it points. Thus in our example above we would the two pointers as follow;
delete wptr; // delete an int object pointed to by wptr delete hstr; // delete a float object pointed to by hptr
In cases where a pointer points to an array, an additional [] is required to indicate that you are de-allocating pointer to an array. For example if we allocate memory as in
int *aPtr=new array
we can de-allocate it like this,
delete [] aPtr;
A serious runtime error can occur if delete is applied to a pointer which points to anything but a dynamically-allocated object (e.g., a variable on the stack). It is harmless to apply delete to a NULL pointer (a pointer initialled to zero).
Because of the limited memory resources, there is always the possibility that dynamic memory may be exhausted during program execution, especially when many large blocks are allocated and none released. Ordinarily, new returns zero if it is unable to allocate memory to a block of the requested size. As programmers it is our responsibility to deal with such possibilities by utilising the exception handling mechanisms available in C++ (to be explained later) which provide a practical method of dealing with such problems.
Pointer Arithmetic
In C++ one can add an integer quantity to or subtract an integer quantity from a pointer, a process known as pointer arithmetic. Pointer arithmetic is different from ordinary integer arithmetic. The outcome of pointer arithmetic depends on the type of the object pointed to. For example, type char is equal one bite while int is 4 bytes. So if
char *RmPtr = "ROOMS"; int RmNo[] = {5, 8, 6, 10}; int *PtrNo = &RmNo[0]; // pointer to first element
RmPtr++ increments RmPtr by one bite (i.e., one char) and makes it point to the second character of *RmPtr or "ROOMS" (i.e. “O”). However, PtrNo++ increments PtrNo by one int (i.e., four bites) making it point to the second element of *PtrNo or RmNo (i.e. 8). The figure below illustrates this diagrammatically.
A pointer to the array actually points to the first element of that array. Important to note also is that the name of the array and the pointer to that array are both pointers pointing to the first element of the array. In other words when we refer to *RmPtr we are referring to the first element of “ROOMS” i.e. “R”. Recall that the first element of an array is in position zero. Adding a number (n) to *RmPtr will make it point to the nth element of the array.
Thus *RmPtr + 1=’O’,…… *RmPtr + 4=’S’. Similarly, we can inrement *PtrNo as RmNo[1]= RmNo + 1=8, RmNo[2]= RmNo + 2=6, and RmNo[3]= RmNo + 3=.10. You can now conclude that RmNo[i]= RmNo + i.
As we indicated the name of the array and the pointer to that array both point to the first element. That means we can also use the poiter name to perform similar arithmetic i.e. *PtrNo + 1=8, *PtrNo + 2=6, and *PtrNo + 3=.10. There is however a difference between poiter to the array and the name of that array in that the name is a constant pointer to the first element of the array and so it cannot be made to point to anywhere else. But a pointer is a variable and can be made to point to any variable of its type.
It is also possible to subtract two pointers of the same type. For example:
int *PtrNo = &RmNo[1]; int *PtrNo1 = &RmNo[3]; int *PtrNo2 = PtrNo – PtrNo1; // i.e. *PtrNo2= RmNo[2];
Pointer arithmetic is very handy when processing the elements of an array. For example if we have
int RmNo[4] = {5, 8, 6, 10}; int *PtrNo1 = &RmNo;
we can declare a another pointer and assign it to one of elements of RmNo as follows
int *PtrNo2 = *PtrNo1 + 2; // *PtrNo2 is equal to 6
Below is an example of pointer arithmetic.
#include<iostream> using namespace std; int MaxNumber (int *Numb, const int size) { int temp=0; int maximum; for (int i =0; i*Numb +i) temp=*(Numb + j); return temp; } int main() { const int size=4; int Array[4] = {6, 8, 5, 10}; int *ArrayPtr=Array; cout<<"The Minimum Number of the array is "<<MaxNumber (ArrayPtr, size)<<endl; return 0; }
Output:
The Minimum Number of the array is 10
In the example above we have passed an int pointer ArrayPtr and the size of the array ArraySize to function MaxNumber. The function determines the maximum number without being restricted to a specific array size.
Function Pointer
Like variables, a function has an address which we can assign to a pointer. Once we have assigned a pointer, we can then use it to indirectly call the function. We declare a pointer to a function as follows
type (*pointerName)(type*, type*); // arguments are optional and they do not need to be pointers
For example
int (*studentPtr)(int, int); // with optional arguments
Defines a function pointer named stundntPtr which can store an address of any function that takes two integer arguments and returns an integer. Parentheses around pointerName are needed to indicate that it is a pointer to a function. Leaving the parentheses as in type *pointerName(type*, type*); would be a declaration of a function that receives two integers arguments and returns a pointer to an integer. Actualy when parentheses are left out the * operator would attempt to deference the value returned from a function call.
So if we have a function
int student (int RegNo, int Age)
We can assign studentPtr as follows
studentPtr = &student; // studentPtr now points to student.The & operator is optional
We can also initialise the pointer on declaration i.e.
int (*studentPtr)(int, int) = student;
The pointer and the function should much their prototypes in the same way our function “student matches pointer “studentPtr”.
Now that we assigned the address of the function to our pointer studentPtr, we can call function “student” either directly or indirectly. The following example illustrates this concept
#include<iostream> using namespace std; int student (int RegNo, int Age) { int form; if(Age<4 && RegNo<=3) form=1; else form=2; return form; } int main() { int Reg=2; int ag=3; int (*studentPtr)(int,int)=&student; cout<<" Calling 'stundent' directly, "; cout<<"The pupil is in form "<<student(Reg,ag)<<endl; cout<<" Calling 'stundent' indirectly using 'studentPtr', "; cout<<"The pupil is in form "<<(*studentPtr)(Reg,ag); return 0; }
Output:
Calling 'stundent' directly, The pupil is in form 1 Calling 'stundent' indirectly using 'studentPtr', The pupil is in form 1
Constant Pointers and Functions
There are four ways of passing a pointer to a function. These are: "non-constant pointer to a non-constant data", "non-constant pointer to a constant data", "constant pointer to a non-constant data" and "constant pointer to a constant data".
The “non-constant pointer to a non-constant data” refers to declaration of both the pointer and the data type it points to without a preceding const key word (i.e. int*nonconsitantPtr; ). In such a situation, the pointer has full access to modify the data. The pointer itself can be reallocated to point to another data.
The “non-constant pointer to a constant data” refers to a declaration of a pointer with a data it points to preceded with a const keyword but not the pointer itself (i.e. cons int* nonconsitantPtr; ). In such a situation, the pointer can access but cannot modify the data. However the pointer can be reallocated to point to another data. Think of a function printing elements of the array. We pass the size and the array into a function but neither do we want to change the elements nor the size. All we want is to print the elements. The size is passed into the function and used by the loop as a control variable and that is all. Therefore it should not be modified. Likewise the elements of the array are only being printed and so there is no need to change them. A non-constant pointer to a constant data will in this case be appropriate.
The “constant pointer to a non-constant data” refers to a declaration of pointer where only its name is preceded by a const keyword (i.e. int* const consitantPtr;). Such a pointer can access and modify the data, although it cannot be reallocated to point to another data. Unless constant pointer is deleted, it always points to its initial data. When passing arguments into a function, a constant pointer to a non-constant data can be used in the same way as an array name. An attempt to re allocate it, results in a compilation error. Constant pointers must be initialized on declaration.
The “constant pointer to a constant data”, gives a minimum privilege status. Both the pointer name and the data its points to are preceded with the const keyword (i.e. const int* const consitantPtr;). A pointer declared as such can access but not modify the data. It cannot be reallocated to point to another data. Any attempt to modify the data or to reallocate the pointer will result in a compilation error. A constant pointer to a constant data is mainly used to read an array. It must be initialized on declaration .
Below is an example the utilizes the four concepts
#include<iostream> using namespace std; void ChangeX(int*xPtr) // a non-const point to a non-constant data is passed as a parameter to function ChangeX() { *xPtr=*xPtr+1; } // A non-const pointer to a constant data is passed as a parameter to function ChangeXPtr(). Non-const point to a non constant data yPtr //is also passed to help reallocate xPtr void ChangeXPtr(const int*xPtr, int*yPtr) { //*xPtr=*xPtr+1; would produce an error because the dat is a constant and so it should be modified cout<<"The value of X using ChangeXPtr () is "<<*xPtr<<endl; xPtr=yPtr; cout<<"xPtr has been reallocated to point to same data with \n"; cout<<"*xPtr prints the value of Y=8 after reallocation as "<<*xPtr<<endl; } void ConstXPtr(int*const xPtr, int*yPtr) //xPtr is now a const point to a non constant data { *xPtr=*xPtr+1; //This now compliles fine because the data is not constant cout<<"The value of X using ChangeXPtr () is "<<*xPtr<<endl; //xPtr=yPtr; This will result in an error becasue xPtr is a constant cout<<"xPtr has failed to be reallocated to point point to yPtr \n"; cout<<"becuase its a const, so we print value y directly with *yPtr as "<<*yPtr<<endl; } // const pointers to constant data as parameters to function ConstXArray() void ConstXArray(const int*const xPtr, const int *const size) { //*size=size+1; and xPtr=size; cannot compile because these are constant pointers to constant data cout<<"The elements of xArray are "; for(int i=0; i<*size; i++) cout<<"The elements of xArray are "<<*xPtr+i<<" "; cout<<endl; } int main() { int x=5; ChangeX(&x); cout<<"The value of 'X=5' has been modified, now X = "<<x<<endl; cout<<endl; int y=8; ChangeXPtr(&x,&y); cout<<endl; ConstXPtr(&x,&y); cout<<endl; const int xSize=10; int xArray[xSize]={1,2,3,4,5,6,7,8,9,10}; ConstXArray(xArray,&xSize); return 0; }
Output:
The value of 'X=5' has been modified, now X = 6 The value of X using ChangeXPtr () is 6 xPtr has been reallocated to point to same data with *xPtr prints the value of Y=8 after reallocation as 8 The value of X using ChangeXPtr () is 7 xPtr has failed to be reallocated to point point to yPtr becuase its a const, so we print value y directly with *yPtr as 8 The elements of xArray are 1 2 3 4 5 6 7 8 9 10
Pointers , Strings and String Arrays
A string is a series of characters treated as a unit. A string can contain letters, digits and special characters such as +,-,*,/,&,$,etc. A literal string is a series of characters written in double quotation marks. For example;
“St. Luke”, “7 Street”, “New York”, “077-653-324”
String characters can be read using the stream extraction operator cin. For example,
char name[14]; cin>>name;
declares a string called name to store 14 characters. The cin extraction operator will read user input data up to 13 characters to leave room for the terminating character (‘\0’). The string entered by the user is read until a white space or an end of file is encountered. As such a string bigger than the specified length including the terminating character can lead to data loss. Extra characters in the string can overwrite the data in memory following the string array. To ensure that only stated limit (i.e the lenght of the array) is read from the user input data, we use the setw stream manipulator as follows;
cin>>setw(14)>>name;
setw(14) will ensure that only 13 characters and the 14th terminating character are read from the user input string and saved into the array name. Characters in excess of specified length (i.e. 13) will not be stored in name although it can be read in and stored in another variable.
However cin provides an alternative (cin.getline) way in which all the line of text including white spaces can be store in the name. For example;
char name[14]; cin.getline(name, 14, ‘\n’)
The cin.getline takes three arguments, the character array (i.e name), the length of the array (i.e 14) and the new line delimiter character (‘\n’). The cin.getline reads user input text until the specified length or the delimiter is encountered and or when the user enters end of file indicator. The delimeter character a default and as such it can be left out as follows;
cin.getline(name, 14)
A pointer based string is an array of characters ending with a null character (‘\0’) which marks the end of the string or the point where the string terminates in memory. A string in an array is accessed via a pointer to its first character.
An array can be an array of pointers. In other words, an array can contain pointers. Such an array is referred to as a string array . An array of pointers or a string array allows for declaration of a char string with a particular fixed length even if the strings entered will be more than the specified elements. Strings in the array can be of varying length, and so it makes sense to use an array string, given that only the initials of the string pointed to by the pointers will be actually stored in the array. This saves memory. Follow the example below.
#include<iostream> using namespace std; int main() { const char * const names[4]={"Steven","Mugume","Noume","Nuwa"}; cout<<" Character elements stored in ‘names’ are "; for (int i=0; i<4; i++) { cout<<*names[i]<<" "; } cout<<endl; cout<<"The actual string Elements of ‘names’ are "; for (int j=0; j<4; j++) { cout<<*(names+j)<<" "; } cout<<endl; return 0; }
Output:
Character elements stored in ‘names’ are S M N N The actual string Elements of ‘names’ are Steven Mugume Noume Nuwa
PREV:Introduction to C++ Control Statements | NEXT:Introduction to Arrays |