C++ 函数模板(Function Template)入门
函数模板是 C++ 泛型编程的基础。本文从「为什么需要模板」讲起,依次介绍模板的定义、实例化、多类型参数、重载与特化,最后整理几个初学者常踩的坑。
一、为什么需要函数模板
假设要写一个求两个数较大值的函数,不用模板就得为每种类型重载一遍:
1 | int max(int a, int b) { return a > b ? a : b; } |
这些函数的逻辑一模一样,只是参数类型不同。函数模板让我们只写一份代码,由编译器根据调用时的类型自动生成对应版本。
二、定义语法
1 | template <class T> // class 是 C++ 关键字,在这里表示「任意类型」 |
class T里的class并不代表 T 必须是类,它可以是int、double等任意类型。两种写法没有任何区别,现代 C++ 更推荐用typename,语义更清晰。
一个完整的例子:
1 | template <typename T> |
注意 T a, T b 中 T 和变量名之间必须有空格,写成 Tb 会被编译器当成一个标识符,直接报错。
三、隐式实例化(implicit instantiation)
模板本身不是函数,它只是一张「图纸」。当我们用具体类型调用它时,编译器才按需生成真正的函数,这叫隐式实例化:
1 |
|
优点:只有真正用到的类型才会生成对应函数,没有用到的类型不产生任何代码。
也可以在调用时手动指定类型参数(实参类型不一致时很有用):
1 | myMax<int>(3, 5); // 显式指定 T = int |
四、显式实例化(explicit instantiation)
除了靠调用触发,也可以主动命令编译器生成某个类型的版本:
1 | template float myMax<float>(float a, float b); // 即使没有调用,也生成 myMax<float> |
它的用途主要有两个:
- 提前编译、分离声明与实现:把模板实现放在
.cpp文件里时,配合显式实例化可以避免「未定义的引用」链接错误; - 减少重复实例化带来的编译开销。
新手阶段几乎用不到显式实例化,知道有这回事即可。注意是「显式」不是「显示」。
五、多个类型参数
模板可以同时声明多个类型参数:
1 | template <typename T1, typename T2> |
调用时 myMax(3, 5.2) 中 T1 = int、T2 = double。不过要注意:返回类型是 T1,如果返回的其实是 b(double),结果会被截断成 T1 类型,可能丢失精度。C++11 之后可以用 auto 让编译器推导返回类型来避免这个问题。
六、函数模板重载
函数模板和普通函数一样,可以靠参数个数不同进行重载:
1 | template <typename T> |
调用时编译器会根据参数个数自动匹配。
七、函数模板特化(specialization)
有时通用模板对某些类型不适用,需要为特定类型单独写一份实现,这就是特化。
比如通用模板直接比较两个 const char*,比的其实是字符串的地址而不是字典序,这是错的。我们为它特化一个用 strcmp 比较内容的版本:
1 |
|
特化的规则:
- 语法上,
template <>后面紧跟函数,并用<具体类型>标明特化的类型; - 当传入参数类型完全匹配特化版本时,编译器优先使用特化函数;
- 类型不完全匹配时(例如传了
char*而不是const char*),仍会使用通用模板实例化。
八、常见的坑
- 模板实现通常要放在头文件(.h/.hpp)里。模板在实例化时必须看到完整定义,只有声明会报链接错误。这和普通函数「声明放 .h、实现放 .cpp」的习惯不同,新手最容易在这里卡住。
- 慎用
max这个名字。如果写了using namespace std;,自己定义的max可能和标准库的std::max冲突,报一堆莫名其妙的错误。学习时建议命名成myMax这类名字(本文示例就是这么做的)。 - 类型参数推导不会做隐式转换。
myMax(3, 5.2)会因为T同时被推导成int和double而报错,需要写成myMax<double>(3, 5.2)或强转参数。 - 模板报错信息通常又长又难懂,先检查「类型是否匹配」和「该类型是否支持模板里用到的运算符」(比如
>)。
九、小结
- 函数模板用一份代码支持任意类型,避免为每种类型重复写重载;
template <typename T>与template <class T>等价,推荐前者;- 隐式实例化靠调用自动生成函数,显式实例化用
template 类型 函数名<类型>(...)主动生成; - 模板支持多个类型参数、重载,以及用
template <>为特殊类型做特化; - 模板定义一般放头文件;自定义函数避开
max等标准库名字。