目录

一、函数指针简介

二、函数指针数组

三、指向重载函数的指针

四、函数指针作为类成员

五、函数指针作为形参

六、 转换表

七、类函数指针成员与注册函数

八、类函数指针数组

九、函数指针测试案例


一、函数指针简介

函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关。例如:

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 parametervoid (*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 madevoid *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();elseptr->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");elseprintf("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 madevoid *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();elseptr->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");elseprintf("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)

测试程序运行输出如下:

c/c++开发,无可避免的函数指针使用案例相关推荐

  1. 【嵌入式开发】C语言 命令行参数 函数指针 gdb调试

    . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/21551397 | http://www.hanshul ...

  2. 嵌入式软件开发培训笔记——C高级 指针和函数

    1.函数指针 2.指针函数 Int *function(int a,char *b) 函数返回值是一个指针 Int *p;    p = function(c,&d); 附:递归 在一个函体体 ...

  3. c++ 函数指针_开发经验分享(5) 修改Makefile实现C/C++混合编程

    ◆  ◆  前言 ◆  ◆ Arduino/Stm32duino上的部分库使用C++写的,所以如果想移植库就需要使Stm32CubeMX生成Makefile支持C++的编译.借由文中方法实现了将Ard ...

  4. STC15分时内核中函数指针的使用问题

    2019独角兽企业重金招聘Python工程师标准>>> 基于前后台设计的系统随着功能的递增变得越来越难以维护, 所以决定为STC15F2K单片机编写一个基于时分的非抢占式内核,方便进 ...

  5. c c++ 函数内数组初值_C/C++函数指针与指针函数

    关于指针,前面文章C语言指针详解有过介绍,这里主要讨论函数指针和指针函数. 1 什么是指针? 定义:指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量; 上面一个 4GB 的内存可以存放 ...

  6. Android JNI编程(六)——C语言函数指针、Unition联合体、枚举、Typedef别名、结构体、结构体指针...

    版权声明:本文出自阿钟的博客,转载请注明出处:http://blog.csdn.net/a_zhon/. 目录(?)[+] 一:函数指针 1.函数指针顾名思义就是定义一个指针变量指向一个函数,和一级指 ...

  7. C语言函数指针 和 OC-Block

    C语言函数指针 和 OC-Block 一. C语言函数指针 关于函数指针的知识详细可参考: http://www.cnblogs.com/mjios/archive/2013/03/19/296703 ...

  8. Static与函数指针 转

    所谓函数指针就是一个指向函数的指针,也就是说我们定义一个函数指针量后,这个变量所在的空间要保存一个函数的地址.那么函数指针除了作为回调函数的传参之外还有什么作用呢?这里我们就结合staitc的作用来探 ...

  9. 【C++】函数 指针类型参数 与 引用类型参数 对比 ( 修改外部变量需要传入的参数要求 | 参数作返回值 )

    文章目录 I . 博客总结 . 指针与引用类型参数 II . 指针作为参数和返回值 III . 引用参数简介 IV . 引用作为参数和返回值 I . 博客总结 . 指针与引用类型参数 1 . 讨论问题 ...

最新文章

  1. vivado使用自带IP核和创建自己定义的IP核
  2. MySQL中如何查询数据(上)
  3. 洛谷P4630 [APIO2018] Duathlon 铁人两项 【圆方树】
  4. Java class loader调试
  5. 设置utf8编码问题
  6. 超分辨率分析(二)--深度学习方案综述
  7. list和forward_list
  8. hash算法_Win10_64 默认应用的UserChoice Hash算法学习
  9. 计算机电子智能化贰级,电子与智能化工程专业承包资质分为一级、二级。
  10. matlab中可调节负载,请教MOC3022控制可控硅的电路调节负载功率的问题
  11. 【目标检测】雷达目标CFAR检测算法
  12. excel求回归直线方程的公式_求回归直线方程的三种方法
  13. C语言【微项目01】—电话号码管理系统(文件操作实现)【2021-06-29】
  14. 五十个SQL基本语句,来看看你的SQL语言过关了吗?
  15. 第1章 开始使用C++
  16. 微信小程序电商实战项目
  17. antd DatePicker 组件 月份和星期显示英文
  18. 超级计算机国产cpu,为何国产超级计算机已经领先全世界了,而国产cpu却依然落后?...
  19. 可视化IDE | 快速高效制作可视化大屏~
  20. GoogleVR怎样在普通场景和VR场景之间进行切换

热门文章

  1. RV1126/RV1109开发之路
  2. PHP json_decode()报错 json_last_error()判断错误类型 解决
  3. 从零开始写一个图像处理程序之四(Prewitt 算子分析)
  4. Verilog——时钟3分频
  5. 放开前和放开后,被这14幅图扎心了
  6. Critical Scenarios definition
  7. 从零开始搭建公司微服务架构技术栈,这套架构绝了...
  8. Maven项目的介绍、入门搭建
  9. jcifs.smb.SmbException: 0xC000007F
  10. 利用Idea生成的类图分析框架源码