1. 函数重载 vs.函数模板
有没有同学想过,C++中的函数重载和函数模板,是一对互相反着来的好兄弟?
- 函数重载:写多个函数,用起来却好像是一个函数
- 函数模板:写一个函数,用起来却好像是多个函数
比如函数重载:
int AddTwo(int a, int b) { return a + b; }
double AddTwo(double a, double b) { return a + b; }
用起来好像是一个函数:
auto r1 = AddTwo(100, 200);
auto r2 = AddTwo(100.1, 200.2);
改用函数模板实现:
template <typename T>
T AddTwo(T const& a, T const& b) { return a + b; }
用起来,仿佛有 N 个函数:
auto r3 = AddTwo(100, 200);
auto r4 = AddTwo(100.1, 200.2);
auto r5 = AddTwo<std::string>("abc", "123");
今天,我们就要上演一场“函数重载”和“函数模板”这对兄弟齐心协力,针对特定问题的实现完美设计的戏……
这出戏的起源,是来自上一节课末了的另外一对兄弟,一对毫无血缘关系的“兄弟”:
struct 鸡精
{
void 忍耐 () {cout << "欢迎品尝!\n";}
};
struct 象妖
{
void 愤怒 () {cout << "大胆!可笑!\n";}
};
尽管可以一眼看出,鸡精和象妖都不是好人,按理说它们应该有一个共同基类:“妖怪”;但,我是处于“愚者”状态的开发者,我就是没能在一开始时为它们加上这个共同基类,现在,甲方爸爸说:
我们这出戏演的是:有两个妖洞:鸡精窝和象妖洞,猴子一前一后跑到它们洞口叫骂:“里面的妖怪都给爷爷出来……” 请用一段 C++ 代码来模拟两个洞里的妖怪的此刻的不同反应……
就在我刚想写一个妖怪的接口,再为它加一个 “反应()” 的纯虚方法并分头在鸡精和象妖现有结构体上实现时,开发大厅某根柱子上贴着的乾隆墨宝:“对修改封闭,对扩展开放” 冷冷地看着我……
“领导,我想为现有的类添加基类和新方法,这样算修改吗?”
因为我已经50岁了……领导最近一直想给我穿小鞋,她叫到 “不管你往现有的结构体中塞入任何东西 ,都算修改!只能开除!”
“那我写派生类怎么算?”
“派生类?‘鸡精’ 这么具体的类,你还要怎么派生?派生类叫什么?叫‘遇到猴子会有反应的鸡精’吗? ”
遇到猴子会有反应的鸡精?听起来就不像是什么正经鸡精……我看了一眼领导,默默地翻起讲泛型编程的书。
2. 原理讲解 🎥
3. 项目演示 🎥
以下是演示项目的完整代码:
#include <iostream>
#include <vector>
using namespace std;
struct 鸡精
{
void 忍耐 () {cout << "欢迎品尝!\n";}
};
struct 象妖
{
void 愤怒 () {cout << "大胆!可笑!\n";}
};
// 适配器,“垫片” - 反应
void 反应(鸡精& jj)
{
jj.忍耐();
}
void 反应(象妖& xy)
{
xy.愤怒();
}
// 叫阵函数模板
template <typename YokaiT>
void 叫阵(char const* 洞名, std::vector<YokaiT> & cave)
{
std::cout << 洞名 << "里面的妖怪快出来见爷爷!\n";
for (auto & yokai : cave)
{
反应(yokai);
}
}
int main()
{
vector<鸡精> cave1;
cave1.push_back(鸡精{});
cave1.push_back(鸡精{});
cave1.push_back(鸡精{});
vector<象妖> cave2;
cave2.push_back(象妖{});
cave2.push_back(象妖{});
叫阵("鸡精窝", cave1);
叫阵("象妖洞", cave2);
}