Pointers are very important, this post is a learning note form the cplusplus.com pointer section as well as the Stanford CS library booklet Pointers and Memory (By Nick Parlante, Copyright ©1998-2000, Nick Parlante). The first topic about pointer is how to declare them, how to dereference them, and how to assign a value to a pointer. Since those concepts are very simple and easy to be understood, we omit those and mainly focus on the following topics:
- Pointer and array
- Pointer arithmetics
- Pointer to pointer
- void pointer
- null pointer
- Pointer to function
Pointer and array
The usage of pointers and arrays are very similar, the core difference between them is that the array name is a constant pointer, while the pointer can be altered in the program. The following program illustrates almost all the possibilities that how to interchangeably use pointers and arrays. This piece of program is very important to understand the difference between pointers and arrays.
1#include <iostream>
2using namespace std;
3
4int main ()
5{
6 int numbers[5];
7 int * p;
8 p = numbers; *p = 10;
9 p++; *p = 20;
10 p = &numbers[2]; *p = 30;
11 p = numbers + 3; *p = 40;
12 p = numbers; *(p+4) = 50;
13
14 for (int n=0; n<5; n++)
15 cout << numbers[n] << ", ";
16
17 return 0;
18}
The output of the program is of course:
110,20,30,40,50
Please note the highlighted line is correct, which we try to assign pointer variables to members
. But members = p
is not valid.
The following two expressions are equivalent and valid both if a is a pointer or if a is an array.
Pointer arithmetics
We can do arithmetic operations on pointer variables, however, the operation depends on the variable type it points to. Suppose we have defined three pointers in our program:
When we do the following operation on them, the results are shown in the following.
Both the increase ++
and decrease --
operators have greater operator precedence than the dereference operator *
, but both have a special behavior when used as suffix (the expression is evaluated with the value it had before being increased). Therefore, the following expression may lead to confusion:
1*p++;
Because ++
has greater precedence than *
, this expression is equivalent to *(p++)
. Therefore, what it does is to increase the value of p
(so it now points to the next element), but because ++
is used as postfix the whole expression is evaluated as the value pointed by the original reference (the address the pointer pointed to before being increased).
Notice the difference with:
1(*p)++ ;
Here, the expression would have been evaluated as the value pointed by p
increased by one. The value of p
(the pointer itself) would not be modified (what is being modified is what it is being pointed to by this pointer).
The following expression is very easy to get wrong, it is a very important point in using pointers and the incremental sign. If we write:
1*p++ = *q++;
Because ++
has a higher precedence than *
, both p
and q
are increased, but because both increase operators ++
are used as postfix and not prefix, the value assigned to *p
is *q
before both p
and q
are increased. And then both are increased. It would be roughly equivalent to:
Pointer to pointer
C++ allows the use of pointers that point to pointers, that these, in its turn, point to data (or even to other pointers). In order to do that, we only need to add an asterisk *
for each level of reference in their declarations:
Void pointer
The void type of pointer is a special type of pointer. In C++, void represents the absence of type, so void pointers are pointers that point to a value that has no type (and thus also an undetermined length and undetermined dereference properties).
This allows void pointers to point to any data type, from an integer value or a float to a string of characters. But in exchange they have a great limitation: the data pointed by them cannot be directly dereferenced (which is logical, since we have no type to dereference to), and for that reason we will always have to cast the address in the void pointer to some other pointer type that points to a concrete data type before dereferencing it.
One of its uses may be to pass generic parameters to a function:
1// increaser
2#include
3using namespace std;
4
5void increase (void* data, int psize)
6{
7 if ( psize == sizeof(char) )
8 { char* pchar; pchar=(char*)data; ++(*pchar); }
9 else if (psize == sizeof(int) )
10 { int* pint; pint=(int*)data; ++(*pint); }
11}
12
13int main ()
14{
15 char a = 'x';
16 int b = 1602;
17 increase (&a,sizeof(a));
18 increase (&b,sizeof(b));
19 cout << a << ", " << b << endl;
20 return 0;
21}
sizeof
is an operator integrated in the C++ language that returns the size in bytes of its parameter. For non-dynamic data types this value is a constant. Therefore, for example, sizeof(char) = 1
, because char type is one byte long.
Null Pointer
A null pointer is a regular pointer of any pointer type which has a special value that indicates that it is not pointing to any valid reference or memory address. This value is the result of type-casting the integer value zero to any pointer type.
Do not confuse null pointers with void pointers. A null pointer is a value that any pointer may take to represent that it is pointing to “nowhere”, while a void pointer is a special type of pointer that can point to somewhere without a specific type. One refers to the value stored in the pointer itself and the other to the type of data it points to.
Pointer to function
Pointer to functions is very helpful usage in C++. C++ allows operations with pointers to functions. The typical use of this is for passing a function as an argument to another function, since these cannot be passed dereferenced. In order to declare a pointer to a function we have to declare it like the prototype of the function except that the name of the function is enclosed between parentheses () and an asterisk (*) is inserted before the name:
1// pointer to functions
2#include <iostream>
3using namespace std;
4
5int addition (int a, int b)
6{ return (a+b); }
7
8int subtraction (int a, int b)
9{ return (a-b); }
10
11int operation (int x, int y, int (*functocall)(int,int))
12{
13 int g;
14 g = (*functocall)(x,y);
15 return (g);
16}
17
18int main ()
19{
20 int m,n;
21 int (*minus)(int,int) = subtraction;
22
23 m = operation (7, 5, addition);
24 n = operation (20, m, minus);
25 cout << n;
26 return 0;
27}
In the example, minus is a pointer to a function that has two parameters of type int. It is immediately assigned to point to the function subtraction, all in a single line:
1int (* minus)(int,int) = subtraction;