Extended Types of Pointers I
Learn about the concept of pointer to pointers and its diverse applications in programming.
In the previous lesson, we saw four basic types of pointers. There are some nontrivial types of pointers that every programmer should know and understand the usage of, including:
Pointer to pointers
void
pointerFunction pointers
Let’s start by discussing pointer to pointers and its applications.
Pointer to pointers
As we know, the pointer holds the address of any data type Type
, and its generic syntax is:
(Type)* ptr = some_address;// Exampleint val;int * ptr = &val;
But what if we want the pointer to hold an address of the pointer of the data type Type*
? To store that address, we need to have a double pointer.
(Type*)* pptr = address_of_pointer;// Exampleint val;int * ptr = &val;int* * pptr = &ptr; // usually written as int** pptr; // `pptr` is just a name you may write any name
Let’s look at an example to see how to use pointer to pointers:
#include <iostream>using namespace std;int main(){int val = 14;int * ptr = & val;cout <<"&val : "<<&val<<'\n';cout <<" val : "<<val<<'\n';cout <<"&ptr : "<<&ptr <<'\n';cout <<" ptr : "<<ptr <<'\n';cout <<"*ptr : "<<*ptr <<'\n';/* int **ptr1 = &val this instruction is not posible the double pointermust be pointed the memory of a pointer*/int **pptr = &ptr;cout<< "&pptr : "<<&pptr<<'\n';cout<< " pptr : "<<pptr<<'\n';cout<< "*pptr : "<< *pptr<<'\n';cout<< "**ptr : " << **pptr << '\n';return 0;}
The code begins by declaring an integer variable
val
and initializing it with the value14
. Then, a pointer variableptr
is declared and assigned the memory address ofval
using the address-of operator (&
). The subsequent lines print out the memory addresses and values ofval
,ptr
, and*ptr
. Next, a double pointerpptr
is declared and assigned the memory address ofptr
. The code then prints out the memory address ofpptr
, the value ofpptr
(which is the address ofptr
), the value pointed to bypptr
(which is the address ofval
), and the value pointed to by the double pointer (**pptr
), which is the value ofval
.
In the following drawing, we can see what the memory structure for the above program looks like. The red arrow doesn’t exist physically (it is just shown to help you understand that it’s a pointer pointing to the memory location).
Tip: When you run the above program, you might get different addresses each time. The above drawing is from when we ran the code while making this lesson.
Let’s look at a nontrivial example of how we can efficiently store upper and lower triangular matrices using the power of double pointers.
By utilizing dynamic memory allocation, pointers, and pointer to pointers, we can efficiently store an upper triangular matrix in memory. This approach allows us to store only the nonzero elements of the matrix and avoid storing certain zero elements, resulting in significant memory savings. Additionally, this approach offers a significant improvement over static two-dimensional arrays, which require each row to be of equal size. With dynamic memory allocation and pointer to pointers, we have the flexibility to store rows of different sizes, providing greater versatility and efficiency in managing memory for an upper triangular matrix.
Let’s write the code for storing a lower triangular matrix:
#include <iostream>using namespace std;void printLowerTriangularMatrix(int** matrix, int n){cout << "Lower Triangular Matrix:" << endl;for (int i = 0; i < n; i++){cout << "\t";for (int j = 0; j < n; j++){if(j>i)cout << "0" << " ";elsecout << matrix[i][j] << " ";}cout << endl;}}void displayMemoryStructure(int** &matrix, int n){cout << "&matrix: "<<&matrix<< " => "<< matrix << endl;// The pointers tablefor(int i=0; i<n; i++) // &matrix[i] => matrix[i]{cout << "\t & matrix["<< i << "]: "<< matrix+i <<" => " << *(matrix+i) << " {";for(int j=0; j<=i; j++){cout << (*(matrix+i)+j) << ": "<<*(*(matrix+i)+j) ;if(j!=i)cout << " , ";}cout << " }" << endl;}}int main(){int rows;// cout << "Dimension: ";cin >> rows;// Dynamic memory allocation for rowsint** matrix = new int*[rows];// Dynamic memory allocation for each rowfor (int i = 0; i < rows; i++)matrix[i] = new int[i + 1];// Storing values in the lower triangular matrixfor (int i = 0; i < rows; i++)for (int j = 0; j <= i; j++)matrix[i][j] = rand()%10; // assigning a random value// Printing the lower triangular matrixprintLowerTriangularMatrix(matrix, rows);cout << "\n\nMemory Structure\n\n";displayMemoryStructure(matrix, rows);// Deallocating the memoryfor (int i = 0; i < rows; i++)delete[] matrix[i];delete[] matrix;return 0;}
Enter the input below
Note: Enter the number of rows. For example:
3
,5
, or any other number.
This code demonstrates the creation and manipulation of a lower triangular matrix using dynamic memory allocation and pointers. The main focus is on memory structure and organization. A double pointer is created in the stack, which points to an array of pointers residing in the heap memory. Each pointer in the array then points to a one-dimensional array.
Visually, the memory structure can be represented as follows:
Each pointer in the array points to a one-dimensional array of varying sizes. The memory addresses of the array elements have a difference of 8 bytes because each pointer takes up 8 bytes of memory. However, when looking at the one-dimensional arrays themselves, the increment factor is 4 bytes since they are arrays of integers.
Overall, this memory structure allows for efficient storage of a lower triangular matrix by dynamically allocating memory only for the required elements. Pointers enable flexibility in accessing and manipulating the matrix while optimizing memory usage.
Practice exercise: Upper triangular matrix
Modify the above code to store an upper triangular matrix. Carefully think about what should be changed. Challenge yourself by seeing if you can also write a program that displays the entire memory structure of the upper triangular matrix.
If you have trouble getting to the solution, you can click the “Solution” button to see how to solve the problem.
Exercise: Allocating higher dimensional dynamic arrays
This idea can be easily extended to make multidimensional arrays. As we saw earlier, when allocating a single-dimensional array, we needed a Type *
. For allocating a two-dimensional array, we need a pointer to pointers Type**
, which will point to an array of Type*
.
Similarly, if we want to extend this idea to allocate a three-dimensional array, then we’ll need Type***
. Let’s look at the following example to construct a cube of the dimension
Here's an example of a
Here’s the implementation, which allocates a 3D cube with each face made up of 'a'
, the next face be made up of the character 'b'
, and so on. Complete the functions highlighted in the code below.
If you have trouble getting to the solution, you can click the “Show solution” button to see how to solve the problem.
#include <iostream>using namespace std;void AllocatingCube(char*** &cube, int size){ // Notice: cube pointer is passed by reference// Write your code here}void initCube(char *** cube, int size){// Write your code here}void printCube(char*** cube, int size){// Write your code here}void deallocateCube(char*** &cube, int size){// Write your code here}int main(){// This main should workint H = 5; // Size of each dimensionchar*** cube;AllocatingCube(cube, H);initCube(cube, H);printCube(cube, H);deallocateCube(cube, H);return 0;}
Expected output
Cube 5x5x5Face 1:A A A A AA A A A AA A A A AA A A A AA A A A AFace 2:B B B B BB B B B BB B B B BB B B B BB B B B BFace 3:C C C C CC C C C CC C C C CC C C C CC C C C CFace 4:D D D D DD D D D DD D D D DD D D D DD D D D DFace 5:E E E E EE E E E EE E E E EE E E E EE E E E E
Applications of pointer to pointer/pointers
Here are a few core applications:
Dynamic memory allocation: Pointer to pointers is used for managing complex data structures and efficient memory allocation.
Function parameters and return values: Pointer to pointers enables modifying variables and passing data between functions, allowing for flexible parameter passing and return values.
Multidimensional arrays: Pointer to pointers facilitates the creation and manipulation of multidimensional arrays without the restrictions imposed by fixed-size multidimensional arrays, providing flexibility in array dimensions. Unlike fixed-size multidimensional arrays, passing multidimensional arrays allocated through a pointer to pointers does not require explicitly specifying each dimension’s size, avoiding wastage of memory and the need for separate functions for each dimension.
Efficient memory management: Pointer to pointers allows for efficient memory management, as memory can be allocated and deallocated dynamically based on program needs.
These points highlight the key applications and advantages of using pointers to pointers in various scenarios.