[C Language] Advanced Pointers (1)

Table of contents

Preface

1. Character pointer

2. Pointer array

3. Array pointer

3.1 Definition of array pointer

 3.2 &array name VS array name

 3.3 Use of array pointers

4. Array parameters and pointer parameters

4.1 One-dimensional array parameter transfer

 4.2 Two-dimensional array parameter transfer

 4.3 First-level pointer parameter passing

 4.4 Second-level pointer parameter passing

5. Function pointer


 

Preface

Pointers play an important role in the C language. As beginners of C language, we have already come into contact with some pointer knowledge in the "Pointers" chapter and know the concept of pointers:

  1. A pointer is a variable used to store an address. The address uniquely identifies a memory space.
  2. The size of the pointer is fixed 4/8 bytes (32-bit platform/64-bit platform).
  3. Pointers have types. The type of the pointer determines the +- integer step size of the pointer and the permissions during the pointer dereference operation.
  4. Pointer arithmetic.

 Now that we have learned the basics of pointers, in this blog, we will delve into some advanced uses of pointers.

1. Character pointer

Among the types of pointers, we know that there is a pointer type called character pointer char* 

 General use:

int main()
{
	char ch = 'w';
	char* pc = &ch;
	return 0;
}

 There is another way to use it:

int main()
{
	const char* pstr = "abcdef";
	printf("%s\n", pstr);
	return 0;
}

Many people have a misunderstanding about the above usage, thinking that the string abcdef is placed in the character pointer pstr, but the essence is that the address of the first character in the string abcdef is placed in pstr.

You can try to prove it below :

  • The address of the string "abcdef" is the address of a, then "abcdef"[3] is equivalent to "" a address [3] "", which confirms that the address of the first character is indeed stored in the pointer pstr.
  • The array name is the address of the first element. Since the character pointer stores the address of the first character, try using array subscripts to access the content pointed to by the character pointer. It turns out that it can be printed out as well.
  • Therefore, you can completely imagine the constant string as an array, and then use the character pointer to receive it, and the operation is consistent with the array.

Note: It is best to use const to modify the character pointer storing the string, because the string is a constant and is not allowed to be modified. If modified, the program will crash.

A classic [interview question] from "Sword Pointer Offer":

What is the final output of the following? 

#include <stdio.h>
int main()
{
	char str1[] = "nash.";
	char str2[] = "nash.";
	const char* str3 = "nash.";
	const char* str4 = "nash.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

【Answer】

str1 and str2 are not same

str3 and str4 are same

【explain】

  • str1 and str2: When using the same constant string to initialize different arrays , different memory blocks will be opened up. str1 and str2 actually create a space to store nash., so their addresses are inconsistent.
  • str3 and str4: When pointing to a string with a pointer, because nash is a constant string and will not be modified, then since it cannot be modified, the compiler does not need to save multiple copies, only one copy is needed, and then the pointers are Just point to the same memory, so the address values ​​are consistent.

【Extended】

int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";

	if (&str3 == &str4)
		printf("Yes");
	else
		printf("No");
	return 0;

【Answer】

 The result is No, because the address values ​​of str3 and str4 are different, but the contents pointed to by str3 and str4 are the same.

2. Pointer array

Here we review, what does the following pointer array mean?

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

Let’s compare it to:

Character array - an array that stores characters

Integer array - an array that stores integers

So:

Pointer array - an array that stores pointers, that is, the elements stored in the array are all pointer types .

 So many people have a question, what is the use of pointer array?

The intuitive feeling of many people is to define abcd and then store their addresses in an array of integer pointers. as follows:

错误的使用方式,【没有意义】
int main()
{
	int a = 0;
	int b = 1;
	int c = 2;
	
	int* arr[3] = { &a,&b,&c };
	return 0;
}

But few people will use it this way. There is no such usage scenario and no one will use it this way. Writing it this way makes no sense.

One of the correct usage scenarios:

【可以使用指针数组模拟一个二维数组】
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	
	int* arr[3] = { arr1,arr2,arr3 };
    //遍历三个数组
	int i = 0;
	for ( i = 0; i < 3; i++)
	{
		int j = 0;
		for ( j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 Use an array of pointers to maintain multiple arrays to simulate a two-dimensional array, so the operation is similar to a two-dimensional array. 

 There is also the use of pointer arrays to maintain multiple strings:

 Of course, there are many other application scenarios, but due to limited space, I will not list them all here.

3. Array pointer

3.1 Definition of array pointer

Are array pointers pointers? Or an array?

Let’s compare it to:

Integer pointer - pointer to integer

Character pointer - pointer to a character

So:

Array pointer - pointer to an array

Which of the following codes is an array pointer?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

 【explain】

p2 is an array pointer.

p2 is first combined with *, indicating that p2 is a pointer variable, and then points to an array of 10 integers. So p is a pointer, pointing to an array, called an array pointer.
Note here: [ ] has a higher priority than *, so () must be added to ensure that p2 is combined with * first.

 3.2 &array name VS array name

Mentioned in previous blogs (link: click to go ):

The array name is the address. Generally speaking: the array name is the address of the first element of the array.

However, two special cases exist:

1. sizeof (array name) , the array name here represents the entire array, and the calculation is the size of the entire array, in bytes.

2. &Array name , the array name here represents the entire array, and the address of the entire array is taken out.

Otherwise , all array names encountered are the addresses of the first elements of the array.

 It can be proved from the above figure that when the first group and the second group +1 are used, only four bytes are skipped, that is, arr represents the address of the first element.

The third group can find that 40 bytes are skipped between &arr and &arr+1, which is the size of an entire array, so it is proved that &arr represents the entire array.

The pointer type determines how many bytes the pointer + 1 + has.

So let’s make a bold guess. The type of arr is int*, and the type of &arr[0] is also int*.

Then the type of &arr is int (*)[10], that is, the array pointer stores the array.

 Now that you have read what an array pointer is, let's do a question to test whether you understand it.

【practise】

What is the type of pc in the code below?

int main()
{
	char* arr[5];
	pc = &arr;  //给pc定义类型

	return 0;
}

 【Answer】

char* (*pc)[5] = &arr; 

【explain】

First, pc must be a pointer, that is (*pc)

Points to an array with 5 elements, that is (*pc)[5]

The type of each element of the array is char*, that is, char* (*pc)[5]

So what are the usage scenarios of array pointers?

 3.3 Use of array pointers

Wrong usage scenarios:

 This scenario is not convenient at all. It is even more troublesome when using arrays. It feels like "taking off your pants and farting". This kind of usage basically doesn't happen.

If you must use a pointer to receive an array, you should also use a pointer to receive the address of the first element rather than the address of the entire array. This is the correct access posture.

In fact, in the process of passing parameters to a one-dimensional array, the formal parameters can be written in the form of an array or in the form of a pointer, because essentially these two methods transfer the address of the first element of the array, which are equal to each other.

Since they are essentially addresses, why should they be written in array form?

That's because being able to write it in array form is entirely to take care of beginners , because the actual parameter is an array, and the formal parameter also defines an array to receive the array. This way is easier for beginners to understand. So even if it is written like this, the essence is still a pointer.

In the same way, there are two ways to pass parameters in a two-dimensional array:

  1. Formal parameters are also in the form of two-dimensional arrays.
  2. The formal parameters are in the form of array pointers.

After learning about pointer arrays and array pointers, let’s review them together and see what the following code means:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

【Answer】

  1. arr is an array that can store 5 integer data.
  2. parr1 is an array that can store an array of integer pointers. The array size is 10.
  3. parr2 is a pointer pointing to an array of 10 elements, the type of each element is int*
  4. parr3 is an array. The array has 10 elements and points to a pointer. The content pointed to by the pointer is an array of 5 elements. The type of each element is int, which is an array that stores array pointers .

Of course, don’t worry if you don’t understand parr3. This form is rarely used and is just a way to expand your knowledge.

4. Array parameters and pointer parameters

4.1 One-dimensional array parameter transfer

 4.2 Two-dimensional array parameter transfer

When passing parameters through a two-dimensional array, the function parameter design can only omit the first [] number. Because for a two-dimensional array, you don't need to know how many rows there are, but you must know how many elements are in a row. This makes calculations easier.

 4.3 First-level pointer parameter passing

 If a first-level pointer is used to pass parameters, the formal parameters can be written as first-level pointers.

#include <stdio.h>
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

 【think】

When the parameter part of a function is a first-level pointer, what parameters can the function receive?

【Answer】

 4.4 Second-level pointer parameter passing

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
}

【think】

 When the parameter of a function is a secondary pointer, what parameters can it receive?

【Answer】

5. Function pointer

Function pointer - a pointer to a function - stores the address of the function

 &Add and Add are completely equivalent, as the following example can prove.

You can define int (*pf2)(int,int) = &Add; and assign &Add to pf2,  which is the function pointer .

  • Add reference function returns ret1
  • At this time, dereferencing pf2 returns ret2 when calling the function.
  • Directly calling the function on pf2 returns ret3
  • Add a lot of dereferences in front of pf2 to return ret4

Finally, when printing the results, we found that all four results were the same.

So we come to the conclusion: when a function pointer calls the function it points to, it can call the function directly as the function name without writing *, and the * sign here is actually just a decoration. It is also to take care of the usage habits of beginners, so it is As a result, when a lot of * signs are added to dereference, the result is still the correct result.

 Let’s look at the interesting code on both ends:

1、

(*(void (*)())0)();
  •  void (*)()               ———— Function pointer type
  • void (*)()  )0 ———— Force type conversion of 0 into a function pointer. That is, address 0 is regarded as the address where the function pointer is stored.
  • (* ( void (*)() )0 )() ———— Call the function through the function pointer, and the function parameters are empty. 

That is, the above code actually calls the function at address 0. This function has no parameters and the return value is void.

 This code is from "C Pitfalls and Pitfalls"

2、

void (*signal(int , void(*)(int)))(int);
  • signal(int, void(*)(int))   ———— signal is a function with two parameters, integer type int and function pointer type void(*)(int).
  • void (* signal(int, void(*)(int))  )(int); ———— The return type of the signal function is also the function pointer type void (*)(int)

But this code looks too complicated. Is there any way to simplify it?

Is there a way to write the code in a form that conforms to our habits: return type first, function name in the middle, function parameters last: void (*)(int) signal(int, void(*)(int)), like this Direct writing is definitely not supported, but it can be optimized through typedef :

typedef void(*pfun_t)(int);  //对void(*)(int)重新起名为pfun_t
pfun_t signal(int, pfun_t);

Rename void(*)(int) to pfun_t, so that the code written in this way will be much clearer.

 


 If you think the author's writing is good, please give the author a big thumbs up to support me. Your support is my biggest motivation for updating!

Guess you like

Origin blog.csdn.net/zzzzzhxxx/article/details/132916716