C++ 模板进阶:非类型参数与特化详解
1. 非类型模板参数
模板参数主要分为两类:类型模板参数和非类型模板参数。类型参数如 typename T 接收的是'类型',而非类型参数则接收具体的'常量值'。
例如在 template <int N> 中,N 就是一个非类型模板参数。它必须在编译期就能确定结果,且不能是浮点数、类对象或字符串。
// 模板参数 <int N> 是非类型参数(传递的是'值')
template <int N>
class FixedArray {
private:
int arr[N]; // 用 N 作为数组长度(编译时就确定了)
public:
void printSize() {
std::cout << "数组长度是:" << N << std::endl;
}
};
int main() {
FixedArray<3> arr3; // N=3,创建一个长度为 3 的数组
FixedArray<5> arr5; // N=5,创建一个长度为 5 的数组
arr3.printSize(); // 输出:数组长度是:3
arr5.printSize(); // 输出:数组长度是:5
return 0;
}
2. 类模板的特化
模板特化(Template Specialization)允许我们为特定条件定义专门的实现,而不使用通用模板的代码。
2.1 函数模板特化
当模板参数满足特定条件时,可以定义特化版本。编译器会优先匹配特化版本。
#include <iostream>
// 通用模板(适用于大多数类型)
template <typename T>
void Print(T value) {
std::cout << "通用模板:" << value << std::endl;
}
// 对 char 类型的特化版本
template <>
void Print<char>(char value) {
std::cout << "char 特化:字符 '" << value << "' 的 ASCII 码是 " << (int)value << std::endl;
}
int main() {
Print(123); // 匹配通用模板,输出:通用模板:123
Print('A'); // 匹配 char 特化版本,输出:char 特化:字符 'A' 的 ASCII 码是 65
return 0;
}
2.2 类模板特化
类模板特化分为全特化和偏特化。全特化是指为类的每一个参数都指定具体类型;若只针对部分参数进行特化,则称为偏特化。
#include <iostream>
#include <string>
// 通用类模板:处理任意类型的数据
template <typename T>
class DataProcessor {
public:
void process(T data) {
std::cout << "通用处理:" << data << "(类型未知,按默认方式处理)" << std::endl;
}
};
// 全特化:专门处理 int 类型
template <>
class DataProcessor<int> {
public:
void process(int data) {
std::cout << "int 专用处理:" << data << "(整数翻倍后为 " << data * 2 << ")" << std::endl;
}
};
// 全特化:专门处理 string 类型
template <>
class DataProcessor<std::string> {
public:
void process(std::string data) {
std::cout << "string 专用处理:" << data << "(字符串长度为 " << data.size() << ")" << std::endl;
}
};
int main() {
DataProcessor<double> dProc;
dProc.process(3.14); // 用通用模板处理
DataProcessor<int> iProc;
iProc.process(10); // 用 int 专用处理
DataProcessor<std::string> sProc;
sProc.process("hello"); // 用 string 专用处理
return 0;
}
3. 模板的分离编译
在大型项目中,通常采用分离编译模式,即每个源文件单独编译生成目标文件,最后链接成可执行文件。对于模板而言,这带来了一个特殊问题:实例化发生在调用处。
如果模板的定义和声明分离在不同的文件中,而调用方无法看到完整的模板定义,就会导致链接错误。因此,最佳实践是将模板的定义(包括函数体和类成员函数的实现)直接放在头文件中,确保所有包含该头文件的源文件都能访问到完整的模板代码。


