📜 ⬆️ ⬇️

Pointers in C ++. Introduction

I would like to clarify one thing from the very beginning - I do not consider myself to be a category of true coders, I myself study in a specialty not related to software development, and this is my first post. Please judge with all severity. So, in due time, either due to the fact that I slept in lectures, or I didn’t particularly delve into this topic, but I had some difficulties when working with pointers in pros. Now, none of my even the smallest bydlokoderkerskaya program can not do without pointers. In this article I will try to tell basic things: what pointers are, how to work with them, and where they can be applied. I repeat, the material outlined below is intended for beginners.


/ * Guys, the article found a lot of errors. Thanks to the people who made their comments. In this regard, after reading the article, be sure to re-read the comments * /

1. General information


So, what is a pointer? A pointer is the same variable, only it is initialized not by the value of one of the many data types in C ++, but by the address, the address of some variable that was previously declared in the code. Let us consider an example:
')
void main(){ int i_val = 7; } 

# Below here, of course, I lied to you guys. The variable i_val is static, it will obviously be placed on the stack. In the heap, space is allocated for dynamic objects. These are important things! But in this context, I, by making myself a remark, will allow myself to keep everything as it is, so do not swear much.

We declared an int variable and initialized it here. What happens when the program is compiled? In the RAM, in the heap, the free space of such a size will be allocated, so that it will be possible to easily place the value of our variable i_val there . The variable will occupy a certain area of ​​memory, occupying several cells depending on its type; Considering that each such cell has an address, we can find out the range of addresses within which the value of the variable is located. In this case, when working with pointers, we need only one address — the address of the first cell, and that is what it will serve as the value with which we initialize the pointer. So:

 void main(){ // 1 int i_val = 7; int* i_ptr = &i_val; // 2 void* v_ptr = (int *)&i_val } 

Using the unary operation of taking the address & , we retrieve the address of the variable i_val and assign it to the pointer. Here you should pay attention to the following things:
  1. The type used when declaring a pointer exactly must match the type of the variable whose address we assign to the pointer.
  2. As the type that is used when declaring a pointer, you can choose the type void . But in this case, when you initialize the pointer, you will have to cast it to the type of the variable it points to.
  3. Do not confuse the address taking operator with a reference to a certain value, which is also visually displayed by the symbol & .

Now, when we have a pointer to the variable i_va l, we can operate with its value not only directly using the variable itself, but also using the pointer to it. Let's see how this works on a simple example:

 #include <iostream> using namespace std; void main(){ int i_val = 7; int* i_ptr = &i_val; //      i_val cout << i_val << endl; // C1 cout << *i_ptr << endl; // C2 } 

  1. Everything is clear here - use the variable itself.
  2. In the second case, we access the value of the variable i_val through a pointer. But, as you have noticed, we do not just use the name of the pointer — the dereference operation is used here: it allows you to go from address to value.

In the previous example, only the display of the variable value on the screen was organized. Can we directly operate on the pointer with the value of the variable to which it points? Yes, of course, for this they are implemented (however, not only for this - but more on that later). All that is needed is to do a pointer dereference:

  (*i_ptr)++; //      : i_val++ // ..     i_val     7,  8. 

2. Arrays


Immediately proceed to the example - consider a static one-dimensional array of a certain length and initialize its elements:

 void main(){ const int size = 7; //  int i_array[size]; //    for (int i = 0; i != size; i++){ i_array[i] = i; } } 

And now we will access the elements of the array using pointers:

 int* arr_ptr = i_array; for (int i = 0; i != size; i++){ cout << *(arr_ptr + i) << endl; } 

What happens here: we initialize the pointer arr_ptr with the start address of the i_array array. Then, in the loop, we output the elements, addressing each with the help of the starting address and offset. I.e:

 *(arr_ptr + 0) 
this is the same zero element, zero offset ( i = 0),
 *(arr_ptr + 1) 
- the first ( i = 1), and so on.

However, a natural question arises here: why assigning to the pointer the address of the beginning of the array, we do not use the operation of taking the address? The answer is simple - using an array identifier without specifying square brackets is equivalent to specifying the address of its first element. The same example, only in the pointer “explicitly” add the address of the first element of the array:

 int* arr_ptr_null = &i_array[0]; for (int i = 0; i != size; i++){ cout << *(arr_ptr_null + i) << endl; } 
Let's go through the elements from the end of the array:
 int* arr_ptr_end = &i_array[size - 1]; for (int i = 0; i != size; i++){ cout << *(arr_ptr_end - i) << endl; } 
Remarks:
  1. Writing array [i] is equivalent to writing * (array + i ). Nobody forbids using them in combination: (array + i ) [1] - in this case, the offset goes to i , and another one. However, in this case it is not necessary to put an expression (array + i ) * . The presence of brackets is “compensates.
  2. Keep track of your “moves” through the elements of the array - especially if you want to use the pornographic recording method such as (array + i) [j].

3. Dynamic memory allocation


Here is a wonderful bun, because of which I use pointers. Let's start with dynamic arrays. Often, when solving a problem, there is a need to use an array of indeterminate size, that is, this size is not known in advance. Here dynamic arrays come to our aid - memory is allocated for them in the process of program execution. Example:

 int size = -1; //    -  // ,   //   size int* dyn_arr = new int[size]; 

What happens here: we declare the pointer and initialize it with the beginning of the array, for which the memory is allocated with the operator new on the size elements. It should be noted that in this case we can use the same techniques in working with pointers as with a static array. What follows from this is to extract - if you need some kind of structure (as an array, for example), but its size is unknown to you in advance, then simply make a declaration of this structure, and initialize it later. I will give a more complete example a little later, but for now - consider double pointers.

What is a pointer to a pointer? This is the same variable that stores the address of another “lower order” pointer. Why is it needed? To initialize a two-dimensional dynamic array, for example:

 const int size = 7; //    7x7 int** i_arr = new int*[size]; for(int i = 0; i != size; i++){ i_arr[i] = new int[size]; } 

And the triple pointer? Three-dimensional dynamic array. Uninteresting, you say, so you can continue to infinity. Oh well. Then let's imagine a situation when we need to place dynamic objects of some class MyClass in a two-dimensional dynamic array. What it looks like (an example only illustrates the use of pointers, the class shown in the example does not carry any meaning):

 class MyClass{ public: int a; public: MyClass(int v){ this->a = v; }; ~MyClass(){}; }; void main(){ MyClass*** v = new MyClass**[7]; for (int i = 0; i != 7; i++){ v[i] = new MyClass*[3]; for (int j = 0; j != 3; j++){ v[i][j] = new MyClass(i*j); } } } 
Here, two pointers are needed to form a matrix in which objects will be located, the third one is actually for placing dynamic objects there (not MyClass a , but MyClass * a ). This is not the only example of the use of pointers of this kind; examples will be discussed below.

4. Pointer as a function argument


To begin with, we will create two dynamic 4x4 arrays and initialize their elements with some values:

 void f1(int**, int); void main(){ const int size = 4; //     //    int** a = new int*[size]; int** b = new int*[size]; //      for (int i = 0; i != size; i++){ a[i] = new int[size]; b[i] = new int[size]; //   for (int j = 0; j != size; j++){ a[i][j] = i * j + 1; b[i][j] = i * j - 1; } } } void f1(int** a, int c){ for (int i = 0; i != c; i++){ for (int j = 0; j != c; j++){ cout.width(3); cout << a[i][j]; } cout << endl; } cout << endl; } 

The f1 function displays the values ​​of the arrays on the screen: its first argument is a pointer to a two-dimensional array, the second is its dimension (one value is indicated, because for the sake of simplicity we agreed to work with arrays where the number of rows coincides with the number of columns).

The task is to replace the values ​​of the elements of the array a with the corresponding elements from the array b , given that this should occur in some function that somehow deals with the processing of arrays. Purpose: to understand the method of passing pointers for their further modification.
  1. Option one. Pass the actual pointers a and b as function parameters:

     void f2(int** a, int** b, int c){ for (int i = 0; i != c; i++){ for (int j = 0; j != c; j++){ a[i][j] = b[i][j]; } } } 
    After calling this function in the body of main - f2 (a, b, 4) the contents of arrays a and b will become the same.
  2. Option two. Replace pointer value: simply assign pointer value b to pointer a.

     void main(){ const int size = 4; //     //    int** a = new int*[size]; int** b = new int*[size]; //      for (int i = 0; i != size; i++){ a[i] = new int[size]; b[i] = new int[size]; //   for (int j = 0; j != size; j++){ a[i][j] = i * j + 1; b[i][j] = i * j - 1; } } //    a = b; } 

    However, we are interested in the case when arrays are processed in some function. What first comes to mind? Pass the pointers as parameters to our function and do the same there: assign a pointer value b to pointer a . That is, to implement the following function:

     void f3(int** a, int** b){ a = b; } 
    Will it work? If we call the function f1 (a, 4) inside the function f3 , we will see that the values ​​of the array have really changed. BUT: if we look at the contents of array a in main, then we find the opposite - nothing has changed. So what is the reason? Everything is very simple: in the function f3 we worked not with the pointer a itself , but with its local copy! All changes that occurred in the function f3 - affected only the local copy of the pointer, but not the pointer itself a . Let's look at the following example:

     void false_eqv(int, int); void main(){ int a = 3, b = 5; false_eqv(a, b); //   a? //  ,  } false_eqv(int a, int b){ a = b; } 
    So, I think you understand what I am leading to. The variable a cannot be assigned in this way to the value of the variable b - because we passed their values ​​directly, and not by reference. It is the same with pointers - using them as arguments in this way, we deliberately deprive them of the possibility of changing the value.
    Option three, or work on the errors for the second option:

     void f4(int***, int**); void main(){ const int size = 4; int** a = new int*[4]; int** b = new int*[4]; for (int i = 0; i != 4; i++){ a[i] = new int[4]; b[i] = new int[4]; for (int j = 0; j != 4; j++){ a[i][j] = i * j + 1; b[i][j] = i * j - 1; } } int*** d = &a; f4(d, b); } void f4(int*** a, int** b){ *a = b; } 

    Thus, in main, we create a pointer d to pointer a , and that is what we pass it as an argument to the replacement function. Now, dereferencing d inside f4 and equating the value of pointer b to it , we replaced the value of this pointer a , and not its local copy, with the value of pointer b .

    By the way, why are we creating dynamic objects? Well, we did not know the size of the array, but why did we make instances of classes dynamic? Yes, because often, the objects we created are ours - they were generated, generated new data / objects for further work, and now it is time for them ... to die [fu, rudely] to leave the stage. And how will we do it? Simply:

     delete(a); delete(b); //       delete(v); //          delete(dyn_array); //      



    On this note, I would like to finish my story. If there are at least a couple of guys who like the style of presentation of the material, then I will try to continue ... oh, whoever I am cheating, I need an invite and that's it, give an invite and your eyes will no longer have to see this nonsense. Just kidding, of course. Scold, comment.

Source: https://habr.com/ru/post/256443/


All Articles