C++中的指针(二) 函数指针

本文介绍了C和C++中函数指针的基本概念及其应用。详细解释了如何定义和使用函数指针,包括菜单操作、排序算法的实现及状态机等应用场景。此外还对比了C与C++中函数指针的不同之处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先说一下C式的函数指针。这种函数指针的应用十分广泛。
对于任何函数 void print(string s),它的指针这样定义:

void (*pfun)(string) = NULL;
pfun= &print;
或者 pfun = print;两种写法没有区别。

pfun
是指针变量名。可以指向任何只带一个string参数,返回void的函数。这里让它指向print()函数。
以后调用它的时候直接写

if (pfun)
pfun("Hello world");
C++
编译器会通过pfun找到print函数,然后call print("Hello world");
一个简单应用是可以作菜单操作。例如在文本模式下的界面,让用户选择如下操作:

"0.print, 1.copy, 2.delete, 3. quit, 4.help"
那么可以写5个函数:
void print();
void copy();
void delete();
void quit();
void help();
然后用一个函数指针数组把他们存在一起:

void (*p[])() = {print, copy, delete, quit, help};
然后根据用户入0,1,2,3,4来直接叫函数
cin >> index;
p[index]();

windows环境下编译这种函数指针被认为是用C/C++呼叫规则(C/C++ calling convention)

就是呼叫函数caller清理函数呼叫时生成的stack。另一种规则叫标准呼叫规则(standard calling convention)

由被叫函数callee清理自己的stack。二者一般情况下区别不大,但standard calling convention更合理,因为这样使函数size变小了一点。
实际上写C/C++函数指针的时候省略了 __cdecl 前缀。
应该写成void (__decel *p[])()
而标准规范用 __stdcall前缀。
也可以用宏CALLBACK,这也就是著名的回调函数了。
使用CALLBACK的另一个好处就是呼叫函数(caller)不需要具体关心被叫函数(callee)是什么而直接呼叫。例如我们要写一个排序函数。可以用各种不同算法。如冒泡法。

void CALLBACK BubbleSort(int *pStart, int *pEnd);
也可以用quick sort
void CALLBACK QuickSort(int *pStart, int *pEnd);

那么呼叫方只需要定义一个指向这种格式的函数指针:

void (CALLBACK *p)(int*, int*)
,然后让p指向想用的函数就可以了。
这里只对int类型排序,实际上这种排序函数可以再叫一个CALLBACK函数来决定排序规则。以使算法可以应用到各种不同类型的变量以及不同的排序规则中。在各算法书上都有介绍。如果大家有兴趣,我可以写一下这个排序函数。
另一个典型的例子是MFCTimer使用的CALLBACK函数,每当Timer Exprie的时候会去叫这个函数,根据返回值决定下一个动作。



C++
中的函数指针与C的不同
class C
{
public:
bool test();
}
这里指向print的指针不是bool *p(),而是bool (C::*p)();
呼叫这个函数的时候这样写:

C c, *pc=&c;
bool (C::*p)() = &C::test;

c.*p();
或者 pc->*p();

赋值那行bool (C::*p)() = &C::test;VS2003里右边可以省去 C::,到了VS2005语法更严格了,被禁止了。

这里的成员函数指针对非静态函数有效。静态函数不依赖于任何object,它的表示方法和C的一样。
对于非静态成员函数的指针的继承关系是这样的:upcast合法,downcast不合法。这样的到的指针永远是安全的。
非静态成员函数指针在实际程序中的应用很多。一个典型的例子是用来写state machine(状态机器?)

例如程序在控制一个机器人的初始化阶段。整个初始化需要三个函数:

1。初始化机器人的身子,

2。初始化机器的左手,

3。初始化机器人的右手。

这样我们在state machine中用两个成员函数指针分别指向当前的状态和下一个状态

bool (CStateMachine::*m_pCurrentState)

bool (CStateMachine::*m_pNextState)。。
一开始永远叫
Start()
CStateMachine::CStateMachine
{
m_pCurrentState = CStateMachine::Start;
}
然后在每一个State里面管理状态变化:

bool CStateMachine::Start()
{
.....
m_pNextState = CStateMachine::InitializeLeftHand();
}

bool CStateMachine::InitializeLeftHand()
{
....
m_pNextState = CStateMachine::InitializeRightHand();
}

bool CStateMachine::InitializeRightHand()
{
....
m_pNextState = NULL;
}

这样很清晰的标志了整个初始化的过程。当然这个过程也可以用很土的程序实现,

设一个flag,然后把flag于函数一一对应。但那样作出来的程序不易懂,同时增加新状态的时候不好维护。
对于CStateMachine的核心部份可以这样控制:对于任何一步操作,如果函数返回true表示成功,执行下一步

(this->*(m_pCurrentState = m_pNextState))()
。如果失败则报错,同时让用户选择重试(Retry)还是放弃(Abort)还是忽略(Ignor)
如果Abort则结束StateMachine
如果Retry则再次叫当前函数
this->*m_pCurrentState();
如果Ignor则忽略当前错误继续下一步。
this->*(m_pCurrentState = m_pNextState)();
当没有下一个状态的时候StateMachine结束。
(m_pNextState == NULL)
这是标准工业中的用法,大家不妨看一看,写成一个标准的class。这将是个很有用的练习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值