C++ 泛型编程(Generic Programming)是一种编写与具体类型无关的通用代码的范式,核心目标是代码复用 和类型安全 ,通过模板(Template)机制实现。相比面向对象的 "运行时多态",泛型编程实现 "编译期多态",在保证灵活性的同时避免了虚函数的运行时开销。
1、函数模板
函数模板定义一个通用函数,其参数类型、返回值类型可以是 "类型参数",编译器会根据传入的实参类型自动生成具体版本(模板实例化)。
cpp
// 函数模板:交换两个任意类型的值
template <typename T> // 声明类型参数 T(typename 可替换为 class)
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int x = 1, y = 2;
swap(x, y); // 编译器自动实例化 swap<int>(int&, int&)
double a = 3.14, b = 2.71;
swap(a, b); // 实例化 swap<double>(double&, double&)
string s1 = "hello", s2 = "world";
swap(s1, s2); // 实例化 swap<string>(string&, string&)
return 0;
}
- 类型参数 :用
template <typename T>
声明(typename
和class
等价),T
是 "占位类型",实例化时被具体类型替换。 - 实例化 :编译器根据实参类型自动生成具体函数(如
swap<int>
),这一过程在编译期完成。 - 多类型参数 :支持多个类型参数,如
template <typename T, typename U>
。
2、类模板
类模板定义一个通用类,类中的成员变量、成员函数的类型可以参数化,典型例子是标准库中的 vector<T>
、map<K, V>
等容器。
cpp
// 类模板:简单的动态数组
template <typename T>
class Array {
private:
T* data;
size_t size;
public:
// 构造函数
Array(size_t n) : size(n) {
data = new T[size];
}
// 析构函数
~Array() {
delete[] data;
}
// 下标访问
T& operator[](size_t index) {
return data[index];
}
size_t get_size() const {
return size;
}
};
int main() {
// 实例化 Array<int> 类型
Array<int> int_arr(3);
int_arr[0] = 10;
int_arr[1] = 20;
// 实例化 Array<string> 类型
Array<string> str_arr(2);
str_arr[0] = "hello";
str_arr[1] = "generic";
return 0;
}
-
实例化方式 :类模板必须显式指定类型参数(如
Array<int>
),编译器不会自动推导。 -
成员函数 :类模板的成员函数可以在类内定义,也可以在类外定义(需用
template <typename T>
修饰)。cpp// 类外定义成员函数 template <typename T> T& Array<T>::operator[](size_t index) { return data[index]; }
3、模板特化
模板特化是对特定类型提供定制化实现,当通用模板不适合某类类型时(如指针、数组),可通过特化覆盖默认行为。分为全特化 和偏特化。
3.1 全特化
对模板的所有类型参数指定具体类型,完全覆盖通用模板。
函数模板全特化
cpp
// 通用模板
template <typename T>
void print(T value) {
cout << "Generic: " << value << endl;
}
// 对 const char* 特化(字符串字面量类型)
template <>
void print<const char*>(const char* value) {
cout << "Specialized for string: " << value << endl;
}
int main() {
print(100); // 调用通用模板:Generic: 100
print("hello"); // 调用特化版本:Specialized for string: hello
return 0;
}
类模板全特化
cpp
// 通用模板
template <typename T>
class Container {
public:
void store(T value) {
cout << "Storing generic type: " << value << endl;
}
};
// 对 int 类型全特化
template <>
class Container<int> {
public:
void store(int value) {
cout << "Storing integer: " << value << " (specialized)" << endl;
}
};
int main() {
Container<double> d;
d.store(3.14); // 通用版本:Storing generic type: 3.14
Container<int> i;
i.store(10); // 特化版本:Storing integer: 10 (specialized)
return 0;
}
3.2 偏特化
对模板的部分类型参数指定具体类型(适用于多参数模板),或对类型参数添加约束(如指针、引用)。
类模板偏特化(多参数模板)
cpp
// 通用模板(两个类型参数)
template <typename T, typename U>
class Pair {
public:
T first;
U second;
Pair(T f, U s) : first(f), second(s) {}
};
// 偏特化:第一个参数为 int
template <typename U>
class Pair<int, U> {
public:
int first;
U second;
Pair(int f, U s) : first(f), second(s) {
cout << "Partial specialization (first is int): " << f << ", " << s << endl;
}
};
int main() {
Pair<double, string> p1(3.14, "pi"); // 通用版本
Pair<int, string> p2(10, "hello"); // 偏特化版本:输出 Partial specialization...
return 0;
}
类模板偏特化(指针类型约束)
cpp
// 通用模板
template <typename T>
class Handler {
public:
void process(T value) {
cout << "Processing value: " << value << endl;
}
};
// 偏特化:T 为指针类型
template <typename T>
class Handler<T*> {
public:
void process(T* value) {
if (value) {
cout << "Processing pointer: " << *value << endl;
} else {
cout << "Null pointer" << endl;
}
}
};
int main() {
Handler<int> h1;
h1.process(100); // 通用版本:Processing value: 100
int x = 200;
Handler<int*> h2;
h2.process(&x); // 偏特化版本:Processing pointer: 200
return 0;
}
4、非类型模板参数
模板参数不仅可以是类型(typename T
),还可以是常量表达式(如整数、指针、引用),称为非类型参数。
固定大小的数组模板
cpp
// 非类型参数 N 表示数组大小(编译期常量)
template <typename T, size_t N>
class FixedArray {
private:
T data[N]; // 编译期确定大小,无需动态内存
public:
T& operator[](size_t index) {
return data[index];
}
size_t size() const { return N; }
};
int main() {
FixedArray<int, 5> arr; // 大小为 5 的 int 数组(编译期固定)
for (size_t i = 0; i < arr.size(); ++i) {
arr[i] = i * 10;
}
return 0;
}
- 约束 :非类型参数必须是编译期可确定的常量 (如字面量、
constexpr
变量)。 - 类型限制 :通常为整数类型(
int
、size_t
)、指针或引用(指向全局对象 / 函数)。
5、模板的编译与实例化
模板的编译机制与普通函数 / 类不同,核心是 "延迟实例化 " 和 "两阶段查找"。
5.1 延迟实例化
编译器仅在模板被使用时(如调用函数模板、创建类模板对象)才生成具体代码(实例化),未使用的模板不会生成代码。
cpp
template <typename T>
void func(T x) {
// 即使 T 不支持 operator++,只要不调用 func,就不会报错
x++;
}
int main() {
// func("hello"); // 若调用,string 不支持 ++,编译报错
return 0;
}
5.2 两阶段查找
模板编译分为两个阶段:
- 第一阶段:解析模板本身(不依赖模板参数的部分),检查语法错误(如缺少分号)。
- 第二阶段 :实例化时(替换类型参数后),检查依赖模板参数的部分(如
x++
是否合法)。
意义:提前发现非依赖于类型参数的错误,提高编译效率。
5.3 模板的声明与定义
模板的定义必须在头文件中(或包含在所有使用它的源文件中),不能像普通函数那样 "头文件声明,源文件定义"。
原因 :实例化时编译器需要看到完整的模板定义才能生成具体代码,若定义在源文件(.cpp
)中,其他文件无法访问,会导致链接错误(undefined reference
)。
6、C++11 后的泛型增强特性
6.1 auto
与 decltype
-
auto
:自动推导变量类型(依赖模板类型推导规则),简化泛型代码。 -
decltype
:推导表达式类型,用于声明与表达式类型一致的变量或函数返回值。cpptemplate <typename T, typename U> auto add(T a, U b) -> decltype(a + b) { // 尾置返回类型,依赖 a + b 的类型 return a + b; } int main() { auto result = add(3, 4.5); // result 类型为 double(3 + 4.5 = 7.5) return 0; }
6.2 模板别名
用 using
定义模板的别名,简化复杂模板类型。
cpp
template <typename T>
using Vec = std::vector<T>; // Vec<int> 等价于 vector<int>
Vec<int> nums; // 简化写法
// 结合非类型参数
template <typename T, size_t N>
using Arr = std::array<T, N>;
Arr<double, 10> data; // 等价于 array<double, 10>
6.3 变参模板
支持参数数量可变的模板(如 printf
风格的函数),通过递归展开参数包实现。
打印任意数量的参数
cpp
// 递归终止函数(无参数)
void print() {}
// 变参模板:展开第一个参数,递归处理剩余参数
template <typename T, typename... Args>
void print(T first, Args... rest) {
cout << first << " ";
print(rest...); // 递归展开剩余参数
}
int main() {
print(1, "hello", 3.14, 'a'); // 输出:1 hello 3.14 a
return 0;
}
关键点 :Args...
称为 "参数包",rest...
用于展开参数包。
7、常见问题
-
什么是泛型编程?C++ 如何实现泛型编程?
泛型编程是一种编写与具体类型无关的通用代码的范式,核心目标是代码复用和类型安全,在编译期根据实际类型生成具体代码。
C++ 主要通过模板(Templates) 实现泛型编程,包括:
- 函数模板:定义通用函数(如
swap<T>
),编译器根据实参类型生成具体函数。 - 类模板:定义通用类(如
vector<T>
),通过显式指定类型参数实例化。
- 函数模板:定义通用函数(如
-
模板特化的作用是什么?全特化与偏特化的区别?
模板特化的作用是为特定类型提供定制化实现,当通用模板对某些类型(如指针、字符串)不适用时,通过特化覆盖默认行为。
区别:
- 全特化 :对模板的所有类型参数 指定具体类型(如
template <> class Container<int>
),完全替换通用模板。 - 偏特化 :对模板的部分类型参数指定具体类型(如多参数模板中固定一个参数),或对类型参数添加约束(如指针、引用),仅覆盖符合条件的实例。
- 全特化 :对模板的所有类型参数 指定具体类型(如
-
为什么模板的定义通常放在头文件中?
因为模板采用 "延迟实例化" 机制:编译器在实例化模板(生成具体类型代码)时,需要看到完整的模板定义(而非仅声明)。
若模板定义放在源文件(
.cpp
)中,其他文件包含头文件时只能看到声明,无法实例化模板,会导致链接错误(undefined reference
)。因此,模板的定义通常与声明一起放在头文件中。 -
模板实例化会导致 "代码膨胀" 吗?如何避免?
会。模板为每个实例化类型生成独立的代码(如
vector<int>
和vector<double>
是两个不同的类,生成两套代码),过多实例化会导致目标文件变大(代码膨胀)。 -
如何在泛型编程中实现策略模式?
cpp// 策略接口作为模板参数 template<typename SortingStrategy> class Sorter { public: void sort(Container& data) { SortingStrategy::sort(data); } }; // 各种策略实现 struct QuickSort { template<typename Container> static void sort(Container& data) { // 快速排序实现 } }; struct MergeSort { template<typename Container> static void sort(Container& data) { // 归并排序实现 } }; // 使用:编译期策略选择 Sorter<QuickSort> quickSorter; Sorter<MergeSort> mergeSorter;