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

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

目录

指针的定义

数组指针

理解数组指针

数组指针的写法:

数组指针的使用

函数指针

理解函数指针

​编辑

函数指针的写法

函数指针的使用

简化代码

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.lryc.cn/news/613306.html

相关文章:

  • 人工智能——自动微分
  • Docker容器部署harbor-小白级教学
  • Dlib库是什么?白话,详细介绍版
  • python中用xlrd、xlwt读取和写入Excel中的日期值
  • GIT操作卡顿
  • 机器学习核心算法与实践要素(全篇)
  • java excel转图片常用的几种方法
  • 玳瑁的嵌入式日记D14-0807(C语言)
  • NVIDIA/k8s-device-plugin仓库中GPU无法识别问题的issues分析报告
  • Linux学习记录 DNS
  • LocalSqueeze(图片压缩工具) v1.0.4 压缩
  • nlp-句法分析
  • ClickHouse数据迁移
  • Redis持久化存储
  • 【网络运维】Linux:NFS服务器原理及配置
  • ansible-playbook之获取服务器IP存储到本地文件
  • Linux---第三天---权限
  • Idea打包可执行jar,MANIFEST.MF文件没有Main-Class属性:找不到或无法加载主类
  • 3a服务器的基本功能1之身份认证
  • LINUX-文件查看技巧,重定向以及内容追加,man及echo的使用
  • Java开发时出现的问题---架构与工程实践缺陷
  • vue开发的计算机课程页面
  • Salesforce 的Event Monitoring和Audit Trail 区别
  • C语言中级_动态内存分配、指针和常量、各种指针类型、指针和数组、函数指针
  • 洛谷P1990 覆盖墙壁
  • AMO:超灵巧人形机器人全身控制的自适应运动优化
  • 前端学习 7:EDA 工具
  • 板块三章节3——NFS 服务器
  • SupChains技术团队:需求预测中减少使用分层次预测(五)
  • 写Rust GPU内核驱动:GPU驱动工作原理简述