目录
泛型编程
引入
模板
函数模板
函数模板的概念
函数模板格式
函数模板的原理
函数模板的实例化
隐式实例化
显式实例化
模板参数的匹配原则
类模板
类模板的定义格式
类模板的实例化
泛型编程
引入
我们在实际编写代码中,经常会遇到不同的类型需要实现同一种功能的实例!
比如:同类型的两个变量进行交换:
代码:
#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() { return 0; }
以上代码中的几个函数,都是在实现同一个交换功能,只是它的类型不同,且都是通过函数重载实现的!实现同一功能的函数我们写了很多遍,这样写就很麻烦了!那么有没有一种方法可以实现一个通用的交换函数呢?在C++中为了解决这一问题,就引出了模板的概念!
模板
概念:
告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码!
看图理解:
我们做一个建筑图标模型,我们可以通过这个模具来弄出各种不同颜色的建筑类型,通过一个同样式模具,可以做出各种同样式不同颜色的建筑图标!而C++中的模板也是如此,告诉编译器一个模板,编译器就可以根据不同的类型利用该模板,生成各类型所对应的代码!
泛型编程:
编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础!
模板分类:
函数模板
函数模板的概念
概念:
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本!
给编译器一个函数模板,编译器根据模板参数的类型自动生成该类型所对应的函数!
函数模板格式
写法格式:
template <typename T1,typename T2,……,typename Tn>
返回值类型 函数名(参数类型) { }
template <class T1,class T2,……,class Tn>
返回值类型 函数名(参数类型) { }
代码:
#include <iostream> using namespace std; //函数模板 //使用typename 来定义模板 template <typename T> void Swap(T& a,T& b) { T tmp=a; a=b; b=tmp; } //使用class 来定义函数模板 template <class T> T Add(T& a,T& b) { //T z=a+b; return a+b; } //注意typename是定义模板的关键字 //也可以使用class来定义 //切记不可用struct来定义模板 int main() { //利用typename来定义模板 int a=10; int b=20; cout<<"交换前:"<< " a = "<<a<<", b = "<< b<<endl; Swap(a,b);//利用模板进行交换 cout<<"交换后:" << " a = "<<a<<", b = "<< b<<endl; double d1=1.11; double d2=2.22; cout<<"交换前:"<< " d1 = "<<d1<<", d2 = "<< d2<<endl; Swap(d1,d2);//利用模板进行交换 cout<<"交换后:" << " d1 = "<<d1<<", d2 = "<< d2<<endl; //class定义模板 cout<<Add(a,b)<<endl; double c=12.3; double d=12.6; cout<<Add(c,d)<<endl; return 0; }
注意:
typename是用来定义模板参数的关键字,也可以使用class来定义(切记:不可使用struct)!
函数模板的原理
原理:
模板是一个蓝图,它本身并不是函数,是编译器使用方式产生特定具体类型函数的模具。所以模板就是将本来我们应该重复做的事情交给了编译器!模板的本质实际上还是函数重载,只不过这些活都交给编译器干了!编译器在内部自动实现了,不需要用户自己实现,用户只需要提供模具就行!
代码:
#include <iostream> using namespace std; template <typename T> void Swap(T& a,T& b) { T temp=a; a=b; b=temp; } int main() { double d1=2.0; double d2=5.0; Swap(d1,d2); int i1=10; int i2=20; Swap(i1,i2); char a='0'; char b='9'; Swap(a,b); return 0; }
理解:
在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符,整型……等类型都是如此!
函数模板的实例化
概念:
用不同类型的参数使用函数模板时,称为函数模板的实例化!
模板的实例化其实就是通过参数类型推演出所需类型的函数这就是模板的实例化!
模板实例化分为:
隐式实例化 和 显式实例化
隐式实例化
隐式实例化:
让编译器根据实参推演模板参数的实际类型
代码:
#include <iostream> using namespace std; template <typename 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.0; cout<<Add(a1, a2)<<endl; cout<<Add(d1, d2)<<endl; //Add(a1,d1);//该语句无法编译通过 /* 该语句不能通过编译,因为在编译期间, 当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int, 通过实参d1将T推演为double类型, 但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错 注意:在模板中,编译器一般不会进行类型转换操作, 因为一旦转化出问题,编译器就需要背黑锅 Add(a1, d1); */ //此时有两种处理方式 // 1.用户自己来强制转换 // 2.使用显式实例化 cout<<endl; Add(a1,(int)d1); cout<<Add(a1,(int)d1)<<endl; double s1 = Add((double)a1,d1); cout<<s1<<endl; return 0; }
显式实例化
显式实例化:
在函数名后的<>指定模板参数的实际类型
可以用显示实例化解决上述代码中的问题
代码:
#include <iostream> using namespace std; template <typename T> T Add(const T& a,const T& b) { return a+b; } int main() { int a=10; double c=20.22; //不同类型可显式实例化 Add<int>(a,c); cout<<Add<int>(a,c)<<endl; cout<<Add<double>(a,c)<<endl; return 0; }
注意:
如果类型不匹配,编译器会尝试进行隐式类型转换,若有强制类型转换,编译器会按照用户强转的类型去转换,若没有或者无法转换成功编译器将会报错!
模板参数的匹配原则
匹配原则:
1. 一个非模板函数,和一个同名的模板函数可以同时存在,且该模板还可以被实例化为这个非模板函数!
代码:
#include <iostream> using namespace std; //非模板函数,只针对int类型求和的函数 int Add(const int& a,const int& b) { return a+b; } //求和模板函数 template <typename T> T Add(const T& left,const T& right) { return left+right; } int main() { Add(1,2);//该函数会去匹配非模板函数 Add<int>(1,2);//该函数会去调用模板(调用编译器特化的Add函数) return 0; }
2. 编译的角度:对于非模板函数与同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板!
代码:
#include <iostream> using namespace std; //专门用来int求和的函数 int Add(int a,int b) { return a+b; } //通用加法模板 template <typename T1,typename T2>//模板可以有多个模板参数 T1 Add(const T1& left,const T2& right) { return left+right; } int main() { Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化 Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 return 0; }
3. 函数模板不允许自动类型转换,但普通函数可以进行自动类型转换
类模板
类模板的定义格式
定义一个动态顺序表模板
#include <iostream> #include <assert.h> using namespace std; //动态顺序表 //注意: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; } template <class T> //尾插 void Vector<T>::PushBack(const T& data) { _pData[_size]=data; ++_size; } int main() { return 0; }
类模板的实例化
实例化:
类模板的实例化与函数模板的实例化不同,类模板实例化在定义对象时需要在类名后跟<>,然后将实例化的类型放在尖括号中即可,类模板名字不是真正的类,而实例化的结果才是真正的类!
代码样例:
代码:
#include <iostream> #include <assert.h> using namespace std; //动态顺序表 //注意: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; } template <class T> //尾插 void Vector<T>::PushBack(const T& data) { _pData[_size] = data; ++_size; } int main() { Vector<int> d1; Vector<double> d2; Vector<char> d3; return 0; }