Hand torn advanced C language --- pointer (full of dry goods! Worth collecting!!)

We have already touched the topic of pointer in the chapter of pointer at the primary stage. We know the concept of pointer:

  1. A pointer is a variable used to store an address, which uniquely identifies a piece of memory space.
  2. The size of the pointer is fixed at 4 / 8 bytes (32-bit platform / 64 bit platform).
  3. Pointers are typed. The pointer type determines the step size of the + - integer of the pointer and the permission of pointer dereference operation.
  4. Pointer operation.

In this chapter, we continue to explore the advanced topic of pointers

catalogue

Character pointer

Pointer array

Array pointer

Definition of array pointer

&Array name VS array name

Use of array pointers

Array parameter, pointer parameter

One dimensional array parameter transfer

Two dimensional array parameter transfer

Primary pointer transfer parameter

Secondary pointer transfer parameter

Function pointer

Use of function pointers

Function pointer array

Use of function pointer array

Pointer to array of function pointers

Callback function

Character pointer

Among the pointer types, we know that one pointer type is character pointer char *;
General use:

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

There is another way to use it as follows:

int main()
{
    char* pstr = "hello world!";//Did you put a string into the pstr pointer variable?
    printf("%s\n", pstr);
    return 0;
}

Code char* pstr = "hello bit"; It is particularly easy for students to think that the string hello bit is put into the character pointer pstr
But the essence is to put the string hello bit The address of the first character is placed in pstr.

 

The above code means to store the address of the first character h of a constant string in the pointer variable pstr.
Then there are such interview questions:

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char *str3 = "hello bit.";
    char *str4 = "hello bit.";
    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;
}

The final output here is:

Here, str3 and str4 point to the same constant string. C/C + + will store the constant string in a separate memory area when several pointers. When pointing to the same string, they actually point to the same block of memory. However, when initializing different arrays with the same constant string, different memory blocks will be opened up. So str1 and str2 are different, and str3 and str4 are different.

Pointer array

In the early stage of C language, we also learned pointer array. Pointer array is an array that stores pointers.

Let's review here. What does the following pointer array mean?

int* arr1[10]; // Array of integer pointers
char *arr2[4]; // Array of first level character pointers
char **arr3[5];// Array of secondary character pointers

Array pointer

Definition of array pointer

Are array pointers pointers? Or an array?
The answer is: pointer.
We are already familiar with: shaping pointer: int * pint; A pointer that can point to shaped data. Floating point pointer: float * pf; can
Pointer to floating point data.
The array pointer should be: a pointer that can point to an array.
Which of the following code is an array pointer?

int *p1[10];
int (*p2)[10];
//What are P1 and P2?

Explanation:

int (*p)[10];
//Explanation: P is first combined with * to indicate that P is a pointer variable, and then points to an array of 10 integers. So p is a pointer to an array, called an array pointer.
//Note here that the priority of [] is higher than that of * sign, so () must be added to ensure that p is combined with * first

&Array name VS array name

This is also an old-fashioned problem. Review the old and know the new. Here we'll learn again.

For the following array:

int arr[10];

What are arr and & arr?
We know that arr is the array name, which indicates the address of the first element of the array.
What is the &arr array name?
Let's look at a code:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}

The operation results are as follows:

It can be seen that the address printed by the array name and & array name is the same.
Are the two the same?
Let's look at another piece of code:

#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("arr = %p\n", arr);
    printf("&arr= %p\n", &arr);
    printf("arr+1 = %p\n", arr+1);
    printf("&arr+1= %p\n", &arr+1);
    return 0;
}

 

According to the above code, we find that although the values of & arr and arr are the same, they should have different meanings.
In fact: & arr represents the address of the array, not the address of the first element of the array. (feel it carefully)
The address of the array is + 1, skipping the size of the entire array, so the difference between & arr + 1 and & arr is 40.

Use of array pointers

How are array pointers used?
Since the array pointer points to an array, the address of the array should be stored in the array pointer.
Look at the code:

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//Assign the address of array arr to array pointer variable p
    //But we rarely write code like this
    return 0;
}

Use of an array pointer:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	//The array name arr represents the address of the first element
	//But the first element of the two-dimensional array is the first row of the two-dimensional array
	//So the arr passed here is actually equivalent to the address of the first row, which is the address of a one-dimensional array
	//You can use array pointers to receive
	print_arr2(arr, 3, 5);
	return 0;
}

After learning pointer array and array pointer, let's review and see the meaning of the following code:

int arr[5];// array
int *parr1[10];// Pointer array
int (*parr2)[10];// Array pointer
int (*parr3[10])[5];// Pointer array of two-dimensional array

Array parameter, pointer parameter

When writing code, it is inevitable to pass [array] or [pointer] to the function. How to design the parameters of the function?

One dimensional array parameter transfer

#include <stdio.h>
void test(int arr[])//ok?   Pass the array name and receive it with the array. There is no problem. The number of array elements can be omitted
{}
void test(int arr[10])//ok?   ditto
{}
void test(int *arr)//ok?   The array name is the address of the first element. It is received with a pointer. No problem
{}
void test2(int *arr[20])//ok?   Receive with the same pointer array, no problem
{}
void test2(int **arr)//ok?   The array name is the address of the first element, and the array element is a pointer, so the address is of type int * *,
                    //      Receive with secondary pointer, no problem
{}
int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};
    test(arr);
    test2(arr2);
}

Two dimensional array parameter transfer
 

void test(int arr[3][5])//ok?   Receive with a two-dimensional array, no problem
{}
void test(int arr[][])//ok?     Receiving with a two-dimensional array, rows can be omitted, columns cannot be omitted, error
{}
void test(int arr[][5])//ok?    correct
{}
//Summary: for the design of two-dimensional array parameters and function parameters, only the first [] number can be omitted.
//Because for a two-dimensional array, you can't know how many rows there are, but you must know how many elements there are in a row.
//This is convenient for calculation.
void test(int* arr)//ok?       The array name is the address of the first element, and the first element of the two-dimensional array is the first line (arr[0]), not the first line
{}                 //          The first (arr[0][0]), so it should be received with a pointer to the first line
void test(int* arr[5])//ok?    Cannot receive with pointer array
{}
void test(int(*arr)[5])//ok?   Receive with array pointer, no problem
{}
void test(int** arr)//ok?      A two-dimensional array cannot be received with a secondary pointer
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

Primary pointer transfer parameter
 

#include <stdio.h>
void print(int *p, int sz)//The first level pointer is transmitted and received with the first level pointer
{
    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]);
    //First level pointer p, passed to function
    print(p, sz);
    return 0;
}

reflection:

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

For example:

void test1(int *p)
{}
//What parameters can the test1 function accept? Integer array, integer variable address, first level pointer
void test2(char* p)
{}
//What parameters can the test2 function accept? Character array, character variable address, first level pointer

Secondary pointer transfer parameter

#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);//Pass secondary pointer
	test(&p);//Pass the address of the first level pointer
	return 0;
}

reflection:

What parameters can be received when the parameter of the function is a secondary pointer?

void test(char **p)
{}
int main()
{
    char c = 'b';
    char*pc = &c;
    char**ppc = &pc;
    char* arr[10];
    test(&pc);//Pass the address of the first level pointer
    test(ppc);//Pass secondary pointer
    test(arr);//ok?   Pass a pointer array. Each element of the array is a pointer,
              //     So the address of the array element is equivalent to the address of the first level pointer. No problem
    return 0;
}

Function pointer

Integer pointer --- the address where the integer is stored

Character pointer --- the address where the character is stored

Array pointer --- the address where the array is stored

By analogy, we can also guess the function of function pointers

Function pointer --- the address where the function is stored

Let's start with a piece of code:

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
}

Array name --- address of the first element of the array

&Array name --- the address of the array (the two methods are different)

Function name --- the address of the function

&Function name --- the address of the function (the two methods are exactly the same)

Output results:

So we can see that the function also has an address!  

The output is two addresses, which are the addresses of the test function. How do we save the address of our function?
Let's look at the code below:

void test()
{
    printf("hehe\n");
}
//Which of the following pfun1 and pfun2 has the ability to store the address of the test function?
void (*pfun1)();
void *pfun2();

First, if you can give a storage address, you need pfun1 or pfun2 to be a pointer. Which is a pointer? The answer is:

Pfun1 can be stored. Pfun1 is first combined with * to explain that pfun1 is a pointer, the pointer points to a function, the pointed function has no parameters, and the return value type is void.

void (*pfun1)() =  &test;

pfun2 here is a function with no parameters and a return value of void * type. Obviously, it cannot store the address of the function.  

We can better understand function pointers by analogy with array pointers:

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int arr[10] = { 0 };

	int(*parr)[10] = &arr;//parr is an array pointer variable
	int(*pf)(int, int) = &Add;//pAdd is a function pointer variable
	return 0;
}

int a = 10; // Remove the name a, and the remaining int is the type of A

int arr[10] = { 0 }; // Remove the array name, and the remaining int [10] is the type of arr

int(*parr)[10] = &arr; // Remove Parr, and the remaining int(*)[10] is the type of parr
int(*pf)(int, int) = &Add; / / remove PF, and the remaining int(*)(int, int) is the type of PF

Use of function pointers

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int(*pf)(int, int) = &Add;
	int ret = Add(2, 3);
	printf("%d\n", ret);
	ret = (*pf)(4, 5);
	printf("%d\n", ret);
	return 0;
}

In the code, we store the address of the function Add into pf, and then dereference pf to find the Add function, so we can directly use the function. Here, we may feel superfluous. Sometimes we can't directly know the function name, but only the address of the function, so we can call the function in this way.

Because the function name and & function name are equivalent when we get the address of the function, we can also use a simpler way:

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int(*pf)(int, int) = NULL;//For the convenience of understanding, we will write it completely here
    pf = Add;//Assign Add (address of function) to pf (indicating that pf is equivalent to Add)
	int ret = Add(2, 3);
	printf("%d\n", ret);
	ret = pf(4, 5);//You can directly use pf to call functions (the previous use (* pf) is to make it easier for beginners to understand)
	printf("%d\n", ret);
	return 0;
}

The results are the same:

Read two interesting pieces of code:

//Code 1
(*(void (*)())0)();
//Code 2
void (*signal(int , void(*)(int)))(int);
//Please explain what these two codes mean

Code 1: the code is a function call

First, look at void(*) (), which is a function pointer without a reference return value of void*, and (void(*) ()) 0 is the address of a function that transforms the 0 coercive type into type void(*) (). Finally (* (void(*) ()) 0) () (void(*) ()) () is the 0 address to find the function and then call the function.

Code 2: code is a function declaration

First, look at signal (int, void(*)(int)). Signal is a function with two parameters. One type is int and the other type is void(*)(int) (function pointer); void(*)(int) is left in the code, which is the return value type (function pointer) of the signal function.

Here we find that the function pointer type is troublesome both in writing and observation. Is there any way to simplify it?

According to what we learned earlier, we can simplify it with typedef:

typedef void(*pfun_t)(int);

int main()
{
	void (*signal(int, void(*)(int)))(int);
	pfun_t signal2(int, pfun_t);

	return 0;
}

Note: both codes are from C traps and defects

Interested friends can read it.

Function pointer array

Array is a storage space for storing data of the same type. We have learned pointer array, such as:

int *arr[10];
//Each element of the array is an int*

The address of the function should be stored in an array. This array is called the function pointer array. How to define the array of function pointers?

int (*parr1[10]])();
int *parr2[10]();//err
int (*)() parr3[10];//err

The answer is: parr1. Parr1 is first combined with [], indicating that parr1 is an array. What is the content of the array? Is of type int (*) ()
Function pointer.

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	//pfArr is an array of function pointers
	int(*pfArr[4])(int, int) = { Add,Sub,Mul,Div };//Note that the type of function pointer of each element in the array should be the same
	return 0;
}

Use of function pointer array

For example, let's write a calculator

General methods:

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

void menu()
{
	printf("***********************************\n");
	printf("********1.Add    2.Sub*************\n");
	printf("********3.Mul    4.Div*************\n");
	printf("*********   0.exit   **************\n");
	printf("***********************************\n");

}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	do
	{
		menu();
		printf("Please select>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("Please enter two operands>");
			scanf("%d%d", &x, &y);
			printf("%d\n", Add(x, y));
			break;
		case 2:
			printf("Please enter two operands>");
			scanf("%d%d", &x, &y);
			printf("%d\n", Sub(x, y));
			break; 
		case 3:
			printf("Please enter two operands>");
			scanf("%d%d", &x, &y);
			printf("%d\n" ,Mul(x, y));
				break;
		case 4:
			printf("Please enter two operands>");
			scanf("%d%d", &x, &y);
			printf("%d\n", Div(x, y));
			break;
		case 0:
			printf("Exit program\n");
			break;
		default:
			printf("Selection error,Please try again\n");
			break;
		}
	} while(input);
	return 0;
}

If there are more calculation methods here, such as shift left, shift right, bitwise and, bitwise OR Then there will be more switch case statements, and there are a lot of redundant code, which makes the program very complex.

So when we learn the function pointer array, we can use it to make the program easy to read:

Improvement Code: transfer table

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

void menu()
{
	printf("***********************************\n");
	printf("********1.Add    2.Sub*************\n");
	printf("********3.Mul    4.Div*************\n");
	printf("*********   0.exit   **************\n");
	printf("***********************************\n");

}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
    // Transfer table
	int(*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };//Create a function pointer array, and input corresponds to the function subscript
	                          // 0  1   2   3   4                     
	do
	{
		menu();
		printf("Please select>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("Exit program\n");
		}
		else if (input > 0 && input < 5)
		{
			printf("Please enter two operands>");
			scanf("%d%d", &x, &y);
			ret = pfArr[input](x, y);//Directly take input as the subscript and call the function directly
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("Selection error,Please try again\n");
		}
		
	} while (input);
	return 0;
}

How is it a lot easier.

Of course, we can also improve the code of method 1. In method 1, there are many duplicate codes in the switch case, which makes it very complex. Can we compress it?

Method 3: callback function (explained in detail later)

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

void Calc(int(*pf)(int, int))//Receive with function pointer
{
	int x = 0;
	int y = 0;
	printf("Please enter two operands>");
	scanf("%d%d", &x, &y);
	int ret = pf(x, y);
	printf("ret = %d\n", ret);
}

void menu()
{
	printf("***********************************\n");
	printf("********1.Add    2.Sub*************\n");
	printf("********3.Mul    4.Div*************\n");
	printf("*********   0.exit   **************\n");
	printf("***********************************\n");

}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("Please select>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Calc(Add);//Take the function address as an argument
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 0:
			printf("Exit program\n");
			break;
		default:
			printf("Selection error,Please try again\n");
			break;
		}
	} while (input);
	return 0;
}

Pointer to array of function pointers

The pointer to the function pointer array is a pointer, the pointer points to an array, and the elements of the array are function pointers;
How to define?

void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//Function pointer pfun
void (*pfun)(const char*) = test;
//Array of function pointers pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//Pointer to function pointer array pfunArr ppfunArr
void (*(*ppfunArr)[10])(const char*) = &pfunArr;//ppfunArr is combined with * first, so it is a pointer,
                                                //The pointer points to an array with 10 elements,
                                                //The type of each element is a function pointer type.
return 0;
}

Callback function

A callback function is a function called through a function pointer. If you pass the pointer (address) of a function as a parameter to another function, when the pointer is used to call the function it points to, we say it is a callback function. The callback function is not called directly by the implementer of the function, but by another party when a specific event or condition occurs, which is used to respond to the event or condition.

Qsort (quick sort) function: This is a built-in function in C language, which is used to sort data. It can sort any type of data.

The function has four parameters:

  1. base: array to be sorted
  2. num: number of array elements
  3. width: the byte size of the element
  4. compare: comparison function (you need to implement it yourself)

Note: void * is a null pointer. It can receive pointers of any data type, but it cannot be used for calculation. Because any type of data needs to be sorted, it cannot be defined with a pointer of one type, so it is received with void *.  

Use of qsort function:

//1. Sorting of integer data
#include <stdio.h>
//The user of the qosrt function has to implement a comparison function
int int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);//First, convert the two data to be compared into integers
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}
//Structure data sorting
struct stu
{
	char name[10];
	int age;
};

int cmp_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e2)->name, ((struct stu*)e1)->name);//First convert the two data to structure type
}


int cmp_age(const void* e1, const void* e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}

int main()
{
	//int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	struct stu S[3] = { {"Zhang San",15}, {"Li Si",20},{"Wang Wu",5} };
	//qsort(S, 3, sizeof(S[0]), cmp_name);
	qsort(S, 3, sizeof(S[0]), cmp_age);

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", (S+i)->name);
	}
	return 0;
}

Note: when comparing two strings, you can no longer subtract directly like integer data, but use the string comparison function( strcmp usage).

In order to understand the principle of qsort more clearly, we use the bubble sorting learned earlier to implement the qsort function.

The parameter design is the same as qsort.  

//General bubble sort
void BubbleSort(void* base, size_t num, size_t width, int(*cmp)(const void*e1,const void*e2))
//size_t: Unsigned integer. In order to avoid parameter error, unsigned integer is used here
{
	size_t i = 0;//Avoid warnings when comparing integers with unsigned integers, so i and j also use size_t definition
	for (i = 0; i < num - 1; i++)
	{
		size_t j = 0;
		for (j = 0; j < num - i - 1; j++)
		{
            //Here we sort in descending order
            //If the data is larger than the following data, they will be exchanged
			if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
			{
				swap((char*)base + width * j, (char*)base + width * (j + 1),width);
			}
		}
	}
}

The void * type cannot be used for calculation, but the starting addresses of the two data must be passed in when passing parameters to cmp and swap functions, so type conversion is required.

In the previous parameter passing, we passed in the size of the data type to be sorted, so here we can convert the data of void * type into char *, and then add width*j to find the starting address of each data.

//Compare integer functions
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}


//Exchange function
void swap(char* e1, char* e2,size_t width)
{
	size_t i = 0;
	for (i = 0; i < width; i++)//According to the size of the data, it is exchanged in one byte
	{                          //The number of exchanges is the number of bytes
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
        e1++;
        e2++;
	}
}


//General bubble sort
void BubbleSort(void* base, size_t num, size_t width, int(*cmp)(const void*e1,const void*e2))
//size_t: Unsigned integer. In order to avoid parameter error, unsigned integer is used here
{
	size_t i = 0;//Avoid warnings when comparing integers with unsigned integers, so i and j also use size_t definition
	for (i = 0; i < num - 1; i++)
	{
		size_t j = 0;
		for (j = 0; j < num - i - 1; j++)
		{
			if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
			{
				swap((char*)base + width * j, (char*)base + width * (j + 1),width);
			}
		}
	}
}


int main()
{
	int arr[10] = { 9,8,7,6,5,4,1,2,3,0 };
	BubbleSort(arr, 10, sizeof(int), cmp_int);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

Operation results:

Have you learned?

It's not easy to create. If you feel a little harvest, please praise it~~

Keywords: C pointer

Added by melissal on Thu, 16 Dec 2021 16:56:17 +0200