c++学习笔记分享--template模版

🧩 什么是模板?

想象一下:你要写一个交换两个数的函数。

如果用 int

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

但如果你还需要交换 doublestring甚至自定义类型呢?没有模板的话,你得为每种类型重写一遍。而有了模板,你只需要写一次:

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

这里的 T是一个占位符,代表任意类型。编译器会根据你实际调用时传入的类型,自动生成对应的函数版本。

你可能会有疑问为什么不用auto自动推导呢?

cpp 复制代码
auto add(auto a, auto b) {    // ❌ C++17之前不允许,C++20才支持这种写法
    return a + b;
}

实际上 C++20 支持了上面那种写法,但本质上是语法糖,编译器会把它变成模板:

cpp 复制代码
// C++20 的 auto 参数写法
auto add(auto a, auto b) { return a + b; }

// 编译器实际上把它变成了:
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { return a + b; }

好的所以我们继续讲模版

📚 两种主要模板

1. 函数模板

就像上面的 mySwap,它是一组函数的蓝图。

cpp 复制代码
template <typename T>
T add(T x, T y) {
    return x + y;
}

// 使用时
int main() {
    cout << add(3, 5) << endl;        // T 被推导为 int
    cout << add(2.5, 3.7) << endl;    // T 被推导为 double
    cout << add<string>("Hello ", "World") << endl; // 显式指定 T 为 string
    return 0;
}
2. 类模板

让整个类的成员都能使用通用类型。

注意:对于类模板,必须显式指定类型参数,不能像函数模板那样自动推导。

根本原因:类模板在创建对象时必须知道完整类型

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

// 使用时
Box<int> intBox;  //显示指定类型
intBox.set(42);

Box<string> strBox; //显示指定类型
strBox.set("Hello");

💡 几个关键点

  • 1.typenameclass可以互换 :在模板参数列表中,template <typename T>template <class T>完全等价。很多人习惯用 typename表示普通类型,用 class表示自定义类型,但技术上没区别。

  • 2.模板不生成代码,直到被实例化 :你写了一个模板,编译器不会立刻生成机器码。只有当你真正使用了 Box<int>mySwap(a,b)时,编译器才会根据具体类型"复制"一份代码出来。

  • 3.模板定义通常放在头文件中:因为实例化时需要看到完整的模板定义,所以不能像普通函数那样声明和定义分离(除非你显式实例化)。

  • 4.一个 template 只作用于紧跟着它的那个东西, 那个"东西"可以是函数、类、或者成员函数

1️⃣ 一个 template 对应一个函数 ✅

cpp 复制代码
template <typename T>
T add(T a, T b) {      // 👈 这个 template 只作用于这个 add 函数
    return a + b;
}

// 下面的函数不受影响
int subtract(int a, int b) {  // 普通函数,不是模板
    return a - b;
}

2️⃣ 一个 template 对应一个类 ✅

cpp 复制代码
template <typename T>
class Box {             // 👈 这个 template 作用于整个 Box 类
private:
    T content;
public:
    void set(T val) { content = val; }   // 这些成员函数自动成为模板的一部分
    T get() const { return content; }
};

// 下面的类不受影响
class NotTemplate {     // 普通类
    int x;
};

3️⃣ ❌ 一个 template 不能同时管多个独立的东西

cpp 复制代码
template <typename T>
T add(T a, T b) { return a + b; }   // ✅ 这个 template 只管 add

T multiply(T a, T b) { return a * b; }  // ❌ 编译错误!T 在这里没有定义
  • 5.声明时怎么写模板?

1️⃣ 函数模板的声明和定义

方式一:声明和定义写在一起(最常见,放在头文件)

cpp 复制代码
// 头文件 mylib.h
template <typename T>
T add(T a, T b) {
    return a + b;
}

方式二:声明和定义分离(要小心

cpp 复制代码
// mylib.h - 只声明
template <typename T>
T add(T a, T b);

// mylib.cpp - 定义
template <typename T>
T add(T a, T b) {
    return a + b;
}

⚠️ 但是这样链接会报错! ​ 因为其他.cpp文件看不到模板的定义。解决办法有两个:

  • 把所有代码放头文件里(推荐新手这样做)

  • 显式实例化(进阶技巧)

cpp 复制代码
// mylib.cpp 尾部加上
template int add<int>(int, int);
template double add<double>(double, double);

2️⃣ 类模板的声明

cpp 复制代码
// 声明一个类模板
template <typename T>
class MyArray {
private:
    T* data;
    int size;
public:
    MyArray(int n);          // 构造函数声明
    T& operator[](int i);    // 成员函数声明
};

// 类外定义成员函数时,也要带上 template<...>
template <typename T>
MyArray<T>::MyArray(int n) {
    data = new T[n];
    size = n;
}

template <typename T>
T& MyArray<T>::operator[](int i) {
    return data[i];
}

注意还有一种前向声明

直接看案例:

cpp 复制代码
// -------- 头文件 A.h ----------
// 前向声明
template <typename T>
class Box;

// 声明一个使用 Box 的函数(只需要指针/引用)
template <typename T>
void printBoxSize(const Box<T>& box);  // ✅ 可以,只用引用

// -------- 头文件 B.h ----------
#include "A.h"

// 尝试定义另一个类,内部使用 Box
class Container {
private:
    Box<int>* ptr;          // ✅ 可以,只用指针
    // Box<int> obj;        // ❌ 不能创建对象
    
public:
    void setPtr(Box<int>* p) { ptr = p; }  // ✅ 可以
};

// -------- Box.h ----------
// Box 的完整定义
template <typename T>
class Box {
private:
    T content;
public:
    void set(const T& value) { content = value; }
    T get() const { return content; }
};

// -------- main.cpp ----------
#include "Box.h"     // 需要包含完整定义才能使用成员
#include "A.h"

int main() {
    Box<int> box;           // ✅ 现在可以了
    box.set(42);
    printBoxSize(box);      // ✅ 可以
    return 0;
}

// 函数定义
template <typename T>
void printBoxSize(const Box<T>& box) {
    // 这里需要 Box 的完整定义,因为要调用成员函数
    // 所以这个定义应该放在 Box.h 之后
    std::cout << box.get() << std::endl;
}

· 6.继承时要不要写模板?

情况1:子类继承父类,父类是模板

✅ 正确写法:子类也必须写成模板

cpp 复制代码
template <typename T>
class Base {
protected:
    T value;
};
//注意这里类没有实例化,是定义

// 子类继承时,T 要从子类的模板参数来
template <typename T>
class Derived : public Base<T> {  // 👈 这里写 Base<T>
public:
    void setValue(T v) { value = v; }  // 注意访问方式
};

❌ 错误写法:

cpp 复制代码
template <typename T>
class Derived : public Base {  // ❌ 编译错误!Base 不是完整类型
};

情况2:父类不是模板,子类是模板

这完全没问题:

cpp 复制代码
class Animal {
public:
    virtual void speak() = 0;
};

template <typename T>
class Dog : public Animal {  // ✅ 可以,父类不需要模板
    T data;
    void speak() override { /* ... */ }
};

情况3:继承一个已经实例化的模板类

cpp 复制代码
template <typename T>
class Vector { /* ... */ };

// 继承 Vector<int> 这个具体类型
class IntVector : public Vector<int> {  // ✅ 合法,父类是具体类型
    // ...
};

END

一定要多练和多理解哦,如果我的笔记对你有帮助,请留下小小的赞蟹蟹