← C++ 教材目录(共 13 章)

第 6 章 模板与泛型编程

red wenzi · 2026-09-15 · 编程语言 · C++ · 📖 预计阅读 24 分钟 · 共 5 道练习
🎯 本章你会学到:函数模板、类模板,以及模板为什么放头文件。建议边读边敲代码,每节的练习先自己做,再展开答案对照。

模板是 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
⚠️ 易错点 typename 和 class 在模板参数里等价。这不是“运行时多态”,是编译期生成代码。

为什么 C++ 模板比 Java 泛型“真”

Java 的泛型是擦除过的:ArrayList<Integer> 底层是 Object[],每次取值要转换。C++ 的 vector<int> 就是一片连续的 int,没有一层间接。这是算法竞赛和数据结构课偏爱 C++ 的原因之一。

⚠️ 易错点 类模板的类型参数不能省略:必须写 std::vector<int>(C++17 起部分场景可推导)。

✍️ 本节练习

6.1.1必做模板求最大值

写模板函数 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;
}

解析 同一份模板自动适配三种类型,编译器在调用处实例化出具体版本。

6.1.2挑战模板求和

写模板函数 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); }
⚠️ 易错点 如果确实想分开,工程做法是 .tpp 文件放定义、头文件末尾 #include 进来;或显式实例化 template class Stack<int>;

非类型模板参数

template <typename T, std::size_t N> 里的 N 是编译期整数,直接写在类型里:FixedArray<double, 8> buf;。它让数组长度成为类型的一部分。

⚠️ 易错点 可变参数模板(typename... Args)和折叠表达式是标准库基础设施,知道存在、能看懂就行,数据结构课不需要自己写。

✍️ 本节练习

6.2.1必做模板栈 Stack

用 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。

6.2.2挑战模板栈适配字符串

复用上面写好的 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 生成一份新代码,这就是泛型复用。

本章小结

🧩 本章综合练习

这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。

6.A综合写一个通用的 Stack<T>:模板类 + 模板函数

实现模板类 Stack<T>(push / pop / top / empty / size,空栈操作抛异常),再写模板函数 maxOf(const std::vector<T>&)。main 里分别用 int 和 std::string 实例化,验证同一份代码能处理不同类型。

输入 第一行整数 n;第二行 n 个整数;第三行整数 m;第四行 m 个单词。

输出 四行:size=nint stack: ...string stack: ...max=最大值(栈内元素按栈顶到栈底输出)。

样例输入
3
5 1 9
2
hi yo
样例输出
size=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 单一定义规则