【C语言进阶5——指针的进阶(2)】


前言

本文接着上文,继续学习指针进阶的知识点:

  • 数组参数、指针参数
  • 函数指针
  • 函数指针数组

4、数组参数、指针参数

首先对上一篇所学知识进行回顾复习,将知识点,画在一张图上就能清楚的对比相同点和不同点:
在这里插入图片描述
在这里插入图片描述

编程时,有时要把数组或者指针传给函数,函数的参数该如何设计?下面举例说明:

4.1 一维数组传参

一维数组接收参数的几种方式
void test(int arr[])//形参与实参的定义相同,用数组接收
{}
void test(int arr[10])//形参与实参的定义相同,用数组接收,[]内数字可以不写
{}
void test(int* arr)//形参定义指针来接受数组首元素的地址
{}
void test2(int* arr[20])//形参与实参的定义相同,定义指针数组来接受参数
{}
void test2(int** arr)//二级指针,数组里的元素都是指针,就是地址
{}                   //数组名也是地址,地址的地址用二级指针

//一维数组传参
int main()
{
	int arr1[10] = { 0 };//一维数组
	int* arr2[20] = { 0 };//指针数组
	test(arr1);
	test2(arr2);
}

4.2 二维数组传参

二维数组接收参数的几种方式
void test(int arr[3][5])//形参与实参的定义相同,定义数组收受参数
{}
void test(int arr[][5])//形参与实参的定义相同。第1个[]内数字可以不写
{}
void test(int arr[][])//错误的,第2个[]内数字必须写
{}
void test(int *arr)//错误的,*arr对地址解引用,
{}				//arr是二维数组名,第一行地址,第一行数组的首元素地址,获得第一个元素0					
void test(int* arr[5])//错误的,这是一个数组,每一个元素是指针,就是地址
{}
void test2(int(*arr)[5])//指针指向的数组,是一个一维数组,数组有5个元素
{}
void test2(int** arr)//错误的,二级指针,存放一级指针的地址
{}                   

//二维数组传参
int main()
{
	int arr[3][5] = { 0 };//二维数组
	二维数组在传参数组名时,传递的是首元素a[0],即第一行数组的地址
	test(arr);
}

在这里插入图片描述

4.3 一级指针传参

void test(int* arr, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *arr++);
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* pa = arr;//直接把数组名给指针即可
	int sz = sizeof(arr) / sizeof(arr[0]);
	test(pa, sz);
	return 0;
}

下面反过来考虑:
当知道一个函数的形参部分为一级指针的时候,在主函数中调用时,应该传什么参数?

void test(int* p)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int arr[10];
	//传参的几种形式,都是可以的
	test(&a);
	test(p);
	test(arr);

	return 0;
}

4.4 二级指针传参

//举例 1
void test(int** p)//二极指针,地址的地址
{}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;

	test(pp);
	test(&p);
}
//举例 2
void test(char** ch)
{}
int main()
{
	char ch = 'w';
	char* pc = &ch;
	char** ppc = &pc;
	char* arr[5];
	char arr1[3][5];

	test(arr);
	test(ppc);
	test(&pc);
	test(arr1);//错误的,二维数组穿的是第一行的地址,就是第一行数组的地址
	//应该char (*p)[5],参数应该用数组指针传参,指向数组的指针
}				
//举例 3
void test1(int(*p)[5])//数组指针,指针指向数组,这个数组有5个元素,每个元素是int类型
{}
void test2(int(*p)[3][5])//指针指向整个二维数组
{
	*p//解引用后代表首行地址
}
int main()
{
	int arr[3][5];
	test1(arr);//二维数组传递的是第一行地址
	test2(&arr);//传递的是整个二维数组的地址,一般不会这样用
}

5、函数指针

通过与前面所学的知识进行类比,就能发现定义指针的奥秘了:

  • 数组指针——指向数组的指针
  • 函数指针——指向函数的指针
    在这里插入图片描述
//举例1
void test()
{
	printf("hehen");
}
int main()
{
	printf("%pn", test);//函数名就是地址
	printf("%pn", &test);
	
	return 0;
}
//举例2
int add(int x, int y)
{
	return x + y;
}
int test(char* ch)
{}
int main()
{
	//int arr[10];
	//arr
	//&arr
	//int* p = arr;
	//int(*p)[10] = &arr;

	int(*pa)(int, int) = add;//就是函数指针变量,将函数的地址保存起来
	int ret = (*pa)(2, 3);//此处的*不起作用,有无都行
	/*int ret = add(2, 3);

	int ret = pa(2, 3);*/
	printf("%dn", ret);
	//int(*pc)(char*) = test;
	return 0;
}
int main()
{
	void(*p)();//这个p的类型函数指针类型,去掉p后,剩下的是void(*)(),指针p指向的地址是一个参数是空,返回类型是空的函数

	//代码1
	(*void(*)()0)();
	解释说明:
	void(*)()是函数指针类型
	(void(*)()0,将0强制转换成函数指针类型,则指针0指向的地址里有一个函数
	*void(*)()0 地址解引用,就是函数名了
	(*void(*)()0)(),调用0地址处放的函数*/

	//代码2
	void(*signal(int, void(*)()(int)))(int);
	解释说明:
	signal(int, void(*)()(int)) 函数两个参数,一个int,一个函数指针
	void(*)()(int)是一个函数指针类型,指向的函数的参数是int,返回值是空
	//如同函数声明 int add(int, int);
	//去掉函数名和参数后,剩下的int就是函数的返回类型
	void(*signal(int, void(*)()(int)))(int)中去除函数名 signal(int, void(*)()(int))后,
	只剩下 void(* )(int),	它就是函数的返回类型,返回的是函数指针类型
	
	//代码2也可以简化:
	typedef void(*pfun_t)(int);
	pfun_t signal(int, pfun_t);
	
}

6、函数指针数组

通过类比学习定义:
在这里插入图片描述

指针数组:
int* arr[10]; //数组的每个元素是int*

函数指针数组:
int(*parr1[5])();
上面去掉数组名parr[5]后,剩下int(*)(),就是函数指针类型,这个数组有5个元素,每个元素都是int(*)()类型

parr1 先和 [] 结合,说明 parr1是数组,数组的内容:是 int (*)() 类型的函数指针
即数组里的每个元素都是指针,即地址,每个地址都存有一个函数,对指针解引用就能调用函数

函数指针数组的用处:
一个简易计算器的函数:

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 res = 0;
	do
	{
		menu();
		printf("请选择 ==> ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数 ==> ");
			scanf("%d %d", &x, &y);
			res = add(x, y);
			printf("ret=%dn", res);
			break;
		case 2:
			printf("请输入2个操作数:>");
			scanf("%d %d", &x, &y);
			res = sub(x, y);
			printf("ret=%dn", res);
			break;
		case 3:
			printf("请输入2个操作数:>");
			scanf("%d %d", &x, &y);
			res = mul(x, y);
			printf("ret = %dn", res);
			break;
		case 4:
			printf("请输入2个操作数:>");
			scanf("%d %d", &x, &y);
			res = div(x, y);
			printf("ret = %dn", res);
			break;
		case 0:
			printf("退出计算器n");
			break;
		default:
			break;
		}
	} while (input);
	return 0;
}

代码改写为使用函数指针数组:

//函数指针数组的用法
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int res = 0;
	int(*paar[5])(int, int) = { 0,add,sub,mul,div };
	do
	{
		menu();
		printf("请选择 ==> ");
		scanf("%d", &input);
		if (input==0)
		{
			printf("退出计算器n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:>");
			scanf("%d%d", &x, &y);
			res = (*paar[input])(x, y);//直接调用函数
			printf("%dn", res);
		}
		else
		{
			printf("选择错误,重新输入数字n");
		}		
	} while (input);
	return 0;
}

总结

指针进阶的内容较多,而且这些内容的名词都相似,如果不能牢固掌握,将会混淆这些知识点。第一遍学习不能完全掌握,要经常复习,温故而知新。

通过类比,可以有效地区分各种定义。下一篇继续学习指针进阶的内容。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>