5.数组参数与指针参数
在写代码时,难免有时会将数组名传递给参数,那么我们该如何设计函数中用于接收数组名的形参呢?有两个方法:
- 将形参设计成数组的格式
- 将形参设计成指针的格式
我们来判断如下的函数部分的形参设计是否恰当呢?
一维数组传参:
#include <stdio.h> void test(int arr[])//ok? {} void test(int arr[10])//ok? {} void test(int *arr)//ok? {} void test2(int *arr[20])//ok? {} void test2(int **arr)//ok? {} int main() { int arr[10] = {0}; int *arr2[20] = {0}; test(arr); test2(arr2); return 0; }
- 第2行:使用恰当。函数调用时传递的是一维数组的数组名,这里形参接收时,也可以写做一维数组的格式,但其本质上不是一维数组。
- 第4行:使用恰当。函数调用时传递的是一维数组的数组名,这里形参接收时,也可以写做一维数组的格式,但其本质上不是一维数组,所以形参中,一维数组中的元素个数并不重要,这样写也是完全恰当的。
- 第6行:使用恰当。函数调用时传递的是一维数组的数组名,数组名代表的是数组首元素的地址,也就是int数据类型的地址,形参可以用int*类型的指针进行接收。
- 第8行:使用恰当。传参时传递的是一维数组的数组名,这里形参接收时,也可以写做一维数组的格式,但要知道其本质上不是一维数组。
- 第10行:使用恰当。函数调用时传递的是一维数组的数组名,数组名代表的是数组首元素的地址,也就是int*数据类型的地址,形参可以用int**类型的指针进行接收。
二维数组传参:
void test(int arr[3][5])//ok? {} void test(int arr[][])//ok? {} void test(int arr[][5])//ok? {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int *arr)//ok? {} void test(int* arr[5])//ok? {} void test(int (*arr)[5])//ok? {} void test(int **arr)//ok? {} int main() { int arr[3][5] = {0}; test(arr); }
- 第1行:使用恰当。函数调用时传递的是二维数组的数组名,这里形参接收时,也可以写做二维数组的格式,但其本质上不是二维数组。并且该二维数组的列数不能省略,行数可以省略。就等同于二维数组中每个一维数组的长度必须明确。
- 第3行:使用不恰当。函数调用时传递的是二维数组的数组名,这里形参接收时,也可以写做二维数组的形式,并且列数是不能省略的。
- 第5行:使用恰当。数调用时传递的是二维数组的数组名,这里形参接收时,也可以写做二维数组的形式,并且这里没有省略列数。
- 第10行:使用不恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分应该使用数组指针进行接收,而这里是整型指针。
- 第12行:使用不恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分应该使用数组指针进行接收,而这里是指针数组。
- 第14行:使用恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分采用的是数组指针进行接收。
- 第16行:使用不恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分应该使用数组指针进行接收,而这里是二级整型指针。
一级指针传参:
#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; }
当一个函数的参数部分是一级指针时,该函数可以接收哪些类型的参数呢?
void test(int* arr);test函数可以接收:整型变量的地址,一级整型指针,一维整型数组的数组名,整型指针数组的元素。(当然这里肯定没有说全)
二级指针传参:
#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; }
当函数的参数为二级指针时,可以接收哪些参数呢?
void test(int **p);test函数可以接收:一级指针的地址,二级指针,一维指针数组的数组名……
6.函数指针
先看一段代码:
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
运行结果:
这里输出的是test函数的地址,那么函数的地址应该使用怎样的指针变量进行保存呢?
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void *pfun2();
pfun1先与*结合,说明pfun1是个指针,指向的函数无参,返回值为void。说明pfun1能存放test函数的地址。
阅读下面两段代码:
代码1是将0转化成一个(void (*)())这个函数指针,再接着对这个指针进行解引用操作,并调用该函数指针指向的函数。
代码2中,由于小括号的优先级比*高,signal先和()结合,signal代表函数名,这个函数有两个参数,分别是int类型的参数和void(*)int(函数指针)类型的参数,这第二个参数是函数指针类型,指向的函数的参数是int,返回值是void。接着将 signal(int , void(*)(int))这部分去掉,剩下的部分,即 void(*)int;signal这个函数的返回值是一个函数指针。所以代码2其实就是signal函数的声明。
关于typedef的在指针中的使用:
下面这两段代码,哪个才能达到我们的效果呢?
typedef int(*parr)[10]; typedef int(*)[10] parr;
答案是前者第一种方式才是可行的。假若要被重新命名的类型中含有*,那么重新命名之后的类型名必须要在*的右边。
函数指针也是类似的:
typedef int(*)(int,int) pf;//错误 typedef int(*pf)(int,int);//正确
了解了typedef在指针中的使用了之后,就可以对上面的两段代码进行改进了。
//代码1 (*(void (*)())0)(); //改进 typedef void(*p)(); (*(p)0)(); //代码2 void (*signal(int , void(*)(int)))(int);//类型重定义。 //改进 typedef void(*pv)(int); pv signal(int,pv);
本节内容暂时到这里,后面还会继续更新指针进阶的内容!!如有不足,敬请指正。