Last Updated:

Pointers and references | Strings & Examples

1. Pointers

Every variable you declare in a program has an address—the number of the memory cell in which it is located. An address is an inherent characteristic of a variable. You can declare another variable that will store this address, called a pointer. Pointers are used when passing parameters to the function that we want to change, when working with arrays, when working with dynamic memory, and in some other cases.

Declaring a pointer has the following syntax: <type> *<IDent> [ = <initializer>];

A pointer can point to values of the base, enumerable type, structure, union, function, pointer.

int *pi;Pointer to int
char *ppc;Pointer to char pointer
int* p, s;Bad ad style is not a pointer! s
int *p, s;It can be seen that varb>s is not a pointer
int *p, *s;Two pointers
char *names[] = {"John", "Anna"};Pointer array

In the last declaration, two operators are used to form the type: * and [ ], one preceding the name and the other after. The use of ad operators would be greatly simplified if they were all either prefixes or suffixes. However, *, [] and () have been designed to reflect their meaning in expressions. Thus, * is a prefix and [] and () are suffixes. Suffix operators are "stronger bound" to the name than prefix operators. Therefore, it means an array of pointers to any objects, and to define types like "pointer to a function", you must use parentheses. *names[]

There are two operations that are relevant to working with pointers: These operations are:

  • address acquisition operation (addressing) &;
  • the operation of taking the value to the address (indirect addressing or dereferencing) *.
int a, *p; 
p = &a;A variable is assigned an address to a variable pa
*p = 0;the value at the address in the variable (that is, the value of the variable ) becomes 0 pа

1.1. Address arithmetic

You can do the following over the pointers:

  • Add a pointer and an integer.
  • Subtract an integer from the pointer.
  • subtract the pointer from the pointer - an integer is obtained.

In this case, the integer added/ subtracted or received does not indicate the number of bytes, but the number of elements of the type to which the pointer points, i.e. this number is multiplied or divided by the size of the type.

 

Subtracting pointers from each other is defined only if both pointers point to elements in the same array (although the language does not allow you to quickly check whether this is the case). Subtracting one pointer from another will result in the number of array elements (an integer) between those pointers. Adding an integer to the pointer and subtracting the integer from the pointer produces a new pointer. If the pointer obtained in this way does not point to an element of the same array (or to the element following the last) as the original pointer, then the result of its use is not determined.

Pointers can be used to process arrays.

int a[100], n, *end, *p; 
end = a + n;// n is the number of elements in the array . The name of the array is the address of its origin (see Lecture 3). Thus, is the address of the element after the last element of the array. aend
for (p = a; p < end; p++) 
printf("%4d", *p); 

From the fact that you can subtract an integer from the pointer, it follows that it is possible to use negative numbers in the operation [ ].

int a[N]; int *endA = a + N - 1, i; for (i = 0; i < N; i++) printf("%4d", endA[-i]);

1.2. Pointer to void

Pointers to the void type have a special application. A pointer to the void type can point to values of any type. However, to perform operations on a pointer on void or on a pointer object, you must explicitly cast the pointer type to a type other than a pointer to void.

A pointer to an object of any type can be assigned to a variable of type void*, one void* can be assigned to another void*, the pair void* can be compared to equality and inequality, and finally, void* can be explicitly converted to a pointer to another type. Other operations can be dangerous because the compiler does not know what kind of object the pointer actually refers to. Therefore, other operations cause a compile-time error message. To use void*, you must explicitly convert it to a pointer of a certain type.

void f(int *pi) 
{ void *pv = pi;

Correct – implicit type conversion from int* to void*
*pv;Error – you cannot derefere void*
pv++;Error – void* increment cannot be incremented
int *pi2 = static_cast<int*>(pv);Explicit conversion to int*
double *pd1 = pv;Error
double *pd2 = pi;Error
double *pd3 = static_cast<double*>(pv);Unsafe!
} 

In general, it is not safe to use a pointer that has been converted to a type other than the type of object it is pointing to.

The main uses of void* are passing pointers to functions that are not allowed to make assumptions about the type of objects, as well as returning objects of an "unspecified" type from functions. To use such an object, you must explicitly convert the pointer type.

Functions that use void* pointers typically exist at the lowest levels of the system, where hardware resources are dealt with. The presence of void* at higher levels is suspicious and is most likely an indicator of error during the design phase.

1.3. Function Pointers

There are only two things you can do with a function: call it and get its address. The pointer obtained by taking the address of the function can then be used to call the function.

void f(int x) { ... } 
void (*pf)(int);A pointer to a function. Parentheses are mandatory!
void g() 
{ pf = &f;// pf points to the f function
pf(0);Calling the f function via a pointer pf
} 

The compiler recognizes what is a pointer and calls the function it points to. That is, dereferencing a pointer to a function using the * operation is optional. Similarly, it is not necessary to use the operation & to obtain the address of the function. pf

Parameters of pointers to a function are declared in the same way as the parameters of the functions themselves. When assigning function types, they must match exactly.

typedef void (*PF)(int);You can use the typedef declaration to declare a function pointer type
PF pf;Declare the pointer to the function itself using a predefined type
void f1(int x) { ... } int f2(int x) { ... } void f3(char x) { ... } 
void f () 
{ pf = &f1;Correct
pf = &f2;Error - Wrong Return Type
pf = &f3;Error - wrong parameter type
} 

The rules for passing parameters when calling functions through a pointer are the same as when calling functions directly.

1.4. Pointers and Constants

Pointer operations involve two objects— the pointer itself and the object it references. Placing the const keyword before declaring a pointer makes an object a constant, not a pointer. To declare the pointer itself as a constant, the declaration operator * const is used, not just *.

void f(char *p) 
char s[] = "const"; 
const char *p1 = s;Pointer to a constant
p1[3] = 'r';Error - indicates a constant p1
p1 = p;Correct
char * const p2 = s;Constant pointer (initialization required)
p2[3] = 'r';Correct
p1 = p;Error - is a constant p2
const char * const p3 = s;Constant pointer to constant
p3[3] = 'r';Error - indicates a constant p3
p3 = p;Error - is a constant p3
} 
 

You can assign an address to a variable pointer to a constant because it is a harmless operation. You cannot, however, assign the address of a constant to an arbitrary pointer, because in this case you could change the value of the constant object.

1.5. Zero

Zero is of type int. Thanks to standard transformations, zero can be used as a constant of any integral type, floating-point type and pointer. The type of zero is determined by context. Zero, typically (but not always), will be physically represented as a sequence of zero bits of appropriate length.

It is guaranteed that there are no objects with a zero address. Therefore, a pointer of zero can be interpreted as a pointer that does not refer to anything.

In C, a NULL macro was usually defined to represent such a null pointer. Because C++ validates types more tightly, using a simple zero instead of null will cause fewer problems.

1.6. Pointer to pointer

It is possible to declare a variable that contains the address of another variable, which in turn is also a pointer. Such a variable may be necessary if you want to change the address of an object in a function. However, the presence of more than two asterisks in the variable declaration most likely indicates poor design.

int **ppi;

Declare a pointer to an integer pointer
void f(int **ppi) 
int *pi = *ppi;The pointer to the integer is assigned a value stored at the address,
...contained in a pointer to the pointer to the whole
}