C++?模板!!!
一、引言
在之前我们一起学习了C++中类和对象、动态内存管理等相关知识,今天我们将一起学习C++中有关模板的相关知识,学完模板之后我们就可以进入C++中非常重要的库---STL了,那么模板究竟有什么奥秘呢?让我们一起来看看吧!
二、什么是泛型编程?
1、引入
泛型编程,顾名思义就是基于广泛意义的编程,在解释之前,我们先看下面这一个情景:
在上面的情景中,为了实现对未知类型的交换,我们借助函数重载实现了三个形参类型不同,功能完全一样的Swap交换函数,任务似乎是完成了,但是还有如下两个问题:
(1).重载函数仅仅是类型不同,代码的服用率太低,同时如果还要完成新类型的交换,我们只能再写新的函数
(2).代码比较难维护,如果代码出现bug或者有新功能需要加入的话,需要一次性改动所有函数
这时候我们便想:能否只告诉编译器一个函数模板,让编译器根据不同的类型利用该模板来生成不同的函数?
2、介绍泛型编程
从上面的情景我们发现我们需要一个类似模板的东西来帮助我们完成以上类型不同功能相同的工作,事实上C++确实为我们提供了这样的语法
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程得以实现的基础,同时模板分为函数模板和类模板,接下来我们将一一介绍
三、函数模板
1、概念
函数模板代表了一组函数,该函数模板与类型无关,在使用时被参数化,根据实参类型产生对应的函数,从而调用
2、语法
template<typename T1,typename T2............>
函数头.......
如下:
template<typename T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}
通过上面的代码便可以完成对任意类型的交换
注意:typename是用来定义模板参数的关键字,也可以使用class代替(struct不行)
3、原理
函数模板是一个蓝图,它本身并不是函数,是编译器使用特定方式产生特定函数的一个摸具,所以它的本质就是将原本我们用户要做的工作交给了编译器,如下图:
在编译阶段,编译器需要根据传入的参数类型来推断生成对应类型的函数以供调用,比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然 后产生一份专门处理double类型的代码,对于字符类型也是如此。
4、函数模板的实例化
用不同类型的参数使用函数模板时,称之为函数模板的实例化,模板实例化分为:隐式实例化和显式实例化
(1).隐式实例化
是让编译器根据传参类型推演模板参数的实际类型,代码如下:
可以看到,我们类似调用正常函数的操作对函数模板进行了调用,正常的打印了结果,这就是隐式实例化,成功的让编译器推断了两种特定的函数,但是如果是如下的情况,编译器就不能正常的推断出对应函数了:
对于以上的这种情况,使用函数模板时所传的参数类型不同,如果将模板参数改写为两个自然可以解决这个问题,但是如果规定不可以使用这种方式呢?这时候我们首先可以在传参时对于某一个实参进行强制类型转换,这时候类型就统一了,还有其它的方法吗?
(2).显式实例化
对于以上的问题,我们可以使用显式实例化的方法解决,在函数名后用'<>'声明一种类型,这时候本次使用函数模板时,就会直接编译产生对应的函数类型,那么使用以下代码就可以很好的解决上面的问题:
可以看到,我们使用以上介绍的两种方法解决了该问题,但是上面的问题不是真正使用显式实例化的情景,真正的情景如下:
template<class T>
T* newplace(int n)
{return new T[n];
}
在以上情境中,编译器不能通过传参类型推断模板参数T的类型,这时候只能使用显式实例化来解决了
5、注意
(1).同名的函数模板和函数可以同时存在,而且该函数模板还可以被实例化为这个非模板函数
(2).对于非模板函数和同名函数如果其它条件都相同,在调动时会优先调用非模板函数二不会使用模板产生实例,如果模板会产生一个更好匹配的函数,那么将选择模板
(3).函数模板不允许自动类型转换,但是普通函数可以
四、类模板
1、类模板的定义
与函数模板类似,在多个类的其它细节一致,只有类型不一致时,可以使用类模板,它的语法如下:
template<class T1,class T2.....>
class 类模板名
{//类的成员定义
};
在下面我们将实现一个简单的vector顺序表,但是只是为了说明类模板的相关问题,在以后STL部分将会和大家一起完成真正的vector:
// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
class Vector
{
public :Vector(size_t capacity = 10): _pData(new T[capacity]), _size(0), _capacity(capacity){}// 使用析构函数演示:在类中声明,在类外定义。~Vector();void PushBack(const T& data);void PopBack();// ...size_t Size() {return _size;}T& operator[](size_t pos){assert(pos < _size);return _pData[pos];}private:T* _pData;size_t _size;size_t _capacity;};// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
Vector<T>::~Vector()
{if(_pData)delete[] _pData;_size = _capacity = 0;
}
观察上面的代码可以看出类模板的使用与函数模板类似,但是要额外注意以下几点:
(1)、当声明与定义分离时,要在定义上面再加上模板参数定义
(2)、类模板的类名与类型名不一致,类型是vector<模板参数列表>,类名是vector这也就是为什么构造、析构函数的名字是vector但是在类外定义时要声明vector<T>
2、类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类,如下所示:
// Vector类名,Vector<int>才是类型Vector<int> s1;Vector<double> s2;
五、结语
这就是本期关于C++中模板相关的所有内容了,希望对大家有所帮助,感谢各位于晏、亦菲的阅读,欢迎大家和我一起讨论、进步。