当前位置:网站首页>深度剖析C语言指针

深度剖析C语言指针

2022-07-06 08:28:00 终为—NULL

每天进步一点点,坚持带来大改变!!!

目录

前言:

1.字符指针

2.指针数组

3.数组指针

4.数组传参,指针传参

5.函数指针

6.函数指针数组

7.指向函数指针数组的指针

8.回调函数

9.指针和数组笔试题练习

10.指针笔试题练习

11.总结: 

前言:

之前我们已经学过指针的一些简单知识:

1.指针就是地址,是标识内存空间的编号。

2.指针变量是用来存放变量的地址,可以通过指针找到变量在内存中存储。

3.指针的大小占4/8个字节(32/64).

4.指针的类型决定了指针加减整数的时候跳过几个字节,解引用的时候能够访问几个字节。

5.指针的运算:指针减指针得到中间元素的个数。

接下来我们继续探讨指针的更多知识,深入C语言指针相关的内容。

1.字符指针

char*

第一种使用:

内存布局:

第二种使用:

内存布局:

关于字符指针的一道面试题: 

#include<stdio.h>

int main()
{
	char* p1 = "abcdef";
	char* p2 = "abcdef";

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	if (p1 == p2)
		printf("p1 == p2\n");
	else
		printf("p1 != p2\n");
	if (arr1 == arr2)
		printf("arr1 == arr2\n");
	else
		printf("arr1 != arr2\n");
	return 0;
}

原因解释: 因为"abcdef"是常量字符串,放在字符常量区,不能被修改,因此在内存中只有一份,所以p1和p2都是存放首字符a的地址,而arr1和arr2在内存中开辟两块不同的空间存放"abcdef",因此地址不相同。

2.指针数组

概念:类比推理
整形数组,用来存放整形的数字,每个元素的类型是int
字符数组,用来存放字符的数组,每个元素的类型是char

指针数组,用来存放指针的数组,每个元素的类型是 数据类型*
 

应用:打印一个二维数组

#include<stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* parr[3] = { arr1,arr2,arr3 };
	//parr是一个整形指针数组,每个元素的类型是int*
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(parr + i) + j));
		}
		printf("\n");
	}

	return 0;
}

 内存布局:

 3.数组指针

概念:类比推理:

整形指针,用来存放整形元素的地址  int*p;
字符指针,用来存放字符元素的地址  char*p;

数组指针,用来存放数组的地址  数据类型(*p)[X];
 

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	//p先和*结合说明p是一个指针变量,然后指向一个大小为10元素的数组
	return 0;
}

注:&数组名代表的是数组的地址

数组指针的使用:不恰当的使用

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(*p + i));//等价于arr[i]
		//*p得到数组首元素的地址+i得到下个元素的地址,*得到这个值
	}
	return 0;
}

打印一维数组使用数组指针,比较麻烦,不建议使用。

打印二维数组

#include<stdio.h>
void print(int(*p)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(p + i) + j));
			//*(p+i)得到二维数组每一行首元素的地址+j解引用得到每一个元素。
			//等价与p[i][j]
		}
		printf("\n");
	}
}
int main()
{

	int arr[3][5] = { { 1,2,3,4,5 }, { 2, 3, 4, 5, 6 } ,{ 3, 4, 5, 6, 7} };
	print(arr, 3, 5);
	//数组名代表数组首元素的地址,二维数组首元素又是一个一维数组
	//所以用数组指针接受,存放数组的地址。
	return 0;
}

总结:

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

int arr[5] : 是一个整形数组,数组有五个元素,每个元素的类型是int;
int* parr1[10]:是一个指针数组,数组有10个元素,每个元素的类型是int*;
int(*parr2)[10]:是一个数组指针,该指针指向的数组有10个元素,每个元素的类型是int
 该指针变量的类型是int(*)[10];

int(*parr3[10])[5]:parr3先和[]结合说明这是一个数组,该数组有10个元素,每个元素的类型是int(*)[5](数组指针);
 

4.数组传参,指针传参

1.数组传参

一维数组传参:

void test1(int arr[])//ok
{}
void test1(int arr[10])//ok
{}
void test1(int*arr)//ok
{}
void test2(int*arr[20])//ok
{}
void test2(int**arr)//ok arr2是一个指针用二级指针接受
{}
int main()
{
	int arr1[10] = { 0 };
	int* arr2[20] = { 0 };
	test1(arr1);
	test2(arr2);
	return 0;
}

二维数组传参:

void test(int arr[3][5])//ok
{}
void test(int arr[][])//err 二维数组传参行可以省略,列不能省略
{}
void test(int arr[][5])//ok
{}
void test(int(*arr)[5])//ok 数组名表示首元素的地址,而二维数组首元素表示第一行数组,
                      //因此可以用数组指针来接受
{}
void test(int**arr)//err 二级指针变量是存放一级指针变量的地址
{}
void test(int*arr[5])//err指针数组用来接受指针,不能接受数组的地址
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

2.指针传参:

一级指针传参

void test(int* p)//ok
{}
int main()
{
	int a = 10;
	int* p = &a;
	test(p);
	return 0;
}

二级指针传参:

void test(int** pp)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	test(pp);//ok
	test(&p);//ok

	return 0;
}

5.函数指针

用来存放函数的地址

#include<stdio.h>

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	printf("%p\n", Add);
	printf("%p\n", &Add);
	return 0;
}

 如何将一个函数的地址存起来呢?

#include<stdio.h>

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int (*pf)(int, int) = &Add;//Add;
	//pf就是一个函数指针(int,int)函数参数的类型
	int ret = (*pf)(2, 3);
	//等价与(pf)(2,3);
	printf("%d\n", ret);
	return 0;
}

下面通过两段代码深入理解函数指针:

第二段代码比较复杂,但是有相同的代码,函数指针,因此可以通过typedef重命名简化代码


typedef void(*pf_t)(int);
int main()
{
	void (*signal(int, void(*)(int)))(int);
	pf_t signal(int pf_t);
	return 0;
}

 函数指针的用途:

模拟实现一个计算器:

#include<stdio.h>
void menu()
{
	printf("*************************\n");
	printf("***1.Add       2.Sub  ***\n");
	printf("***3.Mul       4.Div  ***\n");
	printf("******   0.exit  ********\n");
	printf("*************************\n");
}
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()
{
	int a = 0;
	int b = 0;
	int ret = 0;
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d",&input);
		switch (input)
		{
		case 0:
			printf("退出计算器\n");
			break;
		case 1:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			ret = Add(a, b);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			ret = Sub(a, b);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			ret = Mul(a, b);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			ret = Div(a, b);
			printf("%d\n", ret);
			break;
		default:
			printf("输入有误,请重新输入:\n");
			break;
		}

	} while (input);

	return 0;
}

在我们实现计算器的时候,代码中出现了许多重复冗余的代码,如何规避这些问题呢? 

下面通过函数指针的方式来解决代码中出现重复的问题:

改进后的代码:

#include<stdio.h>
void menu()
{
	printf("*************************\n");
	printf("***1.Add       2.Sub  ***\n");
	printf("***3.Mul       4.Div  ***\n");
	printf("******   0.exit  ********\n");
	printf("*************************\n");
}
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))
{
	int a = 0;
	int b = 0;
	int ret = 0;
	printf("请输入两个操作数:");
	scanf("%d%d", &a, &b);
	ret = pf(a, b);
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出计算器\n");
			break;
		case 1:
			Calc(Add);
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		default:
			printf("输入有误,请重新输入:\n");
			break;
		}

	} while (input);

	return 0;
}

将函数的地址传过去,参数用函数指针来接受,然后通过函数指针调用这个函数。

6.函数指针数组

是一个数组,用来存放函数地址的数组

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()
{
	//如何将上面四个函数的地址存放到一个函数指针数组呢?
	int(*pf[4])(int, int) = { Add,Sub,Mul,Div };
	//pf先和[]结合说明这是一个数组,数组有4个元素,每个元素的类型是
	//int(*)(int,int)函数指针
	return 0;
}

既然可以通过函数指针数组存放函数的地址,那么如何用函数指针数组实现计算器?

#include<stdio.h>
void menu()
{
	printf("*************************\n");
	printf("***1.Add       2.Sub  ***\n");
	printf("***3.Mul       4.Div  ***\n");
	printf("******   0.exit  ********\n");
	printf("*************************\n");
}
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()
{
	int input = 0;
	int a = 0;
	int b = 0;
	int ret = 0;
	int(*parr[5])(int,int) = { 0,Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input < 5)
		{
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			ret = parr[input](a, b);
			printf("%d\n", ret);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	} while (input);

	return 0;
}

当使用函数指针数组的时候又简化了代码,并且在后续修改代码的时候提供了极大的方便

7.指向函数指针数组的指针

是一个指针,用来存放函数指针数组的地址:

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()
{
	//函数指针数组
	int(*pfarr[4]) = {Add,Sub,Mul,Div };
	//ppfArr是一个指向函数指针数组的指针
	//ppfArr先和*结合,说明ppfArr是一个指针,[4]说明指针
	//指向的数组有4个元素,每个元素的类型是int(*)(int,int)
	int(*(*ppfArr)[4])(int, int) = &pfarr;
	return 0;
}

8.回调函数

上面我们在用函数指针实现计算器的时候,写了一个Calc的函数,其实Calc就是一个回调函数,那如何理解回调函数呢?

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
如何对一组整形数组进行排序呢?
#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		int flag = 1;//如果待排序的数组已经有序,则跳出循环
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] < arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

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

排序成降序:

当我们要对一个复杂的对象进行排序的时候,显然冒泡排序不能适用,那如何要对一个复杂对象进行排序呢?

下面来介绍一个库函数qsort()

#include<stdlib.h>//包含头文件
int main()
{
	void qsort(void* base, //待排序的起始位置
		size_t num, //待排序的个数
		size_t width, //待排序元素的大小,单位是字节
		int( * cmp)(const void* e1, const void* e2));
	//是一个函数指针,该函数指针指向的函数有两个参数,e1,e2,函数返回类型是int.
	return 0;
}
//int cmp(const void* e1, const void* e2)
//Return Value Description 
//< 0 e1 less than e2 
//0 e1 equivalent to e2 
//> 0 e1 greater than e2 

使用库函数qsort排序整形数组:

#include<stdlib.h>
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

默认是升序排列:

使用库函数排序复杂对象:

按照一个人的年龄进行排序:

#include<stdio.h>
#include<stdlib.h>
struct stu
{
	int age;
	char name[20];
};
int by_cmp_age(const void* e1, const void* e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int main()
{
	struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), by_cmp_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d ", s[i].name, s[i].age);
		printf("\n");
	}
	return 0;
}

按照一个人的姓名进行排序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu
{
	int age;
	char name[20];
};
int by_cmp_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
int main()
{
	struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), by_cmp_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d ", s[i].name, s[i].age);
		printf("\n");
	}
	return 0;
}

模拟实现库函数qsort:

实现库函数qsort的一个参数是函数指针,而函数指针指向的函数参数是void*类型的指针。

为什么会使用void*类型的指针呢?

下面介绍一下void*的指针的作用

模拟实现qsort

#include<stdio.h>
#include<string.h>
struct stu
{
	int age;
	char name[20];
};
int by_cmp_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
void bubble_qsort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		int flag = 1;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//将传入的数据类型强转化为char*指针类型,然后进行一个字节一个字节的比较
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}
int main()
{
	struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_qsort(s, sz, sizeof(s[0]), by_cmp_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d ", s[i].name, s[i].age);
		printf("\n");
	}
	return 0;
}

 

9.指针和数组笔试题练习

一维整形数组:

 一维字符数组:数组中不包含'\0'

szieof

 strlen:

数组中包含'\0'

sizeof:

strlen:

字符指针:

sizeof:

strlen:

二维数组:

总结:sizeof(数组名),数组名表示整个数组的大小,计算整个数组的大小

&数组名,数组名表示整个数组的地址,除此之外,所有的数组名都表示的是数组首元素的地址。

10.指针笔试题练习

试题一:

试题二:

 试题三:

 试题四:

试题五:

 

试题六:

 

 试题七:

试题八:

 

 

 

11.总结: 

以上是关于对C语言指针知识点的一个详细解剖,包含所有关于指针的知识点和指针相关的笔试题,希望能够在学习的路上互相帮助,共同进步,欢迎点赞留言评论!!!

原网站

版权声明
本文为[终为—NULL]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_65307907/article/details/125068747