指针的进阶(2)
前言
本文接着上文,继续学习指针进阶的知识点:
- 数组参数、指针参数
- 函数指针
- 函数指针数组
4、数组参数、指针参数
首先对上一篇所学知识进行回顾复习,将知识点,画在一张图上就能清楚的对比相同点和不同点:
编程时,有时要把数组或者指针传给函数,函数的参数该如何设计?下面举例说明:
4.1 一维数组传参
一维数组接收参数的几种方式
voidtest(int arr[])//形参与实参的定义相同,用数组接收{}voidtest(int arr[10])//形参与实参的定义相同,用数组接收,[]内数字可以不写{}voidtest(int* arr)//形参定义指针来接受数组首元素的地址{}voidtest2(int* arr[20])//形参与实参的定义相同,定义指针数组来接受参数{}voidtest2(int** arr)//二级指针,数组里的元素都是指针,就是地址{}//数组名也是地址,地址的地址用二级指针//一维数组传参intmain(){int arr1[10]={0};//一维数组int* arr2[20]={0};//指针数组test(arr1);test2(arr2);}
4.2 二维数组传参
二维数组接收参数的几种方式
voidtest(int arr[3][5])//形参与实参的定义相同,定义数组收受参数{}voidtest(int arr[][5])//形参与实参的定义相同。第1个[]内数字可以不写{}voidtest(int arr[][])//错误的,第2个[]内数字必须写{}voidtest(int*arr)//错误的,*arr对地址解引用,{}//arr是二维数组名,第一行地址,第一行数组的首元素地址,获得第一个元素0 voidtest(int* arr[5])//错误的,这是一个数组,每一个元素是指针,就是地址{}voidtest2(int(*arr)[5])//指针指向的数组,是一个一维数组,数组有5个元素{}voidtest2(int** arr)//错误的,二级指针,存放一级指针的地址{}//二维数组传参intmain(){int arr[3][5]={0};//二维数组
二维数组在传参数组名时,传递的是首元素a[0],即第一行数组的地址
test(arr);}
4.3 一级指针传参
voidtest(int* arr,int sz){for(int i =0; i < sz; i++){printf("%d ",*arr++);}}intmain(){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);return0;}
下面反过来考虑:
当知道一个函数的形参部分为一级指针的时候,在主函数中调用时,应该传什么参数?
voidtest(int* p){}intmain(){int a =10;int* p =&a;int arr[10];//传参的几种形式,都是可以的test(&a);test(p);test(arr);return0;}
4.4 二级指针传参
//举例 1voidtest(int** p)//二极指针,地址的地址{}intmain(){int n =10;int* p =&n;int** pp =&p;test(pp);test(&p);}
//举例 2voidtest(char** ch){}intmain(){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],参数应该用数组指针传参,指向数组的指针}
//举例 3voidtest1(int(*p)[5])//数组指针,指针指向数组,这个数组有5个元素,每个元素是int类型{}voidtest2(int(*p)[3][5])//指针指向整个二维数组{*p//解引用后代表首行地址}intmain(){int arr[3][5];test1(arr);//二维数组传递的是第一行地址test2(&arr);//传递的是整个二维数组的地址,一般不会这样用}
5、函数指针
通过与前面所学的知识进行类比,就能发现定义指针的奥秘了:
- 数组指针——指向数组的指针
- 函数指针——指向函数的指针
//举例1voidtest(){printf("hehe\n");}intmain(){printf("%p\n", test);//函数名就是地址printf("%p\n",&test);return0;}
//举例2intadd(int x,int y){return x + y;}inttest(char* ch){}intmain(){//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("%d\n", ret);//int(*pc)(char*) = test;return0;}
intmain(){void(*p)();//这个p的类型函数指针类型,去掉p后,剩下的是void(*)(),指针p指向的地址是一个参数是空,返回类型是空的函数//代码1(*(void(*)())0)();
解释说明:
void(*)()是函数指针类型
(void(*)())0,将0强制转换成函数指针类型,则指针0指向的地址里有一个函数
*(void(*)())0 地址解引用,就是函数名了
(*(void(*)())0)(),调用0地址处放的函数*///代码2void(*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也可以简化:typedefvoid(*pfun_t)(int);pfun_tsignal(int,pfun_t);}
6、函数指针数组
通过类比学习定义:
指针数组:
int* arr[10];//数组的每个元素是int*
函数指针数组:
int(*parr1[5])();
上面去掉数组名parr[5]后,剩下int(*)(),就是函数指针类型,这个数组有5个元素,每个元素都是int(*)()类型
parr1 先和 [] 结合,说明 parr1是数组,数组的内容:是 int(*)() 类型的函数指针
即数组里的每个元素都是指针,即地址,每个地址都存有一个函数,对指针解引用就能调用函数
函数指针数组的用处:
一个简易计算器的函数:
intadd(int x,int y){return x + y;}intsub(int x,int y){return x - y;}intmul(int x,int y){return x * y;}intdiv(int x,int y){return x / y;}voidmenu(){printf("**************************\n");printf("**** 1.add 2.sub ****\n");printf("**** 3.mul 4.div ****\n");printf("**** 0.exit ****\n");printf("**************************\n");}
可以发现,当出现多个函数时,调用过程时相似的,但是代码重复较多,是冗余的:
//常规的写法非常繁琐intmain(){int input =0;int x =0;int y =0;int res =0;do{menu();printf("请选择 ==> ");scanf("%d",&input);switch(input){case1:printf("请输入两个操作数 ==> ");scanf("%d %d",&x,&y);
res =add(x, y);printf("ret=%d\n", res);break;case2:printf("请输入2个操作数:>");scanf("%d %d",&x,&y);
res =sub(x, y);printf("ret=%d\n", res);break;case3:printf("请输入2个操作数:>");scanf("%d %d",&x,&y);
res =mul(x, y);printf("ret = %d\n", res);break;case4:printf("请输入2个操作数:>");scanf("%d %d",&x,&y);
res =div(x, y);printf("ret = %d\n", res);break;case0:printf("退出计算器\n");break;default:break;}}while(input);return0;}
代码改写为使用函数指针数组:
//函数指针数组的用法intmain(){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");}elseif(input >=1&& input <=4){printf("请输入2个操作数:>");scanf("%d%d",&x,&y);
res =(*paar[input])(x, y);//直接调用函数printf("%d\n", res);}else{printf("选择错误,重新输入数字\n");}}while(input);return0;}
总结
指针进阶的内容较多,而且这些内容的名词都相似,如果不能牢固掌握,将会混淆这些知识点。第一遍学习不能完全掌握,要经常复习,温故而知新。
通过类比,可以有效地区分各种定义。下一篇继续学习指针进阶的内容。
版权归原作者 初学C语言者 所有, 如有侵权,请联系我们删除。