当前位置: 首页 > ai >正文

数组指针-函数指针-回调函数

目录

指针的定义

数组指针

理解数组指针

数组指针的写法:

数组指针的使用

函数指针

理解函数指针

​编辑

函数指针的写法

函数指针的使用

简化代码

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中又调用了该函数来完成人物,这就是回调函数。

http://www.xdnf.cn/news/17141.html

相关文章:

  • 大屏数据展示页面,数据可视化可以用到的框架和插件
  • docker启动出现Error response from daemon: Container的问题【已解决】
  • List、ArrayList 与顺序表
  • VSCode:基础使用 / 使用积累
  • shell基础之EOF的用法
  • React:受控组件和非受控组件
  • 2025年测绘程序设计模拟赛一--地形图图幅编号及图廓点经纬度计算
  • 202506 电子学会青少年等级考试机器人六级实际操作真题
  • 简单介绍cgroups以及在K8s中的应用
  • copy_file_range系统调用及示例
  • CubeFS存储(一)
  • HTML应用指南:利用GET请求获取全国OPPO官方授权体验店门店位置信息
  • css优化、提升性能方法都有哪些?
  • LINUX-磁盘管理
  • 基于2025年《Science》期刊论文的科研图表Python绘制分析
  • 二、Envoy静态配置
  • Linux环境下部署SSM聚合项目
  • 阿里云polardb-x 2.0迁移至华为云taurusdb
  • 安卓雷电模拟器安装frida调试
  • BottomSheetDialogFragment 设置背景为透明无效果(解决方法)
  • antd组件select下拉数据分页加载
  • vcpkg: 一款免费开源的C++包管理器
  • 计算机网络:如何判断B或者C类IP地址是否划分了子网
  • 基于Hadoop的木鸟民宿数据分析与可视化、民宿价格预测模型系统的设计与实现
  • CAN通信
  • 解决Node.js v12在Apple Silicon(M1/M2)上的安装问题
  • 使用R将nc文件转换为asc文件或者tif文件
  • 下载Android studio
  • try catch throw的本质
  • Linux《进程间通信(上)》