🧰 如果说函数重载是"同名不同命",那模板就是"同名同命,但能适配所有类型"。你只需要写一套代码,编译器会自动帮你生成 int 版、double 版、string 版……这就是 C++ 模板的"万能模具"魔法。本文从模板基础到实战编程,带你彻底掌握这个泛型编程的核心武器。


📖 文章导航

  • 8.1 模板简介:模板是什么?为什么需要它?
  • 8.2 函数模板:让函数适配任意类型
  • 8.3 类模板:让类适配任意类型
  • 8.4 模板编程:实战——栈类模板 & 链表类模板

8.1 模板简介——从"重复劳动"到"一劳永逸"

1.1 问题:没有模板的世界有多痛苦?

假设你要写一个求最大值的函数:

// int 版本
int maxInt(int a, int b) {
    return (a > b) ? a : b;
}

// double 版本
double maxDouble(double a, double b) {
    return (a > b) ? a : b;
}

// char 版本
char maxChar(char a, char b) {
    return (a > b) ? a : b;
}

// string 版本
string maxString(string a, string b) {
    return (a > b) ? a : b;
}

// 😩 每种类型都要写一遍,逻辑完全相同,只有类型不同!

💡 这就像你要做圆形的饼干、方形的饼干、心形的饼干,每种形状都买一个模具——太浪费了!如果有一个万能模具,换个形状就能用,那该多好?

1.2 解决方案:模板

// 一个模板搞定所有类型!
template <typename T>
T maxValue(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << maxValue(3, 5) << endl;           // int 版,输出 5
    cout << maxValue(3.14, 2.71) << endl;     // double 版,输出 3.14
    cout << maxValue('a', 'z') << endl;       // char 版,输出 z
    cout << maxValue(string("apple"), string("banana")) << endl;  // string 版,输出 banana
}

🎯 一句话理解模板:模板就是"类型的参数化"——把类型当作参数传进去,编译器帮你生成对应类型的代码。


8.2 函数模板——让函数变成"万能钥匙"

2.1 函数模板的定义

// 语法:
// template <typename T>    或    template <class T>
// 返回值类型 函数名(参数列表) { 函数体 }

// typename 和 class 在模板参数中完全等价,用哪个都行
template <typename T>
T maxValue(T a, T b) {
    return (a > b) ? a : b;
}

2.2 函数模板的使用

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

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

int main() {
    // 方式一:自动类型推导(编译器根据参数推导 T 的类型)
    cout << maxValue(3, 5) << endl;        // T 被推导为 int
    cout << maxValue(3.14, 2.71) << endl;  // T 被推导为 double

    // 方式二:显式指定类型
    cout << maxValue<double>(3, 4.5) << endl;  // 显式指定 T 为 double
    // 3 被转为 3.0,结果是 4.5

    // string 也能用!
    cout << maxValue<string>("hello", "world") << endl;  // world

    return 0;
}

2.3 多类型参数的函数模板

// 两个不同的类型参数
template <typename T1, typename T2>
void showPair(T1 a, T2 b) {
    cout << "(" << a << ", " << b << ")" << endl;
}

int main() {
    showPair(1, 3.14);          // (1, 3.14)
    showPair("hello", 42);      // (hello, 42)
    showPair('A', true);        // (A, 1)
    return 0;
}

2.4 函数模板与普通函数的"较量"

// 普通函数
int maxValue(int a, int b) {
    cout << "调用了普通函数" << endl;
    return (a > b) ? a : b;
}

// 函数模板
template <typename T>
T maxValue(T a, T b) {
    cout << "调用了函数模板" << endl;
    return (a > b) ? a : b;
}

int main() {
    maxValue(3, 5);          // 调用了普通函数(普通函数优先匹配)
    maxValue(3.14, 2.71);    // 调用了函数模板(类型不匹配普通函数)
    maxValue<>(3, 5);        // 调用了函数模板(<> 强制调用模板版本)
    maxValue<int>(3, 5);     // 调用了函数模板(显式指定类型)
}

💡 匹配优先级:普通函数 > 模板生成的函数。但加了 <> 就强制走模板。

2.5 函数模板的限制与特化

// 通用模板
template <typename T>
T maxValue(T a, T b) {
    return (a > b) ? a : b;
}

// 针对 const char* 的特化版本(因为默认比较的是指针地址,不是字符串内容!)
template <>
const char* maxValue<const char*>(const char* a, const char* b) {
    return (strcmp(a, b) > 0) ? a : b;
}

int main() {
    // 通用模板:比较的是指针地址(不靠谱)
    const char* s1 = "apple";
    const char* s2 = "banana";
    cout << maxValue(s1, s2) << endl;     // 调用特化版本,正确比较字符串内容

    // 用 string 就没这个问题
    cout << maxValue(string("apple"), string("banana")) << endl;  // banana
}

8.3 类模板——给类也装上"万能适配器"

3.1 类模板的定义

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

// 定义一个类模板
template <typename T>
class Box {
    T content;  // T 类型的成员
public:
    Box(T val) : content(val) {}
    T getContent() const { return content; }
    void setContent(T val) { content = val; }
    void show() const { cout << "盒子里装的是: " << content << endl; }
};

3.2 类模板的实例化

int main() {
    // 实例化:必须显式指定类型(类模板不能自动推导!)
    Box<int> intBox(42);
    intBox.show();  // 盒子里装的是: 42

    Box<double> doubleBox(3.14);
    doubleBox.show();  // 盒子里装的是: 3.14

    Box<string> strBox("Hello Template!");
    strBox.show();  // 盒子里装的是: Hello Template!

    // 修改内容
    intBox.setContent(100);
    cout << intBox.getContent() << endl;  // 100

    // Box<> autoBox(42);  // ❌ 编译错误!类模板必须显式指定类型

    return 0;
}

💡 函数模板 vs 类模板

  • 函数模板:可以根据参数自动推导类型
  • 类模板:必须显式指定类型(C++17 起部分情况可以推导)

3.3 多类型参数的类模板

template <typename K, typename V>
class Pair {
    K key;
    V value;
public:
    Pair(K k, V v) : key(k), value(v) {}
    K getKey() const { return key; }
    V getValue() const { return value; }
    void show() const { cout << key << " => " << value << endl; }
};

int main() {
    Pair<string, int> student("张三", 90);
    student.show();  // 张三 => 90

    Pair<int, double> data(1, 3.14);
    data.show();  // 1 => 3.14

    return 0;
}

3.4 默认模板参数

// 给模板参数设置默认值
template <typename T, typename Container = vector<T>>
class Stack {
    Container data;
public:
    void push(const T& val) { data.push_back(val); }
    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() {
    // 使用默认的 vector<int> 作为底层容器
    Stack<int> s1;
    s1.push(10);
    s1.push(20);
    cout << s1.top() << endl;  // 20

    // 自定义底层容器为 deque<int>
    Stack<int, deque<int>> s2;
    s2.push(30);
    cout << s2.top() << endl;  // 30

    return 0;
}

💡 默认模板参数就像函数的默认参数——不传就用默认值,传了就用你传的。

3.5 类模板的成员函数在类外定义

template <typename T>
class Calculator {
    T value;
public:
    Calculator(T v);
    T getValue() const;
    Calculator<T> add(const Calculator<T>& other) const;
};

// 类外定义时,每个函数前都要加 template <typename T>
template <typename T>
Calculator<T>::Calculator(T v) : value(v) {}

template <typename T>
T Calculator<T>::getValue() const {
    return value;
}

template <typename T>
Calculator<T> Calculator<T>::add(const Calculator<T>& other) const {
    return Calculator<T>(value + other.value);
}

int main() {
    Calculator<int> a(10), b(20);
    Calculator<int> c = a.add(b);
    cout << c.getValue() << endl;  // 30
}

8.4 模板编程实战——造两个经典数据结构

4.1 栈类模板(Stack)

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

template <typename T>
class Stack {
    static const int MAX_SIZE = 100;
    T data[MAX_SIZE];
    int topIndex;

public:
    Stack() : topIndex(-1) {}

    // 入栈
    void push(const T& val) {
        if (topIndex >= MAX_SIZE - 1) {
            throw overflow_error("栈满了!溢出了!");
        }
        data[++topIndex] = val;
    }

    // 出栈
    void pop() {
        if (isEmpty()) {
            throw underflow_error("栈空了!没东西可弹!");
        }
        topIndex--;
    }

    // 查看栈顶
    T top() const {
        if (isEmpty()) {
            throw underflow_error("栈空了!没东西可看!");
        }
        return data[topIndex];
    }

    // 判空
    bool isEmpty() const { return topIndex == -1; }

    // 大小
    int size() const { return topIndex + 1; }
};

int main() {
    // int 栈
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    intStack.push(30);
    cout << "栈顶: " << intStack.top() << endl;  // 30
    cout << "大小: " << intStack.size() << endl;  // 3

    intStack.pop();
    cout << "弹出后栈顶: " << intStack.top() << endl;  // 20

    // string 栈
    Stack<string> strStack;
    strStack.push("Hello");
    strStack.push("World");
    cout << "栈顶: " << strStack.top() << endl;  // World

    // double 栈
    Stack<double> dblStack;
    dblStack.push(3.14);
    dblStack.push(2.71);
    cout << "栈顶: " << dblStack.top() << endl;  // 2.71

    return 0;
}

💡 一个 Stack 模板,三种类型(int/string/double)都能用——这就是模板的威力!

4.2 链表类模板(Linked List)

#include <iostream>
using namespace std;

template <typename T>
class LinkedList {
    // 节点结构
    struct Node {
        T data;
        Node* next;
        Node(const T& val) : data(val), next(nullptr) {}
    };

    Node* head;
    int count;

public:
    LinkedList() : head(nullptr), count(0) {}

    // 析构函数:释放所有节点
    ~LinkedList() {
        Node* current = head;
        while (current) {
            Node* temp = current;
            current = current->next;
            delete temp;
        }
    }

    // 头部插入
    void pushFront(const T& val) {
        Node* newNode = new Node(val);
        newNode->next = head;
        head = newNode;
        count++;
    }

    // 尾部插入
    void pushBack(const T& val) {
        Node* newNode = new Node(val);
        if (!head) {
            head = newNode;
        } else {
            Node* current = head;
            while (current->next) {
                current = current->next;
            }
            current->next = newNode;
        }
        count++;
    }

    // 删除指定值(第一个匹配项)
    bool remove(const T& val) {
        if (!head) return false;

        // 如果要删的是头节点
        if (head->data == val) {
            Node* temp = head;
            head = head->next;
            delete temp;
            count--;
            return true;
        }

        // 查找并删除
        Node* current = head;
        while (current->next && current->next->data != val) {
            current = current->next;
        }

        if (current->next) {
            Node* temp = current->next;
            current->next = temp->next;
            delete temp;
            count--;
            return true;
        }

        return false;
    }

    // 查找
    bool contains(const T& val) const {
        Node* current = head;
        while (current) {
            if (current->data == val) return true;
            current = current->next;
        }
        return false;
    }

    // 大小
    int size() const { return count; }

    // 判空
    bool empty() const { return count == 0; }

    // 遍历打印
    void print() const {
        Node* current = head;
        while (current) {
            cout << current->data;
            if (current->next) cout << " -> ";
            current = current->next;
        }
        cout << " -> NULL" << endl;
    }

    // 用 [] 访问(重载运算符)
    T operator[](int index) const {
        if (index < 0 || index >= count) {
            throw out_of_range("下标越界了!");
        }
        Node* current = head;
        for (int i = 0; i < index; i++) {
            current = current->next;
        }
        return current->data;
    }
};

int main() {
    // ===== int 链表 =====
    cout << "=== int 链表 ===" << endl;
    LinkedList<int> intList;
    intList.pushBack(10);
    intList.pushBack(20);
    intList.pushBack(30);
    intList.pushFront(5);
    intList.print();  // 5 -> 10 -> 20 -> 30 -> NULL

    cout << "第2个元素: " << intList[1] << endl;  // 10
    cout << "包含20? " << (intList.contains(20) ? "是" : "否") << endl;  // 是

    intList.remove(20);
    intList.print();  // 5 -> 10 -> 30 -> NULL

    // ===== string 链表 =====
    cout << "\n=== string 链表 ===" << endl;
    LinkedList<string> strList;
    strList.pushBack("Hello");
    strList.pushBack("C++");
    strList.pushBack("Template");
    strList.pushFront("Hi");
    strList.print();  // Hi -> Hello -> C++ -> Template -> NULL

    strList.remove("Hello");
    strList.print();  // Hi -> C++ -> Template -> NULL

    cout << "大小: " << strList.size() << endl;  // 3

    // ===== double 链表 =====
    cout << "\n=== double 链表 ===" << endl;
    LinkedList<double> dblList;
    dblList.pushBack(3.14);
    dblList.pushBack(2.71);
    dblList.pushBack(1.41);
    dblList.print();  // 3.14 -> 2.71 -> 1.41 -> NULL

    return 0;
}

输出:

=== int 链表 ===
5 -> 10 -> 20 -> 30 -> NULL
第2个元素: 10
包含20? 是
5 -> 10 -> 30 -> NULL

=== string 链表 ===
Hi -> Hello -> C++ -> Template -> NULL
Hi -> C++ -> Template -> NULL
大小: 3

=== double 链表 ===
3.14 -> 2.71 -> 1.41 -> NULL

模板进阶技巧(选读)

类型约束:用 static_assert 限制模板类型

template <typename T>
T safeDivide(T a, T b) {
    // 限制 T 只能是算术类型(int、double 等)
    static_assert(is_arithmetic<T>::value, "T 必须是算术类型!");
    if (b == 0) throw runtime_error("除数不能为零!");
    return a / b;
}

int main() {
    cout << safeDivide(10, 3) << endl;     // 3
    cout << safeDivide(10.0, 3.0) << endl; // 3.33333
    // safeDivide<string>("a", "b");       // ❌ 编译错误:T 必须是算术类型!
}

可变参数模板(C++11)

// 递归终止
void print() {}

// 可变参数模板:支持任意数量的参数
template <typename T, typename... Args>
void print(T first, Args... rest) {
    cout << first;
    if (sizeof...(rest) > 0) cout << ", ";
    print(rest...);  // 递归调用
}

int main() {
    print(1, 3.14, "hello", 'A', true);
    // 输出:1, 3.14, hello, A, 1
}

总结:模板知识全景图

C++ 模板
│
├── 📖 模板简介(8.1)
│   → "类型的参数化",写一次代码适配所有类型
│
├── 🔧 函数模板(8.2)
│   ├── 定义:template <typename T> 返回类型 函数名(参数)
│   ├── 使用:自动推导 / 显式指定
│   ├── 重载:普通函数优先,<> 强制走模板
│   └── 特化:针对特定类型定制版本
│
├── 📦 类模板(8.3)
│   ├── 定义:template <typename T> class 类名
│   ├── 实例化:必须显式指定类型 Box<int>
│   ├── 多参数:template <typename K, typename V>
│   ├── 默认参数:template <typename T = int>
│   └── 类外定义:每个成员函数前都要加 template
│
└── 🏗️ 模板编程实战(8.4)
    ├── 栈类模板:push / pop / top / isEmpty
    └── 链表类模板:pushFront / pushBack / remove / contains / []

🎯 一句话总结:模板就是 C++ 的"万能模具"——你只定义一次"形状",编译器就能帮你做出 int 版、double 版、string 版的任何"饼干"。代码写一遍,类型千变万化,这就是泛型编程的魅力!


如果觉得有帮助,欢迎点赞收藏 👍 有问题可以在评论区讨论!

更多推荐