C++ 模板初阶:从泛型编程、函数模板到类模板,一篇打通基础概念

🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《指针合集》 | 《C语言基础》 | 《数据结构》 | 《机器学习导论》 | 《前端基础》 | 《python基础》 ✨ 数据即知识,压缩即智能
目录
- [C++ 模板初阶:从泛型编程、函数模板到类模板,一篇打通基础概念](#C++ 模板初阶:从泛型编程、函数模板到类模板,一篇打通基础概念)
-
- 一、为什么需要泛型编程?
-
- [1.1 从交换函数开始](#1.1 从交换函数开始)
- [1.2 什么是泛型编程?](#1.2 什么是泛型编程?)
- 二、函数模板
-
- [2.1 什么是函数模板?](#2.1 什么是函数模板?)
- [2.2 函数模板的基本格式](#2.2 函数模板的基本格式)
- [2.3 typename 和 class 有什么区别?](#2.3 typename 和 class 有什么区别?)
- 三、函数模板的原理
-
- [3.1 函数模板不是函数](#3.1 函数模板不是函数)
- [3.2 模板会不会让代码只保留一份?](#3.2 模板会不会让代码只保留一份?)
- C++ 模板属于编译期机制。 !在这里插入图片描述(https://i-blog.csdnimg.cn/direct/0e6a34d1c3244155976b9d51fa9f94ad.png#pic_center))
- 四、函数模板的实例化
-
- [4.1 什么是模板实例化?](#4.1 什么是模板实例化?)
- [4.2 隐式实例化](#4.2 隐式实例化)
- [4.3 一个常见错误:两个参数类型不同](#4.3 一个常见错误:两个参数类型不同)
- [4.4 解决方式一:手动类型转换](#4.4 解决方式一:手动类型转换)
- [4.5 解决方式二:显式实例化](#4.5 解决方式二:显式实例化)
- [4.6 解决方式三:使用多个模板参数](#4.6 解决方式三:使用多个模板参数)
- 五、模板参数匹配原则
-
- [5.1 普通函数和函数模板可以同时存在](#5.1 普通函数和函数模板可以同时存在)
- [5.2 匹配完全相同时,优先调用普通函数](#5.2 匹配完全相同时,优先调用普通函数)
- [5.3 显式指定模板参数时,会调用模板版本](#5.3 显式指定模板参数时,会调用模板版本)
- [5.4 如果模板能提供更好的匹配,会选模板](#5.4 如果模板能提供更好的匹配,会选模板)
- [5.5 模板一般不主动帮你做类型转换](#5.5 模板一般不主动帮你做类型转换)
- 六、函数模板使用时的常见坑
-
- [6.1 模板不是万能替代重载](#6.1 模板不是万能替代重载)
- [6.2 模板要求类型支持相关操作](#6.2 模板要求类型支持相关操作)
- 七、类模板
-
- [7.1 为什么需要类模板?](#7.1 为什么需要类模板?)
- [7.2 类模板的基本格式](#7.2 类模板的基本格式)
- [7.3 类模板不是类](#7.3 类模板不是类)
- 八、类模板成员函数的定义
-
- [8.1 类内定义成员函数](#8.1 类内定义成员函数)
- [8.2 类外定义成员函数](#8.2 类外定义成员函数)
- [8.3 为什么模板不建议声明和定义分离到 .h 和 .cpp?](#8.3 为什么模板不建议声明和定义分离到 .h 和 .cpp?)
- 九、函数模板和类模板的区别
-
- [9.1 实例化方式不同](#9.1 实例化方式不同)
- [9.2 使用场景不同](#9.2 使用场景不同)
- [十、用类模板改造 Stack](#十、用类模板改造 Stack)
-
- [10.1 基础版本](#10.1 基础版本)
- [10.2 这个 Stack 还不完美](#10.2 这个 Stack 还不完美)
- 十一、模板初阶常见错误总结
-
- [11.1 忘记写 template 声明](#11.1 忘记写 template 声明)
- [11.2 把类模板当成类使用](#11.2 把类模板当成类使用)
- [11.3 类外定义成员函数时忘记 Stack<T>](#11.3 类外定义成员函数时忘记 Stack
) - [11.4 误以为模板会自动处理所有类型转换](#11.4 误以为模板会自动处理所有类型转换)
- [11.5 模板声明和定义分离导致链接错误](#11.5 模板声明和定义分离导致链接错误)
- 十二、本文总结
一、为什么需要泛型编程?
1.1 从交换函数开始
假设我们要写一个交换函数。
交换两个 int:
cpp
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
交换两个 double:
cpp
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
交换两个 char:
cpp
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
这几段代码的逻辑完全一致:
- 创建临时变量;
- 保存左值;
- 左值接收右值;
- 右值接收临时变量。
唯一不同的是类型。
如果每遇到一种新类型就写一个新函数,会带来几个问题:
- 代码重复度高;
- 维护成本高;
- 一个地方写错,多个重载版本都可能要改;
- 新类型出现时,还要继续补函数;
- 代码越来越臃肿。
这时候我们自然会想:
能不能写一个"模子",让编译器根据不同类型自动生成对应函数?
这就是模板要解决的问题。
1.2 什么是泛型编程?
泛型编程可以简单理解为:
编写与具体类型无关的通用代码。
所谓"泛型",就是不提前绑定某一个具体类型。
比如交换两个变量这件事,本质上并不关心它们是 int、double,还是 string。
只要这个类型支持:
cpp
T temp = left;
left = right;
right = temp;
那么这套交换逻辑就能成立。
这就是泛型编程的价值:
把"类型变化"交给编译器,把"通用逻辑"保留下来。
在 C++ 中,模板就是泛型编程最基础、最核心的工具。

二、函数模板
2.1 什么是函数模板?
函数模板不是一个普通函数。
它更像是一个"函数生成规则"。
它描述了一类函数的共同逻辑,具体类型在使用时再由编译器确定。
例如:
cpp
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
这里的 T 不是一个真实类型。
它是一个模板参数,可以在调用时被替换成:
intdoublecharstring- 其他自定义类型
比如:
cpp
int a = 1, b = 2;
Swap(a, b);
编译器会根据实参类型推导出:
cpp
T = int
于是生成一个处理 int 的 Swap 函数。
再比如:
cpp
double x = 1.1, y = 2.2;
Swap(x, y);
编译器会推导出:
cpp
T = double
然后生成一个处理 double 的 Swap 函数。
这就是函数模板的基本思想。
2.2 函数模板的基本格式
函数模板的一般格式是:
cpp
template<typename T1, typename T2, ..., typename Tn>
返回值类型 函数名(参数列表)
{
// 函数体
}
最常见的写法是单个模板参数:
cpp
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
如果有多个模板参数,也可以这样写:
cpp
template<typename T1, typename T2>
void Func(T1 x, T2 y)
{
// ...
}
其中:
cpp
template<typename T>
表示声明一个模板参数 T。
后面的函数体里就可以把 T 当成一个类型来使用。
2.3 typename 和 class 有什么区别?
模板参数中,下面两种写法都可以:
cpp
template<typename T>
也可以写成:
cpp
template<class T>
在这里,typename 和 class 都表示:
T 是一个类型参数。
所以这两个函数模板是等价的:
cpp
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
cpp
template<class T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
但是注意:
cpp
template<struct T>
这种写法不可以。
在模板参数声明里,可以用 typename 或 class,不能用 struct 代替。
实际写代码时,很多人更喜欢用 typename,因为它语义更直接:这里需要的是一个类型。
三、函数模板的原理
3.1 函数模板不是函数
函数模板本身不是函数,它是编译器生成函数的模具。
比如:
cpp
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
这段代码本身并不会直接生成某一个具体函数。
只有当你真正调用它时:
cpp
int a = 1, b = 2;
Swap(a, b);
编译器才会根据 a 和 b 的类型推导出:
cpp
T = int
然后生成类似下面这样的函数:
cpp
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
当你写:
cpp
double x = 1.1, y = 2.2;
Swap(x, y);
编译器又会生成一个 double 版本:
cpp
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
所以模板真正做的事情是:
把重复写代码的工作交给编译器。

3.2 模板会不会让代码只保留一份?
从源代码角度看,我们只写了一份模板。
但从编译生成的代码角度看,不同类型可能会生成不同版本。
例如:
cpp
Swap(a, b); // int 版本
Swap(x, y); // double 版本
编译器可能会生成:
cpp
Swap<int>
Swap<double>
也就是说,模板不是运行时才判断类型,而是编译期根据类型生成代码。
这点和很多脚本语言里的"动态类型"不一样。
C++ 模板属于编译期机制。

四、函数模板的实例化
4.1 什么是模板实例化?
使用不同类型调用函数模板时,编译器根据类型生成对应函数版本。
这个过程叫:
函数模板实例化。
例如:
cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
return 0;
}
这里会发生两次实例化:
cpp
Add<int>
Add<double>
也就是编译器会生成两个具体版本。
4.2 隐式实例化
隐式实例化就是:
让编译器根据实参类型自动推导模板参数。
例如:
cpp
Add(a1, a2);
a1 和 a2 都是 int,所以编译器推导:
cpp
T = int
再看:
cpp
Add(d1, d2);
d1 和 d2 都是 double,所以编译器推导:
cpp
T = double
这就是最常见的模板使用方式。

4.3 一个常见错误:两个参数类型不同
看下面代码:
cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a = 10;
double d = 20.0;
Add(a, d);
return 0;
}
这段代码通常不能通过编译。
原因是模板参数列表里只有一个 T。
但是调用时:
cpp
Add(a, d);
第一个参数让编译器推导:
cpp
T = int
第二个参数又让编译器推导:
cpp
T = double
一个 T 不能同时既是 int 又是 double。
所以编译器会报错。

4.4 解决方式一:手动类型转换
可以把其中一个参数转成另一个类型:
cpp
Add(a, (int)d);
这样两个参数都是 int,编译器就能推导:
cpp
T = int
或者:
cpp
Add((double)a, d);
这样两个参数都是 double,编译器就能推导:
cpp
T = double
这种方式简单直接,但需要程序员自己决定转换方向。
4.5 解决方式二:显式实例化
显式实例化就是:
在函数名后面的尖括号中明确指定模板参数类型。
例如:
cpp
Add<int>(a, d);
这表示:
cpp
T = int
然后 d 会尝试转换成 int。
也可以写:
cpp
Add<double>(a, d);
这表示:
cpp
T = double
然后 a 会尝试转换成 double。
显式实例化的好处是:
你明确告诉编译器这次模板参数到底是什么。
4.6 解决方式三:使用多个模板参数
如果你希望两个参数本来就可以是不同类型,可以把函数模板写成两个模板参数:
cpp
template<class T1, class T2>
auto Add(T1 left, T2 right)
{
return left + right;
}
这样:
cpp
int a = 10;
double d = 20.0;
Add(a, d);
编译器可以推导:
cpp
T1 = int
T2 = double
这种写法更加灵活。
不过这里的返回值类型要注意。
如果返回值写成 T1:
cpp
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
那么 int + double 的结果会被转换成 int 返回,可能丢失小数部分。
现代 C++ 中可以使用 auto 让编译器推导返回类型。
五、模板参数匹配原则
5.1 普通函数和函数模板可以同时存在
C++ 允许普通函数和同名函数模板同时存在。
例如:
cpp
int Add(int left, int right)
{
return left + right;
}
template<class T>
T Add(T left, T right)
{
return left + right;
}
这里既有普通函数:
cpp
int Add(int, int)
也有函数模板:
cpp
template<class T>
T Add(T, T)
它们可以共存。
5.2 匹配完全相同时,优先调用普通函数
如果调用:
cpp
Add(1, 2);
两个实参都是 int。
普通函数:
cpp
int Add(int, int)
可以直接匹配。
模板函数也可以实例化出:
cpp
Add<int>(int, int)
这时编译器通常会优先选择普通函数。
原因也很好理解:
既然已经有现成的精确匹配函数,就没必要再从模板生成一个。

5.3 显式指定模板参数时,会调用模板版本
如果你写:
cpp
Add<int>(1, 2);
这就明确告诉编译器:
我要调用模板实例化出来的
Add<int>版本。
这时就不会调用普通函数。
所以:
cpp
Add(1, 2);
可能调用普通函数。
而:
cpp
Add<int>(1, 2);
会调用模板生成的版本。
5.4 如果模板能提供更好的匹配,会选模板
看下面代码:
cpp
int Add(int left, int right)
{
return left + right;
}
template<class T1, class T2>
auto Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2);
Add(1, 2.0);
}
对于:
cpp
Add(1, 2);
普通函数完全匹配,所以调用普通函数。
对于:
cpp
Add(1, 2.0);
普通函数需要把 2.0 从 double 转成 int。
而函数模板可以生成:
cpp
Add<int, double>
两个参数都能更准确匹配。
所以这时模板版本反而可能更合适。
简单记:
普通函数优先,但不是永远优先。谁匹配得更好,最终就更可能被选择。
5.5 模板一般不主动帮你做类型转换
在普通函数调用中,编译器经常会做隐式类型转换。
比如:
cpp
void Func(double x);
Func(10);
这里 10 可以从 int 转成 double。
但在模板参数推导过程中,编译器通常不会为了推导模板参数而随意做这种转换。
例如:
cpp
template<class T>
T Add(T left, T right);
int a = 10;
double d = 20.0;
Add(a, d);
它不会直接把 a 转成 double,也不会直接把 d 转成 int 来帮你推导。
而是发现一个 T 推导出了两个类型,于是报错。
可以记一句:
普通函数调用更愿意做类型转换,模板参数推导更强调类型匹配。
六、函数模板使用时的常见坑
6.1 模板不是万能替代重载
函数模板可以减少重复代码,但它不是说所有函数重载都不需要了。
比如:
cpp
template<class T>
T Add(T left, T right)
{
return left + right;
}
如果大部分类型都可以用这套逻辑,那么模板很好。
但如果某个类型需要特殊处理,就可能仍然需要重载或特化。
例如字符串拼接、指针比较、自定义对象比较,有时并不适合简单套用通用逻辑。
6.2 模板要求类型支持相关操作
比如:
cpp
template<class T>
T Add(T left, T right)
{
return left + right;
}
这个模板能不能用于某个类型,取决于这个类型是否支持:
cpp
left + right
如果类型不支持 operator+,编译就会失败。
再比如:
cpp
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
这个模板要求类型支持:
- 拷贝构造
- 赋值操作
所以模板代码看起来"通用",但并不是对任何类型都无条件成立。
它依赖类型本身具备相应能力。
七、类模板
7.1 为什么需要类模板?
函数模板解决的是函数逻辑复用问题。
类模板解决的是类结构复用问题。
比如我们写一个栈。
如果只支持 int:
cpp
class StackInt
{
private:
int* _array;
size_t _capacity;
size_t _size;
};
如果再支持 double,可能要写:
cpp
class StackDouble
{
private:
double* _array;
size_t _capacity;
size_t _size;
};
如果还要支持 char、string、自定义类型呢?
显然不能每个类型都写一个栈。
因为这些栈的逻辑几乎一样:
- 入栈
- 出栈
- 扩容
- 判空
- 获取栈顶
真正变化的只是元素类型。
这时候就可以使用类模板。
7.2 类模板的基本格式
类模板的一般格式是:
cpp
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
最常见的是单模板参数:
cpp
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
private:
T* _array;
size_t _capacity;
size_t _size;
};
这里的 T 表示栈中元素的类型。
如果以后写:
cpp
Stack<int> st1;
那么 T 就是 int。
如果写:
cpp
Stack<double> st2;
那么 T 就是 double。
7.3 类模板不是类
类模板不是具体的类,类模板实例化之后才是真正的类。
例如:
cpp
template<typename T>
class Stack
{
// ...
};
这里的 Stack 只是类模板名。
它不是一个完整类型。
不能直接写:
cpp
Stack st;
必须指定模板参数:
cpp
Stack<int> st1;
Stack<double> st2;
其中:
cpp
Stack<int>
才是真正的类型。
cpp
Stack<double>
也是一个真正的类型。
它们是由同一个类模板生成的两个不同类型。

八、类模板成员函数的定义
8.1 类内定义成员函数
最简单的写法是直接在类模板内部定义成员函数:
cpp
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data)
{
_array[_size] = data;
++_size;
}
private:
T* _array;
size_t _capacity;
size_t _size;
};
这种写法适合初学阶段理解。
8.2 类外定义成员函数
如果成员函数在类外定义,写法要完整一些。
先在类中声明:
cpp
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4);
void Push(const T& data);
private:
T* _array;
size_t _capacity;
size_t _size;
};
类外定义构造函数:
cpp
template<typename T>
Stack<T>::Stack(size_t capacity)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
类外定义 Push:
cpp
template<typename T>
void Stack<T>::Push(const T& data)
{
_array[_size] = data;
++_size;
}
注意这里有两个关键点。
第一,类外定义时前面还要写:
cpp
template<typename T>
第二,类名位置要写:
cpp
Stack<T>::Push
不能只写:
cpp
Stack::Push
因为 Stack 不是具体类型,Stack<T> 才表示模板参数为 T 的类模板实例。
8.3 为什么模板不建议声明和定义分离到 .h 和 .cpp?
普通类通常可以这样组织:
cpp
Stack.h // 声明
Stack.cpp // 定义
但是类模板不建议简单这样拆。
原因是:
模板只有在实例化时,编译器才会根据具体类型生成代码。
如果模板定义放在 .cpp 文件里,而另一个 .cpp 文件只看到了 .h 里的声明,编译器在实例化时可能看不到模板函数的完整定义。
结果就可能出现链接错误。
所以模板代码通常有两种处理方式:
第一,把模板声明和定义都放在头文件中。
这是最常见、最简单的方式。
第二,使用显式实例化。
比如你明确只支持:
cpp
Stack<int>
Stack<double>
可以在 .cpp 中显式实例化对应版本。
不过初学阶段更推荐第一种:
模板声明和定义放在同一个头文件中。

九、函数模板和类模板的区别
9.1 实例化方式不同
函数模板通常可以依靠实参自动推导类型:
cpp
Swap(a, b);
Add(1, 2);
编译器能根据函数实参推导模板参数。
但类模板通常需要显式写出类型:
cpp
Stack<int> st1;
Stack<double> st2;
因为创建对象时,编译器不能总是从上下文中准确推导出你想要的模板参数。
所以初学阶段可以记:
函数模板常常可以自动推导,类模板通常要在类名后面写
<类型>。
9.2 使用场景不同
函数模板主要用于:
- 通用函数逻辑
- 交换
- 比较
- 查找
- 加法
- 打印
- 工具函数
类模板主要用于:
- 通用数据结构
- 栈
- 队列
- 顺序表
- 链表
- 容器
- 智能指针
- 迭代器
比如标准库中的很多容器,本质上就是类模板:
cpp
vector<int>
vector<double>
list<string>
map<string, int>
这些写法的本质都是:
用同一个类模板,实例化出不同类型的容器。
十、用类模板改造 Stack
10.1 基础版本
下面写一个简单的类模板版本 Stack:
cpp
#include <iostream>
using namespace std;
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
~Stack()
{
delete[] _array;
_array = nullptr;
_capacity = 0;
_size = 0;
}
void Push(const T& data)
{
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
T* tmp = new T[newCapacity];
for (size_t i = 0; i < _size; ++i)
{
tmp[i] = _array[i];
}
delete[] _array;
_array = tmp;
_capacity = newCapacity;
}
_array[_size] = data;
++_size;
}
void Pop()
{
if (_size > 0)
{
--_size;
}
}
const T& Top() const
{
return _array[_size - 1];
}
bool Empty() const
{
return _size == 0;
}
size_t Size() const
{
return _size;
}
private:
T* _array;
size_t _capacity;
size_t _size;
};
使用时:
cpp
int main()
{
Stack<int> st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
while (!st1.Empty())
{
cout << st1.Top() << endl;
st1.Pop();
}
Stack<double> st2;
st2.Push(1.1);
st2.Push(2.2);
while (!st2.Empty())
{
cout << st2.Top() << endl;
st2.Pop();
}
return 0;
}
这就是类模板的价值。
我们只写了一份 Stack,但它可以支持:
cpp
Stack<int>
Stack<double>
Stack<char>
Stack<string>
甚至支持自定义类型。
10.2 这个 Stack 还不完美
上面的版本适合理解类模板,但它还是有一些问题的。
例如:
Top()没有处理空栈情况;- 没有写拷贝构造;
- 没有写赋值运算符重载;
- 扩容时使用赋值,要求
T支持赋值; - 对异常安全考虑不够;
- 更推荐实际使用标准库
std::vector或std::stack。
但作为模板初阶案例,它已经能说明核心思想:
数据结构的逻辑不变,元素类型交给模板参数控制。
十一、模板初阶常见错误总结
11.1 忘记写 template 声明
错误:
cpp
typename T>
void Swap(T& left, T& right)
{
}
正确:
cpp
template<typename T>
void Swap(T& left, T& right)
{
}
11.2 把类模板当成类使用
错误:
cpp
Stack st;
正确:
cpp
Stack<int> st;
因为 Stack 是类模板名,不是具体类型。
11.3 类外定义成员函数时忘记 Stack
错误:
cpp
template<typename T>
void Stack::Push(const T& data)
{
}
正确:
cpp
template<typename T>
void Stack<T>::Push(const T& data)
{
}
11.4 误以为模板会自动处理所有类型转换
错误理解:
cpp
Add(a, d);
其中 a 是 int,d 是 double,编译器会自动统一类型。
实际情况是:
如果模板只有一个 T,编译器可能无法推导出唯一类型。
可以改成:
cpp
Add<int>(a, d);
或者:
cpp
Add<double>(a, d);
或者把模板改成:
cpp
template<class T1, class T2>
auto Add(T1 left, T2 right)
{
return left + right;
}
11.5 模板声明和定义分离导致链接错误
错误做法:
cpp
// Stack.h
template<typename T>
class Stack
{
public:
void Push(const T& data);
};
cpp
// Stack.cpp
template<typename T>
void Stack<T>::Push(const T& data)
{
}
然后在其他文件里使用:
cpp
Stack<int> st;
st.Push(1);
可能出现链接错误。
初学阶段建议直接把模板定义写在头文件里。
十二、本文总结
本文主要讲了 C++ 模板初阶中的几个核心知识点。
泛型编程:
- 编写与具体类型无关的通用代码;
- 是代码复用的重要方式;
- 模板是 C++ 泛型编程的基础。
函数模板:
- 表示一族函数;
- 本身不是具体函数;
- 编译器根据实参类型生成对应函数版本;
- 可以隐式实例化,也可以显式实例化。
模板参数推导:
- 编译器根据实参推导模板参数;
- 一个模板参数不能同时推导成两个不同类型;
- 普通函数可以进行自动类型转换,模板推导更强调类型匹配。
模板匹配原则:
- 普通函数和函数模板可以同名共存;
- 精确匹配时通常优先普通函数;
- 如果模板能生成更好匹配的版本,也可能选择模板;
- 显式写
<类型>可以指定调用模板版本。
类模板:
- 用于生成一族类;
- 类模板本身不是具体类型;
Stack<int>、Stack<double>才是真正类型;- 类外定义成员函数时要写
Stack<T>::函数名; - 模板声明和定义通常放在头文件中。