加载中...
C++中函数指针有什么作用呢?
第1节:代码改善:一个“坑爹”的文字类冒险游戏
第2节:在禁止多重继承的情况下,如何设计“直立智慧猩猩”类?
第3节:C++多线程代码中的“乱序”执行现象
第4节:C++中函数指针有什么作用呢?
第5节:为什么我用c++写的游戏那么简陋?
第6节:多线程读写socket导致的数据混乱的原因是什么?
第7节:WebSocket 是什么原理?为什么可以实现持久连接?
第8节:怎样在c++中实现instanceof?
第9节:一个函数多处 return 是好风格吗?
第10节:C++中虚函数相比非虚函数的优势
第11节:为什么 C::C::C::C::foo() 能编译成功?
第12节:如何静态反射C++枚举的名字
第13节:看C++大叔如何拥 java 妹子入怀……
第14节:坨——理解递归实现“汉诺塔”代码的关键
第15节:C++编译器如何实现 const(常量)?
第16节:C++如何为断言加上消息
第17节:初学C++到什么水平,算是合格的初级开发工程师?
第18节:C++编程要避免使用单例模式吗?
第19节:学习C++要学boost库吗?
第20节:C++的继承就是复制吗?
第21节:C++构造函数失败,如何中止创建对象?
第22节:C++学完多线程后,学什么呢?
第23节:string_view 适合用做函数的返回值类型吗?
第24节:为指针取别名,为何影响const属性?
第25节:std::enable_shared_from_this 的存在意义?
课文封面

解决问题,若不能依赖于(优雅的)数据结构, 那就得依赖于(粗鄙的)流程结构。通常,函数使用流程结构来组织,一旦函数可以数据化,就能让数据结构也过来帮忙表达复杂的操作。

问题

知乎网友问:在C++中函数指针有什么作用呢,为什么不直接调用函数而要使用函数指针?

南老师回答

函数指针让“函数”可以是一个“变量”。

通常我们认为一件事由“动作”和“数据”组成,比如“小明泡妞”中,小明是数据,妞是数据,泡是动作。动作和数据边界清晰,各自含义也直观。

把动作数据化,真的是各种编程语言一路下来心心念念的要有各种实现各种改进的念想。就以典型面向过程式的C语言为例:函数指针在C语言中就存在;并且相比支持面向对象的其它语言,比如C++ ,函数指针在C语言更加广泛地应用;地位很高。用不好甚至不会用函数指针的C程序员,基本就是“雏”。

渣男泡妞的例子

事实上在现实的生活经验中,人类就有大量需要"动作数据化"的时候。

以“小明泡妞“为例。小明不是雏,相反,他是渣:他同时向5个女生发起约会;这个5个女生和他的亲近关系各不相同,以小红和小白为例:小红每次吃饭都拿脚在餐桌下勾小明……的拖鞋;小白则只要小明盯她超过0.05秒就会甩一巴掌大骂“流氓”。

所以,同样是“泡”由于泡的对象不同,导致五种“泡”的动作包含的实质内容,天差地别,近乎没有共性——尽管从外部看,它们都是一类操作:泡妞。

五个几乎没有共性的操作,你是要分5个函数各自写,还是揉入1个函数用一堆if/else来判断,区分?显然是个正常人都会选前者。于是就有:

void PaoA(char const* name) { cout << "和" << name << "看文艺电影" << endl; } void PaoB(char const* name) { cout << "陪" << name << "逛街购物,我花钱" << endl; } void PaoC(char const* name) { cout << name << ",走,到军博看大炮去!" << endl; } void PaoD(char const* name) { cout << name << ",我们来谈人生好吗?" << endl; } void PaoE(char const* name) { cout << name << ",川菜还是撸串?" << endl; }

你以为我们是讲多态——小明依据最终成功约到的女生不同,执行不同的Pao行为?

不是不是,我想说的是小明把5位女生都成功约好,并且时间安排得非常合理,于是这个周末,小明要做的,就是逐个调用一遍就对了!

逐个调用?现在有两种做法,一个是:

void WeekendDate() { PaoA("丁晓晓"); PaoB("贾玲玲"); PaoC("林子绿"); PaoD("王梦"); PaoE("水原希"); }

这就是你说的“直接调用函数”——但别忘了我们的关键业务需求:小明之渣。小明下周可能又约另外5个甚至6个;并且约的对象也在变;约到后泡的操作也会变……如此,就会想到最好还是做两个数组,一个存储女生(的姓名),一个存储对应的“泡”法:

#include <iostream> using namespace std; void PaoA(char const* name) { cout << "和" << name << "看文艺电影" << endl; } void PaoB(char const* name) { cout << "陪" << name << "逛街购物,我花钱" << endl; } void PaoC(char const* name) { cout << name << ",走,到军博看大炮去!" << endl; } void PaoD(char const* name) { cout << name << ",我们来谈人生好吗?" << endl; } void PaoE(char const* name) { cout << name << ",川菜还是撸串?" << endl; } char const* names [] = { "丁晓晓", "贾玲玲", "林子绿", "王梦", "水原希" }; using F = void (*)(char const*); F functions [] = { PaoA, PaoB, PaoC, PaoD, PaoE }; void WeekendDate() { for (auto i=0; i<sizeof(names)/sizeof(names[0]); ++i) { functions[i](names[i]); } } int main() { WeekendDate(); }

以上代码100%可直接跑,输出如下:

和丁晓晓看文艺电影 陪贾玲玲逛街购物,我花钱 林子绿,走,到军博看大炮去! 王梦,我们来谈人生好吗? 水原希,川菜还是撸串?

下周,再下周……当约的人有变化,我们就修改names数组,再对应修改每个人名对应可执行的动作(也已经是数组)就好了;至于那个 “WeekendDate”所要表达的周末约会都不用变啦……

渣中之渣:并发泡妞可以吗?

事主小明表示担心:再下下周,同样是5个人,同样是周末,同样是约会,但我想开群体活动,五个一起来!

多大的事,别说理论上的"同时",就是真要多线程并发(小明有四个分身)约会也没问题啊!事实上业务需求越复杂,往往代表数据之间的关系或耦合也越紧密,此时names和 functions这样的数据化设计,也就越能体现它们的正确性。以并发为例:

#include <iostream> #include <thread> using namespace std; void PaoA(char const* name) { cout << "和" << name << "看文艺电影" << endl; } void PaoB(char const* name) { cout << "陪" << name << "逛街购物,我花钱" << endl; } void PaoC(char const* name) { cout << name << ",走,到军博看大炮去!" << endl; } void PaoD(char const* name) { cout << name << ",我们来谈人生好吗?" << endl; } void PaoE(char const* name) { cout << name << ",川菜还是撸串?" << endl; } using F = void (*)(char const*); F functions [] = { PaoA, PaoB, PaoC, PaoD, PaoE }; char const* names [] = { "丁晓晓", "贾玲玲", "林子绿", "王梦", "水原希" }; constexpr std::size_t N = sizeof(names)/sizeof(names[0]); void WeekendDate() { for (auto i=0; i<N; ++i) { functions[i](names[i]); } } void CrazyWeekendDate() { std::thread trds [N]; for (std::size_t i=0; i<N; ++i) { auto trd = std::thread([i] { functions[i](names[i]); }); trds[i] = std::move(trd); } for (auto& trd: trds) { trd.join(); } } int main() { std::cout << "辛苦的周末:\n"; WeekendDate(); std::cout << "\n"; std::cout << "混乱的周末:\n"; CrazyWeekendDate(); }

运行后的一种输出可能是:

辛苦的周末: 和丁晓晓看文艺电影 陪贾玲玲逛街购物,我花钱 林子绿,走,到军博看大炮去! 王梦,我们来谈人生好吗? 水原希,川菜还是撸串? 混乱的周末: 水原希,川菜还是撸串? 王梦,我们来谈人生好吗? 林子绿,走,到军博看大炮去! 陪贾玲玲逛街购物,我花钱 和丁晓晓看文艺电影

如果改回“直 接 调 用”,那个线程创建的过程,大概是这样的:

auto trd = std::thread([i] { if (names[i] == std::string("丁晓晓")) { paoA(names[i); } else if (names[i] == std::string("贾玲玲")) { paoB(names[i); } else if (names[i] == std::string("林子绿")) { paoC(names[i); } else if (...) { .... } });

或者改用判断 “i”的值——但那样会更不直观……

小结

1 解决问题,若不能依赖于(优雅的)数据结构, 那就得依赖于(粗鄙的)流程结构。
2 函数通常使用流程结构来组织,一旦函数可以数据化,就能让数据结构也过来帮忙表达复杂的操作。

因此,多数语言以及多数有品的程序员,都希望有将“动作”数据化的能力。更多这方面的讨论,有需要的请阅读《白话C++》。

你说在“混乱的周末”里,没有对cout 对象加锁,屏幕输出可能会混杂在一起——在本例中,这是特性,而不是bug;不信你自己实践一下,同时带5个女人出门试试!

“你们一个接一个的说话,避免并发冲突”
“干嘛要这样有规矩,我们就喜欢七嘴八舌。”