一、什么是模板(Template)
模板是 C++ 支持的泛型编程机制,可以用来编写与类型无关的代码,实现代码的复用。编译器根据调用时提供的类型参数生成对应版本代码,称为模板实例化。
二、函数模板(Function Template)
- 模板函数在编译时 根据传入参数类型自动生成对应函数代码(实例化),普通函数是编写时确定类型。
- 模板函数可以避免为不同类型写多个重载版本。
语法
cpp
template<typename T>
返回类型 函数名(参数列表) {
// 使用类型T的代码
}
或者:
cpp
template<class T>
返回类型 函数名(参数列表) {
// 使用类型T的代码
}
typename
和 class
在这里是等价的。
示例:通用交换函数
cpp
template<typename T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
使用:
cpp
int x = 1, y = 2;
mySwap(x, y); // 自动推导T为int
double a = 1.1, b = 2.2;
mySwap(a, b); // 自动推导T为double
三、类模板(Class Template)
语法
cpp
template<typename T>
class ClassName {
public:
T data;
void print();
};
cpp
template<typename T>
class MyClass {
public:
void show();
};
template<typename T>
void MyClass<T>::show() {
std::cout << "Show: " << typeid(T).name() << std::endl;
}
四、模板参数多样性
模板不仅可以是类型,还可以是非类型参数:
非类型参数可以是整数、指针、引用、布尔值等
cpp
template<typename T, int size>
class Array {
T arr[size];
public:
void printSize() {
std::cout << "Size: " << size << std::endl;
}
};
也可以是多个参数:
cpp
template<typename T1, typename T2>
void printPair(T1 a, T2 b) {
std::cout << a << " and " << b << std::endl;
}
//使用
printPair(1, 2.5); // 输出:1 and 2.5
也可以是可变参数:
C++11 引入,支持接收任意数量的参数。
cpp
//打印所有参数
void print() { } // 终止递归
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // 递归展开
}
//使用
print(1, 2.5, "Hello"); // 输出:1 2.5 Hello
五、模板特化(Template Specialization)
有时你希望对特定类型的模板做定制化处理。
一、类模板特化
cpp
template<typename T>
class Printer {
public:
void print(const T& value) {
std::cout << "General: " << value << std::endl;
}
};
// 对 std::string 类型进行特化
template<>
class Printer<std::string> {
public:
void print(const std::string& value) {
std::cout << "String: \"" << value << "\"" << std::endl;
}
};
使用:
cpp
Printer<int> p1;
p1.print(123); // General: 123
Printer<std::string> p2;
p2.print("Hello"); // String: "Hello"
二、函数模板特化
cpp
template<typename T>
void printValue(T value) {
std::cout << "Generic: " << value << std::endl;
}
template<>
void printValue<char>(char value) {
std::cout << "Char (int): " << static_cast<int>(value) << std::endl;
}
三、模板递归(用于编译期计算)
示例:计算阶乘(编译期)
cpp
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
使用:
cpp
int result = Factorial<5>::value; // 120
四、模板默认参数
cpp
template<typename T = int>
class MyContainer {
T value;
public:
MyContainer(T v) : value(v) {}
void show() { std::cout << value << std::endl; }
};
使用:
cpp
MyContainer<> c1(100); // 使用默认int
MyContainer<double> c2(3.14); // 指定double
五、模板别名(C++11)
cpp
template<typename T>
using Vec = std::vector<T>;
Vec<int> nums; // 相当于 std::vector<int>
六、SFINAE(Substitution Failure Is Not An Error)
允许根据类型特征选择不同实现(高级技巧)
cpp
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
check(T t) {
std::cout << "Integral type\n";
}
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value>::type
check(T t) {
std::cout << "Floating point type\n";
}
七、模板中的 decltype
和 auto
自动推导
cpp
template<typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
八、小结对照表
模板技巧 | 作用/用途 |
---|---|
函数模板 | 定义通用函数 |
类模板 | 定义通用类 |
模板特化 | 为特定类型提供特殊实现 |
非类型参数 | 指定大小、常量等 |
递归模板 | 编译时递归计算,如阶乘 |
可变参数模板 | 接收任意数量类型参数 |
SFINAE | 启用/禁用特定模板实例(用于类型选择) |
auto + decltype |
自动推导返回类型 |
函数模板声明为什么放在头文件中
在 C++ 中,函数模板与类模板 的定义和声明有一个重要区别 于普通函数或类:模板的定义必须可见于调用点,否则编译器在实例化时找不到它的实现。下面是详细解释与示例:
普通函数 vs 函数模板
类型 | 声明和定义可以分离吗? | 实现可以放 .cpp 文件吗? |
---|---|---|
普通函数 | ✅ 可以 | ✅ 可以 |
函数模板 | ❌ 不推荐分离 | ❌ 不建议放在 .cpp 文件 |
为什么函数模板不建议将定义放在 .cpp
?
因为模板的代码是在实例化(即使用)时生成 的,编译器必须看到定义才能生成代码。这意味着:
- 模板的定义通常要放在头文件中;
- 如果只写了声明放在头文件,而定义放在
.cpp
,链接时会报错undefined reference
。
正确方式:函数模板定义写在头文件
my_template.hpp
cpp
#ifndef MY_TEMPLATE_HPP
#define MY_TEMPLATE_HPP
#include <iostream>
template<typename T>
void printValue(const T& val) {
std::cout << val << std::endl;
}
#endif
使用:
cpp
#include "my_template.hpp"
int main() {
printValue(42); // 输出:42
printValue("hello"); // 输出:hello
}
错误方式:声明在 .hpp
,定义在 .cpp
my_template.hpp
cpp
#ifndef MY_TEMPLATE_HPP
#define MY_TEMPLATE_HPP
template<typename T>
void printValue(const T& val); // 只有声明
#endif
my_template.cpp
cpp
#include "my_template.hpp"
#include <iostream>
template<typename T>
void printValue(const T& val) {
std::cout << val << std::endl;
}
这段代码编译时不会出错,但链接时报错:
undefined reference to `printValue<int>(int const&)`
因为模板定义在 .cpp
,在被 main.cpp
使用时不可见,无法实例化。
如果你一定要分离定义和声明?
你可以使用 include "xxx.tpp"
的方式,将定义放在单独文件中,但最终仍然要包含进 .hpp
文件中。
my_template.hpp
cpp
#ifndef MY_TEMPLATE_HPP
#define MY_TEMPLATE_HPP
template<typename T>
void printValue(const T& val); // 声明
#include "my_template.tpp" // 引入定义
#endif
my_template.tpp
cpp
template<typename T>
void printValue(const T& val) {
std::cout << val << std::endl;
}
这种方式是 Boost、Eigen 等大型模板库常用的技巧。
类模板也是一样的原则
类模板也必须将定义放在头文件中。
cpp
// MyClass.hpp
template<typename T>
class MyClass {
public:
void show();
};
template<typename T>
void MyClass<T>::show() {
std::cout << "Show" << std::endl;
}