一、函数指针简介
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关。例如:
char* (*pf1)(char * p1,char *p2);
这是一个函数指针,其真实词意如果转换一下,似乎更好理解,只是编译器不会这样排版而已:
char* (*)(char * p1,char *p2) pf1;
将 pf 声明为指向函数的指针,它所指向的函数带有两个 char* 类型的形参和 char*类型的返回值。注意*pf两侧的圆括号是必需的,否则就成了返回char**类型的普通函数声明了。参数类型是必须的,但参数名不是必须的,可以省略。
char* (*pf1)(char*,char*); //char* (*)(char*,char*) pf1;
通常我们在开发中,尤其是应用层级开发中,较少使用函数指针,也不建议使用函数指针。但是由于函数指针其特殊性,可以迸发出很多巧妙的代码组织方法,尤其是很多底层驱动开发中,不少地方都会用到函数指针。
函数指针类型相当地冗长。使用 typedef 为指针类型定义同义词,可将函数指针的使用大大简化:
typedef char* (*pfunc)(char*, char*);
声明函数指针后,需要给予初始化或赋值才能使用:
//先定义一个真实函数
char* fun1(char * p1,char *p2)
{
return ((0== strcmp(p1,p2))?p1:p2);
};
//char* (*pf1)(char*, char*) = &fun1;//直接初始化
char* (*pf1)(char*, char*);
pf1 = &fun1; //fun1等同于char* (*)(char*, char*)
char p1[]="abc";
char p2[]="bcd";
printf("%s\n", (*pf1)(p1,p2));
函数名作为右值时,在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针,因此采用&func或func给函数指针赋值都可以,以下四种中可以达成目的。
//func1除了用作函数调用的左操作数以外,对 fun1的任何使用都被解释为char* (*)(char*, char*)
char* (*pf1)(char*, char*) = fun1;//直接初始化
char* (*pf2)(char*, char*) = &fun1;//直接初始化
char* (*pf3)(char*, char*);//
char* (*pf4)(char*, char*);//
pf3 = func1;
pf4 = &func1;
typedef char* (*pfunc)(char*, char*);
pfunc pf5 = fun1;
pfunc pf6 = &fun1;
函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。
char* (*pf1)(char*, char*);
pf1 = &fun1;
//
pfunc pf3 = 0; //初始化为 0,表示该指针不指向任何函数
pf3 = pf1;
char fun3(char * p1,char *p2)
{
return ((0== strcmp(p1,p2))?p1:p2);
};
char* fun4(char p1,char p2)
{
return ((0== strcmp(p1,p2))?p1:p2);
};
pf3 = fun3;//eroor,返回类型不一致
pf3 = &fun4;//eroor,参数类型不一致
指向函数的指针可用于调用它所指向的函数,支持显式或隐式方式。
char p1[]="abc";
char p2[]="bcd";
pfunc pf3 = fun1;
printf("%s\n",pf3(p1,p2)); //隐式调用
printf("%s\n",(*pf3)(p1,p2)); //显式调用
二、函数指针数组
函数指针数组,就是将函数指针存储在一个数组里,假设定义一个函数指针:
void (*FuncPtr)();
那么FuncPtr就是一个函数指针,既然 FuncPtr是一个指针,那就可以储存在一个数组里。
void (*FuncPtr[])();
或
void (*FuncPtr[2])();
定义一个函数指针数组。它是一个数组,数组名为 FuncPtr,数组内存储了 3 个或未知个数的指向函数的指针。这些指针指向一些返回值类型为void、无参数的函数。当然也可以通过typedef 修饰一下,就像定义变量数组一样:
typedef void (*FuncPtr)();
FuncPtr pfunc[3];
同样,函数指针数组也要给其赋值,指向真实函数地址才能使用
typedef void (*FuncPtr)();
void doSomething1()
{
printf("doSomething1\n");
};
void doSomething2()
{
printf("doSomething2\n");
};
void (*fp[2])();
fp[0] = &doSomething1;
fp[1] = doSomething2;
fp[0](); //函数调用
FuncPtr pfunc[2] = {doSomething1,doSomething2};
pfunc[0](); //函数调用
FuncPtr funcPtrArray[2];
funcPtrArray[0] = doSomething1; //可以直接用函数名
funcPtrArray[1] = &doSomething2; //可以用函数名加上取地址符
funcPtrArray[1](); //函数调用
如果函数返回值有差异,但又想通过函数指针数组归一化起来,在确保安全前提下,通过reinterpret_cast进行函数指针类型转换,注意c中是没有reinterpret_cast的。
typedef void (*FuncPtr)();
int doSomething3()
{
printf("doSomething3\n");
return 0;
};
FuncPtr funcPtrArray[3];
funcPtrArray[0] = doSomething1; //可以直接用函数名
funcPtrArray[1] = &doSomething2; //可以用函数名加上取地址符
//注意doSomething3直接传递是类型不符合的,它对于funcPtrArray是一个错误类型
//c++的reinterpret_cast 可以让你迫使编译器以你的方法去处理,
//转换函数指针,C++不保证所有的函数指针都被用一样的方法表示,在一些情况下这样的转换会产生不正确的结果.
funcPtrArray[2] = reinterpret_cast<FuncPtr>(&doSomething3);//g++编译时生效
c语言编译时,可仿c++的reinterpret_cast创建一个类似的带参宏定义
#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR)) //仿c++的reinterpret_cast
//此处省略声明定义
funcPtrArray[2] = reinterpret_cast(FuncPtr, &doSomething3);//gcc或g++编译时都生效
funcPtrArray[2]();
三、指向重载函数的指针
C++语言允许使用函数指针指向重载的函数,假设在func.cpp中定义了函数func_test
//func.cpp
int func_test(int p1, int p2)
{
return (p1<p2)?p1:p2;
};
在test.cpp中通过extern实现重载。
//test.cpp
extern int func_test(int, int);
//指向重载函数的指针
int (*pft)(int, int) = &func_test;
printf("min_val:%d\n", (*pft)(6,7));
注意指针的类型必须与重载函数的精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误:
//test.cpp
int (*pf2)(int) = &func_test; //error,invalid parameter
void (*pf3)(int, int);
pf3 = &func_test; // error, invalid return type
四、函数指针作为类成员
函数指针也是指针,既然是指针,就可以作为类成员变量来使用。
假设有如下类CallBack,其成员变量是一个函数指针CallBackPtr及void*的指针,初始化时传递一个函数指针和void变量指针实现初始化,通过doCallBack函数具体调用CallBackPtr实现函数回调:
//在传递函数指针时,建议进行这种异常规格的检查
typedef void (*CallBackPtr)(void *data_);
class CallBack
{
public:
CallBack(CallBackPtr fPtr, void *data_)
: func(fPtr), data(data_) {}
void doCallBack() const throw();
private:
CallBackPtr func; // function to call when
// callback is made
void *data; // data to pass to callback
};
void CallBack::doCallBack() const throw()
{
func(data);
}
该类使用时,定义具体业务实现的真实函数,将这些函数指针传递给CallBack类,业务实际需要滞后到实现具体业务场景明确时,实现其逻辑,如下:
void callBackFcn1(void *data_)
{
printf("callBackFcn1\n");
};
void callBackFcn2(void *data_) throw()
{
printf("callBackFcn2\n");
};
//回调,将具体实现放置构造传递进去的具体函数指针
void *callBackData;
CallBack c_instance(callBackFcn1,callBackData);
c_instance.doCallBack();
在传递函数指针时,建议进行这种异常规格的检查,大家可以想想如下函数指针定义时,指向的真实函数定义该如何设计。
typedef void (*CallBackPtr)(void *data_) throw();//如果不提供异常说明,该指针就可以指向能够抛出任意类型异常的具有匹配类型的函数
typedef void (*CallBackPtr)(void *data_) throw(runtime_error);//指定异常时,只能抛出 runtime_error 类型的异常
typedef void (*CallBackPtr)(void *data_) const throw();//类型定义更严格,源指针的异常说明必须至少与目标指针的一样严格
五、函数指针作为形参
函数指针也是指针,既然是指针,就可以作为参数传递给函数,函数就可以在其内部使用该函数指针,从而实现对函数指针指向的函数进行调用,实现函数回调。想STL标准了内的排序函数 模板就是通过传递一个数值比较的函数指针来实现的。
//函数指针作为形参
class Aclass
{
public:
Aclass(void* data_) : vdata(data_)
{
};
void doSomething1()
{
printf("Aclass doSomething1\n");
};
void doSomething2()
{
printf("Aclass doSomething2\n");
};
void* getData()
{
return vdata;
}
private:
void* vdata;
};
void myfunc(Aclass* ptr,void* data,bool (*compare)(void const* pd1, void const* pd2))
{
if(compare(ptr->getData(),data))
ptr->doSomething1();
else
ptr->doSomething2();
};
bool compare_int(void const* pd1, void const* pd2)
{
return (*(int*)pd1=*(int*)pd2)?false:true;
}
如上述代码,myfunc函数参数很复杂,将类对象,数值及函数指针作为参数传递进去。函数指针通过重定义一下:
typedef bool (*Func_compare)(void const* pd1, void const* pd2);
void myfunc(Aclass* ptr,void* data,Func_compare compare);
函数调用如下:
int aval = 10;
int bval = 8;
Aclass a_obj(&aval);
myfunc(&a_obj,&bval,compare_int); //
六、 转换表
有以下一种功能要求,这是一个实现操作符计算的分支设计,如果有上百个操作符需要实现呢,这个switch语句就会很长:
switch(oper)
{
case ADD:
ret = add(val1,val2);
break;
case SUB:
ret = sub(val1,val2);
break;
case MUL:
ret = mul(val1,val2);
break;
case DIV:
ret = div(val1,val2);
break;
......
default:
ret = 0.0;
break;
}
通过建立一个函数指针数组作为转换表,就可以实现类似switch的代码功能:
enum Method{
ADD=0,
SUB,
MUL,
DIV
};
typedef float (*operFunc)(float fa, float fb);
operFunc operf[] = {add,sub,mul,div};
其调用如下,确保这些操作符函数定义在初始化列表之前,初始化列表的函数名顺序取决与程序用来表示每个操作符的整型代码。
//操作符具体实现函数
float add(float fa, float fb)
{
return fa+fb;
};
float sub(float fa, float fb)
{
return fa-fb;
};
float mul(float fa, float fb)
{
return fa*fb;
};
const float abs_min_val = 0.0000001;
float div(float fa, float fb)
{
if((fb<abs_min_val)&&(fb>(-abs_min_val)))
{
return fa/abs_min_val;
}
return fa/fb;
};
//函数指针数组调用,实现类switch功能
float afval = 7.8, bfval = 8.5;
float ret_fval = operf[Method::ADD](afval,bfval);
printf("%0.4f\n",ret_fval);
七、类函数指针成员与注册函数
函数指针是指针,就可以作为类的成员变量,通常用于将具体业务实现剥离在类外定义实现,在业务执行是注册到类中,从而针对具体业务来调用需要的业务模块(业务函数)。
//函数指针成员,注册函数
class BClass
{
public:
BClass()
{
my_compare = NULL;
};
typedef bool (*compare)(void const* pd1, void const* pd2);
void my_compare_do(void const* cm1, void const* cm2) //具体实现通过调用外部函数
{
if(NULL==my_compare)
printf("please register func first!\n");
if(my_compare(cm1,cm2)) //外部函数调用
printf("do it A\n");
else
printf("do it B\n");
};
void register_func(compare cp1) //注册函数
{
my_compare = cp1;
};
compare my_compare; //函数指针变量
};
在具体业务逻辑过程中,先给类实例注入调用函数,在调用具体函数实现业务逻辑。
//注册函数,实现外部函数调用
BClass bObject;
bObject.register_func(compare_int);
bObject.my_compare_do(&aval,&bval);
八、类函数指针数组
同样地,函数指针还可以通过函数指针数组作为类成员列表,这些成员的实现细节当然是可以在类内部,外部或外部模块来具体实现。例如,在很多程序会这样做,在程序中的每个类只要声明函数指针数组,然后在外部来具体实现业务,甚至是交给二次开发者依据具体业务场景来针对性设计功能函数。
//类函数指针数组test(成员列表)
class OperateKey
{
public:
OperateKey()
{
};
enum Directions { FORWARD, BACK, UP, DOWN };
OperateKey& do_it(Directions direct_)
{
//记需要有效性判断
(this->vtbl[direct_])();
return *this;
};
typedef void (*Action)();
static Action vtbl[];
};
void forward()
{
printf("forward\n");
};
void back()
{
printf("back\n");
};
void up()
{
printf("up\n");
};
void down()
{
printf("down\n");
};
OperateKey::Action OperateKey::vtbl[] = {forward, back, up, down};
类的函数指针数组成员列表就像平常类实例使用数组变量一样使用即可,只是数组变量是作为变量使用,而函数指针数组存储的是函数名(函数地址),作为一个个函数使用。
//类函数指针数组成员列表
OperateKey myKeys;
myKeys.do_it(OperateKey::FORWARD);
myKeys.do_it(OperateKey::DOWN);
九、函数指针测试案例
创建test.cpp和func.cpp文件,主要是代码是在test.cpp中实现。
func.cpp,用来测试函数指针重载
//
int func_test(int p1, int p2)
{
return (p1<p2)?p1:p2;
};
test.cpp
#include <string.h>
#include <stdio.h>
//函数指针test
char* fun1(char * p1,char *p2)
{
return ((0== strcmp(p1,p2))?p1:p2);
};
char* fun2(char * p1,char *p2)
{
return ((0== strcmp(p1,p2))?p2:p1);
};
typedef char* (*pfunc)(char*, char*);
//函数指针数组test
typedef void (*FuncPtr)();
#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))
void doSomething1()
{
printf("doSomething1\n");
};
void doSomething2()
{
printf("doSomething2\n");
};
int doSomething3()
{
printf("doSomething3\n");
return 0;
};
//
extern int func_test(int, int);
//回调
//在传递函数指针时,建议进行这种异常规格的检查
typedef void (*CallBackPtr)(void *data_);
//typedef void (*CallBackPtr)(void *data_) throw();?//如果不提供异常说明,该指针就可以指向能够抛出任意类型异常的具有匹配类型的函数
//typedef void (*CallBackPtr)(void *data_) throw(runtime_error);?//指定异常时,只能抛出 runtime_error 类型的异常
//typedef void (*CallBackPtr)(void *data_) const throw();?//类型定义更严格,源指针的异常说明必须至少与目标指针的一样严格
class CallBack
{
public:
CallBack(CallBackPtr fPtr, void *data_)
: func(fPtr), data(data_) {}
void doCallBack() const throw();
private:
CallBackPtr func; // function to call when
// callback is made
void *data; // data to pass to callback
};
void CallBack::doCallBack() const throw()
{
func(data);
}
void callBackFcn1(void *data_)
{
printf("callBackFcn1\n");
};
void callBackFcn2(void *data_) throw()
{
printf("callBackFcn2\n");
};
//函数指针作为形参
class Aclass
{
public:
Aclass(void* data_) : vdata(data_)
{
};
void doSomething1()
{
printf("Aclass doSomething1\n");
};
void doSomething2()
{
printf("Aclass doSomething2\n");
};
void* getData()
{
return vdata;
}
private:
void* vdata;
};
void myfunc(Aclass* ptr,void* data,bool (*compare)(void const* pd1, void const* pd2))
{
if(compare(ptr->getData(),data))
ptr->doSomething1();
else
ptr->doSomething2();
};
bool compare_int(void const* pd1, void const* pd2)
{
return (*(int*)pd1=*(int*)pd2)?false:true;
}
//
float add(float fa, float fb)
{
return fa+fb;
};
float sub(float fa, float fb)
{
return fa-fb;
};
float mul(float fa, float fb)
{
return fa*fb;
};
const float abs_min_val = 0.0000001;
float div(float fa, float fb)
{
if((fb<abs_min_val)&&(fb>(-abs_min_val)))
{
return fa/abs_min_val;
}
return fa/fb;
};
typedef float (*operFunc)(float fa, float fb);
enum Method{
ADD=0,
SUB,
MUL,
DIV
};
//函数指针成员,注册函数
class BClass
{
public:
BClass()
{
my_compare = NULL;
};
typedef bool (*compare)(void const* pd1, void const* pd2);
void my_compare_do(void const* cm1, void const* cm2)
{
if(NULL==my_compare)
printf("please register func first!\n");
if(my_compare(cm1,cm2))
printf("do it A\n");
else
printf("do it B\n");
};
void register_func(compare cp1)
{
my_compare = cp1;
};
compare my_compare;
};
//类函数指针数组test(成员列表)
class OperateKey
{
public:
OperateKey()
{
};
enum Directions { FORWARD, BACK, UP, DOWN };
OperateKey& do_it(Directions direct_)
{
(this->vtbl[direct_])();
return *this;
};
typedef void (*Action)();
static Action vtbl[];
};
void forward()
{
printf("forward\n");
};
void back()
{
printf("back\n");
};
void up()
{
printf("up\n");
};
void down()
{
printf("down\n");
};
OperateKey::Action OperateKey::vtbl[] = {forward, back, up, down};
int main(int argc, char* argv[])
{
//char* (*pf1)(char * p1,char *p2);//定义一个函数指针变量,等同于下一句
char* (*pf1)(char*, char*);
char p1[]="abc";
char p2[]="bcd";
//char* (*pf1)(char*, char*) = &fun1;//直接初始化
pf1 = &fun1; //fun等同于char* (*)(char*, char*)
printf("%s\n", (*pf1)(p1,p2));
pfunc pf2=fun2;
printf("%s\n",(*pf2)(p1,p2));
//
pfunc pf3 = 0; //初始化为 0,表示该指针不指向任何函数
pf3 = pf1;
printf("%s\n",pf3(p1,p2)); //隐式调用
pf3 = pf2;
printf("%s\n",(*pf3)(p1,p2)); //显式调用
//
FuncPtr pfuncs[2] = {doSomething1,doSomething2};
FuncPtr funcPtrArray[3];
funcPtrArray[0] = doSomething1; //可以直接用函数名
funcPtrArray[1] = &doSomething2; //可以用函数名加上取地址符
//注意doSomething3直接传递是类型不符合的,它对于funcPtrArray是一个错误类型
//c++的reinterpret_cast 可以让你迫使编译器以你的方法去处理,
//转换函数指针,C++不保证所有的函数指针都被用一样的方法表示,在一些情况下这样的转换会产生不正确的结果.
//funcPtrArray[2] = reinterpret_cast<FuncPtr>(&doSomething3);//g++编译时生效
funcPtrArray[2] = reinterpret_cast(FuncPtr, &doSomething3);//gcc或g++编译时都生效
for(int i=0; i<3; i++)
{
funcPtrArray[i]();
}
//指向重载函数的指针
int (*pft)(int, int) = &func_test;
printf("min_val:%d\n", (*pft)(6,7));
//回调
void *callBackData;
CallBack c_instance(callBackFcn1,callBackData);
c_instance.doCallBack();
//
int aval = 10;
int bval = 8;
Aclass a_obj(&aval);
myfunc(&a_obj,&bval,compare_int); //
//
operFunc operf[] = {add,sub,mul,div};
float afval = 7.8, bfval = 8.5;
float ret_fval = operf[Method::ADD](afval,bfval);
printf("%0.4f\n",ret_fval);
//注册函数
BClass bObject;
bObject.register_func(compare_int);
bObject.my_compare_do(&aval,&bval);
//类函数指针数组成员列表
OperateKey myKeys;
myKeys.do_it(OperateKey::FORWARD);
myKeys.do_it(OperateKey::DOWN);
return 0;
}
编译
g++ test.cpp func.cpp -o test.exe
或者构建Makefile文件,调用make指令
CX = g++
BIN := .
TARGET := test.exe
FLAGS :=
Include := .
source := test.cpp func.cpp
$(TARGET) :
$(CX) $(FLAGS) $(source) -I$(Include) -o $(BIN)/$(TARGET)
clean:
rm $(BIN)/$(TARGET)
测试程序运行输出如下: