1.数组传参
1-1一维数组传参
正向:实参给定,猜猜看形参可以怎么写?
测试1:
注:这里没有要求打印,所以没有传元素个数sz
测试2:
void test2(int* arr2[10])//bingo void test2(int* arr2[])//bingo void test2(int** arr2)//bingo int main() { int* arr2[10] = { 0 }; test2(arr2);//数组名是首元素(int*类型)的地址 return 0; }
1-2 二维数组传参
void test(int arr[3][5])//bingo-列必须写上,且必须写对 void test(int arr[][5])//bingo -敲黑板!!!行可以省略,但是列不可以省略 void test(int arr[][])//error -列必须写上,且必须写对 void test(int(*p)[5])//bingo -数组指针,指针指向的是一个一维数组 int main() { int arr[3][5] = { 0 }; test(arr);//数组名是首元素(int [5]类型)的地址 return 0; }
关于我对二维数组及多维数组的理解:
由二维数组引申的两个普遍规律:
1. 我们所知的复合类型(比如数组,结构体等)的类型名都是首元素的地址(两个特殊情况除外)。
2. 对于多维数组定义或者传参时,只有第一维数组的数组元素可以省略,其余维必须写上,且必须写对!!!
2 指针传参
反向:形参给定,猜猜看实参可以怎么写?
2-1 一级指针传参
void test1(int* ptr)//一级指针:存放普通变量的地址 { //... } int main() { int a = 10; int* p = &a; test1(&a); test1(p);//p是一级指针变量的内容 int arr[10]; test1(arr); return 0; }
2-2 二级指针传参
void test2(int** pp)//二级指针:存放一级指针的地址 { //... } int main() { char ch = 'a'; char* pch = &ch; char** ppch = &pch; test2(&pch); test2(ppch); //错误范例1: test2(&&ch);ch的地址(数据)是没有地址的,只有ch的地址被变量存起来(变量)才有地址 char* arr[5]; test2(arr);//数组名是首元素(也就是一级指针变量)的地址 //错误范例2: char arr2[3][5]; test2(arr);//error!!!!// arr是二维数组数组名,表示的是一维数组的地址 return 0; }
2-3 关于传&arr和arr
2-3-1 这里以二维数组为例,讲一讲实参和形参的匹配问题
void test1(int(*p)[5])// int[5]*类型 { ; } void test2(int(*p)[3][5]) //int[3][5]*类型 { ; } int main() { int arr[3][5]; test1(arr);//二维数组首元素(整个一维数组)的地址 test2(&arr);//整个二维数组的地址 return 0; }
这里以一维数组为例,讲一讲函数内要想打印的具体实现(&arr的鸡肋问题):
如果在主函数调用的时候传&arr的话就太鸡肋了!(因为你传整个数组的地址,你又不能一次性打印出来,你还得对整个数组的地址进行解引用。
解引用后就是一维数组的数组名,因为这个数组名不是那两个特殊情况,所以这个数组名又摇身一变,变成数组首元素的地址,到这里就和直接在主函数调用的时候传arr的效果是一样的)
void Print1(int* arr,int n) { printf("Print1:>\n"); for (int i = 0; i < n; i++) { printf("arr[%d]=%d\t", i, *(arr + i)); } } void Print2(int(*arr)[3], int n) { printf("Print2:>\n"); for (int i = 0; i < n; i++) { printf("arr[%d]=%d\t", i, *(*arr + i));//鸡肋 } } int main() { int arr[3] = { 1,2,3 }; Print1(arr,3); printf("\n\n"); Print2(&arr,3); return 0; }
3 函数指针
函数是放在代码区的,只要是定义了就会在编译阶段就会分配空间,和全局变量一样。
3-1 函数指针的引入
int Add(int a, int b) { return a + b; } int main() { //函数也有地址 printf("%p\n", Add); printf("%p\n", &Add);//-取地址只是摆设捏 //有地址能不能用能不能用指针变量存起来-函数指针 int (*p1)[3];//-数组指针 int(*p2)(int,int) = &Add;//-函数指针,指向函数的指针 printf("%p\n", p2); return 0; }
[]和()的运算符优先级都比*高
关于为什么要有函数参数的一点思考:
-这和数组指针类似,int(*p)[5],这个[5]是对指针指向的内容的必要说明,也就是所指向数组类型的一部分,不可省略。
-同理,函数指针的类型里的返回值和形参都是对所指向函数的必要说明。
char* test(int(*p)[5], char*) { return NULL; } //问题:来照猫画虎写一个指向这个函数的函数指针 //答案:char*(*p)(int(*)[5], char*) = &test;
3-2 函数指针的脱裤子放屁使用【先见一见基本操作】
int Add(int a, int b) { return a + b; } int main() { printf("test1:\n%p\n", &Add); printf("test2:\n%p\n", Add); int(*p)(int, int) = &Add; //int(*p)(int, int) = Add; printf("test3:\n%d\n", Add(2, 3)); printf("test4:\n%d\n", (*p)(2,3)); printf("test5:\n%d\n", p(2,3)); //在获得函数地址时,&和不加&都可以,但是加上&更好理解 //在通过调用函数时, *和不加*都可以,但是加上*更好理解,且必须要带上括号 return 0; }
3-3 试图看懂大佬写的代码
代码1:
1. (*(void(*)())(); 2. //提示:这个整体是函数调用
子例程:函数
参考:《C陷阱和缺陷》
代码2:
1. void(* signal(int,void(*)())(int); 2. //提示:这个整体是函数声明
小小勘误:图片中第3步中指针类型应该改为函数指针类型
4 函数指针数组
4-1函数指针数组的引入和基本使用
只要你前面学会了,这里就是一样的套用,我这里就不啰嗦了
int Add(int a, int b) { return a + b; } int Sub(int a, int b) { return a - b; } int Mul(int a, int b) { return a * b; } int Div(int a, int b) { return a / b; } int main() { //字符指针数组 char* arr1[5]; //整型指针数组 int* arr2[5]; //函数指针数组 //int(*pf[4])(int, int); //在没有函数指针数组之前... int(*pf1)(int, int) = Add; int(*pf2)(int, int) = Sub; int(*pf3)(int, int) = Mul; int(*pf4)(int, int) = Div; //有函数指针数组之后... int(*pf[4])(int, int) = { Add,Sub,Mul,Div }; for (int i = 0; i < 4; i++) { int ret=pf[i](6, 2); printf("%d\n", ret); } return 0; }
函数指针数组的优缺点:
- 优点:不用一个一个定义变量去存储函数的地址,然后一个一个去调用
- 缺点:函数指针数组既然是数组,就要求是相同类型元素的集合,也就是返回值和参数类型的一样才能放到函数指针数组内,统一进行操作。
4-2 函数指针数组的妙用
函数指针数组实现加减乘除运算器,这里的函数指针数组被称为转移表
int Add(int a, int b) { return a + b; } int Sub(int a, int b) { return a - b; } int Mul(int a, int b) { return a * b; } int Div(int a, int b) { return 1.0*a / b; } void meau(void) { printf("**************************\n"); printf("****** 1.Add *****\n"); printf("****** 2.Sub *****\n"); printf("****** 3.Mul *****\n"); printf("****** 4.Div *****\n"); printf("****** 0.exit *****\n"); printf("**************************\n"); } int main() { int input = 0; int operand1 = 0; int operand2 = 0; int(*pf[5])(int, int) = {0,Add,Sub,Mul,Div}; meau(); do { printf("请输入你的选择:>"); scanf("%d", &input); if (input == 0) { printf("退出程序\n"); break; } else if (input >= 1 && input <= 4) { printf("请输入两个操作数:>"); scanf("%d%d", &operand1, &operand2); printf("%d\n", pf[input](operand1,operand2)); } else { printf("输入非法,请重新输入\n"); continue; } } while (input); return 0; }
5 回调函数
回调函数:把函数1的地址作为函数2的函数参数,从而调用函数2,然后再函数2实现过程中通过指针调用函数1,那么这个被其他函数调用的函数(函数1)就被称为回调函数。
void test1(void(*p)()) { (*p)(); } void test2() { printf("test2\n"); } int main() { test1(&test2); }
5-1 回调函数的使用举例1:计算器
原来的switch case 语句好多冗余的语句,又有前提减加乘除的函数参数和返回值类型相同,所以可以使用回调函数处理这个问题。
void cal(int (*p)(int, int)) { int o1 = 0; int o2 = 0; printf("请输入两个操作数:>"); scanf("%d%d", &o1, &o2); printf("%d\n", p(o1, o2)); } int main() { int input = 0; meau(); do { printf("请输入你的选择:>"); scanf("%d", &input); switch (input) { case 1: cal(Add); break; case 2: cal(Sub); break; case 3: cal(Mul); break; case 4: cal(Div); break; case 0: printf("exit\n"); break; default: printf("非法\n"); } } while (input); return 0; }
这里的Add,Sub,Mul,Div函数都是回调函数,通过传不同函数的地址给Cal函数,Cal函数内部用函数指针接收,从而实现了Cal函数的多重功能。