这一篇我来简单介绍一下一个实用的东西——模板
模板是泛型编程的关键,泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
举例子,我们要实现不同类型的交换函数
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;
}如图,通过函数重载实现了double类型,char类型,int类型的交换函数。但这存在几个缺点:
1.仅仅只是类型不同,导致代码复用率很低,要是再多加几个新类型的交换函数,还得再重写。
2.代码的可维护性低,一个出错可能导致所有重载都得修改
那么,能否告诉编译器一个模板,让编译器根据不同类型,利用模板生成对应类型的代码呢?
所以祖师爷就想到了这点,于是创造了模板。
模板又分为两个:一个是函数模板,一个是类模板

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型,通过他的函数模板生产特定的函数。
函数模板的格式:
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}如图,把参数类型都替换成T,然后在原来函数的头上加一行:template<typename T>
也可以是多个参数:template<typename T1,typename T2,.... ,typename Tn>
typename T是会替换掉函数内的参数T,并且typename等效于class,也就是你能这么写:
template<class T1,typename T2..... > class和template随便替换。
(小知识:为什么要用T代表类型?其实T是type的简写,然后类型都喜欢用大写,所以用T。多个参数就可以用T1,T2等来表达,如果没有具体意思一般就是用T,当然你要用X也行,随便。名字随便取的,但最好有代表意思,提高代码规范性。)
需要注意,函数模板本身只是一个蓝图,是编译器生成对应函数的模具。

如图。编译器在编译阶段,会使用模板。编译器需要根据传入的参数类型来推演生成对应类型的函数,比如用double类型使用函数模板时,编译器通过对实参的推演,将参数T确定为double类型,然后产生一份专门处理double类型的函数,对于别的也一样。
用不同类型的参数使用函数模板时,称为函数模板的实例化,实例化又分为:显式实例化 隐式实例化
隐式实例化:让编译器根据实参推模板参数的实际类型
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); //根据int类型推演
Add(d1, d2); //根据double类型推演
return 0;
}如图,会根据实参类型进行推演。 但必须类型统一,不然就会编译报错 ,如:Add(a1,d2)
一个int , 一个double ,编译器不知道如何选择。此时有两种选择,一是强制转换,二是显式实例化。
强制转换:Add(a1,(int)d2),或者 Add((double)a1,d2)
显式实例化:在函数名后的< >中指定模板参数的实际类型
int main()
{
int a = 10;
double b = 20.0;
// 显式实例化
Add<int>(a, b);
return 0;
}如图,这就是显式实例化,在函数名后面<>指明类型,图中是int,也可以是double 如果类型不匹配,编译器会尝试进行隐式类型转
换,如果无法转换成功,编译器将会报错。
第一部分:
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
这句话可能比较难懂,首先模板函数(突出函数)就是由函数模板(突出模板)这个模板生成的函数,非模板函数就是 不是由函数模
板生成的函数,自己实现的,真正的函数。
举个例子帮助大家理解:
// 专门处理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版本
}如图。第一个函数就是,非模板函数,专门写出来的,为int服务的函数,再下面一个就是通用加法函数,也就是函数模板。
test中,Add(1,2) ,编译器会优先匹配 已存在的 非模板 int 加法函数,不进行函数模板实例化。根本原因在于:非模板函数本身就具有更高的匹配优先级
第二部分:
对于非模板函数 与 同名函数模板,如果其他条件相同,在调用时会优先调用非模板函数,不会使用函数模板进行实例化。
如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
同样我举个例子:
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}如图。第一个是专门处理int的加法函数,第二个是函数模板,是两个不同参数的模板
test中,Add(1,2) ,两个参数都是int,有直接匹配的非模板函数,所以不需要使用函数模板。 但是 Add(1,2.0),一个是int,一个
是double,用函数模板可以生成更匹配的版本(类型匹配,一个int,一个double),所以编译器会使用函数模板进行实例化。
小知识:模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
类模板与函数模板类似,我们直接展开讲,首先是类模板的定义格式:
// 类模版
template<typename T> //定义模板
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity]; //改为开辟T类型的空间,表示存放T类型
_capacity = capacity;
_size = 0;
}
private:
T* _array; //改为了T
size_t _capacity;
size_t _size;
};(首先知道:模版不建议声明和定义分离到两个文件.h 和.cpp,会出现链接错误)
如图,这就是类模板的应用。大体上与函数模板相同,把需要更换的类型换成T类型,就行了。
但由于 传实参 利用构造函数创建 类对象 的时候,参数可能不传T类型(如图构造函数是传int,没传T,因为没传T,所以编译器不知
道你的类需要什么类型的T,编译器便无法利用模板推演),这样就无法创建对象。所以利用 类模板 实例化 模板类 的时候,
必须显式实例化:
int main()
{
Stack<int> st1; //显式实例化,存int
Stack<double> st2; //显式实例化,存double
return 0;
}为什么不用typedef int T ,这样不是也能代替模板吗?
其实不能代替。T为int后,double怎么办?其他类型比如char又该怎么办? 因为类不能重载,所以将Stack类 typedef int T 后,就不能像图中实例化double类型了,除非拷贝一份stack类,然后改名stack1或者别的,重新创建一个类,这样代码重复率很高,不划算。 浓缩理解:typedef int T 是类型别名,只能固定一种类型(如 int)。
原本类的定义中,类名是类型,在 模板类 / 类模板 中就不是了。因为Stack<int> 和Stack<double> 本质上不是同一个类型。(因为存储的数据类型就不一样)
模板类 / 类模板 中,只有加上模板参数,才是类型。
在这里,Stack 本身不是类型,只有当它绑定具体模板参数(如 Stack<int>、Stack<double>)后,才成为真正的 “类型”。
//类内声明定义不分离的Push函数:
template<class T>
void Push(const T& data)
{
// 扩容
_array[_size] = data;
++_size;
}
private:
//参数
};这是从上面的类模板中摘下来的部分代码(合在一起太长了不方便观看)。如图,类内函数也需要声明模板 template<class T> 。
为什么已经对类class Stack整体声明过模板参数了,到里面的类函数也需要声明模板参数?
因为实际上 类模板 中的函数就是 函数模板 ,而函数模板就需要声明模板参数。 类都是类模板了,那类其中的成员函数当然也是函数模板。
如标题,这是一个模板的语法,就是模板参数可以给缺省值。举例:
template<class T = int>
class A
{
private:
T a1;
T a2;
};
int main()
{
A<> aa1; //全缺省构造 A模板类,不传模板参数默认int
A<double> aa2; //传模板参数了,不使用缺省值
return 0;
}也可以是多个模板参数,多个缺省值:
template<class T1 = int,class T2 = double>
class B
{
private:
T1 b1;
T2 b2;
};
int main()
{
B<> bb1; //全缺省,默认第一个是int,第二个是double
B<double> bb2; //半缺省,T1 变成 double ,T2 维持缺省值
B<double,int> bb3; //T1变double T2变int ,不使用缺省值
return 0;
}和之前学的是一样的,缺省值只能从右往左给。
模板初阶到这里就讲完了,感谢大家支持~后面我会慢慢拓展模板进阶,以及模板的使用,请大家多多点赞!