数组指针-函数指针-回调函数
目录
指针的定义
数组指针
理解数组指针
数组指针的写法:
数组指针的使用
函数指针
理解函数指针
编辑
函数指针的写法
函数指针的使用
简化代码
typedef
阅读结构复杂代码
函数指针数组
理解函数指针数组
函数指针数组的使用
指向函数指针数组的指针
理解指向函数指针数组的指针
如何定义
回调函数
理解回调函数
回调函数的使用
指针的定义
内存会划分为一个个的内存单元,每个内存单元都有一个独立的编号 - 编号也称为地址。
地址在C语言中也被称为指针。
指针(地址)需要存储起来 - 存储到变量中,这个变量也就被称为指针变量。
指针的大小根据32位平台或64位平台固定是4/8个字节。
地址是物理的电线上产生。
32位机器 - 32根地址线 - 1/0
32个0/1组成的二进制序列,把这个二进制序列就作为地址,32个bit位才能存储这个地址,也就是需要4个字节才能存储。所以指针变量的大小就是4个字节。
同理64位机器上,地址的大小是64个0/1组成的二进制序列,需要64个bit位存储,也就是8个字节。
数组指针
理解数组指针
我们都知道:
整形指针 - 指向整形变量的指针,存放整形变量的地址的指针变量
字符指针 - 指向字符变量的指针,存放字符变量的地址的指针变量
数组 - 存放一组同类型数据的集合。
数组名的理解:
数组名是数组首元素的地址
有2个例外:
1. sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节。2. &数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址。
除此之外,所有的地方的数组名都是数组首元素的地址。
这里看一段代码:
int main()
{int arr[10] = { 0 };printf("%p\n", arr);// int*printf("%p\n", &arr[0]);// int*printf("%p\n", &arr);// int(*)[10]return 0;
}
其中:
arr表示数组名,数组名是数组首元素的地址,所以其类型为整形指针int*。
&arr[0]表示数组首元素的地址,所以其类型也为整形指针int*。
&arr表示获取数组名的地址,也就是获取整个数组的地址,所以其类型为指向数组的指针(数组指针)int(*)[10]。
使用监视器检测的结果:
综上:
数组指针 - 指向数组指针,存放的是数组变量的地址的指针变量
数组指针的写法:
我们知道:
整形数组的定义为为:int arr[5] = { 1,2,3,4,5 }
整形指针的写法为:int* p = arr
而数组指针是一个指针,那在定义时就可以先写一个指针:int* pArr
而其又指向了一个数组:int (*pArr)[5]
赋值:int (*pArr)[5] = &arr;
注意:
指针数组:是数组,是存放指针的数组。如:int* arr[5]。
数组指针,是指针,是指向数组的指针。如:int(*arr) [5]。
指针数组:是数组,是存放指针的数组。如:int* arr[5]。
数组指针,是指针,是指向数组的指针。如:int(*arr) [5]。
数组指针的使用
数组指针一般在二维数组上使用才方便,这里以打印二维数组举例:
未使用数组指针:二维数组传参,形参是二维数组的形式
void Print(int arr[][5], int r, int c) // 二维数组传参,行可以省,列不能省
{for (int i = 0; i < 3; i++){for (int j = 0; j < 5; j++){printf("%d", arr[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;
}
使用数组指针:二维数组传参,形参是数组指针
void Print(int (*p)[5], int r, int c)
{for (int i = 0; i < r; i++){for (int j = 0; j < c; j++){// *(p + i) 解引用数组指针的第i个元素(这个元素是指针)// *(*(p + i))解引用上面拿到的指针printf("%d", *(*(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 Add(int x, int y)
{return x + y;
}int main()
{printf("%p\n", &Add);printf("%p\n", Add);return 0;
}
打印&Add和Add的地址会得到一样的值,通过监视可以看到&Add是一个指针,Add是一个函数,但打印函数名得到的是和&Add一样的地址,所以函数名实际上也表示函数存储的地址。
函数指针的写法
&Add得到的是地址,也就是指针。通过前面的监视我们发现&Add得到的是int(*)(int, int)这样的一个指针,而这就是函数指针。所以&Add就可以拿int (*pf)(int, int)接收,即int (*pf)(int, int) = &Add;
注意:
pf = &Add pf是函数指针变量
(*pf) = &Add 给它一个*说明它是指针,给它括起来
(*pf)(int, int) = &Add 指针的指向是一个函数,函数的参数
int(*pf)(int, int) = &Add 函数的返回类型是一个int
如果(*pf)没有()的话就成了int* pf(int, int),其中pf(int, int)表示函数和参数类型,返回的是int*
函数指针的使用
int Add(int x, int y)
{return x + y;
}int main()
{int (*pf)(int, int) = &Add;printf("%d\n", Add(3, 5));// 写法1printf("%d\n", (*pf)(4, 5));// 该写法int*一定要加括号,*pf(4,5)的意思是为函数pf传参4,5,再对其返回值解引用// 写法2printf("%d\n", pf(4, 5));// 简化写法,pf 与 Add都是地址return 0;
}
简化代码
typedef
C语言允许用户使用 typedef 关键字来定义自己习惯的数据类型名称,来替代系统默认的基本类型名称、数组类型名称、指针类型名称与用户自定义的结构型名称、共用型名称、枚举型名称等。一旦用户在程序中定义了自己的数据类型名称,就可以在该程序中用自己的数据类型名称来定义变量的类型、数组的类型、指针变量的类型与函数的类型等。
阅读结构复杂代码
这里来看一段结构比较复杂的代码:
void (*signal(int, void(*)(int)))(int);
其中signal是一个库函数,通过signal函数,程序可以定义某些信号(如SIGINT,由按下Ctrl+C产生)到达时要执行的处理程序。
从signal开始看,因为signal是一个库函数,所以其(int, void(*)(int))为它的形参,int,void(*)(int)分别为signal需要传入的形参,而void(*)(int)表示一个返回类型为void,形参为int的函数指针。然后其外层是void(*…)(int)的结构,这意味这signal返回了一个void(*)(int)的类型。
不能发现这样阅读的成本有点高,但如果我们使用typedef自定义一个void(*)(int)的数据类型就将大大降低阅读成本。
typedef void (*Func)(int); // 定义函数指针类型
Func signal(int signum, Func func);
函数指针数组
理解函数指针数组
函数指针数组,顾名思义,就是一个数组,该数组存放的是函数指针。
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 (*pf1)(int, int) = Add;int (*pf2)(int, int) = Sub;int (*pf3)(int, int) = Mul;int (*pf4)(int, int) = Div;// 函数指针数组int (*pfArr[4])(int, int) = {Add, Sub, Mul, Div};return 0;
}
在这里面,int (*pfArr[4])(int, int)表示函数指针数组。
理解*pfArr[4]:*,变量名,[]放在一起时,变量名会先和[]结合,表示这是一个数组,这时候再加上前面的*则表示这个数组存放的是指针,即指针数组。
int (*pfArr[4])(int, int)后面的(int, int)表示指针指向的是一个函数,该函数需要传递两个int类型的形参,前面的int表示该函数的返回类型是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 ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret = %d\n", ret);break;case 2:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}
不难发现该代码非常的冗余
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("ret = %d\n", ret);
break;
这段代码出现频率非常的高,除了使用的函数不同外,其它部分几乎都一模一样。
通过函数指针数组优化冗余代码:
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 ret = 0;//int (*pf)(int, int);// 函数指针//int (*pfArr[4])(int, int);// 函数指针数组//int (* (*p)[4])(int, int) = &pfArr;// 函数指针数组的地址// *p表示这是一个指针,(*p)[4]表示该指针指向的是一个数组,int(*)(int,int)是一个函数指针int (*pfArr[5])(int, int) = { NULL,Add, Sub, Mul, Div };do{menu();printf("请选择:>");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("选择错误,重新选择\n");}} while (input);return 0;
}
指向函数指针数组的指针
理解指向函数指针数组的指针
指向函数指针数组的指针是一个指针,这个指针指向一个数组,该数组的每一个元素都是函数指针。
如何定义
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 (*pf1)(int, int) = Add;int (*pf2)(int, int) = Sub;int (*pf3)(int, int) = Mul;int (*pf4)(int, int) = Div;// 函数指针数组int (*pfArr[4])(int, int) = {Add, Sub, Mul, Div};// 指向函数指针数组的指针int (*(*ppfArr)[4])(int, int) = &pfArr;return 0;
}
*ppfArr表示ppfArr是一个指针,该指针指向的是一个数组(*ppfArr)[4],该数组中存放的是int (*)(int, 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");
}void Calc(int (*pf)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:Calc(Add);break;case 2:Calc(Sub);break;case 3:Calc(Mul);break;case 4: Calc(Div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}
以Add为例,Calc(Add)表示将Add的地址(函数名表示函数的地址)传递给Calc时,Calc中又调用了该函数来完成人物,这就是回调函数。