函数指针就是指向函数的指针;本质上还是一个指针
首先我们要知道函数的地址就是函数名,取地址函数名也是函数的地址;这两个方式事等价的
类如:
int Add(int x, int y)
{
return x + y;
}
# include
int main()
{
int(*p)(int, int) = Add; //这里函数名就是函数的地址
int add = (*p)(10, 20); //通过函数指针调用函数
//int add = p(10, 20);
printf("%d", add);
return 0;
}
首先关注int(*p)(int, int) = Add
;这里说明 有*
号,说明p是个指针
,指向函数
,函数的返回类型为int
,参数列表为(int,int)
; 那解引用p得到就是指针指向的东西,该指针指向的就是 函数,所以*p
就是函数名,那么我就可以这样调用函数:(*p)(10,20)
;那既然函数名就是函数的地址,那也就是说还可以这么用指针当作函数名,可以这么调用函数 :p(10,20)
;
所以说,有函数指针,调用函数的方式就多了两种,这三种调用的方式是等价的:
(*p)(10,20)
,通过解引用指针调用函数
p(10,20)
,通过函数指针调用函数
Add(10,20)
;通过函数名调用函数
C语言规定:其实函数指针的变量名就是函数名,通过函数指针去调用函数时候,不需要解引用函数指针,也可以调用函数。
注意:
这三种函数指针的调用方式是等价的,说明调用函数时候*号没用
看一道题:void( * (signal( int ,void(*)(int)) ) (int) 请问如何理解这句声明:
首先我们从简单的看起:
void(*)(int)
,看这个*
说明是一个指针,指针的类型为去掉指针名,剩下的
就是指针类型,那这里没有指针名,所以指针类型为 void(*)(int)
,指向的函数返回类型为为 void 参数列表为 >int
,看signal( int ,void(*)(int))
;这就是signal 函数声明。 而 去掉函数声明,剩下的void(* )(int)
又是一个>函数指针,即为signal函数声明返回的类型,所以我们可以这么说:
void( * (signal( int ,void(*)(int)) ) (int)
是函数声明,signal
是函数名, 返回类型为void(*) (int)
,参数列表为int
和void(*)(int)
;返回类型是一个函数指针,指向的函数返回类型为void
和参数列表为int
;
上面的函数声明等价下面的方式:
typedef void(*pfun_t)(int);//可以这么想 typedef void(* )(int) p_fun_t;
//把 void(* ) (int )类型 命名为 pfun_t类型;
pfun_t signal(int,P_fun_t);
其实你可以类比一下数组,比如 定义数组声明时候 int arr[10]
; 数组的类型为 int [10]
;写法 也是把名字插入到类型中间,而函数的类型也是,写法也是讲函数名插入到类型中间
看看另一道题目:*((void(*)())0)()
,解释一些这语句标识什么意思。(来自《c缺陷和指针》)
解释:
*( ( void(*)() ) 0 ) ();
首先我们知道一个数组前面带一个括号,括号里面是类型的话,是发生强制类型转化的。
这里就是 0 为数组 ,( void(*)() )0,这个意思就是把0转化为 void(*) () 类型
我们知道 ( void(*)() ) 0 其实就是把 0转化为 函数指针类型。那么此时 0就表示 函数地址了。
然后 * 号标识解引用该函数指针类型的 0 ,即*( ( void(*)() ) 0 ) 表示解引用函数指针类型的0的地址得到指针指向的内容,其实就是函数名了。
最后的括号表示用函数指针类型解引用去调用函数。
所以最终解释为:*( ( void(*)() ) 0 ) (); 这是一次函数调用,讲 0强制转化为函数指针类型,通过解引用该 指针类型去调用 0 位置的地址,得到函数名,然后 调用函数。
函数指针到底有什么用途呢?到时候学深入你就知道了,这里只是点播一下,以后碰到不至于不会。
其实指针函数本质是函数,之所以叫指针函数,只不过其返回类型是指针类型。
如:
int* Add(int x,int y); Add就是指针函数
指针函数没有什么神秘的,就是平时我们写的返回类型为指针的函数而已。
值得注意的一点是,不要返回局部变量的地址,让指针去接收她,不然,会有意想不到的错误发生。
函数指针数组,本质还是数组,只不过数的返回类型为 函数指针。
我们知道,在一个声明语句中,一个变量(指针变量也是变量)的类型,是去掉变量名剩下的类型。
所以 int arr[5]; arr的类型为 int[5];
int*arr[5]; arr 的类型为 int*[5];
int *(*parr)[5];parr 的类型为 int *(*)[5];
你可能,不理解类型的含义是什么?(当然前面的指针篇幅做出过解释),但是
你却可以清楚的知道 一个变量的 类型是什么。
所以 看看这个 声明
int(*arr[5])(int,int) ;
arr的类型为 :int(* [5])(int ,int);
这是什么呢?假如我把int(*arr[5])(int,int) 写成 int(*)(int,int) arr[5] ;
是否看得更清楚:这样写,则类型为 int(*)(int,int)[5] ;
其实就是一个数组类型,但是我们却不可以这么写,需要写成 这种方式int(* [5])(int ,int);
声明时候也是写成:int(*arr[5])(int,int) ; 即使我们知道 arr是一个数组 ,也是需要 插入到类型的中间去,这是c语言规定的写法咯。
所以我们来解读一下:int(*arr[5])(int,int)
声明的含义
int(*arr[5])(int,int);
首先:arr是一个数组,该数组有5个元素,每个元素的类型为函数指针类型 ,即int(*)(int,int);该函数指针指向的函数
返回类型为 int ,参数列表为有两个 分别为int int;
在我的C语言指针的那些事:第一篇(有指针书写的技巧),系统的讲过数组的元素类型,数组类型,变量类型,指针类型,指针指向的类型到底怎么看出来的。顺便说了书写指针技巧。
总得来说:
要得到变量的类型,只要去点变量名就可以,这个变量名广泛(指针变量,函数名,数组名,普通变量名)都是变量名。
要得到指针指向的类型,只要去掉指针名和一个指针*星号,这个星号要和指针名挨着的星号呀,剩下都属于指针指向的变量类型。无论你的指针是多少级的指针,都是合适用的。
函数指针数组,可以存放函数名,也就是函数的地址,因为函数的地址类型就是函数指针。
我们先不讲函数指针数组,先引入一个案例,看看是否能优化,用什么办法优化。(这是极其简单的案例,不涉及过多的细节思考,只为了讲明白函数指针数组的用途)
首先我们有一个需求,就是实现一个计算器,该计算器 有 加减乘除 的方法。
通过输入对应的选项可以执行你指向的加减乘除法,然后输入你的计算数字,可以显示计算结果。
分析需求:
我们可以设计四个函数,分别为加法 减法 乘法 除法 函数;
通过循环语句和 witch语句去控制选择 是算 加 减 乘 除;
其次输入计算条件,得到计算结果。
那么开始编码:大概就是这样:
#include
int add(int a, int b) //加法函数
{
return a + b;
}
int sub(int a, int b) //减法函数
{
return a - b;
}
int mul(int a, int b) //乘法函数
{
return a*b;
}
int div(int a, int b)//除法函数
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
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");
breark;
default:
printf( "选择错误\n" );
break;
}
} while (input);
return 0;
}
本代码很简单,目的是谈一谈什么地方可以优化。
你发现了吗?代码是否很冗长,不够美观。是因为我调用函数时候,写出了 四个调用函数。这写函数的代码基本一致,逻辑也是一致。返回类型也是一样。
是否有更好的办法去解决这个函数调用的问题,我希望有一种方式:可以写1 种代码的形式,就可以调用 上面 4个函数。那就是我们的函数指针数组,数组就是处理一些类型相同的代码而产生的。
所以一旦我们设计把 不同的函数名,初始化函数指针数组,通过下标就可以访问到数组的函数名,再通过()函数调用符号,就可以直接调用函数了,这不美哉?
说干就干。
#include
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y); //重点关注这句
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
来分析分析:
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
这就是函数指针数组的写法了,里面初始值为:函数名;
当我们调用时候:通过该语句
ret = (*p[input])(x, y);
就可以通过数组名p和下标运算符[],访问到数组元素啦,
由于数组元素是函数名(函数地址),所以解引用一次,加个函数调用符号,就可以调用函数了。
这真实秒啊。
学了那么多指针,发现了吗,这些指针的名字都是一些排列组合形成的。
要明白指向函数指针数组的指针是什么东西,首先需要观察它的最后两个字:指针;说明就是指针,前面都是修饰作用。
所以:
指向函数指针数组的指针:就是指针指向的类型为函数指针数组类型。
那么我们知道声明这个函数指针类型吗?
想想我上面说过的话,如何定位,知道一个变量的类型。
好了,我们看看这个声明:void (*(*ppfunArr)[10])(const char*)
是不是很复杂? 那么我们来拆解一下:
提出变量名 ppfunArr 剩下的是变量名的类型 :void( (* (*)[10] )(const char*);
假如我们继续把指针ppfunArr挨着的星号*去掉,得到就是指针ppfunArr指向的类型:
void( (* [10] )(const char*);
看到这个有没有熟悉的感觉?
不就是一个函数指针数组嘛。对的,就是它。
所以我们如何解释这个声明呢?
解释: 首先 ppfunArr是一个指针,该指针指向的类型为函数指针数组,
该数组里面有10个元素,每个元素的类型为函数指针,
该函数指针指向的函数返回类型为 void ,参数列表为 const char*;
其实 指向函数指针的数组指针,了解的一点就够了,再复杂的不必要了解了,了解到这里,可以说对指针有着非常非常深刻的认识了,至于这个指针到底有什么应用,等到以后有需求时候,或者看别人代码时候,你就会知道怎么应用了,我们都是通过需求来应用知识滴,所以希望,大家可以知道这个点,不要认为学的东西没有用,理解这么复杂指针没有用。你保不定什么时候就用上了呢,对吧。
还有 ,可能,大家对复杂类型的声明还是不太会解释,或者说阅读还是有一定的困难。
所以我会写一篇番外篇: 教你如何阅读复杂类型的声明,图文并茂帮你解决。
接下来还有一些指针的其他内容讲,比如一些常见指针笔试题,和一些其他有关的知识,回调函数等。