For more blog posts, see the complete catalog of the romantic carriage of audio and video systems learning

The last article on C language pointer (2) mainly about the relationship between pointer and array, string pointer, pointer variable as a function parameter, like the movie finally entered the climax, next, the content of the pointer will enter the deep water area, we sit firmly, immediately set out ~

Today we are going to talk about: second-level Pointers, two-dimensional arrays, pointer arrays, function Pointers, function pointer arrays, structure Pointers. Why do you say deep water? Because the previous 2 as long as understand the concept basic can learn, but this chapter, the concept began to change around, no longer flat, even a little fancy, so need to consume some brain cells to understand, but do not worry, I have confidence in my expression (do not understand also spray.

The secondary pointer

A secondary pointer, as the name implies, is a pointer to a pointer, one pointer to another pointer, that is, the value of the pointer is the address of the other pointer.

Suppose there is a variable a of type int, p1 is a pointer variable to A, p2 is a pointer variable to P1, their relationship is shown as follows:The code definition is:

int a =5;
int *p1 = &a;
int **p2 = &p1;
Copy the code

Similarly, there are multi-level Pointers, but second-level Pointers are generally used, and more multi-level Pointers are rarely used. So what does a second-order pointer do?

Change the pointer inside a function to a pointer outside the function:

In ramble on the C language pointer (a) “through the pointer inside the function to modify function external variable value” chapter said that must change in the function in the value of variables defined outside a function, must pass through a pointer, the and so on, through the secondary secondary pointer to a function pointer, transfer within the function outside a pointer pointing to modify function.

Such as:

void secondPointer(int **a) {
    int *p = static_cast<int* > (malloc(sizeof(int)));// use malloc to ensure that memory is not freed after leaving the function
    *p = 5;
    *a = p;// Change the pointer to the pointer to which a secondary pointer is passed
    printf("secondPointer *a %#X\n", *a);
}

int main(a) {
    int i = 10;
    int *a = &i;
    printf("before secondPointer a %#X\n", a);// Change the address to which a was pointing

    secondPointer(&a);
    printf("after secondPointer a %#X\n", a);// Change the address after A to the address before A
    printf("after secondPointer *a %d\n", *a);// Change the address to which a was pointing
}
Copy the code

Running results:

before secondPointer a 0X62FE1C
secondPointer *a 0XDB1C40
after secondPointer a 0XDB1C40
after secondPointer *a 5
Copy the code

You can see that with the secondPointer function, A successfully points to 5 (the variable p points to in the function) and the address is changed.

2 d array

Another common use of second-level Pointers is to manipulate two-dimensional arrays.

Let’s start with a two-dimensional array, which is simply an array of arrays. Each array element is an array. Conceptually, it is two-dimensional, with rows and columns, but in memory all array elements are arranged consecutively with no “gaps” between them. Take the following two-dimensional array A as an example:

int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };

Conceptually, the distribution of A is like a matrix:

0   1   2   3
4   5   6   7
8   9  10  11
Copy the code

But in memory, the distribution of A is one-dimensional and linear, and the entire array occupies a contiguous chunk of memory:

The two-dimensional array in C language is arranged in line, that is, a[0] line is stored first, then A [1] line is stored, and finally A [2] line is stored. The four elements in each row are also placed in sequence. Array A is of type int, each element occupies 4 bytes (assuming the current environment occupies 4 bytes int), the entire array occupies 4×(3×4) = 48 bytes.

First, we introduce the concept of array Pointers, i.e. Pointers to arrays, defined as follows:

int (*p)[4] = &a;// The array pointer p points to array A
Copy the code

, with an array name array name said in the expression of the first address is the first element of the array, so the array name arithmetic step length is the length of the array elements, and array pointer step length is really the total size of the array (if you don’t understand the step length, please see: rambling C language pointer (a) a pointer arithmetic section) :

int a[] = {1.2.3.4.5.6};
    int (*p)[6] = &a;
    printf("secondPointer sizeof(int) %d\n".sizeof(int));
    printf("secondPointer p %#X\n", p);
    printf("secondPointer p+1 %#X\n", p+1);
Copy the code

SecondPointer Sizeof (int) 4 secondPointer P 0X62FE00 secondPointer P +1 0X62FE18

Notice that p+1 is 24 bytes larger than p (base 10), which is the size of six ints.

C allows you to decompose a two-dimensional array into one-dimensional arrays for processing. For array A, it can be decomposed into three one-dimensional arrays, namely A [0], a[1], and a[2]. Each one-dimensional array contains four elements, for example, a[0] contains a[0][0], a[0][1], a[0][2], a[0][3].

Suppose the address of the 0th element in array A is 1000, then the starting address of each one-dimensional array is shown as follows: 在On C Language Pointers (2)The array name of the one-dimensional array A refers to the first address of the array element type. Similarly, the two-dimensional array A above,A refers to the first element (array A [0], where a[n] represents the NTH array (row NTH)).Because a is a pointer to an array, the step size is the length of the arrayIn this case, 4 times sizeof (int). And since a[n] represents the NTH array,Therefore, a[n] itself can be regarded as the array name of the NTH array, that is, a[n] itself is a pointer to the address of the first element of the NTH array, pointing to the element type of the NTH array, so the step size is the size of the array element typeIn this case, sizeof (int).

 int a[3] [4] = {{0.1.2.3}, {4.5.6.7}, {8.9.10.11}};printf("sizeof(*(a)):%d\n".sizeof(*a));// Find the size of the data a points to
    printf("a:%d\n", a);
    printf("a+1%:d\n", a+1);Sizeof (*a) = sizeof(*a);

    printf("sizeof(*a[0]):%d\n".sizeof(*a[0]));// Find the size of the data pointed to by a[0]
    printf("a[0]:%d\n", a[0]);
    printf("a[0]+1:%d\n", a[0] +1);Sizeof (*a[0]); sizeof(*a[0]);

    printf("&a:%d\n", &a);// Remember that the array name is not converted to a pointer when it is an ampersand, so it is an array address
    printf("&a+1:%d\n", &a+1);// look at the step size of &a
Copy the code

Sizeof (*(a)):16 A :6487536 A +1%:d sizeof(* A [0]):4 A [0]:6487536 A [0]+ 1:6487540&a: 6487536&a +1:6487584

It can be seen that the address a and A [0] point to is the first address of the first element of the first array of the whole two-dimensional array, but the step size of A is 16, that is, the pointing type is int[4], and the step size of A [0] is 4, that is, the pointing type is int.

According to the result, it can be further deduced that a+n refers to the address of the first element of the NTH +1 array, and the type pointing to is a one-dimensional array, so (a+n) represents an array. A [x]+y refers to the first address of the y+1 element in the x+1 array. The pointing type is an array element, and because a[x] is equivalent to (a+x), and because a[x]+y is equivalent to a one-dimensional array a[x] offset by y steps, that is, a[x]+y is equivalent to * ((a+x)+y), Represents an array element **. Summary: under a [I] = = (a + I) a [I] [j] = = * (a [I] + j) = = * (* (a + I) + j)

Thus, the traversal of the two-dimensional array can be further optimized:

int a[3] [4] = {{0.1.2.3}, {4.5.6.7}, {8.9.10.11}};for(int i=0; i<3; i++){
   for(int j=0; j<4; j++)
      printf("value :%d\n",*(*(a+i)+j));
}
Copy the code

&a+1 = 12*sizeof(int); &a+1 = 12*sizeof(int)

Pointer to an array

Pointer array, as the name implies, is an array of Pointers, that is, the elements of the array are Pointers.

The array pointer is defined as:

int (*p)[2];
Copy the code

[] has a higher priority than [], so we add () to make and p a pointer, and then [] to make the pointer point to the array.

Pointer arrays are defined as:

int *p[2];
Copy the code

P is combined with [], so p is an array, and then with *, indicating that the array element is of type pointer.

Pointer arrays can be defined as follows:

int a = 1, b = 2, c = 3;
// Define an array of Pointers
int *arr[3] = {&a, &b, &c};// Create a pointer array
Copy the code

Before we say the array name in expression into a pointer to the first element of the array, the array pointer array name also can be in expression of transit into a pointer, the pointer is also to the first array element, and the first element is the pointer array, and so the array name into a pointer will naturally become a secondary pointer.

Because the array name arr is converted to a second-level pointer, we add two ** to the value:

int a = 1, b = 2, c = 3;
// Define an array of Pointers
int *arr[3] = {&a, &b, &c};// Create a pointer array
printf("%d, %d, %d\n", **(arr+0), **(arr+1), **(arr+2));// Because the array name arr is converted to a second-level pointer, we need to add 2 ** to the value
Copy the code

Running results: 1, 2, 3

One common use of pointer arrays is the use of and string arrays:

 char *str[3] = {
            "man"."woman"."pig"
    };
Copy the code

Remember that STR must be an array of Pointers, because stored in the constant area of the string itself, it is converted in the expression to a pointer to char (the first character address).

So when reading a character from a string in a character creation array, we often use a secondary pointer to operate, such as printing the fourth character of the second string in STR above:

printf("%c\n", *(*(str+1) +3));//* (STR +1) *(STR +1) *(STR +1)+3)
Copy the code

Running result: a

A function pointer

An array takes up a continuous area of memory, a function also takes up a continuous area of memory, in the design of C language, and also like an array, the function name can also represent a function first address ~ ~ so, make a pointer to hold function have become the first address this pointer, it is called a function pointer.

Function Pointers are defined as follows:

int(*p)(int.int);
Copy the code

This statement defines a pointer variable p to the function. First, it is a pointer variable, so it has an “*”, that is, (* p); The second int indicates that the pointer can point to a function that returns an int. The two ints in parentheses indicate that the pointer variable can point to a function that takes two arguments and is of type int. We define a pointer variable p that can point to a function that returns a value of type int and takes two integer arguments.

The pointer p refers to a function of type int (int, int).

The general format is: function return value type (pointer variable name) (function argument list); *

So what does a function pointer mean? Familiar with Java’s shoes must know callback interface this thing, when we are in a class method or there is a logic need from external incoming, often with a callback interface, familiar with children’s shoes must know kotlin function in kotlin can be as member variables and parameters, so the function of a function pointer in C is very similar.

For example, calculate is a calculation function, but the exact calculation needs to be passed in externally. Here, the function pointer P is passed in as a parameter to represent a specific calculation logic:

/** * add * @param a * @param b * @return */
int plus(int a,int b){
    return a+b;
}

/** * subtraction * @param a * @param b * @return */
int sub(int a,int b){
    return a-b;
}

* @param a * @param b * @param p * @return */
int calculate(int a,int b,int (*p)(int.int)){
    return p(a,b);
}

int main(a) {
    int a = 2;
    int b = 1;
    // Determine the calculate method at this point
    printf("calculate plus %d\n", calculate(a,b,plus));
    printf("calculate plus %d\n", calculate(a,b,sub));
}
Copy the code

Results: Calculate Plus 3 Calculate Plus 1

Structure pointer:

After reading the previous articles, this is really just a pointer to a structure object, just like a Java reference, so it’s easy to understand.

Suppose there is a cat structure:

struct Cat{
        int head;/ / the cat head
        int tail;/ / the cat tail
    };

    Cat cat;
    cat.head = 5;
    Cat* c = &cat;
    printf("c->head: %d\n", c->head);
Copy the code

(There is nothing to say, except that the basic type is changed to the structure type, which is too easy for those familiar with object-oriented languages.)

But here’s an interesting point, which is an example of why Pointers can be a powerful and dangerous tool:

Cat. tail is an int, but what happens if we try to cast cat.tail to the head value of the converted pointer?

((Cat*)&(cat.tail))->head=12;
printf("&(cat.head): %d\n", &(cat.head));// For clarification, print the head address
printf("&(cat.tail): %d\n", &(cat.tail));// Print the tail address for clarification
  
printf("cat.head: %d\n", cat.head);
printf("cat.tail: %d\n", cat.tail);
Copy the code

Run the following command: &(cat.head): 6487572 &(cat.tail): 6487576 cat.head: 0 cat.tail: 12

Head: 0, cat.tail: 12? I assigned 12 to head. Isn’t it a little magical? Let’s see what we did.

((Cat*)&(cat.tail))->head=12;
Copy the code

In this line of code, we give the computer a cat.tail and then tell the computer to treat the tail as if it were a cat! Give the cat’s head a value of 12, and the computer gets carsick and can’t tell the cat’s head from its tail.

If you really understand the pointer step size mentioned in the ramble on Pointers (1) in C, it should be easy to see why. In fact, the computer doesn’t care whether you give a Cat or a dog, it only cares about the memory. The structure Cat is given to the computer, and the computer doesn’t understand what head and tail are, but it knows that these are two segments of memory created by the Cat.

We can see that head and tail are in contiguous memory, like this:

When the &(cat.tail) operation is, the pointer looks like this:(Cat*)&(cat.tail), the computer treats Cat as the blue part above:

((Cat*)&(cat.tail))->head=12; Printf (” Cat. Tail: %d\n”, Cat. Tail); printf(” Cat. Tail: %d\n”, Cat. It’s 12 when you print it out.

For a similar example, look at arrays:

int arr[5];
arr[3] = 1 << 16; * * ((short*)arr)[6] = 2; **// Key, the arR pointer is strongly cast to short, and the pointer is moved forward by 6 steps
printf("arr[3]: %d\n", arr[3]);
Copy the code

What’s the print result?

Operation result: ARR [3]: 65538

And with the structure example, you should be able to guess why.

After running arr[3] = 1 << 16; After that, the state in memory is:

After executing ((short*)arr)[6], in the eyes of the computer, arR becomes:

(The current environment is small – end mode)((short*)arr)[6] = 2; After:

Printf (“arr[3]: %d\n”, arr[3]); When, the two small cells in the figure above are combined again, and the result is 65538 (small end mode, so the small cell on the right is high).

Of course, because the C language array is not out of bounds check, so very free, can also do the following operations:

int i = *(((short*)(&arr[- 1+))4);
Copy the code

This is based on the ashore example should be able to think of the answer ~~

The reason why the above several SAO flavor full operation, the purpose is to let you can really look at the pointer from the point of view of memory, do not be confused by the surface, must be based on the rampage on C language pointer (a) pointer arithmetic operation section of the last soul torture to study the pointer related problems, really understand the pointer from the essence.

That’s the end of the C blog post, and the next one is about C++ : First taste of the C++ world