【c++面向对象编程】第41篇:函数模板与类模板:泛型编程的基石

目录

一、为什么需要模板?

二、函数模板

基本语法

多个类型参数

函数模板的重载

三、类模板

基本语法

成员函数类外定义

多个模板参数

[四、实例化:隐式 vs 显式](#四、实例化:隐式 vs 显式)

隐式实例化(最常见)

显式实例化

[隐式 vs 显式对比](#隐式 vs 显式对比)

五、模板与继承

模板类作为基类

继承模板参数

六、模板与友元

模板类的友元函数

所有实例共享的友元

七、完整例子:泛型动态数组

八、常见错误

[1. 模板定义与实现分离](#1. 模板定义与实现分离)

[2. 忘记 template 关键字](#2. 忘记 template 关键字)

[3. 模板参数名冲突](#3. 模板参数名冲突)

[4. 依赖名称需要 typename](#4. 依赖名称需要 typename)

九、这一篇的收获


一、为什么需要模板?

写一个交换两个整数的函数:

cpp

复制代码
void swapInt(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

如果要交换 doublestringchar......你得写 N 个几乎一样的函数:

cpp

复制代码
void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; }
void swapString(string& a, string& b) { string temp = a; a = b; b = temp; }
// 无穷无尽...

模板的解决方案:写一次,适用于所有类型。

cpp

复制代码
template <typename T>
void swapValues(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// 使用
int x = 5, y = 10;
swapValues(x, y);  // T 被推导为 int

double d1 = 1.2, d2 = 3.4;
swapValues(d1, d2);  // T 被推导为 double

二、函数模板

基本语法

cpp

复制代码
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 使用
int m1 = max(3, 5);           // T 推导为 int
double m2 = max(3.14, 2.71);  // T 推导为 double
// max(3, 4.5);               // ❌ 错误:T 推导冲突(int vs double)

多个类型参数

cpp

复制代码
template <typename T1, typename T2>
T1 convert(const T2& val) {
    return static_cast<T1>(val);
}

int i = convert<int>(3.14);  // 显式指定 T1,T2 推导为 double

函数模板的重载

cpp

复制代码
template <typename T>
void print(T value) {
    cout << "通用版本: " << value << endl;
}

void print(int value) {
    cout << "int 特化: " << value << endl;
}

int main() {
    print(42);      // 调用普通函数(优先)
    print(3.14);    // 调用模板(无普通函数匹配)
    print<int>(42); // 显式调用模板版本
}

三、类模板

基本语法

cpp

复制代码
template <typename T>
class Stack {
private:
    vector<T> data;
public:
    void push(const T& val) { data.push_back(val); }
    T pop() {
        T top = data.back();
        data.pop_back();
        return top;
    }
    bool empty() const { return data.empty(); }
};

// 使用
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
cout << intStack.pop();  // 20

Stack<string> strStack;
strStack.push("hello");

成员函数类外定义

cpp

复制代码
template <typename T>
class Box {
private:
    T content;
public:
    void set(const T& val);
    T get() const;
};

// 类外定义需要重复 template 声明
template <typename T>
void Box<T>::set(const T& val) {
    content = val;
}

template <typename T>
T Box<T>::get() const {
    return content;
}

多个模板参数

cpp

复制代码
template <typename Key, typename Value>
class Pair {
private:
    Key first;
    Value second;
public:
    Pair(const Key& k, const Value& v) : first(k), second(v) {}
    Key getFirst() const { return first; }
    Value getSecond() const { return second; }
};

Pair<string, int> p("age", 25);

四、实例化:隐式 vs 显式

隐式实例化(最常见)

编译器根据使用方式自动生成代码:

cpp

复制代码
Stack<int> intStack;     // 隐式实例化 Stack<int>
Stack<double> dblStack;  // 隐式实例化 Stack<double>

每次用新类型实例化,编译器都会生成一份完整代码。

显式实例化

提前告诉编译器生成特定类型的代码(通常放在 .cpp 文件中):

cpp

复制代码
// 在 .cpp 文件中
template class Stack<int>;    // 显式实例化 Stack<int>
template class Stack<double>; // 显式实例化 Stack<double>

// 函数模板显式实例化
template void swapValues<int>(int&, int&);

用途

  • 减少编译时间(避免在多处隐式实例化)

  • 将模板实现放在 .cpp 文件中(而不是头文件)

  • 发布库时只提供实例化后的类型

隐式 vs 显式对比

特性 隐式实例化 显式实例化
代码位置 使用处 显式声明处
编译时机 使用时 编译时确定
头文件 需要完整定义 只需声明
适用场景 通用模板 固定类型集合

五、模板与继承

模板类作为基类

cpp

复制代码
template <typename T>
class Base {
protected:
    T value;
public:
    Base(const T& v) : value(v) {}
    virtual void print() const { cout << "Base: " << value << endl; }
};

// 派生类可以是普通类或模板类
class DerivedInt : public Base<int> {
public:
    DerivedInt(int v) : Base<int>(v) {}
    void print() const override { cout << "DerivedInt: " << value << endl; }
};

template <typename T>
class DerivedTemplate : public Base<T> {
public:
    DerivedTemplate(const T& v) : Base<T>(v) {}
    void print() const override { 
        cout << "DerivedTemplate: " << Base<T>::value << endl; 
    }
};

继承模板参数

cpp

复制代码
template <typename T>
class Container : public T {  // 继承自模板参数
    // 这是 Mixin 模式
};

六、模板与友元

友元函数/类也可以与模板配合。

模板类的友元函数

cpp

复制代码
template <typename T>
class MyClass {
private:
    T secret;
public:
    MyClass(const T& s) : secret(s) {}
    
    // 所有 MyClass<T> 实例的友元(同类型)
    friend void showSecret(const MyClass<T>& obj) {
        cout << obj.secret << endl;
    }
};

int main() {
    MyClass<int> obj1(42);
    showSecret(obj1);  // 42
    
    MyClass<string> obj2("hello");
    showSecret(obj2);  // hello
    
    // showSecret(obj1) 和 showSecret(obj2) 是不同函数
}

所有实例共享的友元

cpp

复制代码
template <typename T>
class MyClass {
private:
    T secret;
public:
    MyClass(const T& s) : secret(s) {}
    
    // 所有 MyClass<任意类型> 的友元
    template <typename U>
    friend void showAnySecret(const MyClass<U>& obj);
};

template <typename U>
void showAnySecret(const MyClass<U>& obj) {
    cout << obj.secret << endl;
}

七、完整例子:泛型动态数组

cpp

复制代码
#include <iostream>
#include <stdexcept>
using namespace std;

template <typename T>
class DynamicArray {
private:
    T* data;
    size_t size;
    size_t capacity;
    
    void resize(size_t newCapacity) {
        T* newData = new T[newCapacity];
        for (size_t i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        delete[] data;
        data = newData;
        capacity = newCapacity;
    }
    
public:
    DynamicArray() : data(nullptr), size(0), capacity(0) {}
    
    ~DynamicArray() {
        delete[] data;
    }
    
    // 拷贝构造(深拷贝)
    DynamicArray(const DynamicArray& other) 
        : size(other.size), capacity(other.capacity) {
        data = new T[capacity];
        for (size_t i = 0; i < size; i++) {
            data[i] = other.data[i];
        }
    }
    
    // 移动构造
    DynamicArray(DynamicArray&& other) noexcept
        : data(other.data), size(other.size), capacity(other.capacity) {
        other.data = nullptr;
        other.size = 0;
        other.capacity = 0;
    }
    
    void push_back(const T& val) {
        if (size >= capacity) {
            resize(capacity == 0 ? 1 : capacity * 2);
        }
        data[size++] = val;
    }
    
    T pop_back() {
        if (size == 0) {
            throw out_of_range("数组为空");
        }
        return data[--size];
    }
    
    T& operator[](size_t index) {
        if (index >= size) {
            throw out_of_range("索引越界");
        }
        return data[index];
    }
    
    const T& operator[](size_t index) const {
        if (index >= size) {
            throw out_of_range("索引越界");
        }
        return data[index];
    }
    
    size_t getSize() const { return size; }
    bool empty() const { return size == 0; }
    
    // 友元输出运算符(模板类友元)
    template <typename U>
    friend ostream& operator<<(ostream& os, const DynamicArray<U>& arr);
};

template <typename U>
ostream& operator<<(ostream& os, const DynamicArray<U>& arr) {
    os << "[";
    for (size_t i = 0; i < arr.size; i++) {
        os << arr.data[i];
        if (i < arr.size - 1) os << ", ";
    }
    os << "]";
    return os;
}

int main() {
    DynamicArray<int> intArr;
    intArr.push_back(10);
    intArr.push_back(20);
    intArr.push_back(30);
    cout << intArr << endl;
    cout << "pop: " << intArr.pop_back() << endl;
    cout << intArr << endl;
    
    DynamicArray<string> strArr;
    strArr.push_back("C++");
    strArr.push_back("模板");
    cout << strArr << endl;
    
    // 拷贝构造
    DynamicArray<string> strArr2 = strArr;
    cout << "拷贝: " << strArr2 << endl;
    
    return 0;
}

输出:

text

复制代码
[10, 20, 30]
pop: 30
[10, 20]
[C++, 模板]
拷贝: [C++, 模板]

八、常见错误

1. 模板定义与实现分离

cpp

复制代码
// ❌ 错误:将模板实现放在 .cpp 文件
// 编译器在隐式实例化时看不到定义,导致链接错误
// 模板实现通常放在头文件中

2. 忘记 template 关键字

cpp

复制代码
template <typename T>
void MyClass<T>::func() { ... }  // ✅ 需要 template<typename T>

3. 模板参数名冲突

cpp

复制代码
template <typename T>
class MyClass {
    T data;      // 这里的 T 是模板参数
    typedef int T;  // ❌ 错误:T 已被使用
};

4. 依赖名称需要 typename

cpp

复制代码
template <typename T>
void func() {
    T::iterator it;           // ❌ 编译器不知道 iterator 是类型
    typename T::iterator it2; // ✅ 告诉编译器这是一个类型
}

九、这一篇的收获

你现在应该理解:

  • 函数模板template <typename T> 定义,类型自动推导或显式指定

  • 类模板Stack<int> 实例化,成员函数类外定义需重复模板声明

  • 实例化:隐式(使用时)和显式(提前声明)

  • 模板与继承:模板类可作为基类,派生类可以是普通类或模板类

  • 模板与友元:可以声明特定类型的友元,或所有类型的友元

💡 小作业:实现一个 Optional<T> 类模板,类似 std::optional 的子集。支持:hasValue()value()valueOr(default)reset()。测试 intstring、自定义类型。


下一篇预告:第42篇《模板特化与偏特化:为特定类型定制实现》------泛型版本不能满足所有类型怎么办?模板特化为特定类型提供专属实现,偏特化为部分约束类型提供定制。下篇详解。

相关推荐
熊猫_豆豆2 小时前
麦克斯韦方程组(电磁效应Python展示)
开发语言·python·电磁感应·麦克斯韦方程组
SilentSamsara2 小时前
属性查找顺序:实例 → 类 → 父类的完整 MRO
开发语言·python·算法·青少年编程
不知名的老吴2 小时前
浅谈:树形动态规划中的换根技巧
算法·动态规划
一条大祥脚2 小时前
2021-2022 ICPC Southwestern Europe Regional Contest
算法·深度优先·图论
运维行者_2 小时前
云计算连接性与互操作性
服务器·开发语言·网络·web安全·网络基础设施
郝学胜-神的一滴2 小时前
Qt 高级开发 010: 从跨界面传值到自定义信号
开发语言·c++·qt·程序人生·用户界面
社交怪人2 小时前
【浮点数相除的余】信息学奥赛一本通C语言解法(题号1029)
c语言·开发语言
努力弹琴的大风天2 小时前
如何用AI开发matlab/Simulink工具栏模块,实现相关的功能
开发语言·人工智能·matlab
天若有情6732 小时前
自研极简C++软交互事件系统:干掉观察者模式、碾压前端事件机制
c++·观察者模式·交互·事件