大家好,这里是彩妙呀~

在我们将stl容器之前,我们先来简单介绍一下我们后续经常使用的一个新对象:模版。
目录
什么是模版
泛型编程
假设,我们有一个类似交换的函数:
cpp
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
使用函数重载虽然可以实现功能,但存在以下局限性:
- 重载函数仅因类型不同而存在,代码复用率较低。每当出现新类型时,都需要手动添加对应的函数;
- 可维护性较差,一旦某个重载函数出错,可能导致所有相关重载均需修改。
那么,能否让编译器根据一个通用代码框架,自动为不同类型生成对应的函数实现呢?
模版类型
模板是C++实现泛型编程的一种关键机制。它使得我们可以编写能够处理不同数据类型的代码,而不必针对每种类型都重写相同的功能。
通过使用模板,我们能够以一种更加抽象和通用的方式设计程序,从而显著提高代码的复用性和灵活性。

函数模板格式
cpp
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
/*
模版参数列表 <class 类型1,class 类型2 .....>
类比|
函数参数列表 <类型 变量1,类型 变量2 ......>
注意的是,两个可以相互使用:template<class T1,typename T2>,但是不建议这么用
*/
//例如
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
注意:
- typename是 用来定义模板参数关键字,也可以使用class**(**切记:不能使用struct代替 class)
- 每一个模版类型 T 只能对应一中类型(如 int,double等),因为编译器是按照咱第一个传参类型来生成对应的函数,所以如果类型不匹配,编译则会出错。
函数模板的原理
函数模版本质上并非是一个函数,而是为了创建所需函数的一个蓝图,这个蓝图可以使编译器用特定的方式来生成特定具体类型的函数:

在编译器的编译阶段,当使用模板函数时,编译器会根据传递的实际参数类型来推断并生成对应类型的函数以供调用。
例如,若使用double类型来调用函数模板,编译器便会通过推断实参类型,将T确定为double类型,进而生成一份专门处理double类型数据的代码。
同样地,对于字符类型,编译器也会进行相应的类型推断和代码生成。
函数模版的实例化
不同类型参数使用函数模版时,被称为函数实例化
而模版参数实例化分为:隐式实例化与显式实例化
cpp
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int result , result2;
// 编译器推导过程:
// 1. 看到 add(5, 10)
// 2. 推导 T = int
// 3. 生成 int add<int>(int, int)
result = add(5, 10);
// 4. 看到 add(3.14, 2.71)
// 5. 推导 T = double
// 6. 生成 double add<double>(double, double)
result2 = add(3.14, 2.71);
return 0;
}
隐式实例化
让编译器按照所传第一个实参,自己去推断函数所使用的类型:
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);
}
但前面我们说过,如果传参的类型不一样,编译器会报错。那么我们该怎么去解决这个问题呢?
有多种解决方式:
设计多个模版来匹对多种类型的参数:
cpp
// 基本形式
template<typename T1,typename T2>
T max(T1 a, T2 b) {
return a > b ? a : b;
}
// 使用
int x = max(3, 5); // T1,T2 推导为 int
double y = max(3.14, 2.71); // T1,T2 推导为 double
int m = max(3 , 2.11) // T1 推导为int , T2推导为double
强转传参函数,从而保持函数类型一致
cpp
template<class T>
void s_swap(T a, T b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int x = 1, y = 2;
double n = 1.1, m = 2.2;
s_swap(x, y);
//强转
s_swap(x, (int)n);
return 0;
}
第三种:使用显式实例化
显示实例化
程序员明确要求编译器生成特定类型的实例。
只需要在使用函数模版时,在中间加一个<类型>就可以:
cpp
template<class T>
void s_swap(T a, T b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int x = 1, y = 2;
double n = 1.1, m = 2.2;
s_swap<int>(m,x);
return 0;
}
有些情况下,必须要使用显式实例化:
cpptemplate<class T , class K> K* fun1(int n) { return new K[n]; } int main() { /*int x = 1, y = 2; double n = 1.1, m = 2.2;*/ fun1(122); //error:没有与参数列表匹配的函数模板"fun1"实例 return 0; }上面代码要构建一个数组,但是函数模版中没有K对应的传参(编译器无法推导K的参数类型),所以这里只能由我们来显式实例化这里的函数fun1.
模板参数的匹配原则
如果同时存在同名的函数与函数模版,且函数模版还可以被实例化。那么,编译器优先会调用同名函数而非使用函数模版来推演函数。
cpp
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
}
类模版
类模板的定义格式
cpp
template<class T1, class T2, ..., class Tn>
//可以使用class 与 typename,本质上没有区别
class 类模板名
{
// 类内成员定义
};
我们先看一个例子:
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;
}
//函数中传参类型基本上用模版T,且尽量使用引用来传值传参
void Push(const T& data)
{
//因为我们的类是类模版创建出来的,所以寻常的relloc之类的扩容方法基本上不适用
//所以我们得自己写方法来进行扩容
//最简单的方法就是另开辟一个空间,大小为现在的2倍,在逐一拷贝一一对应的值(即深拷贝)
//最后别忘了delete原来空间,防止内存泄漏
if (_size == _capacity)
{
//手动异地扩容
T* tem = new T[_capacity * 2];
//拷贝数据
memcpy(tem, _array, _capacity);
//防止内存泄漏,释放原地址
delete _array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = data;
}
~Stack()
{
delete[] _array;
_size = 0;
_capacity = 0;
}
private:
T* _array;
size_t _capacity;
size_t _size;
};
类模板的实例化
类模板的实例化与函数模板的实例化有所不同。
在类模板实例化时,需要紧跟在类模板名称之后使用尖括号"<>",并在其中指定实例化的类型。
类模板的名称本身并不代表一个真正的类,只有经过实例化后所产生的结果才是一个具体的类。
cpp
int main()
{
//注意 :类模版都是显式实例化
Stack<int> st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
Stack<double> st2;
st2.Push(1);
st2.Push(2);
st2.Push(3);
return 0;
}
注意:
模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误(因为前置知识储备不足,所以彩妙模版进阶会详细说明)。
好啦,本篇博客到此就结束啦,下节我们会开启stl之路啦,喜欢文章的小伙伴可以点点赞,关注彩妙,获取更多优质好文吧~
