了解模板之前我们要先知道什么是泛型编程:泛型编程的代表作品STL是一种高效、泛型、可交互操作的软件组件。STL以迭代器 (Iterators)和容器(Containers)为基础,是一种泛型算法(Generic Algorithms)库。泛型编程让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。泛型即是指具有在多种数据类型上皆可操作的含义,而模板也是泛型编程中的一种典型例子。
函数模板
模板我们生活中也听得不少了,建房时都会有图纸,然后人们可以通过图纸来建房的基本框架。有了基本框架,任何人都可以进行自己喜欢的类型风格来装修。在C++中,模板就像图纸,我们写好了模板,也就有了基本的框架,装修风格就像C++中的类型千变万化,编译器最终会通过类型来推演出我们最终想要的函数或类。
交换函数是我们编程或刷题中最常见的函数之一,即使我们有了重载函数,但是还是因为变量的类型不同而要写多个交换函数。现在,你学会了模板,无论多少种类型变量的交换,都只要写一次就能完成。
声明模板格式:template<typename T1,typename T2,typename T3, ...>
,在函数或者类的上面添加,及表示该函数为模板函数或者模板类。注:typename可以替换成class,但是不能使用struct。
//声明模板
template <typename T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
如何使用呢?
template <typename T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
void test()
{
int a = 1;
int b = 2;
Swap(a, b);
cout << a << " " << b << endl;
double c = 1.1;
double d = 2.2;
Swap(c, d);
cout << c << " " << d << endl;
char e = 'a';
char f = 'b';
Swap(e, f);
cout << e << " " << f << endl;
}
看上去每次Swap都会调用模板函数,但是底层其实不是这样子的,在编译阶段,编译器会将你传入的实参来推演出模板函数的类型,产生一个新的函数,并将T
替换成相对应的类型,再调用这个新的函数。我们可以通过反汇编来看看新的函数的地址。
函数的地址都是不一样的,且函数名后都会加上传入的实参的类型,我们再来看一张图,加深我们对模板底层的理解。
看到这里你一定理解和使用模板了吧,可能会有很多小伙伴想钻牛角尖,说能不能传两个不一样的类型,让编译器去推导呢?你看你坏的很,编译器不知道你想转换成哪种类型,他是不会帮你转换的。如果明确要转换的类型,编译器才会转换。
那我们想让编译器推导参数不同类型的函数可以吗?当然可以,那就必须显示实例化或者将参数强转成对应的类型
template<typename T>
T Add(T left, T right)
{
return left + right;
}
void test()
{
int a = 1;
double b = 1.1;
//1、显示实例化 (告诉编译器生成类型为int的Add函数)
Add<int>(a, b);
//2、强转为int,此时参数类型一致
Add(a, (int)b);
}
1、当一个非模板函数和一个同名模板函数同时存在时,会优先调用非模板函数
2、非模板函数和同名模板函数同时存在,编译器会根据传入的参数匹配更好的函数
3、如果知名需要进行实例化,则直接实例化,不管是否存在同名非模板函数
类模板
定义类模板和函数模板方式一样,在类前声明模板
template <class T>
class seqList
{
public:
seqList(int n)
:_data(new T[n])
, _size(0)
, _capacity(n)
{}
private:
T* _data;
size_t _size;
size_t _capacity;
};
如果在类中声明成员函数,要在类外定义函数,那么定义函数时也要在前面加上模板声明
template <class T>
class seqList
{
public:
seqList(int n)
:_data(new T[n])
, _size(0)
, _capacity(n)
{}
//声明成员模板函数
T seqListAt(size_t pos);
private:
T* _data;
size_t _size;
size_t _capacity;
};
//类外定义成员函数
template <class T>
T seqList<T>::seqListAt(size_t pos)
{
return _data[pos];
}
在我们实例化对象时,因为是自定义类型,编译器无法推导出我们想要的类的类型,所以我们必须显示实例化。
格式:类名<类型> 对象名(参数)
//类模板实例化:类名<类型> 对象名(参数)
seqList<int> sq1(2);
seqList<double> sq2(4);
seqList<char> sq3(6);