
目录
1.5.1. 一个非模板函数函数可以喝同名的模板函数同时存在,并且模板函数依然可以实例化为这个非模板函数
1.5.2. 在其他条件都完全一样,那么在编译器的优化下,会优先调用非模板函数而不是有函数模板生成一个实例,当然如果函数模板生成一个更加匹配的则会调用函数模板
问题引入:
学习模版之前我们要学习泛型编程,泛型编程是一种代码风格。所谓泛型编程就是已经可能抽象的方式去编写程序的算法,使其不依赖于算法上的数据形式。泛型变成只编写与数据类型无关的通用代码,今天学习的就是泛型编程的基础。
模板分为:函数模板和类模板。
1. 函数模板
1.1. 函数模板的概念
函数模版是一个函数群体,它并不依赖于函数关系。当被使用时会被参数化,然后根据参数类型产生一个特定类型的函数版本。
1.2. 函数模板的格式
template <typename T1,typename T2,typename T3……typename Tn>
返回值类型 函数名 (参数列表){ }
//函数模板 template <class T> void Swap(T& left,T& right) { T tmp = left; left = right; right = tmp; }PS:typename是用来定义模板参数的关键字,也可以使用class,但是不能用struct替换class。
1.3. 函数模板的定义
函数模板严格意义上来讲并不是真正的函数,它只是一个蓝图,是编译器来产生特定参数类型函数的模具。换句话说,就是通过这个函数模板,我们将本来我们应该做的事情交给了编译器。
我们这里通过一个Swap()函数来进行说明:
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; } int main() { int a = 0; int b = 1; double c = 2.2; double d = 3.3; Swap(a, b); Swap(c, d); return 0; }很明显,这段代码中我们使用到了函数重载,但是会有这么几个缺点:
- 这两个重载函数只有参数类型不同,代码复用率较低。一旦出现新的参数类型,又要重新重载函数
- 可维护性也较差,因为一旦一个函数从重载出错,就可能全部出错
那如果使用函数模板就会变成这样:
//Swap()函数 //template <typenname T> template <class T> viod Swap(T& left,int &right) { T tmp = left; left = right; right = tmp; }这样的话,我们就解决了上面的两个问题:
在编译器阶段,函数模板的使用就是,编译器会根据传入的参数类型的,来生成一份对应类型的函数代码以供调用。
比如,当传入的类型是int时,编译器就会通过对实参类型的推演,将T类型确认为int,然后生成一份int类型的代码。
1.4. 函数模板的实例化
当不同的参数被传入函数模板时,就会变成函数模板的实例化,函数模板的实例化被分为显式实例化和隐式实例化:
1.4.1. 隐式实例化
隐式实例化就是懒到极致,直接让编译器去根据传入的参数推演:
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 20.0, d2 = 10.0; Add(a1, a2); Add(d1, d2); return 0; }但是我们来看下面这个代码:
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10; double d1 = 20.0; Add(a1, d1); return 0; }这个代码与上面的区别就是传入的参数并不是统一的:
这种情况编译器是推演不出来的,那么这个时候有两种解决方式,一是用户进行强制类型转换,二是显式实例化。
int main() { int a1 = 10; double d1 = 20.0; Add(a1, (int)d1);//用户自己来强制转换 return 0; }
1.4.2. 显式实例化
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10; double d1 = 20.0; Add<int>(a1, d1);//显示实例化成int Add<double>(a1, d1);//显示实例化成double return 0; }就比如这个时候,我显式地将函数模板的参数类型设置为int、double,这个时候传入参数的时候就会根据已经显式规定的参数类型进行隐式类型转换,如果转换失败才会报错。
1.5. 模板参数的匹配原则
1.5.1. 一个非模板函数函数可以喝同名的模板函数同时存在,并且模板函数依然可以实例化为这个非模板函数
// 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; } int main() { Add(1, 2); // 与非模板函数匹配,编译器不需要特化 Add<int>(1, 2); // 调用编译器特化的Add版本 return 0; }1.5.2. 在其他条件都完全一样,那么在编译器的优化下,会优先调用非模板函数而不是有函数模板生成一个实例,当然如果函数模板生成一个更加匹配的则会调用函数模板
// 专门处理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(1, 2); //模板函数可以生成更加匹配的版本 //编译器根据实参生成更加匹配的Add函数 Add(1, 2.0); return 0; }1.5.3. 函数模板不允许自动类型转换,但是普通函数可以
2. 类模板
2.1. 类模板的格式
template <class T1, class T2, ..., class Tn> class 类模板名 { // 类内成员定义 };下面使用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; }
2.2. 类模板的实例化
(本篇完)






169

被折叠的 条评论
为什么被折叠?



