函数模板 (Function Templates)
C++ 函数模板 (Function Templates)
函数模板是 静态多态(Static Polymorphism) 的核心机制之一,允许编写与类型无关的泛型代码。通过模板,可以在编译时为不同类型生成具体函数,提高代码复用性和灵活性。请优先了解和重载相关知识, 本文结合代码和底层原理,详细解析函数模板的实现、实例化规则及高级用法。
1. 函数模板基础
1.1 定义与语法
函数模板通过 template
关键字定义,使用类型参数 T
表示泛型类型:
template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}
typename T
表示类型参数(也可用class T
,二者等价)。- 模板参数可定义多个(如
template <typename T, typename U>
)。
1.2 模板实例化
编译器根据调用时的实参类型 隐式实例化 具体函数:
int main() {int a = 5, b = 10;cout << max(a, b) << endl; // 隐式实例化为 int max(int, int)double x = 3.14, y = 2.71;cout << max(x, y) << endl; // 隐式实例化为 double max(double, double)return 0;
}
1.3 底层原理
- 编译器为每个类型生成独立函数(如
max<int>
和max<double>
)。 - 符号表中生成唯一名称(Name Mangling),例如:
max<int>
→_Z3maxIiET_S0_S0_
max<double>
→_Z3maxIdET_S0_S0_
查看生成符号(Linux/g++):
g++ -c main.cpp -o main.o
nm main.o
输出示例:
00000000 T _Z3maxIiET_S0_S0_
00000010 T _Z3maxIdET_S0_S0_
2. 类型推导与显式指定
2.1 自动类型推导
编译器根据实参推断模板参数类型:
max(5, 10); // T → int
max(3.14, 2.71); // T → double
2.2 显式指定类型
若类型无法推导(如返回值类型不同),需显式指定:
template <typename T, typename U>
T max(T a, U b) {return (a > b) ? a : b;
}int main() {// 显式指定 T 为 double,U 由实参推导为 intcout << max<double>(5, 3.14) << endl; // 返回 5.0(类型为 double)return 0;
}
3. 多类型参数与返回类型
3.1 多类型参数
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // C++11 后置返回类型return a + b;
}int main() {cout << add(1, 2.5) << endl; // T = int, U = double → 返回 double (3.5)return 0;
}
3.2 返回类型优化(C++14)
C++14 支持 auto
自动推导返回类型:
template <typename T, typename U>
auto add(T a, U b) {return a + b;
}
4. 模板特化与重载
4.1 模板特化
为特定类型提供优化实现:
// 通用模板
template <typename T>
bool isEqual(T a, T b) {return a == b;
}// 特化版本(针对 double,处理浮点精度)
template <>
bool isEqual<double>(double a, double b) {return fabs(a - b) < 1e-9;
}int main() {cout << isEqual(5, 5) << endl; // 1(通用模板)cout << isEqual(0.1 + 0.2, 0.3) << endl; // 1(特化模板)return 0;
}
4.2 函数重载
模板函数可与普通函数重载:
template <typename T>
void print(T a) {cout << "模板: " << a << endl;
}void print(const char* a) { // 重载版本cout << "重载: " << a << endl;
}int main() {print(10); // 调用模板函数print("Hello"); // 调用重载函数return 0;
}
5. 模板参数类型
5.1 非类型模板参数
模板参数可以是整型、指针或引用:
template <typename T, int N>
T scale(T value) {return value * N;
}int main() {cout << scale<int, 5>(10) << endl; // 50(10 * 5)return 0;
}
5.2 默认模板参数
C++11 支持默认模板参数:
template <typename T = int>
T getDefault() {return T();
}int main() {cout << getDefault() << endl; // 0(int 默认值)cout << getDefault<double>() << endl; // 0.0return 0;
}
6. 模板的编译与链接
6.1 两阶段编译
- 模板定义检查:检查语法和模板参数合法性。
- 实例化检查:在实例化时检查类型相关操作(如
T::unknown
)。
6.2 显式实例化
减少重复编译开销:
// 显式实例化声明(头文件)
extern template int max<int>(int, int);// 显式实例化定义(源文件)
template int max<int>(int, int);
7. 高级主题
7.1 可变参数模板(C++11)
处理任意数量参数:
template <typename... Args>
void print(Args... args) {(cout << ... << args) << endl; // C++17 折叠表达式
}int main() {print(1, " + ", 2, " = ", 3); // 输出:1 + 2 = 3return 0;
}
7.2 完美转发(C++11)
保留参数的值类别(左值/右值):
template <typename T>
void relay(T&& arg) { // 通用引用process(std::forward<T>(arg)); // 完美转发
}
8. 总结
8.1 核心优势
- 类型安全:避免宏或
void*
的类型不安全操作。 - 代码复用:同一逻辑适用于不同类型。
- 性能:编译时生成代码,无运行时开销。
8.2 使用场景
- 容器类(如
std::vector<T>
)。 - 算法库(如
std::sort
)。 - 数学计算(如矩阵运算)。
8.3 注意事项
- 代码膨胀:过多实例化可能增加二进制体积。
- 编译时间:复杂模板会增加编译时间。
- 错误信息:模板错误信息通常难以理解。
多选题
题目 1:模板类型推导
以下代码中,调用 print(ptr)
时,模板参数 T
的类型是什么?
template <typename T>
void print(T arg) { /* ... */ }int main() {const int* ptr = nullptr;print(ptr);return 0;
}
A. const int*
B. int*
C. const int
D. int
题目 2:显式模板参数与隐式推导
以下代码的输出是什么?
template <typename T>
void func(T a, T b) { cout << "模板版本" << endl; }void func(int a, double b) { cout << "重载版本" << endl; }int main() {func<int>(10, 3.14);return 0;
}
A. 模板版本
B. 重载版本
C. 编译失败,类型冲突
D. 运行时错误
题目 3:模板特化与重载的优先级
以下代码的输出是什么?
template <typename T>
void process(T a) { cout << "通用模板" << endl; }template <>
void process<int>(int a) { cout << "int 特化" << endl; }void process(double a) { cout << "double 重载" << endl; }int main() {process(10); // 调用哪个版本?process(10.0); // 调用哪个版本?return 0;
}
A. 通用模板
和 double 重载
B. int 特化
和 double 重载
C. int 特化
和 通用模板
D. 编译失败,存在歧义
题目 4:可变参数模板与折叠表达式
以下代码是否能编译?输出是什么?
template <typename... Args>
void printSum(Args... args) {cout << (args + ...) << endl; // C++17 折叠表达式
}int main() {printSum(1, 2, 3);printSum(1.5, 2.5);return 0;
}
A. 编译失败,语法错误
B. 输出 6
和 4
C. 输出 6
和 4.0
D. 输出 6
和 4.0
(类型为 double
)
题目 5:完美转发与通用引用
以下代码中,relay
函数是否能正确转发参数的值类别(左值/右值)?
template <typename T>
void relay(T&& arg) { // 通用引用process(arg); // 直接传递 arg
}void process(int& x) { cout << "左值" << endl; }
void process(int&& x) { cout << "右值" << endl; }int main() {int a = 10;relay(a); // 调用 process(int&)relay(10); // 调用 process(int&&)return 0;
}
A. 是,输出 左值
和 右值
B. 否,两次都输出 左值
C. 否,两次都输出 右值
D. 编译失败,relay
函数无法推断类型
答案与解析
题目 1:模板类型推导
答案:A
解析:
- 模板参数
T
的类型推导遵循 按值传递规则:arg
的类型是T
,实际参数类型是const int*
。- 推导时
T
直接匹配为const int*
(指针本身的类型,保留const
)。
- 如果参数是引用(如
T& arg
),则T
会被推导为const int*
,但此处是值传递。
题目 2:显式模板参数与隐式推导
答案:A
解析:
func<int>(10, 3.14)
显式指定T = int
,因此模板参数为int
。- 此时函数签名变为
void func(int, int)
,但第二个参数3.14
是double
,触发隐式转换(double
→int
)。 - 重载版本
void func(int, double)
参数类型更匹配,但显式模板参数会强制使用模板函数。
题目 3:模板特化与重载的优先级
答案:B
解析:
- 规则:非模板函数优先级 > 模板特化 > 通用模板。
process(10)
:- 优先匹配
process<int>
的特化版本。
- 优先匹配
process(10.0)
:- 优先匹配非模板函数
process(double)
。
- 优先匹配非模板函数
- 若没有特化或重载,才会匹配通用模板。
题目 4:可变参数模板与折叠表达式
答案:D
解析:
- 折叠表达式
(args + ...)
计算所有参数的和。 printSum(1, 2, 3)
→1 + 2 + 3 = 6
(类型int
)。printSum(1.5, 2.5)
→1.5 + 2.5 = 4.0
(类型double
)。- 输出时,
cout
根据类型显示为6
和4.0
。
题目 5:完美转发与通用引用
答案:B
解析:
relay
函数使用通用引用(T&&
),但未使用std::forward
。arg
在函数内部始终是左值(即使原始参数是右值)。process(arg)
始终调用process(int&)
,导致两次输出左值
。- 修复方法:使用
process(std::forward<T>(arg))
。
总结
这些题目覆盖了函数模板的核心概念和陷阱,包括类型推导、特化、重载优先级、可变参数模板和完美转发。通过分析这些场景,可以更深入理解模板在静态多态中的复杂行为。