函数模板
函数模板是C++泛型编程的核心工具之一。它允许程序员编写与类型无关的通用函数,由编译器在编译时根据实际使用情况生成特定类型的函数版本。函数模板极大地提高了代码的复用性和可维护性。
问题场景:重复的代码逻辑
在程序设计中,经常会遇到逻辑完全相同、仅数据类型不同的函数。
求绝对值函数的重载
| // 整数绝对值
int absInt(int x) {
return x < 0 ? -x : x;
}
// 双精度浮点数绝对值
double absDouble(double x) {
return x < 0 ? -x : x;
}
// 长整型绝对值
long absLong(long x) {
return x < 0 ? -x : x;
}
|
这三个函数的逻辑完全相同,只是数据类型不同。如果为每个需要的类型都编写重载版本,会导致:
- 代码冗余:大量重复的函数体。
- 维护困难:修改逻辑时需要同步修改所有版本,容易遗漏。
- 可扩展性差:每增加一个类型就需要增加一个重载版本。
- 容易出错:不同版本之间可能出现不一致。
能否只编写一份通用代码,让编译器根据不同数据类型自动生成对应的函数?
函数模板(Function Template) 正是为此而生的解决方案。
函数模板的定义
基本语法
函数模板是一个通用函数的蓝图,它定义了一个函数族,其中的类型参数可以在使用时被具体类型替换。
函数模板的语法格式
template <typename 类型参数1, typename 类型参数2, ...>
返回类型 函数名(参数列表) {
// 函数体
}
template 关键字声明这是一个模板。
- 尖括号
<> 中包含类型参数列表。
typename 关键字声明类型参数(也可以用 class,二者含义相同)。
- 类型参数可以像普通类型一样在函数体中使用。
第一个函数模板
绝对值函数模板
| #include <iostream>
using namespace std;
// 定义函数模板
template <typename T>
T absValue(T x) {
return x < 0 ? -x : x;
}
int main() {
int n = -5;
double d = -5.5;
long l = -100L;
// 编译器自动推导类型,生成对应版本的函数
cout << absValue(n) << endl; // 5(调用 int 版本)
cout << absValue(d) << endl; // 5.5(调用 double 版本)
cout << absValue(l) << endl; // 100(调用 long 版本)
return 0;
}
|
typename 与 class
在模板参数声明中,typename 和 class 可以互换使用。typename 是 C++ 标准化后引入的关键字,语义更清晰。
两种写法
// 使用 typename(推荐,语义更清晰)
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
// 使用 class(传统写法,与 typename 完全等价)
template <class T>
T max(T a, T b) {
return a > b ? a : b;
}
区别
在模板参数声明中,typename 和 class 完全等价。但 typename 更能准确表达"这是一个类型参数"的含义,建议优先使用。
函数模板的实例化
什么是实例化
函数模板本身不是函数,它是生成函数的"蓝图"。当编译器遇到对函数模板的调用时,会根据实参类型生成一个具体的函数,这个过程称为模板实例化(Template Instantiation)。
实例化过程
当编译器看到 absValue(n)(其中 n 是 int 类型)时:
- 根据实参
n 的类型推断模板参数 T 为 int。
- 生成一个具体的函数:
int absValue(int x) { return x < 0 ? -x : x; }。
- 编译并链接这个生成的函数。
生成的函数称为模板函数(Template Function)。
隐式实例化(类型推导)
当编译器能够从函数实参中推导出模板参数类型时,不需要显式指定。
隐式实例化
| template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int i1 = 3, i2 = 5;
double d1 = 3.14, d2 = 2.71;
// 隐式实例化:编译器根据实参类型推导 T
int result1 = add(i1, i2); // T 推导为 int
double result2 = add(d1, d2); // T 推导为 double
// 不同参数类型时无法推导
// auto r3 = add(i1, d1); // 错误!T 不能同时是 int 和 double
return 0;
}
|
类型推导的限制
当模板参数无法从实参中唯一推导时,编译会失败:
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int i = 3;
double d = 3.14;
// 错误:T 可以推导为 int 或 double,不唯一
// auto r = add(i, d);
// 解决方案:显式指定类型
auto r = add<double>(i, d); // 将 i 转换为 double
return 0;
}
显式实例化(指定类型)
在某些情况下,需要显式指定模板参数类型。
显式实例化
| template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int i = 3;
double d = 3.14;
// 显式实例化:在 <> 中指定 T 的类型
double result1 = add<double>(i, d); // 将 i 隐式转换为 double
int result2 = add<int>(i, d); // 将 d 隐式转换为 int(截断)
cout << result1 << endl; // 6.14
cout << result2 << endl; // 6
return 0;
}
|
显式实例化声明(C++11)
C++11 允许在函数模板定义处进行显式实例化声明,强制编译器生成特定类型的函数。
显式实例化声明
| // 函数模板定义
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
// 显式实例化声明:强制生成 int 和 double 版本
template int max<int>(int, int);
template double max<double>(double, double);
int main() {
// 可以直接使用,编译器已经生成了对应版本
cout << max(3, 5) << endl; // 5
cout << max(3.14, 2.71) << endl; // 3.14
return 0;
}
|
多类型参数的函数模板
函数模板可以有多个类型参数,适用于不同类型的操作数。
多个类型参数
| #include <iostream>
using namespace std;
// 两个类型参数
template <typename T1, typename T2>
void display(T1 a, T2 b) {
cout << "T1: " << a << ", T2: " << b << endl;
}
// 返回类型为 double,参数类型为模板参数
template <typename T>
double toDouble(T value) {
return static_cast<double>(value);
}
// 返回类型与参数类型不同的模板(使用 auto 推导)
template <typename T>
auto square(T x) -> decltype(x * x) {
return x * x;
}
int main() {
display(10, 3.14); // T1=int, T2=double
display("hello", 100); // T1=const char*, T2=int
cout << toDouble(5) << endl; // 5.0
cout << toDouble(3.14f) << endl; // 3.14
cout << square(5) << endl; // 25(int)
cout << square(3.14) << endl; // 9.8596(double)
return 0;
}
|
返回类型推导
当返回类型依赖于模板参数且无法简单确定时,可以使用:
auto + decltype(C++11):auto func(T x) -> decltype(x * x)
auto 返回类型推导(C++14):auto func(T x) { return x * x; }
函数模板与函数重载
函数模板可以和普通函数一起构成重载关系。当同时存在模板和普通函数时,编译器会优先选择最匹配的版本。
模板与重载的配合
| #include <iostream>
using namespace std;
// 函数模板:适用于任意类型
template <typename T>
void print(T value) {
cout << "Template: " << value << endl;
}
// 普通函数:专门用于 int 类型
void print(int value) {
cout << "Specialized int: " << value << endl;
}
// 普通函数:专门用于 const char*
void print(const char* value) {
cout << "Specialized string: " << value << endl;
}
int main() {
print(10); // 调用普通函数(int 版本更匹配)
print(3.14); // 调用模板(double 没有普通函数)
print("hello"); // 调用普通函数(const char* 版本更匹配)
print('A'); // 调用模板(char 没有普通函数)
// 可以显式指定使用模板版本
print<int>(20); // 强制调用模板的 int 版本
return 0;
}
|
运行结果:
Specialized int: 10
Template: 3.14
Specialized string: hello
Template: A
Template: 20
重载解析规则
- 优先匹配普通函数:如果普通函数与实参类型完全匹配,优先调用普通函数。
- 其次匹配模板:如果没有完全匹配的普通函数,尝试从模板实例化。
- 显式指定模板参数:可以强制使用模板版本,忽略普通函数。
函数模板的特化
全特化
当模板的通用实现不适用于某些特定类型时,可以对这些类型提供特化(Specialization)版本。
函数模板的全特化
| #include <iostream>
#include <cstring>
using namespace std;
// 通用模板:适用于任意类型
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
// 特化版本:针对 const char*(C风格字符串)
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return strcmp(a, b) == 0;
}
int main() {
int x = 5, y = 5;
cout << isEqual(x, y) << endl; // 1(使用通用模板)
double d1 = 3.14, d2 = 3.14;
cout << isEqual(d1, d2) << endl; // 1(使用通用模板)
const char* s1 = "hello";
const char* s2 = "hello";
const char* s3 = "world";
cout << isEqual(s1, s2) << endl; // 1(使用特化版本,比较内容)
cout << isEqual(s1, s3) << endl; // 0(使用特化版本)
return 0;
}
|
特化语法
template <> 表示这是一个特化版本。
- 函数名后跟
<类型> 指定特化的类型。
- 特化版本的参数类型必须与通用模板一致。
特化 vs 重载
对于函数模板,特化和重载都能实现"针对特定类型的定制行为",但二者有所区别。
优先选择重载
对于函数模板,重载通常比特化更优先:
// 通用模板
template <typename T>
void func(T x) { cout << "Template" << endl; }
// 特化版本(不推荐,重载更好)
template <>
void func<int>(int x) { cout << "Specialization" << endl; }
// 重载版本(推荐)
void func(int x) { cout << "Overload" << endl; }
int main() {
func(10); // 调用重载版本(优先于特化)
return 0;
}
建议
对于函数模板,优先使用普通函数重载来处理特定类型,而不是模板特化。类模板则必须使用特化。
函数模板的约束
隐式约束
函数模板并非可以处理所有类型。模板函数体中使用的操作,必须对实际类型参数是合法的。
模板的隐式约束
| template <typename T>
T max(T a, T b) {
return a > b ? a : b; // 要求 T 类型支持 > 运算符
}
template <typename T>
T sum(T a, T b) {
return a + b; // 要求 T 类型支持 + 运算符
}
template <typename T>
void print(T value) {
cout << value; // 要求 T 类型支持 << 运算符
}
|
如果需要自定义类型作为模板参数,必须重载模板中使用到的运算符。
自定义类型作为模板参数
| #include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
// 重载 > 运算符(支持 max 模板)
bool operator>(const Point& other) const {
return (x * x + y * y) > (other.x * other.x + other.y * other.y);
}
// 重载 << 运算符(支持 print 模板)
friend ostream& operator<<(ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
};
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
Point p1(3, 4);
Point p2(5, 0);
Point p3 = max(p1, p2);
cout << "Max point: " << p3 << endl; // 距离更大的是 (5, 0)
return 0;
}
|
C++20 的概念(Concepts)
C++20 引入了概念(Concepts),可以显式约束模板参数,提供更清晰的错误信息。
使用概念约束(C++20)
| #include <concepts>
// 要求 T 类型支持 < 运算符
template <typename T>
requires std::totally_ordered<T>
T max(T a, T b) {
return a > b ? a : b;
}
// 或者使用更简洁的语法
template <std::totally_ordered T>
T max(T a, T b) {
return a > b ? a : b;
}
|
综合示例
函数模板示例
| #include <iostream>
#include <vector>
using namespace std;
// === 1. 简单函数模板:交换两个值 ===
template <typename T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
// === 2. 函数模板:查找数组最大值 ===
template <typename T>
T maxInArray(const T arr[], int size) {
T maxVal = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > maxVal) {
maxVal = arr[i];
}
}
return maxVal;
}
// === 3. 函数模板:输出容器内容(支持 vector) ===
template <typename T>
void printContainer(const vector<T>& container) {
cout << "[";
for (size_t i = 0; i < container.size(); i++) {
cout << container[i];
if (i < container.size() - 1) cout << ", ";
}
cout << "]" << endl;
}
// === 4. 多类型参数:将容器转换为字符串 ===
template <typename T>
string toString(const T& value) {
return to_string(value);
}
// string 特化
template <>
string toString(const string& value) {
return value;
}
// === 5. 函数模板与重载配合 ===
template <typename T>
void show(T value) {
cout << "Template: " << value << endl;
}
void show(int value) {
cout << "Overloaded int: " << value << endl;
}
int main() {
// 测试 1:交换
int a = 5, b = 10;
cout << "Before swap: a=" << a << ", b=" << b << endl;
mySwap(a, b);
cout << "After swap: a=" << a << ", b=" << b << endl;
double c = 3.14, d = 2.71;
mySwap(c, d);
cout << "After swap: c=" << c << ", d=" << d << endl;
// 测试 2:数组最大值
int intArr[] = {3, 7, 2, 9, 5};
double doubleArr[] = {3.14, 2.71, 1.41, 6.28};
cout << "Max int: " << maxInArray(intArr, 5) << endl; // 9
cout << "Max double: " << maxInArray(doubleArr, 4) << endl; // 6.28
// 测试 3:容器输出
vector<int> vi = {1, 2, 3, 4, 5};
vector<string> vs = {"hello", "world", "C++"};
printContainer(vi);
printContainer(vs);
// 测试 4:多类型与特化
cout << "toString(100) = " << toString(100) << endl;
cout << "toString(3.14) = " << toString(3.14) << endl;
cout << "toString(\"hello\") = " << toString(string("hello")) << endl;
// 测试 5:模板与重载
show(10); // Overloaded int: 10
show(3.14); // Template: 3.14
show("hello"); // Template: hello
show<int>(20); // Template: 20(强制使用模板)
return 0;
}
|
小结
-
函数模板的定义:template <typename T> + 普通函数定义,类型参数 T 代表通用类型。
-
模板实例化:
- 隐式实例化:编译器根据实参类型自动推导模板参数。
- 显式实例化:在
<> 中指定模板参数类型。
-
多类型参数:template <typename T1, typename T2> 支持多个类型参数。
-
模板与重载:
- 普通函数优先于模板函数匹配。
- 可以用
show<int>(value) 强制使用模板版本。
-
模板特化:为特定类型提供专门的实现,使用 template <> 语法。
-
模板的约束:模板函数体中使用的操作必须对实际类型参数合法。