目录
2.4、普通指针变量传递给const指针变量的引用(权限缩小)
一、非类型模版参数:
1、介绍:
模板参数分类类型形参与非类型形参。 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。 非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
2、使用:
#define N 20 template<class T> struct stack { private: T _arr[N]; T _top; }; int main() { stack<int> s1; stack<int> s2; return 0; }
如上述代码,模拟实现一个数组栈时,栈s1想要10个空间,栈s2想要20个空间,此时的结构就无法实现。
此时我们就可以使用非类型模版参数,即增加一个非类型模版参数:
template<class T,size_t N> struct stack { private: T _arr[N]; T _top; }; int main() { stack<int,10> s1; stack<int,20> s2; return 0; }
此时,我们需要多少变量,就传多少值。
3、注意:
(1)、非类型模版参数只能传整形常量,浮点数、类对象以及字符串等等是不允许作为非类型模板参数的。 (2)、非类型的模板参数必须在编译期就能确认结果。 (3)、模版是按需实例化,只实例化调用过的函数,这就导致即使某些模版函数里面有一些错误,但如果没有调用该函数的话,也不会检查出来。 如上,N为常量,即使fun函数里面对N进行++,但没有对象调用fun函数,编译器就不会报错。
4、应用
应用一:位图
应用二:array容器
二、模版特化
(一)、概念
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。 例如用仿函数进行大小比较时,如果传入的是指针,我们预期是想比较指针所指向内容的大小,但它会直接比较指针(地址)的大小,这是就需要对这一类型进行特化。
(二)、函数模版特化
1、步骤:
1、必须要先有一个基础的函数模板(正常模版) 2、关键字template后面接一对空的尖括号<> 3、函数名后跟一对尖括号,尖括号中指定需要特化的类型 4、函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
2、举例:
template<class T> class test { public: test(T a1):a(a1) {} bool operator<(test a2) { return this->a < a2.a; } private: int a; }; //函数特化 template<class T> bool myLess(T s1, T s2) { cout << "程序走了正常函数模版" << endl; return s1 < s2; } template<> bool myLess<test<int>*>(test<int>* s1, test<int>* s2) { cout << "程序走了特化" << endl; return *s1 < *s2; } int main() { test<int> a1(10); test<int> a2(20); //正常情况 cout << "正常情况:" << myLess(10, 20) << endl; //特殊情况 test<int>* pa1 = &a1; test<int>* pa2 = &a2; cout << "特殊情况:" << myLess(pa1, pa2); return 0; }
如上,当我们实现一个函数的功能是比较大小时,如果此时我们实参传的是值的指针,那么函数就会读指针(即地址)进行比较,这样的结果是不确定的,因为申请的空间不确定谁大谁小,同时也不符合我们的预期。此时我们就需要对该指针类型进行特化,如上的test<int>*,根据特化步骤单独写一个特化模版函数,当传入的参数是test<int>*类型时,程序就会调用特化后的函数。
(通俗来讲就是,对某一个类型单独进行处理,当传入的实参类型与特化类型匹配的话就会进入特化后的模版函数)。
3、不建议使用函数模版特化
不建议使用函数模版特化,因为当遇见参数列表有引用或const修饰时的情况,处理起来会变得很麻烦。
上述情况用编译器优先匹配的原则也可以解决,有现成用现成。
bool myLess(test<int>* s1, test<int>* s2) { cout << "编译器优先匹配" << endl; return s1 < s2; }
(三)、类模版特化
1.全特化:
全特化即是将模板参数列表中所有的参数都确定化。//类模板特化 //正常模版 template<class T1,class T2> class myTest { private: T1 _a1; T2 _a2; public: void fun() { cout << "调用正常模版" << endl; } }; //全特化指针类型 template<> class myTest<int*,int*> { private: int* _a1; int* _a2; public: void fun() { cout << "调用全特化int指针模版" << endl; } }; int main() { //正常模版 myTest<int,int> aa; aa.fun(); //全特化int指针 myTest<int*, int*> bb; bb.fun(); return 0; }
注意全特化模版的写法,template<>里面没有数据,将所有模版替换为具体类型(int*为例),这样当,类实例化时传递的参数与之匹配时,就会使用全特化模版。
2、偏特化
2.1、部分特化
将模板参数类表中的一部分参数特化//偏特化 //正常模版 template<class T1,class T2> class myTest { private: T1 _a1; T2 _a2; public: void fun() { cout << "调用正常模版" << endl; } }; //部分参数特化 template<class T1> class myTest<T1,int*> { private: T1 _a1; int* _a2; public: void fun() { cout << "调用部分参数模版" << endl; } }; int main() { myTest<char, int*> s1; s1.fun(); return 0; }
类似的,这里我们特化了第二个参数为具体的int*,那么当传模版参数中只要第二个模版参数的类型是int型,那么就会实例化部分参数特化的类。要注意写法。
2.2、参数更进一步的限制
偏特化并不仅仅是指特化部分参数,还可以是针对模板参数更进一步的 条件限制 所设计出来的一个特化版本。如限制类型是指针或者引用等。上述写法将模版参数全部限制成指针,不管什么类型的指针,只要两个模版参数都是原生指针,那么就会实例化进一步限制的特化模版类。//偏特化 //正常模版 template<class T1,class T2> class myTest { private: T1 _a1; T2 _a2; public: void fun() { cout << "调用正常模版" << endl; } }; 部分参数特化 //template<class T1> //class myTest<T1,int*> //{ //private: // T1 _a1; // int* _a2; //public: // void fun() // { // cout << "调用部分参数模版" << endl; // } //}; //进一步限制类型 template<class T1,class T2> class myTest<T1*, T2*> { private: T1* _a1; T2* _a2; public: void fun() { cout << "调用进一步限制成指针的模版" << endl; } }; int main() { myTest<char, int> s1; s1.fun(); myTest<char*, int*> s2; s2.fun(); myTest<double*, char*> s3; s3.fun(); return 0; }
2.3、注意:
(1)、全特化和进一步限制特化的区别,通俗来讲可以理解为,全特化只针对一种类型进行特化,而后者则是针对某一类类型,如指针类,引用类.
(2)、同时存在全特化和偏特化,则会优先匹配全特化,后匹配偏特化,因为全特化的类型更加明确,类似于编译器优先匹配原则。如果都匹配不上的就会匹配正常模版。
2.4、普通指针变量传递给const指针变量的引用(权限缩小)
我们知道,普通指针变量是可以传递给const修饰的指针变量的,这样所指向的值是不能改变的,这属于权限缩小问题。同时普通变量也可以传递给引用。
那么当把普通指针变量传递给const指针变量的引用该怎么书写呐?
错误写法:
因为当把int* 传递给const int* 的引用时,中间会产生一个临时变量而临时变量具有常属性,所以我们应该使用const指针变量的const引用进行接收:
正确写法:
三、模版的分离编译
1、什么是分离编译?
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链 接起来形成单一的可执行文件的过程称为分离编译模式。 例如将函数的声明放进 .h 文件,函数的定义放 .cpp 文件,程序翻译的前三个过程.h 和 .cpp是分别进行,最后链接阶段在链接起来。
2、模版的分离编译
函数模版和类模版都不支持声明和定义分离,所以最好的办法就是将声明和定义放在同一个源文件。
四、模版的优缺点
1.优点:
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生 2. 增强了代码的灵活性
2、缺点:
1. 模板会导致代码膨胀问题,也会导致编译时间变长 2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误标签:进阶,模版,函数,C++,参数,fun,特化,指针 From: https://blog.csdn.net/hffh123/article/details/143700591