Skip to content

C++ 模板

模板是 C++ 的一个基本特性,实现了 泛型编程。它们允许你编写可以操作不同数据类型的代码(函数或类),而无需为每种类型重写代码。

可以将模板视为一个蓝图。你只需定义一次结构或算法,使用占位符类型名称。当你将模板用于特定类型(如 int 或 std::string)时,编译器会根据蓝图生成该特定类型的实际代码。

C++ 标准库大量依赖于模板。诸如 std::vector、std::list 和 std::map 之类的容器,以及 std::sort 和 std::find 之类的算法,都是基于模板的。这使得你可以使用相同的底层 std::vector 模板代码来创建 std::vector<int>、std::vector<double> 或 std::vector<MyClass>。

函数模板定义了一个可以操作各种数据类型的泛型函数。其语法通常如下所示:

template <typename T> // 或 template <class T>
return_type function_name(parameter_list) {
// 函数体使用类型 T
}

在这里,typename T(或 class T)将 T 声明为模板参数,一个类型的占位符。你可以在函数签名和函数体中使用 T,就好像它是一个特定的类型一样。

示例:一个用于查找两个值中最大值的函数模板。

#include <iostream>
#include <string>
// 定义一个名为 'maximum' 的函数模板
// 它接受两个类型为 T 的常量引用,并返回一个类型为 T 的常量引用
template <typename T>
const T& maximum(const T& a, const T& b) {
// 假设类型 T 支持小于运算符 (<)
return (a < b) ? b : a;
}
int main() {
int i = 39;
int j = 20;
// 编译器生成 maximum<int>(const int&, const int&)
std::cout << "Max(i, j): " << maximum(i, j) << std::endl;
double f1 = 13.5;
double f2 = 20.7;
// 编译器生成 maximum<double>(const double&, const double&)
std::cout << "Max(f1, f2): " << maximum(f1, f2) << std::endl;
std::string s1 = "Hello";
std::string s2 = "World";
// 编译器生成 maximum<std::string>(const std::string&, const std::string&)
std::cout << "Max(s1, s2): " << maximum(s1, s2) << std::endl;
// 你也可以显式指定类型:
// std::cout << "Max(i, j): " << maximum<int>(i, j) << std::endl;
return 0;
}

输出:

Max(i, j): 39
Max(f1, f2): 20.7
Max(s1, s2): World

编译器从传递给 maximum 的参数推断出类型 T。对参数使用 const T& 是高效的,因为它避免了复制潜在的大对象。

类模板允许你定义泛型类。其语法类似于函数模板:

template <typename T1, typename T2, ...> // 或 class T1, class T2, ...
class ClassName {
// 使用 T1, T2 等类型的类成员
private:
T1 member1;
T2 member2;
public:
ClassName(T1 val1, T2 val2);
void someMethod(T1 arg);
// ...
};

在这里,T1、T2 等是占位符类型,在创建类对象时指定。

示例:一个泛型 Stack 类模板。

#include <iostream>
#include <vector>
#include <stdexcept> // 用于 std::out_of_range
#include <string>
#include <utility> // 用于 std::move
template <typename T>
class Stack {
private:
std::vector<T> elems; // 内部存储使用 std::vector
public:
// 通过复制压入元素
void push(const T& elem) {
elems.push_back(elem);
}
// 通过移动压入元素 (C++11)
void push(T&& elem) {
elems.push_back(std::move(elem)); // 对于可移动类型高效
}
// 弹出(移除)顶部元素
void pop() {
if (empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back();
}
// 返回顶部元素的常量引用
const T& top() const {
if (empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back();
}
// 返回顶部元素的可变引用
T& top() {
if (empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back();
}
// 检查栈是否为空
[[nodiscard]] bool empty() const noexcept { // C++11 noexcept, C++17 nodiscard
return elems.empty();
}
// 获取栈当前大小
[[nodiscard]] size_t size() const noexcept {
return elems.size();
}
};
int main() {
try {
Stack<int> intStack; // 整数栈
Stack<std::string> stringStack; // 字符串栈
// 操作整数栈
std::cout << "Pushing 7 onto intStack..." << std::endl;
intStack.push(7);
std::cout << "Top element: " << intStack.top() << std::endl;
std::cout << "Pushing 15 onto intStack..." << std::endl;
intStack.push(15);
std::cout << "Top element: " << intStack.top() << std::endl;
std::cout << "Popping intStack..." << std::endl;
intStack.pop();
std::cout << "Top element: " << intStack.top() << std::endl;
// 操作字符串栈
std::cout << "\nPushing \"hello\" onto stringStack..." << std::endl;
stringStack.push("hello");
std::cout << "Top element: " << stringStack.top() << std::endl;
std::string world = "world";
std::cout << "Pushing \"world\" onto stringStack..." << std::endl;
stringStack.push(std::move(world)); // 移动语义示例
std::cout << "Top element: " << stringStack.top() << std::endl;
std::cout << "Is 'world' string empty now? " << (world.empty() ? "Yes" : "No") << std::endl;
std::cout << "Popping stringStack twice..." << std::endl;
stringStack.pop();
stringStack.pop();
// 这将抛出异常
std::cout << "Attempting to pop empty stringStack..." << std::endl;
stringStack.pop();
} catch (const std::out_of_range& ex) {
std::cerr << "\nCaught Exception: " << ex.what() << std::endl;
return 1; // 指示错误
} catch (const std::exception& ex) {
std::cerr << "\nCaught general exception: " << ex.what() << std::endl;
return 1;
}
return 0;
}

示例输出:

Pushing 7 onto intStack...
Top element: 7
Pushing 15 onto intStack...
Top element: 15
Popping intStack...
Top element: 7
Pushing "hello" onto stringStack...
Top element: hello
Pushing "world" onto stringStack...
Top element: world
Is 'world' string empty now? Yes
Popping stringStack twice...
Attempting to pop empty stringStack...
Caught Exception: Stack<>::pop(): empty stack

注意:当在类定义之外定义类模板的成员函数时,你需要重复模板声明,并使用作用域解析运算符和模板参数(例如,template <typename T> void Stack<T>::pop() { ... })。

现代 C++ 引入了强大的模板特性,例如:

  • 变长参数模板(C++11): 允许模板接受可变数量的参数。
  • 折叠表达式(C++17): 提供了一种简洁的语法,用于将运算符应用于参数包(来自变长参数模板)的所有元素。
  • 类模板参数推导 (CTAD)(C++17): 在许多情况下,允许编译器从构造函数参数推导类模板的模板参数(例如,写 std::pair p(1, "hello"); 而不是 std::pair<int, const char*> p(1, "hello);`)。
  • Concepts(C++20): 允许指定对模板参数的约束,改善错误消息,并基于要求实现模板重载。