C++:模板精讲
泛型编程当我们实现一个交换函数想要实现不同类型的交换可以使用函数重载#includeiostreamusing namespace std;voidSwap(intleft,intright){inttempleft;leftright;righttemp;}voidSwap(charleft,charright){chartempleft;leftright;righttemp;}voidSwap(doubleleft,doubleright){doubletempleft;leftright;righttemp;}但是这样有几个不好的地方重载的函数仅仅是类型不同代码复用率较低只要有新类型出现就需要用户自己增加对应的函数。代码的可维护性比较低一个出错可能所有的重载均出错。所以在C中就出现了模板这个概念编写与类型无关的代码是代码复用的一种基础模板是泛型编程的基础。函数模板函数模板代表了一个函数家族该函数模板与类型无关在使用时被参数化根据实参类型产生函数的特定类型版本。被参数化的这个过程叫做函数模板的示例化。函数模板的格式templatetypenameT1,typenameT2即返回类型 函数名参数列表{}#includeiostreamusingnamespacestd;templatetypenameTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}intmain(){charax;charbc;Swap(a,b);couta bendl;return0;}注意typename 是用来定义模板参数关键字也可以使用class切记不能使用struct代替class。typename和class 只有一种情况不能等价比如 T::iterator 前面只能用 typename说明这是一个类型templateclassTvoidfunc(){// 这里必须写 typename不能写 classtypenameT::iterator it;}函数模板的原理函数模板是一个标准复制件它本身并不是函数是编译器用使用方式产生特定具体类型函数的摸具。所以模板就是将本来我们要重复做的事交给了编译器。在编译器阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。函数模板的实例化用不同类型的参数使用函数模板时称为函数模板的实例化。函数模板实例化分为隐式实例化和显式实例化隐式实例化#includeiostreamusingnamespacestd;templateclassTTAdd(constTleft,constTright){returnleftright;}intmain(){inta110,a220;doubleb15.2,b213.14;coutAdd(a1,a2)endl;coutAdd(b1,b2)endl;return0;}显式实例化#includeiostreamusingnamespacestd;templateclassTTAdd(constTleft,constTright){returnleftright;}intmain(){inta10;doubleb20.0;//显示实例化coutAddint(a,b)endl;return0;}注意如果在实例化中类型不匹配编译器一般不会进行类型转化操作因为一旦转化出现问题编译器就需要背锅。模板参数的匹配机制1.一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被示例化为这个非模板函数。#includeiostreamusingnamespacestd;intAdd(intleft,intright){returnleftright;}templateclassTTAdd(constTleft,constTright){returnleftright;}intmain(){Add(1,2);Addint(1,2);}2.对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调动非模板函数而不会从该模板产生一个实例。如果模板可以产生一个具有更好匹配的函数那么选择该模板。#includeiostreamusingnamespacestd;intAdd(intleft,intright){returnleftright;}templateclassT1,classT2T1Add(constT1left,constT2right){returnleftright;}intmain(){Add(1,2);Add(1,2.0);return0;}模板函数不允许自动类型转化但是普通函数可以进行自动类型转化。类模板类模板的定义格式templateclassT1,clasT2,......classTnclass类模板名{//类内成员变量}#includeiostreamusingnamespacestd;templatetypenameTclassStack{public:Stack(size_t capacity4){_arraynewT[capacity];_capacitycapacity;_size0;}private:T*_array;size_t _capacity;size_t _size;};intmain(){Stackintst1;Stackdoublest2;return0;}类模板的实例化类模板的实例化和函数模板的实例化不同类模板实例化需要在类模板名字后跟 然后将实例化的类型放在中即可类模板名字不是真正的类而实例化的结果才是真正的类。非类型模板参数模板参数分类类型形参和非类型形参类型形参出现在模板参数列表中跟在class 或者typename之类的参数类型名称之后。非类型形参就是用一个常量作为类函数模板的一个参数在类函数模板中可以将该参数当成常量来使用。#includeiostreamusingnamespacestd;namespaceyjjh{templateclassT,size_t N10classarray{public:array(){for(inti0;iN;i){_array[i]T();}_sizeN;}Toperator[](size_t index){return_array[index];}constToperator[](size_t index)const{return_array[index];}size_tsize()const{return_size;}boolempty()const{return0_size;}private:T _array[N];size_t _size;};}intmain(){yjjh::arrayint,5a1;couta1[4]endl;return0;}注意浮点数类对象以及字符串是不允许作为非类型模板参数的浮点数在计算机里是二进制近似存储模板要求不同值必须生成不同类浮点数因为精度问题做不到绝对安全区分类对象太复杂有构造、析构、成员变量编译器无法在编译期把一个完整对象编码进类型字符串是数组 / 指针地址不确定编译器无法把不确定地址当作模板常量非类型模板参数必须在编译期间就确认结果模板的特化通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果需要特殊处理比如实现了一个专门用来进行小于比较的函数模板。#includeiostreamusingnamespacestd;classDate{public:Date(intyear1900,intmonth1,intday1){_yearyear;_monthmonth;_dayday;}booloperator(constDated2){if(_yeard2._year){returnfalse;}elseif(_yeard2._year){if(_monthd2._month){returnfalse;}elseif(_monthd2._month){return_dayd2._day;}}returntrue;}private:int_year;int_month;int_day;};templateclassTboolLess(T left,T right){returnleftright;}intmain(){coutLess(1,2)endl;Dated1(2026,4,23);Dated2(2025,6,7);coutLess(d1,d2)endl;coutLess(p1,p2)endl;return0;}可以看到Less大多数情况下都可以正常比较但是在特殊场景下就得到错误的结果。上述示例中p1指向的d1显然小于p2指向的d2对象但是Less内部没有比较p1和p2指向的对象内容而比较的是p1 和 p2 指针的地址这就无法到达预期而错误。所以我们需要模板特化模板特化模板特化在原模板类的基础上针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。函数模板特化步骤1.必须要先有基础的函数模板2.关键字template后面接一对尖括号3.函数名后跟一对尖括号尖括号中指定需要特化的类型4.函数形参表必须要和模板函数的基础函数类型完全相同如果不同编译器可能会报一些奇怪的错误#includeiostreamusingnamespacestd;classDate{public:Date(intyear1900,intmonth1,intday1){_yearyear;_monthmonth;_dayday;}booloperator(constDated2){if(_yeard2._year){returnfalse;}elseif(_yeard2._year){if(_monthd2._month){returnfalse;}elseif(_monthd2._month){return_dayd2._day;}}returntrue;}private:int_year;int_month;int_day;};templateclassTboolLess(T left,T right){returnleftright;}//对Less模板进行特化templateboolLessDate*(Date*left,Date*right){return*left*right;}intmain(){coutLess(1,2)endl;Dated1(2026,4,23);Dated2(2025,6,7);coutLess(d1,d2)endl;Date*p1d1;Date*p2d2;coutLess(p1,p2)endl;return0;}注意一般情况下如果函数模板遇到不能处理或者处理有误的类型为了实现简单通常都是将该函数直接给出。该实现简单明了对于一些参数类型复杂的函数模板特化后代码可读性不强。类模板特化必须要先有基础的函数模板关键字template后面接一对尖括号类名后面接一对尖括号填上类型全特化全特化是将模板参数列表中所有的参数都确定化#includeiostreamusingnamespacestd;templateclassT1,classT2classData{public:Data(){coutDataT1,T2endl;}private:T1 _d1;T2 _d2;};templateclassDataint,char{public:Data(){coutDataT1,T2endl;}private:int_d1;char_d2;};偏特化偏特化任何针对模板参数进一步进行条件限制设计的特化版本。1.部分特化usingnamespacestd;templateclassT1,classT2classData{public:Data(){coutDataT1,T2endl;}private:T1 _d1;T2 _d2;};templateclassT1classDataT1,int{public:Data(){coutDataT1,intendl;}private:T1 _d1;int_d2;};2.参数更进一步的限制#includeiostreamusingnamespacestd;templateclassT1,classT2classData{public:Data(){coutDataT1,T2endl;}private:T1 _d1;T2 _d2;};templatetypenameT1,typenameT2classDataT1*,T2*{public:Data(){coutDataT1*,T2*endl;}private:T1 _d1;T2 _d2;};templatetypenameT1,typenameT2classDataT1,T2{public:Data(){coutDataT1,T2endl;}private:T1 _d1;T2 _d2;};类模板特化的应用有如下专门按照小于补交的类模板Less:#includeiostream#includevector#includealgorithmusingnamespacestd;classDate{friendostreamoperator(ostreamout,constDated);public:Date(intyear1900,intmonth1,intday1){_yearyear;_monthmonth;_dayday;}booloperator(constDated2)const{if(_yeard2._year){returnfalse;}elseif(_yeard2._year){if(_monthd2._month){returnfalse;}elseif(_monthd2._month){return_dayd2._day;}}returntrue;}private:int_year;int_month;int_day;};ostreamoperator(ostreamout,constDated){outd._year-d._month-d._day;returnout;}templateclassTstructLess{booloperator()(constTx,constTy)const{returnxy;}};模板的分离编译假如有一下场景模板的声明与定义分离开在头文件中进行声明在源文件中完成定义会直接报错// a.htemplateclassTvoidfunc(T a);// 声明// a.cpptemplateclassTvoidfunc(T a){}普通函数 / 类编译.cpp就生成具体机器码声明放头文件、定义放源文件支持分离编译。模板函数 / 类本质是代码蓝图没有具体类型不会主动生成实体代码。模板只有在 被指定类型使用实例化 时才会替换类型、生成对应代码。若分离编译模板定义写在.cpp外部文件只引入头文件声明编译各自文件时两边都没法实例化生成代码链接阶段找不到函数实体报错。解决方法将声明和定义放到一个.h文件中模板定义的位置显示实例化不推荐使用模板总结优点模板复用了代码节省资源更快的迭代开发增强了代码的灵活性缺点模板会导致代码膨胀问题也会导致编译时间变长出现模板编译错误时错误信息非常凌乱不一定位错误
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2551522.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!