C++ 模板

模板(template)是 C++ 最强大也最复杂的特性之一。简单说,模板让你写一份"通用代码",适用于任意类型——这就是泛型编程(generic programming)。C++ 标准库(STL)几乎全部由模板构成,template 是 STL 的基石。

一、为什么需要模板?

假设你要写一个 max 函数,接受两个数返回较大值。不用模板,你得为每种类型写一份:

函数重载缓解了"代码使用"的复杂度,但实现仍是重复的。模板让你只写一份

二、函数模板

#include <iostream>
using namespace std;

// 没有模板:针对每种类型都写一份
// int maxx(int a, int b) { return a > b ? a : b; }
// double maxx(double a, double b) { return a > b ? a : b; }
// string maxx(string a, string b) { return a > b ? a : b; }

// 函数模板:用一个虚拟类型 T 代替具体类型
template <typename T>
T maxx(const T& a, const T& b) {
    return a > b ? a : b;
}

int main() {
    cout << maxx(3, 5) << endl;              // 5(T=int)
    cout << maxx(3.14, 2.71) << endl;        // 3.14(T=double)
    cout << maxx(string("apple"), string("banana")) << endl; // banana(T=string)
    return 0;
}

语法解释:

三、类模板

类模板让你定义通用容器和工具类。STL 的 vector、map 全是类模板:

#include <iostream>
#include <vector>
using namespace std;

// 类模板:用类型参数 T 定义通用容器/类
template <typename T>
class Stack {
private:
    vector<T> data;
public:
    void push(const T& x) { data.push_back(x); }
    void pop() { data.pop_back(); }
    T top() const { return data.back(); }
    bool empty() const { return data.empty(); }
    size_t size() const { return data.size(); }
};

int main() {
    Stack<int> si;          // 显式指定 T=int
    si.push(1);
    si.push(2);
    cout << si.top() << endl;   // 2

    Stack<string> ss;       // T=string
    ss.push("hello");
    ss.push("world");
    cout << ss.top() << endl;   // world

    // C++17 支持类模板参数推导(CTAD)
    Stack sd = Stack<double>{};   // 之前必须写 <double>
    return 0;
}

注意:

四、多个模板参数与非类型参数

#include <iostream>
using namespace std;

// 多个模板参数
template <typename K, typename V>
struct Pair {
    K key;
    V value;
};

// 模板参数可以有默认值
template <typename T, int N>
class Array {
    T data[N];
public:
    T& operator[](int i) { return data[i]; }
    int size() { return N; }
};

int main() {
    Pair<string, int> p{"age", 25};
    cout << p.key << " = " << p.value << endl;   // age = 25

    // 非类型模板参数(必须是编译期常量)
    Array<double, 5> arr;
    for (int i = 0; i < arr.size(); i++) arr[i] = i * 0.5;
    cout << arr[3] << endl;   // 1.5
    return 0;
}

模板参数有两大类:

五、模板参数推导与 auto 返回

#include <iostream>
using namespace std;

template <typename T>
void print(T x) {
    cout << x << endl;
}

template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {   // C++11 尾随返回类型
    return a + b;
}

int main() {
    print(42);          // T=int(编译器自动推导)
    print("hello");     // T=const char*
    print(3.14);        // T=double

    cout << add(1, 2.5) << endl;     // 3.5(double)
    cout << add(string("a"), string("b")) << endl;  // ab

    // 显式指定模板参数(通常不必要,编译器能推导)
    print<int>(100);

    // C++20 引入 concepts 可以约束模板参数
    return 0;
}

六、模板特化

有时候某个特定类型需要特殊处理。模板特化(specialization)让你为特定类型提供专门实现:

#include <iostream>
using namespace std;

template <typename T>
T maxx(const T& a, const T& b) {
    return a > b ? a : b;
}

// 全特化:对某个特定类型提供专门实现
template <>
const char* maxx(const char* const& a, const char* const& b) {
    return strcmp(a, b) > 0 ? a : b;
}

// 类模板特化示例
template <typename T>
class TypeName {
public:
    static const char* get() { return "未知类型"; }
};

// 针对 int 的特化
template <>
class TypeName<int> {
public:
    static const char* get() { return "int"; }
};

int main() {
    cout << maxx(3, 5) << endl;   // 5(用通用模板)
    cout << maxx("apple", "banana") << endl;  // banana(用特化版本)

    cout << TypeName<double>::get() << endl;  // 未知类型
    cout << TypeName<int>::get() << endl;     // int
    return 0;
}

七、模板的编译期本质

理解模板的关键:模板是"代码生成"机制。模板本身不编译成代码,只有被"实例化"时编译器才生成具体代码:

// 模板是"编译期"机制:编译器根据调用生成具体代码
// 模板本身不生成代码,只有被"实例化"时才生成

template <typename T>
T add(T a, T b) { return a + b; }

// 调用 add(1, 2):编译器实例化出 int add(int, int)
// 调用 add(1.5, 2.5):编译器实例化出 double add(double, double)
// 这是"代码生成",编译后的二进制里有两份不同的 add

// 模板的代价:
// 1. 编译慢:每个用到的类型都要生成一份代码
// 2. 二进制大:模板代码膨胀(code bloat)
// 3. 错误信息难懂:编译器报错会带一长串模板实例化栈

// 模板的强大:
// 1. 性能:编译期生成,运行时零开销
// 2. 类型安全:每个实例都是强类型
// 3. 复用:写一份代码,适用所有类型(STL 就靠它)

这种"编译期代码生成"带来:

八、模板是 STL 的基石

STL(标准模板库)全部由模板构成:

#include <iostream>
#include <vector>
using namespace std;

// STL 全部由模板构成
// vector<int>、vector<string>、vector<vector<int>> ...

template <typename T>
void printAll(const vector<T>& v) {
    for (const T& x : v) {
        cout << x << " ";
    }
    cout << endl;
}

int main() {
    vector<int> nums = {3, 1, 4, 1, 5};
    printAll(nums);             // 3 1 4 1 5

    vector<string> names = {"Alice", "Bob", "Carol"};
    printAll(names);            // Alice Bob Carol

    // 没有模板,你必须写 printAllInt、printAllString、printAllDouble ...
    // 这就是模板的意义:消除重复
    return 0;
}

没有模板,STL 不可能存在——你不可能为每种类型都写一份 vector、list、map。模板 = 代码复用的终极武器

九、变参模板(C++11)

这是 C++11 引入的高阶特性,接受任意数量、任意类型的参数:

#include <iostream>
using namespace std;

// 变参模板(C++11):接受任意数量、任意类型的参数
// 参数包(parameter pack):typename... Args

// 递归终止:无参数时调这个
void print() {
    cout << endl;
}

// 递归展开:取第一个,剩下的递归处理
template <typename T, typename... Args>
void print(const T& first, const Args&... rest) {
    cout << first << " ";
    print(rest...);   // 把剩下的传给自己
}

int main() {
    print(1, 2.5, "hello", 'c', string("end"));
    // 输出: 1 2.5 hello c end

    // 这种"递归展开"是 C++11/14 的写法
    // C++17 引入了"折叠表达式",更简洁
    return 0;
}

// 变参模板是现代 C++ 最重要的特性之一
// std::make_unique、std::tuple、fmt::format 都靠它实现

变参模板的实际用途:

新手不用精通变参模板,知道它存在、能看懂 STL 实现就够了。

十、模板进阶(了解)

模板能做的事情远不止泛型。C++ 模板是图灵完备的,可以在编译期做任意计算:

这些属于高级主题,工程师日常用得很少,了解即可。

小结

这一章你认识了 C++ 模板:函数模板、类模板、多参数与默认值、参数推导、特化(全/偏)、变参模板。理解了模板是"编译期代码生成",是 STL 的基石。下一篇我们正式进入 STL——C++ 程序员的"瑞士军刀"。

← 上一篇 C++ 继承

下一篇 C++ STL

✈️💬