1. 泛型编程
#include<iostream> using namespace std; void swap(int& a, int& b) { int tmp = 0; tmp = a; a = b; b = tmp; } void swap(double& a, double& b) { double tmp = 0; tmp = a; a = b; b = tmp; } int main() { int a = 1; int b = 2; swap(a, b); double a1 = 1.0; double b1 = 2.0; swap(a1, b1); return 0; }
正常来说,对于不同类型的变量进行交换,需要实现不同的swap函数,这样实现有些太繁琐了
为了解决相似函数的不同调用问题,C++提出泛型编程,编写与类型无关的通用代码,实现代码复用 即模板
模板主要分为函数模板和类模板
2.函数模板
1.模板格式
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
typename是用来定义模板参数关键字,也可以使用class,两者目前是没区别的,但是由于STL大部分用的class,所以建议使用class
2.模板原理
//泛型编程----模板 template <class T> void swap(T& a, T& b) { T tmp = a; a = b; b = tmp; } int main() { int a = 1; int b = 2; swap(a, b); double a1 = 1.0; double b1 = 2.0; swap(a1, b1); return 0; }
想要使用swap函数交换不同类型,直接调用模板就可以了
那int类型交换与double类型交换,使用是同一个swap函数吗?
通过查看反汇编发现,两者调用的不是一个swap函数
实际上调用的并不是这个模板,而是通过这个模板实例化生成的代码
3.函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
1.隐式实例化
实参传给形参,自动推演模板类型
template <class T> T add(T& pa, T& pb) { return pa + pb; } int main() { int a = 1; int b = 2; double p1 = 1.0; double p2 = 2.0; //同类型进行可以正常运行 add(a, b);//自动推演类型为int add(p1, p2);//自动推演类型为double //----------- addd(a, p1);//a与p1是不同类型,会报错 return 0; }
- 不同类型去模板推演会出现歧义,a传过去将T推演成int,而p1传过去把T推演成double,T无法确定推演int还是double
2.显示实例化
- 为了解决自动推演不同类型造成歧义的问题,使用显示实例化
- 在函数名后的<>中指定模板参数的实际类型
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.1, d2 = 20.1; Add(a1, a2); Add(d1, d2); cout << Add<int>(a1, d1) << endl;//显示实例化 }
- 指定T的类型为int ,d1由于是double类型,所以在传参时会发生隐式类型转换变成int
4.模板参数的匹配原则
1.非模板和模板函数共存时
template<class T>//模板 T Add(const T& left, const T& right) { return left + right; } int Add(const int& left, const int& right)//自己写的 { return left + right; } int main() { int a1 = 10, a2 = 20; Add(a1, a2); //Add<int>(a1,a2);//显示实例化 }
- 自己写的和模板是可以同时存在的,通过调试可以发现调用的是自己写的那个
- 因为调用自己写的成本更低一些,使用模板还需要实例化生成代码,而自己写的直接可以使用
2.编译器会选取相对而言最为匹配的一个进行调用
template<class T1,class T2> T1 Add(T1 left, T2 right) { return left + right; } template<class T> T Add(const T& left, const T& right) { return left + right; } int Add(const int& left, const int& right) { return left + right; } int main() { int a1 = 10; double a2 = 20.2; Add<int>(a1, a2); }
在上述的两个模板和自己实现的函数中,编译器会选取相对而言最为匹配的一个进行调用, 即调用template<class T1,class T2>这个模板来实现
3.类模板
1.定义格式
template<class T1, class T2, …, class Tn>
class 类模板名
{
// 类内成员定义
};
2.有typedef的存在为什么还有类模板?
typedef int STdatatype; class stack { private: STdatatype* _a; size_t top; size_t capacity; }; int main() { stack s1;//想要S1存储int stack s2;//想要S2存储double return 0; }
- 如果想要改变栈储存的类型可以选择改变typedf定义的类型
- 但是若想要两个栈分别储存不同的数据类型typedef做不到
- 两份类的代码几乎是一致的,但若想达到目的就需要再拷贝一份出来,就有些太繁琐了
template <class T> class stack { public: stack(int capacity=4) { _a = new T[capacity]; _top = 0; _capacity = capacity; } ~stack() { delete[]_a; _capacity = _top = 0; } private: T* _a; size_t top; size_t capacity; }; int main() { stack <int>s1;//想要S1存储int stack <double>s2;//想要S2存储double return 0; }
类模板只能显示实例化,这样就可以达到s1存储int,S2存储double
3.类模板的实例化
-类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector才是类型
Vector s1;
Vector s2;
4.声明和定义分离
template<class T> class Vector { public: Vector(size_t capacity = 10) : _pData(new T[capacity]) , _size(0) , _capacity(capacity) {} ~Vector();//析构函数类中的声明 private: T* _pData; size_t _size; size_t _capacity; }; template <class T>//析构函数在类外面定义 要加上模板 Vector<T>::~Vector() { detele[]_pData; _pData = nullptr; _size = _capacity = 0; } int main() { Vector<int> v; return 0; }
- 以析构函数为例,在类里面声明,在类外面定义
- 对于模板,vector是类名,但不是类型,加上实例化的模板参数后才是类型,如vector
- 析构函数在类外面定义 ,需要使用类型 vector < T>,而T作为模板需要调用template < class T >