文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

C++ 类成员函数指针语法的友好指南

2024-12-03 03:47

关注

一旦你理解了一般原则,C++ 类成员函数指针不再那么令人生畏。

如果你正在寻找性能、复杂性或许多可能的解决方法来解决问题,那么在涉及到极端的情况下,C++ 总是一个很好的选择。当然,功能通常伴随着复杂性,但是一些 C++ 的特性几乎难以分辨。根据我的观点,C++ 的 类成员函数指针 也许是我接触过的最复杂的表达式,但是我会先从一些较简单的开始。

文章中的例子可以在我的 Github 仓库 里找到。

C 语言:函数指针

让我们先从一些基础开始:假设你有一个函数接收两个整数作为参数返回一个整数:

  1. int sum(int a, int b) {
  2. return a+b;
  3. }

在纯 C 语言中,你可以创建一个指向这个函数的指针,将其分配给你的 sum(...) 函数,通过解引用来调用它。函数的签名(参数、返回类型)必须符合指针的签名。除此之外,一个函数指针表现和普通的指针相同:

  1. int (*funcPtrOne)(int, int);
  2.  
  3. funcPtrOne = ∑
  4.  
  5. int resultOne = funcPtrOne(2, 5);

如果你使用指针作为参数并返回一个指针,这会显得很丑陋:

  1. int *next(int *arrayOfInt){
  2. return ++arrayOfInt;
  3. }
  4.  
  5. int *(*funcPtrTwo)(int *intPtr);
  6.  
  7. funcPtrTwo = &next;
  8.  
  9. int resultTwo = *funcPtrTwo(&array[0]);

C 语言中的函数指针存储着子程序的地址。

指向类成员函数的指针

让我们来进入 C++:好消息是你也许不需要使用类成员函数指针,除非在一个特别罕见的情况下,比如说接下来的例子。首先,你已经知道定义一个类和其中一个成员函数:

  1. class MyClass
  2. {
  3. public:
  4.  
  5. int sum(int a, int b) {
  6. return a+b;
  7. }
  8.  
  9. };

1、定义一个指针指向某一个类中一个成员函数

声明一个指针指向 MyClass 类成员函数。在此时,你并不知道想调用的具体函数。你仅仅声明了一个指向 MyClass 类中任意成员函数的指针。当然,签名(参数、返回值类型)需要匹配你接下想要调用的 sum(...) 函数:

  1. int (MyClass::*methodPtrOne)(int, int);

2、赋值给一个具体的函数

为了和 C 语言(或者 静态成员函数)对比,类成员函数指针不需要指向绝对地址。在 C++ 中,每一个类中都有一个虚拟函数表(vtable)用来储存每个成员函数的地址偏移量。一个类成员函数指针指向 vtable 中的某个条目,因此它也只存储偏移值。这样的原则使得 多态 变得可行。

因为 sum(...) 函数的签名和你的指针声明匹配,你可以赋值签名给它:

  1. methodPtrOne = &MyClass::sum;

3、调用成员函数

如果你想使用指针调用一个类成员函,你必须提供一个类的实例:

  1. MyClass clsInstance;
  2. int result = (clsInstance.*methodPtrOne)(2,3);

你可以使用 . 操作符来访问,使用 * 对指针解引用,通过提供两个整数作为调用函数时的参数。这是丑陋的,对吧?但是你可以进一步应用。

在类内使用类成员函数指针

假设你正在创建一个带有后端和前端的 客户端/服务器 原理架构的应用程序。你现在并不需要关心后端,相反的,你将基于 C++ 类的前端。前端依赖于后端提供的数据完成初始化,所以你需要一个额外的初始化机制。同时,你希望通用地实现此机制,以便将来可以使用其他初始化函数(可能是动态的)来拓展你的前端。

首先定义一个数据类型用来存储初始化函数(init)的指针,同时描述何时应调用此函数的信息(ticks):

  1. template<typename T>
  2. struct DynamicInitCommand {
  3. void (T::*init)(); // 指向额外的初始化函数
  4. unsigned int ticks; // 在 init() 调用后 ticks 的数量
  5. };

下面一个 Frontend 类示例代码:

  1. class Frontend
  2. {
  3. public:
  4.  
  5. Frontend(){
  6. DynamicInitCommand<Frontend> init1, init2, init3;
  7.  
  8. init1 = { &Frontend::dynamicInit1, 5};
  9. init2 = { &Frontend::dynamicInit2, 10};
  10. init3 = { &Frontend::dynamicInit3, 15};
  11.  
  12. m_dynamicInit.push_back(init1);
  13. m_dynamicInit.push_back(init2);
  14. m_dynamicInit.push_back(init3);
  15. }
  16. void tick(){
  17. std::cout << "tick: " << ++m_ticks << std::endl;
  18. std::vector<DynamicInitCommand<Frontend>>::iterator it = m_dynamicInit.begin();
  19.  
  20. while (it != m_dynamicInit.end()){
  21. if (it->ticks < m_ticks){
  22. if(it->init)
  23. ((*this).*(it->init))(); // 这里是具体调用
  24.  
  25. it = m_dynamicInit.erase(it);
  26.  
  27. } else {
  28. it++;
  29. }
  30. }
  31. }
  32. unsigned int m_ticks{0};
  33. private:
  34.  
  35. void dynamicInit1(){
  36. std::cout << "dynamicInit1 called" << std::endl;
  37. };
  38.  
  39. void dynamicInit2(){
  40. std::cout << "dynamicInit2 called" << std::endl;
  41. }
  42.  
  43. void dynamicInit3(){
  44. std::cout << "dynamicInit3 called" << std::endl;
  45. }
  46.  
  47. unsigned int m_initCnt{0};
  48. std::vector<DynamicInitCommand<Frontend> > m_dynamicInit;
  49. };

在 Frontend 完成实例化后,tick() 函数会被后端以固定的时间时间调用。例如,你可以每 200 毫秒调用一次:

  1. int main(int argc, char* argv[]){
  2. Frontend frontendInstance;
  3.  
  4. while(true){
  5. frontendInstance.tick(); // 仅用于模拟目的
  6. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  7. }
  8. }

Fronted 有三个额外的初始化函数,它们必须根据 m_ticks 的值来选择调用哪个。在 ticks 等于何值调用哪个初始化函数的信息存储在数组 m_dynamicInit 中。在构造函数(Frontend())中,将此信息附加到数组中,以便在 5、10 和 15 个 tick 后调用其他初始化函数。当后端调用 tick() 函数时,m_ticks 值会递增,同时遍历数组 m_dynamicInit 以检查是否必须调用初始化函数。

如果是这种情况,则必须通过引用 this 指针来取消引用成员函数指针:

  1. ((*this).*(it->init))()

总结

如果你并不熟悉类成员函数指针,它们可能会显得有些复杂。我做了很多尝试和经历了很多错误,花了一些时间来找到正确的语法。然而,一旦你理解了一般原理后,方法指针就变得不那么可怕了。

这是迄今为止我在 C++ 中发现的最复杂的语法。

 

来源:Linux中国内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯