C++模版进阶

avatar
作者
猴君
阅读量:0

前言

这一篇博客讲一下模版的其他内容,进阶内容,后面就会讲继承了,难度上升

1. 非类型模版参数

所谓非类型模版参数,就是在模版参数列表中,即可以存在类型参数,也可以存在非类型参数

template<class T, size_t N> class Array { private: 	T arr[N]; };  int main() { 	Array<int, 100> a1; 	//这个就表示建立一个100个整型的数组 	Array<int, 10> a1; 	return 0; } 

这个也是根据不同的模版参数,编译器生成不同的类

template<class T, size_t N> class Array { public: 	func() 	{ 		N--; 	} private: 	T arr[N]; };  int main() { 	Array<int, 100> a1; 	a1.func(); 	//这个就表示建立一个100个整型的数组 	Array<int, 10> a1; 	return 0; } 

在这里插入图片描述

还有要值得注意的是,非类型的模版参数是个常量,不可修改的
还有就是模版是不会进行编译的,或者说,只会对模版的外壳进行编译,不会对模版的内部进行编译,所以说模版内部有错误,不管是什么错误都是不会检查出来的,只有在调用那个函数的时候才会检查出来错误
还有值得说的就是,在C++20之前是只支持整型的非类型模板参数的,C++20就支持了内置类型的非类型模板参数
编译器默认是C++14,但是是可以修改的
在这里插入图片描述
在这里插入图片描述

 template<class T, double N> class Array { public: 	int func() 	{ 		N--; 	} private: 	//T arr[N]; }; 
	Array<int, 12.2> a; 

只支持内置类型,不支持自定义类型

2. array

在这里插入图片描述

接下来,我们来认识一下array,array其实就是一个数组的类和模版,和数组没什么区别

	array<int,10> a; 

这就表示建立了一个10个整型的数组,但是这个和C语言的数组有什么区别吗,区别只有一个,那就是有关越界访问的
在这里插入图片描述
int arr[10],是这样的,会在数组的后面加上一两个位置,这两个位置设置为一些固定值,程序结束时看这些值有没有被改变,如果被改变了的话,说明就越界了,注意,这个报错是在程序结束时才报错

	int arr[10] = { 0 }; 	arr[11] = 10; 

在这里插入图片描述
比如这个更改了后面一个的值就报错说越界了

但是array是只要越界了就会马上报错,虽然比我们直接定义数组有点好处,但是这个array并没有什么用,为什么呢,因为还有vector啊,vector不比array好用多了?
我们再来讲一个其他东西

void PrintVector(const vector<int>& v) { 	vector<int>::const_iterator it = v.begin(); 	while (it != v.end()) 	{ 		cout << *it << " "; 		++it; 	} 	cout << endl; } 

这是一个vector的打印函数,但是它只能打印int的,不能打印其它的,于是我们就可以实现一个打印函数的模板了

template<class T> void PrintVector(const vector<T>& v) { 	vector<T>::const_iterator it = v.begin(); 	while (it != v.end()) 	{ 		cout << *it << " "; 		++it; 	} 	cout << endl; } 
	vector<double> a = { 1.1,2.3,3.4,5.3 }; 	PrintVector(a); 

但是我们这样写却不能正常运行

template<class T> void PrintVector(const vector<T>& v) { 	typename vector<T>::const_iterator it = v.begin(); 	while (it != v.end()) 	{ 		cout << *it << " "; 		++it; 	} 	cout << endl; } 

在这里插入图片描述
但是我们在前面加上typename就可以正常使用了,为什么呢
第一是因为vector::const_iterator能这样使用的只有静态变量,typedef内容,内部类,到底是哪个呢,因为有T嘛,还没有初始化,所以不知道是什么,加上typename就指定了是那种typedef或者变量
第二就是没有实例化,所以不能随便访问,万一有错呢,所以加上那个东西
反正怎么说呢,记住这个东西就可以了,目前只有这种情况要特殊加上typename

3. 模板的特化

先看个例子

template<class T> class myless { public: 	bool operator()(const T& t1, const T& t2) 	{ 		return t1 < t2; 	} }; 
	myless<Date> a; 	cout<<a(Date(23, 2, 3), Date(22, 3, 4)); 

在这里插入图片描述
这个可以正常比较

	myless<Date*> a; 	cout<<a(new Date(23, 2, 3),new Date(22, 3, 4)); 

在这里插入图片描述
在这里插入图片描述
但这个就无法正常比较了,原因就是底层比较的是指针,开辟空间的指针间的大小是无法确定的
解决办法就是重新建立一个仿函数

template<class T> class myless1 { public: 	bool operator()(const T& t1, const T& t2) 	{ 		return *t1 < *t2; 	} }; 
	myless1<Date*> a; 	cout<<a(new Date(23, 2, 3),new Date(22, 3, 4)); 

在这里插入图片描述
这是一种方法,再来个模板,但是我们还有其他方法就是模板特化

3.1 函数模板特化

 template<class T> bool Less(T left, T right) { 	return left < right; } 

还是这个函数,还是不能比较指针类的东西,于是就可以特化了

template<> bool Less<int*>(int* left, int* right) { 	return *left < *right; } 

函数模板的特化就是这个样子,这个就可以比较int类型的指针了

	cout << Less(new int(1), new int(2)); 

在这里插入图片描述
需要什么就特化什么
在这里插入图片描述
但是函数模板特化有个很坑的点就是参数有const有&还有的时候容易出错,比如上面这个就不行,为什么呢,因为模板的意思是left不能修改,但是特化的意思是left不能修改,所以不一样,所以不行
在这里插入图片描述
把const移到*后面就可以了,还有就是const要在&的前面,因为模板都在前面

3.2 类模板特化

template<class T1,class T2> class Date { public: 	Date() 	{ 		cout << "template<class T1,class T2>" << endl; 	} private: };  template<> class Date<int,double> { public: 	Date() 	{ 		cout << "class Date<int,double>" << endl; 	} private: }; 
	Date<int, double> a; 

在这里插入图片描述
像这种全部模板参数都特化,叫做全特化
下面介绍几个偏特化

template<class T> class Date<T ,double> { public: 	Date() 	{ 		cout << "class Date<int,double>" << endl; 	} private: }; 

这个表示第二个模板参数是double就调用这个

template<class T1,class T2> class Date<T1*, T2*> { public: 	Date() 	{ 		cout << "class Date<T1*, T2*>" << endl; 		cout << typeid(T1).name() << endl; 	} private: }; 
	Date<int*,int*> a; 

在这里插入图片描述
这个特化就表示只要是指针就调用这个模板,其中T1和T2不一定是指针,这样是为了以后用T1和T2定义非指针变量
这个的作用就是我们一开始提的,如果传的是Date的话,那么就可以用这个来比较了,T1就是Date,专门针对Date来特化一个

template<class T1, class T2> class Date<T1&, T2&> { public: 	Date() 	{ 		cout << "class Date<T1&, T2&>" << endl; 		cout << typeid(T1).name() << endl; 	} private: }; 
	Date<int&,int&> a; 

在这里插入图片描述
这个就表示,只要是引用就调用这个模板,而且T1和T2的类型原理和指针是类似的

4. 模板分离编译

接下来我们讲一下模板分离编译,也就是解释为什么模板类的函数声明与定义不能分开的原因
为什么呢
如果说模板类的函数声明与定义分离,声明在.h中,定义在.cpp中,我们又说过,模板是不会进行编译的,意思是.cpp里面的函数就不会进行连接,也就相当于.cpp里面的内容你都白写了,根本没有用,那么这样的话,你要是使用模板的时候,就会去包含的头文件里面找,结果只有声明没有定义,那么就会出错。
所以最好的解决办法就是不要声明与定义分离

总结

下一篇博客讲继承

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!