个人备案的网站名称,做公司标志用哪个网站,网站制作价格多少钱,旅行网站开发意义目录
1. 函数名的理解
1.1 “函数名”和“函数名”的含义
1.2 函数(名)的数据类型
2. 函数指针(变量)
2.1 函数指针(变量)的创建格式
2.2 函数指针(变量)的使用格式
2.3 例子 判别
3. typedef 关键字
3.1 typedef的作用
3.2 typedef的运作逻辑 和 函数指针类型…目录
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 ab;
}
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()
{//创建函数指针paddPADD padd add;//以函数指针的方式调用函数addint 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
#includestdio.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包含计算器函数与菜单函数
#includecounter.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——普通版
#includecounter.h
int main()
{int input; //input对应菜单的选择int x, y; //x和y分别对应运算的数值a和bint 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的回调函数是addbreak;case 2:calculator(sub);//case2的回调函数是subbreak;case 3:calculator(mul);//case3的回调函数是mulbreak;case 4:calculator(div);//case4的回调函数是divbreak;case 0:printf(程序已成功退出\a\n);break;default:printf(选择错误请重新选择\n);}} while (input);return 0;
} 本期分享完毕感谢大家的支持Thanks♪(ω)