🧩 什么是模板?
想象一下:你要写一个交换两个数的函数。
如果用 int:
cpp
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
但如果你还需要交换 double、string甚至自定义类型呢?没有模板的话,你得为每种类型重写一遍。而有了模板,你只需要写一次:
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.typename和class可以互换 :在模板参数列表中,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
一定要多练和多理解哦,如果我的笔记对你有帮助,请留下小小的赞蟹蟹