目录
1. 函数名的理解
1.1 “函数名”和“&函数名”的含义
1.2 函数(名)的数据类型
2. 函数指针(变量)
2.1 函数指针(变量)的创建格式
2.2 函数指针(变量)的使用格式
2.3 例子 · 判别
3. typedef 关键字
3.1 typedef的作用
3.2 typedef的运作逻辑 和 函数指针类型的重命名
4. 函数指针数组
5. 转移表
5.1 转移表的概念
5.2 转移表 与 加减乘除计算器
6. 回调函数
6.1 回调函数的概念
6.2 回调函数 与 加减乘除计算器
1. 函数名的理解
1.1 “函数名”和“&函数名”的含义
我们在《指针之旅(3)—— 指针与数组》中了解过“数组名”代表数组首元素的地址,“&数组名”代表整个数组的地址,共2种含义。但是在函数这可就不一样了:
“函数名”和“&函数名” 在数值上相同,在含义上也相同,都表示函数的地址。
代码演示:
int add(int a, int b)
{
return a+b;
}
int main()
{
printf("add = %p\n",add);
printf("&add = %p\n", &add);
return 0;
}
add与&add数值一样:
它们的含义也相同,且都不能进行+-操作:
1.2 函数(名)的数据类型
函数的数据类型:
- 在函数声明中,去掉函数名就是函数的数据类型了:
- 即“ 返回类型 (参数表) ”
比如:
这里的加法函数add的声明是“int add(int a, int b)”,它的数据类型是“int (int , int)”。(其实在函数声明时,参数表中的变量名可以省略)
2. 函数指针(变量)
2.1 函数指针(变量)的创建格式
我们类比推理一下:
- 字符指针-->是指针变量-->存放的是字符变量的地址
- 整型指针-->是指针变量-->存放的是整型变量的地址
- 函数指针-->是指针变量-->存放的是函数的地址
所以我们知道了函数指针的定义:函数指针(变量)存放的是函数的地址。
函数指针(变量)的2种初始化:
1. 返回类型 函数名 (参数表); //先要有函数
2. ❶同返回类型 (* 函数指针名) (同参数表) = &函数名; //再有函数指针
❷同返回类型 (* 函数指针名) (同参数表) = 函数名;
(两种初始化都可以,因为“函数名”和“&函数名”代表的含义相同)
以加法函数“int add(int a,int b)”举例,函数指针的创建和初始化可以写成:
- int (*pf)( int a, int b) = &add;
- int (*pf)( int , int ) = add;
(参数表中的变量名可写可不写)
为什么创建方式是“ int (*pf)(int, int) = &add ”,而不是“ int(*)(int, int) pf = &add ”?
原因:“(参数表)”中的小括号()是函数调用操作符,函数调用操作符有两种功能,(1)是声明函数(2)是调用函数。无论哪种功能,它的结合规则都是自左向右结合。
补充:函数指针创建时,函数调用操作符此时的作用是(1)声明函数。由于这里声明的对象是指针,所以使得该指针获得了函数的性质。
2.2 函数指针(变量)的使用格式
函数指针(变量)的2种使用格式:
❶ (*函数指针名)(参数1,参数2,…); //可理解为“ *&函数名(参数表)”
❷ 函数指针名 (参数1,参数2,…); //可理解为“ 函数名(参数表)”
这两种方式是一样的。
代码举例:
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*pf)(int, int) = &add;
printf("%d\n", (*pf)(60, 4));
printf("%d\n", pf(60, 4));
return 0;
}
可以看到,两种使用格式都能算出正确的结果。
2.3 例子 · 判别
例子1:
( *( void (*)( ) ) 0 )( ); //请解读该语句的意思,提示:切入点是viod(*)()
图解:
这句话的意思是:
(1)先将0强制转换成void(*)()型的函数指针。
即:( void (*)() ) 0;
(2)然后去调用0地址处的函数。
即:( *0 )( );
例子2:
void ( * signal(int , void(*)(int) ) )(int); //请解读该语句的意思
提示:
(1)切入点是 signal、void(*)(int)和最后一个(int)。
(2)以 “函数名 (参数表)” 的形式组合。
图解:
写成这样让大家好理解一点:(但在编译器中不能写成这样,是错的)
void (*)(int) signal ( int , void(*)int ) ;
| | |
| | 这是参数表,调用该函数时要输入这两种类型的变量
| signal是函数名
void(*)(int)是函数的返回类型
3. typedef 关键字
3.1 typedef的作用
typedef作用的对象只有数据类型,用来给类型进行重命名(定义新的别名)。可以将复杂的类型,简单化。
最基础的使用方式:
1. typedef 要被重命名的类型 新的别名;
⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
- typedef unsigned int uint;
//虽然unsigned与int直接有空格,但是unsigned int是一种计算机中的内置类型,是合起来看的
可以看到,旧的类型重命名后依然能被使用。
3.2 typedef的运作逻辑 和 函数指针类型的重命名
typedef的运作逻辑是:
在正常创建变量(指针、数组、函数)的格式基础上,原本书写变量名(指针名、数组名、函数名)的地方,typedef把该地方的内容当作是旧类型的别名。
比如,我们要创建一个数组int arr[10],但是要求用typedef的方式创建。
这个arr数组的类型是int [10],我们可以把它重命名为int10。
但正确的重名方式是:typedef int int10[10];
而不是:typedef int [10] int10;
如下:
函数指针类型的重命名规则也是如此:
比如,我们把 指向加法函数的指针类型 重命名成PADD,应该写成:typedef int(*PADD)(int , int);
代码如下:
int add(int a, int b)
{
return a + b;
}
typedef int (*PADD)(int a, int b); //参数表中的变量名可不写
int main()
{
//创建函数指针padd
PADD padd = &add;
//以函数指针的方式调用函数add
int ret = padd(4, 10);
printf("4与10之和为%d\n", ret);
return 0;
}
结果:
4. 函数指针数组
函数指针数组中的每个元素,它的数据类型都是函数指针类型。
函数指针数组的创建格式:
- 在函数指针名后面加上下标引用操作符[ ],并写上数组元素的个数:
- 即“ 返回类型 ( *函数指针数组名[数组元素个数] )( 参数表 ) ”
补充:谁最先与名字结合,该名称的本质就是什么。[ ]的优先级比*的优先级高,所以下标引用操作符[ ]最先与名字结合,所以函数指针数组的本质是一个数组。
比如我们要创建一个有3个元素的数组parr,每个元素的类型都是int (*)( )。
正确的格式:
- int (*parr[3])( ) = {0};
常见错误格式:
- int *parr[3]( );
- int (*)( ) parr[3];
5. 转移表
5.1 转移表的概念
转移表的本质是一个函数指针数组。传统的条件选择语句(如switch)在处理大量操作时会变得冗长,而转移表通过将操作映射到函数指针数组中,可以显著减少代码的重复性。
5.2 转移表 与 加减乘除计算器
我们来做一个只有加减乘除的整型计算器,一开始我们会这样想:
头文件counter.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
void menu();
函数文件counter.c(包含计算器函数与菜单函数)
#include"counter.h"
int add(int a, int b) //1.加法
{
return a + b;
}
int sub(int a, int b) //2.减法a-b
{
return a - b;
}
int mul(int a, int b) //3.乘法
{
return a * b;
}
int div(int a, int b) //4.除法a/b
{
return a / b;
}
void menu() //菜单页面
{
printf("*************************\n");
printf(" 1:加法\t2:减法 \n");
printf(" 3:乘法\t4:除法 \n");
printf("-> 0:退出 \n");
printf("*************************\n");
printf("请选择并输入对应的数字:");
}
最后我们写成一个简单的计算器程序: test1.c
版本1——普通版:
#include"counter.h"
int main()
{
int input; //input对应菜单的选择
int x, y; //x和y分别对应运算的数值a和b
int ret; //ret接收计算器运算后结果
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入2个操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("计算结果是:%d\n\n", ret);
break;
case 2:
printf("输入2个操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("计算结果是:%d\n\n", ret);
break;
case 3:
printf("输入2个操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("计算结果是:%d\n\n", ret);
break;
case 4:
printf("输入2个操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("计算结果是:%d\n\n", ret);
break;
case 0:
printf("程序已成功退出\a\n");
break;
default:
printf("选择错误,请重新选择:\n");
break; //这个break可以不写
}
} while (input); //input等于0就退出程序
return 0;
}
我们可以发现,这个基础的版本有很多重复的部分。比如:
- printf("输入2个操作数:");
- scanf("%d %d", &x, &y);
- ret = add(x, y);
- printf("计算结果是:%d\n\n", ret);
这4句相似语句重复出现了4次
思考一下,能不能让这4句话只出现一次?
- 这4句相似的话唯一不同的地方在于“ret = 函数返回值”。
- 加减乘除这4个函数,他们都是int (int, int)型,我们可以用一个函数指针数组来对这4个函数进行映射。
于是我们有了下面这个版本: test2.c
版本2——转移表法:
int main()
{
int input; int x, y; int ret;
//创建转移表
int (*pfun[5])(int, int) = { 0,add,sub,mul,div }; //pfun[0]=0后,使得函数选择与下标序号一一对应。
do
{
menu();
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pfun[input](x, y);//通过下标引用操作符[]和函数调用操作符()来使用对应的函数
printf("计算结果是:%d\n", ret);
}
else if (input == 0)
{
printf("程序已成功退出\a\n");
}
else
{
printf("选择错误,请重新选择:\n");
}
} while (input);
return 0;
}
6. 回调函数
6.1 回调函数的概念
回调函数的概念:
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。
回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
举例说明:
假设现在有一个叫blog的函数,它的函数声明是这样的:int blog( int (*pfun)(int , int) , int n);
- 那么pfun所指向的函数就是回调函数,函数指针pfun接收着回调函数的地址。
- blog函数是调用回调函数的函数。(也叫主调函数 或者 回调触发函数)
6.2 回调函数 与 加减乘除计算器
对于版本1的重复的4句话,我们可以通过函数回调的方式使其变成1句话: test3.c
版本3——函数回调版
//调用回调函数的汇合函数
void calculator(int (*pfun)(int, int)) //输入的参数是计算器函数的地址,形参pfun是函数指针
{
int x, y;
printf("输入2个操作数:");
scanf("%d %d", &x, &y);
//使用计算器函数
int ret = pfun(x, y);
printf("计算结果是:%d\n", ret);
}
int main()
{
int input;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
calculator(add);//case1的回调函数是add
break;
case 2:
calculator(sub);//case2的回调函数是sub
break;
case 3:
calculator(mul);//case3的回调函数是mul
break;
case 4:
calculator(div);//case4的回调函数是div
break;
case 0:
printf("程序已成功退出\a\n");
break;
default:
printf("选择错误,请重新选择:\n");
}
} while (input);
return 0;
}
本期分享完毕,感谢大家的支持Thanks♪(・ω・)ノ