---------------------------------------------------------------------------------------------------------------------------------
每日大补鸡汤:把早餐吃好,把身体照顾好,把手里的事做好,把亲近的人待好,你要的一切正在路上。早安!
---------------------------------------------------------------------------------------------------------------------------------
一:泛型编程
先看一例代码,交换两个数据:
#include<iostream>
using namespace std;
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;
}
// .....
int main()
{
int a = 1, b = 2;
swap(a, b);
cout << "a = " << a << " " << "b = " << b << endl;
double ad = 1.1, bd = 2.2;
swap(ad, bd);
cout << "ad = " << ad << " " << "bd = " << bd << endl;
char ac = 'a', bc = 'b';
swap(ac, bc);
cout << "ac = " << ac << " " << "bc = " << bc << endl;
return 0;
}
// 运行结果:
a = 2 b = 1
ad = 2.2 bd = 1.1
ac = b bc = a
我就想要简简单单的交换两个数据,但是这也太麻烦了吧,几种类型就要写几种函数重载的swap交换函数。这实在是太挫了。
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
就如同一个摸具。使得编译器根据“摸具”来生成对应的代码。
如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件 (即生成具体类型的代码)。那就相当的very good了。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
二:函数模板
2.1:什么是函数模板?
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时才被参数化,根据实参类型产生函数的特定类型版本。
2.2:函数模板的格式
template<typename T1, typename T2, ......, typename Tn>
返回值类型 函数名(参数列表) { }
像刚才的swap函数,使用模板就是这样:
#include<iostream>
using namespace std;
template<typename T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
cout << "a = " << a << " " << "b = " << b << endl;
double ad = 1.1, bd = 2.2;
Swap(ad, bd);
cout << "ad = " << ad << " " << "bd = " << bd << endl;
char ac = 'a', bc = 'b';
Swap(ac, bc);
cout << "ac = " << ac << " " << "bc = " << bc << endl;
return 0;
}
// 运行结果:
a = 2 b = 1
ad = 2.2 bd = 1.1
ac = b bc = a
模板参数与函数参数的区别:
- 模板参数定义的是类型(T)
- 函数参数定义的是变量。
注意:typename是用来定义模板参数关键字,也可以使用class
template<class T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
2.3:函数模板的原理
先说明一点,函数模板并不是一个函数,而是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
编译器在编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
2.4:函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
2.4.1:隐式实例化
让编译器根据实参推演模板参数的实际类型
看实例代码:
#include<iostream>
using namespace std;
template<class T>
T ADD(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a = 1, b = 2;
double ad = 1.1, bd = 2.2;
ADD(a, b);
ADD(ad, bd);
return 0;
}
编译器在编译期间,看到该实例化时,自动推演其实参类型,比如ADD(a,b),通过实参a将T推演为int,通过实参b将T推演为int类型。符合要求。
但是ADD(a,ad)呢?符合要求吗?答案是不行的,因为编译器在自动推演实参类型时,通过实参a将T推演为int,通过实参ad将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。那么该如何解决这一问题呢?1.我们自己强制转化;2.使用显示实例化。
我们自己强制转化:ADD(a, (int)ad);
2.4.2:显示实例化
在函数名后的<>中指定模板参数的实际类型
int main()
{
int a = 1;
double ad = 1.1;
cout << ADD<int>(a, ad) << endl;
cout << ADD<double>(a, ad) << endl;
return 0;
}
// 打印运行结果:
2
2.1
2.5:模板参数的匹配
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
发现两个Add(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;
}
int main()
{
Add(10, 20); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(100, 200.0); // 模板函数可以生成更加匹配的版本,编译器生成更加匹配的Add函数
return 0;
}
即:编译器永远选择更匹配更“不费力”的。
三:类模板
3.1:类模板的定义格式
定义格式:
template<class T1, class T2, class T3, ..., class Tn>
class 类模板名
{
// 类内成员定义};
3.2:类模板实例化
例如一个顺序表:
template<class T>
class vector // vector并不是一个具体的类,而是一个“模具”
{
public:
vector(size_t capacity = 10)
:_a(new T[capacity])
,_size(0)
,_capacity(capacity)
{}
void push_back(const T& val){
// ...
}
size_t size() {
return _size;
}
T& operator[](size_t i)
{
return _a[i];
}
~vector()
{
if (_a)
{
delete[] _a;
_a = nullptr;
}
_size = _capacity = 0;
}
private:
T* _a;
size_t _size;
size_t _capacity;
};
int main()
{
// 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
vector<int> vi; // 可以实例化出任意类型
vector<double> vd;
vector<char> vc;
vector<string> vs;
return 0;
}
那么若在类外部定义类内函数呢?(在类内声明,类外部定义)
template<class T>
class vector
{
public:
// ...
void pop_back();
// ...
private:
T* _a;
size_t _size;
size_t _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template<class T>
void vector<T>::pop_back()
{
_size--;
}
四:非类型模板参数
根据“非类型模板参数”这一名字就能知道,编译器是支持非类型形参的。
template<class T, size_t N>
class array
{
// ...
}
即:模板参数分类类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。(这就决定了非类型形参是只支持整形的)
// 非类型模板参数
template<class T,size_t N = 10>
class vector
{
public:
// ...
size_t size() const { return _size; }
bool empty() const { return _size == 0; }
~vector()
{
if (_a)
delete[] _a;
_size = _capacity = 0;
}
private:
T* _a = new T[N];
size_t _size = 0;
size_t _capacity = N;
};
int main()
{
vector<int, 10> vi;
vector<double, 3> vd;
vector<char, 20> vc;
vector<string, 5> vs;
return 0;
}
注意:
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
五:模板的特化
5.1:关于特化
所谓特化,就是指针对某些类型进行特殊化处理。即在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。
不过,在一般的情况下就与普通函数比较,普通函数的代码可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。
5.2:类模板特化
5.2.1:全特化
将模板参数列表中所有的参数都确定化
直接看实例:
#include<iostream>
using namespace std;
template<class T1, class T2>
class Date
{
public:
Date()
{
cout << "Date<T1, T2>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
template<>
class Date<int,double>
{
public:
Date()
{
cout << "Date<int, double>" << endl;
}
};
int main()
{
Date<int, int> d1;
Date<int, double> d2;
return 0;
}
// 运行结果:
Date<T1, T2>
Date<int, double>
下面的Date类叫做上面Date类的特化(特殊化)。
5.2.2:半特化
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本
直接看实例:
#include<iostream>
using namespace std;
template<class T1, class T2>
class Date
{
public:
Date()
{
cout << "Date<T1, T2>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
template<class T1>
class Date<T1,double>
{
public:
Date()
{
cout << "Date<T1, double>" << endl;
}
};
int main()
{
Date<int, int> d1;
Date<char, double> d2;
return 0;
}
// 运行结果:
Date<T1, T2>
Date<T1, double>
即:转化一部分模板部分参数。
六:模板总结
优点:
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
- 增强了代码的灵活性
缺陷:
- 模板会导致代码膨胀问题,也会导致编译时间变长
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误