c++模板进阶知识讲解(对模板的进一步的运用与理解)

c++模板进阶知识讲解(对模板的进一步的运用与理解) 非类型模板参数模板参数分类类型形参与非类型形参。类型形参即出现在模板参数列表中跟在class或者typename之类的参数类型名称。非类型形参就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。这种应用场景可以用在你如果要定义的对象是静态变量就可以考虑使用比如:静态数组templateclassT,size_t N10classArray{public://......一些函数private:T*_a[N];size_tsize();};如上实例中定义的类里的模板参数里N就是非类型模板参数它的类型一般是整型浮点数与类类型都不行到c20才支持。 这种静态数组的类在STL容器的array中是使用的它的里面的数组就是静态数组 https://legacy.cplusplus.com/reference/array/array/?kwarray 这是在cplusplus上的详细对array的内容与vector的区别就是它的数组是定长的。模板的特化函数模板的特化通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果需要特殊处理比如实现了一个专门用来进行小于比较的函数模板templateclassTboolLess(constTleft,constTright){returnleftright;}比如这是用来比较的函数模板对于传入的为非指针的类型比较逻辑不变但如果是传入指针类型的话这个模板实例化之后就是比较两个指针的大小在一般情况下是毫无意义的coutLess(1,2)endl;double*p1newdouble(2.5);double*p2newdouble(4.9);coutLess(p1,p2)endl;比如这里的p1与p2的比较比较最后比较的是指针的大小而不是它们的指针指向的内容的比较这种比较是无意义的templateboolLessdouble*(double*constleft,double*constright){return*left*right;}这里的语法就是写个模板但是不带任何参数这就是模板特化也叫做全特化然后类名跟定义的参数是一样的然后尖括号里面带具体类型比如这里是double* 函数体要接受的 形参也是尖括号里的类型然后用解引用后的内容比较它调用函数就会找最匹配的函数模板第一个输出会找初始的那个第二个会找刚刚写的特化的那个写特化的函数模板一定要先有初始的写的不是具体类型的模板否则就会报错要先有这个才能写后面具体类型的模板特化类模板的特化全特化跟函数模板的语法类似也是templatetemplateclassT1,classT2classData{public:Data(){coutDataT1, T2endl;}private:T1 _d1;T2 _d2;};比如如上的一个简单的类模板如果要写它的全特化就是templateclassDataint,char{public:Data(){coutDataint, charendl;}private:int_d1;char_d2;};全特化就是里面不带任何参数而且写的特化后的模板里面的除了类名要一样其他的比如成员变量和成员函数都可以不一样甚至可以不写都行如果定义的对象的类型与特化的更匹配就会调用特化的类进行构造否则就会使用最初的类模板进行构造两者是互不干扰的但是必须要先有类模板才有后面的特化模板这里自己可以尝试一下然后调试写不同类型会调用那个类去构造偏特化意思为对部分模板参数进行具体类型显示比如templateclassT1,classT2classData{public:Data(){coutDataT1, T2endl;}private:T1 _d1;T2 _d2;};// 将第二个参数特化为inttemplateclassT1classDataT1,int{public:Data(){coutDataT1, intendl;}private:T1 _d1;int_d2;};保留第一个参数显示具体类型第二个参数如果将第一个显示具体化那就在就行后面类名跟的尖括号也要变特别重要的一种:如果对指针类型进行偏特化因为我们想要的函数一般不会直接返回指针比较的值所以可以如下这样写:templateclassT1,classT2classDataT1*,T2*{public:Data(){coutDataT1*, T2*endl;}};intmain(){Dataint*,int*d1;这里的T1和T2的实例化类型都是int 而不是int* 这是编译器的特殊处理这样会给用户的体验更好因为如果是int* 那类名中的就是二级指针对二级指针的操作又很麻烦所以这里就这样处理后对一级指针的操作不管是比较还是什么都很方便举个应用场景:在我们以前学的优先级队列——priority_queue中我们的类模板参数中有默认的仿函数来做比较堆中的父子与孩子结点的优先级templateclassT//仿函数的应用structless{booloperator()(constTx,constTy){returnxy;}};templateclassT,classcontainerstd::vectorT,classComparelessTclasspriority_queue{..........};我们默认是建大堆出优先级高的数据,现在我创建个对象里面装的数据是日期类型的值priority_queueDate*q1;q1.push(newDate(2026,4,10));q1.push(newDate(2026,4,11));q1.push(newDate(2026,4,12));while(!q1.empty()){cout*q1.top() ;q1.pop();}如果我们这样比较就是比较指针的排序我们要的是根据日期的大小的来出列所以我们要偏特化仿函数模板来针对这个来写:templateclassT//仿函数的应用structless{booloperator()(constTx,constTy){returnxy;}};templateclassTstructlessT*{booloperator()(T*constx,T*consty){return*x*y;}};所以当时传的是指针类型的日期就会走偏特化模板 然后里面的比较就会走它的类里的 的比较方式最后就是排序就会是从大到小的输出:模板分离编译我们之前提过在有模板的函数里不能做函数的声明与定义会发生链接错误。函数的执行会通过这几个过程 预处理——编译——汇编——连接在main()函数中调用普通函数最先会在一般是在.h文件里有函数的声明它会在预处理展开头文件它要call函数的地址在编译层面他会在定义的函数里取一般是第一行执行命令的地址但你有函数的声明就相当于向编译器保证有函数的定义所以它会去其他文件里找找到就call 地址后 连接是与头文件连接如果是带有函数模板的类或函数的话首先在头文件里找到声明然后去其它文件找定义但是其它文件的定义都是未实例化的函数不是你传的实参的类型的参数它是一个泛型的所以就找不到定义也就是call 中没有地址返回给它最后在连接的时候就会报错但也有种方法就是显示实例化对象比如你传的是int类型就显示写 如下test.h文件 test.cpp文件templateclassTvoidfun(T x){.........}templateclassTtemplateclassTvoidfun(T x);voidefun(intx);但是这样你每传个不同类型的参数都要再写一遍显示的实例化所以就很麻烦所以不推荐这样写解决办法就是:将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的https://blog.csdn.net/pongba/article/details/19130 更详细的讲解请看这位大佬的解释