模板是 C++ 泛型的基础,也是标准库能同时支持 vector<int>、vector<double>、vector<string> 的原因。数据结构课上你会被要求“用模板实现一个顺序表”,这一章要理解得比别人多一点。
6.1 函数模板
心智模型:模板不是一份“能装任何类型的代码”,而是编译器按类型生成具体代码的模具。
从重复代码到模板
C 语言里只能为每种类型写一份 max 函数;模板让你写一份:template <typename T> T maxOf(T a, T b)。编译器在调用处自动推导类型并实例化:写了 maxOf(3, 5) 就生成 int 版本,写了 maxOf(1.5, 2.5) 就再生成 double 版本。
#include <iostream>
#include <string>
template <typename T>
T maxOf(T a, T b) { return a > b ? a : b; }
int main() {
std::cout << maxOf(3, 5) << '\n'; // 实例化为 maxOf<int>
std::cout << maxOf(1.5, 2.5) << '\n'; // maxOf<double>
std::cout << maxOf(std::string("ab"), std::string("cd"));
}
5
2.5
cd
为什么 C++ 模板比 Java 泛型“真”
Java 的泛型是擦除过的:ArrayList<Integer> 底层是 Object[],每次取值要转换。C++ 的 vector<int> 就是一片连续的 int,没有一层间接。这是算法竞赛和数据结构课偏爱 C++ 的原因之一。
✍️ 本节练习
写模板函数 template <typename T> T maxOf(T a, T b),分别用 int、double、std::string 调用并输出。
输入 无
输出 三行:5、2.5、cd。
(本题无输入)5
2.5
cd💡 提示 std::string 的 > 比较字典序。
✅ 查看参考答案与解析
#include <iostream>
#include <string>
template <typename T>
T maxOf(T a, T b) { return a > b ? a : b; }
int main() {
std::cout << maxOf(3, 5) << '\n';
std::cout << maxOf(1.5, 2.5) << '\n';
std::cout << maxOf(std::string("ab"), std::string("cd")) << '\n';
return 0;
}
解析 同一份模板自动适配三种类型,编译器在调用处实例化出具体版本。
写模板函数 template <typename T> T sumOf(const std::vector<T>& v) 求 vector 元素之和,分别用 vector<int> 和 vector<double> 测试。
输入 无
输出 两行:15、7.5。
(本题无输入)15
7.5💡 提示 遍历用范围 for,累加到 T 类型的变量。
✅ 查看参考答案与解析
#include <iostream>
#include <vector>
template <typename T>
T sumOf(const std::vector<T>& v) {
T sum = T{};
for (const T& x : v) { sum += x; }
return sum;
}
int main() {
std::vector<int> vi{1, 2, 3, 4, 5};
std::vector<double> vd{1.5, 2.5, 3.5};
std::cout << sumOf(vi) << '\n';
std::cout << sumOf(vd) << '\n';
return 0;
}
解析 T{} 得到该类型的零值;const T& 避免拷贝。
6.2 类模板与“必须放头文件”
这是模板最反直觉的规则:普通函数声明放头文件、定义放 .cpp;模板刚好相反。
模板必须写在头文件里
模板只有被实例化时才生成真正的代码,而实例化发生在使用它的那个源文件里——编译器必须能看到完整定义才能生成代码。所以模板类/函数的声明和定义都放头文件。
#include <vector>
template <typename T>
class Stack {
public:
void push(const T& v) { data_.push_back(v); }
void pop() { if (!data_.empty()) { data_.pop_back(); } }
T& top() { return data_.back(); }
bool empty() const { return data_.empty(); }
private:
std::vector<T> data_;
};
int main() { Stack<int> st; st.push(1); }
非类型模板参数
template <typename T, std::size_t N> 里的 N 是编译期整数,直接写在类型里:FixedArray<double, 8> buf;。它让数组长度成为类型的一部分。
✍️ 本节练习
用 std::vector<T> 作底层写模板类 Stack<T>,实现 push、pop、top、empty。main 里用 Stack<int> 压入 1、2,输出栈顶,再 pop 后输出栈顶。
输入 无
输出 两行:2、1。
(本题无输入)2
1💡 提示 pop 前检查 empty()。
✅ 查看参考答案与解析
#include <iostream>
#include <vector>
template <typename T>
class Stack {
public:
void push(const T& v) { data_.push_back(v); }
void pop() { if (!data_.empty()) { data_.pop_back(); } }
T& top() { return data_.back(); }
bool empty() const { return data_.empty(); }
private:
std::vector<T> data_;
};
int main() {
Stack<int> st;
st.push(1);
st.push(2);
std::cout << st.top() << '\n'; // 2
st.pop();
std::cout << st.top() << '\n'; // 1
return 0;
}
解析 类模板必须写出具体类型 Stack<int>;成员的类型依赖 T。
复用上面写好的 Stack 模板类,这次压入两个 std::string("hi"、"yo"),输出栈顶,pop 后再输出。体会“一份模板类,int 和 string 都能用”。
输入 无
输出 两行:yo、hi。
(本题无输入)yo
hi💡 提示 Stack<std::string> ss; 即可,代码其他部分不用改。
✅ 查看参考答案与解析
#include <iostream>
#include <string>
#include <vector>
template <typename T>
class Stack {
public:
void push(const T& v) { data_.push_back(v); }
void pop() { if (!data_.empty()) { data_.pop_back(); } }
T& top() { return data_.back(); }
bool empty() const { return data_.empty(); }
private:
std::vector<T> data_;
};
int main() {
Stack<std::string> ss;
ss.push("hi");
ss.push("yo");
std::cout << ss.top() << '\n'; // yo
ss.pop();
std::cout << ss.top() << '\n'; // hi
return 0;
}
解析 模板类实例化时编译器为 string 生成一份新代码,这就是泛型复用。
本章小结
- 模板=编译期模具:一次编写,按类型实例化生成多份代码。
- 函数模板自动推导类型;类模板必须写出具体类型。
- 模板的完整定义必须放在头文件里(实例化发生在使用处)。
🧩 本章综合练习
这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。
实现模板类 Stack<T>(push / pop / top / empty / size,空栈操作抛异常),再写模板函数 maxOf(const std::vector<T>&)。main 里分别用 int 和 std::string 实例化,验证同一份代码能处理不同类型。
输入 第一行整数 n;第二行 n 个整数;第三行整数 m;第四行 m 个单词。
输出 四行:size=n、int stack: ...、string stack: ...、max=最大值(栈内元素按栈顶到栈底输出)。
3
5 1 9
2
hi yosize=3
int stack: 9 1 5
string stack: yo hi
max=9💡 提示 模板定义要放在调用之前(通常在头文件里);空栈操作抛异常,不要返回垃圾值。
✅ 查看参考答案与解析
#include <cstddef>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
template <typename T>
class Stack {
public:
void push(const T& v) { data_.push_back(v); }
void pop() {
if (data_.empty()) { throw std::runtime_error("pop from empty stack"); }
data_.pop_back();
}
const T& top() const {
if (data_.empty()) { throw std::runtime_error("top of empty stack"); }
return data_.back();
}
bool empty() const { return data_.empty(); }
std::size_t size() const { return data_.size(); }
private:
std::vector<T> data_;
};
template <typename T>
T maxOf(const std::vector<T>& v) {
if (v.empty()) { throw std::runtime_error("empty vector"); }
T best = v[0];
for (const T& x : v) {
if (best < x) { best = x; }
}
return best;
}
int main() {
int n{};
std::cin >> n;
std::vector<int> vi(n);
Stack<int> si;
for (int i = 0; i < n; ++i) {
std::cin >> vi[i];
si.push(vi[i]);
}
std::cout << "size=" << si.size() << '\n';
std::cout << "int stack:";
while (!si.empty()) {
std::cout << ' ' << si.top();
si.pop();
}
std::cout << '\n';
int m{};
std::cin >> m;
Stack<std::string> ss;
for (int i = 0; i < m; ++i) {
std::string s;
std::cin >> s;
ss.push(s);
}
std::cout << "string stack:";
while (!ss.empty()) {
std::cout << ' ' << ss.top();
ss.pop();
}
std::cout << '\n';
std::cout << "max=" << maxOf(vi) << '\n';
return 0;
}
解析 模板让 Stack<int> 和 Stack<std::string> 由编译器各生成一份;这也是标准库 vector 能装任何类型的原因。
模板 · ODR 单一定义规则