前言
写 C++ 时,你是不是常为 “int、double 类型要写两套一样逻辑的代码” 而烦?模板就是来解决这个问题的 —— 用泛型编程让一段代码适配多种类型,不用重复造轮子。
📚 C++ 初阶
-【 C++发展史、命名空间、输入输出、缺省参数、函数重载 】
-【 C++引用、内联函数、auto、范围 for、nullptr 】
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

传统编程中,处理不同类型的数据时,通常会使用函数重载,但这种方式存在明显缺陷:
模板本质是一种 “代码蓝图”,它允许我们在编写代码时不指定具体数据类型,而是用占位符(模板参数)替代。编译器会在编译(编译阶段)阶段,根据传入的实参类型或显式指定的类型,自动生成对应类型的具体代码,实现 “一次编写,多类型适配” 的效果。
函数模板的定义需以template关键字开头,后跟模板参数列表,语法格式如下:
template<typename T1, typename T2, ..., typename Tn>
返回值类型 函数名(参数列表)
{
// 函数体(使用模板参数T1、T2...作为类型)
}typename用于声明模板参数类型,也可替换为class,二者在模板参数中完全等价(不能用struct(class和strcut的区别之一));T1、T2),适配多类型参数的函数场景。【示例】:通用交换函数
template<typename T>
void Swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}函数模板本身不会生成可执行代码,编译器在编译时会根据调用方式,推导模板参数类型并生成具体函数:
Swap(1, 2)时,推导T为int,生成Swap<int>函数;Swap(3.14, 5.20)时,推导T为double,生成Swap<double>函数;
函数模板支持两种实例化方式,满足不同使用场景:
隐式实例化:编译器根据实参类型自动推导模板参数,无需手动指定(常用);
template<class T>
T Add(const T& a, const T& b) { return a + b; }
int main()
{
Add(1, 2); // 隐式推导T为int
Add(1.0, 2.0); // 隐式推导T为double
}显式实例化:手动指定模板参数类型,强制编译器生成对应版本,适用于参数类型不一致的场景;
Add<int>(1, 2.5); // 强制T为int,2.5隐式转换为int【错误用法】

【正确用法】
1、显式实例化

2、多参数匹配

当存在非模板函数与模板函数同名时,编译器遵循以下匹配规则:
【示例】:匹配规则演示
// 非模板函数
int Add(int a, int b) { return a + b; }
// 函数模板
template<class T>
T Add(T a, T b) { return a + b; }
int main()
{
Add(1, 2); // 匹配非模板函数
Add(1, 2.0); // 匹配模板,生成Add<int, double>
}类模板用于生成通用类,支持不同类型的数据存储和操作,语法格式如下:
template<class T1, class T2, ..., class Tn>
class 类名
{
// 类成员定义(成员变量、成员函数可使用模板参数)
};【示例】:通用栈类
template<typename T>
class Stack
{
public:
// 构造函数
Stack(size_t capacity = 4)
: _array(new T[capacity])
, _capacity(capacity)
, _size(0) {}
// 成员函数声明
void Push(const T& data);
void Pop();
T Top() const;
private:
T* _array; // 存储数据的数组(类型为模板参数T)
size_t _capacity; // 栈容量
size_t _size; // 栈中元素个数
};类模板的成员函数若在类外定义,需重新声明模板参数列表,并在类名后指定模板参数:
template<class T>
void Stack<T>::Push(const T& data)
{
// 扩容逻辑(简化版)
if (_size >= _capacity)
{
T* temp = new T[_capacity * 2];
memcpy(temp, _array, sizeof(T) * _size);
delete[] _array;
_array = temp;
_capacity *= 2;
}
_array[_size++] = data;
}
template<class T>
void Stack<T>::Pop()
{
if (_size > 0)
{
_size--;
}
}与函数模板不同,类模板(C++17 前)必须显式指定模板参数,编译器无法自动推导类型:
// 显式实例化int类型栈
Stack<int> intStack;
intStack.Push(10);
intStack.Push(20);
// 显式实例化double类型栈
Stack<double> dblStack;
dblStack.Push(3.14);
dblStack.Push(5.20);注意:Stack是类名,不是具体类型,Stack<int>、Stack<double>才是独立的具体类型。
支持默认模板参数:可为模板参数指定默认值,省略部分参数时使用默认值;
template<typename T = int, size_t N = 10>
class Array { /* 实现固定大小数组 */ };
Array<> arr; // 使用默认参数T=int,N=10静态成员独立:每个实例化类型的类拥有独立的静态成员,互不影响;
template<typename T>
class Foo
{
public:
static int count;
};
Foo<int>::count = 0; // 与Foo<double>::count独立
Foo<double>::count = 0;【总结】: 函数模板:支持类型推导,编译器根据传递的参数自动推导模板参数。 类模板:不支持类型推导,必须显式指定模板参数类型,因为类的结构和使用方式更复杂,无法通过简单的参数推导出类型。