前言
在模版初阶我们学习了函数模版和类模版的相关知识,对模版有了一定的了解,接下来我们将对模版进行进一步的了解,话不多说,直接上货!!!
本节内容
1. 非类型模板参数
2. 类模板的特化
3. 模板的分离编译
1.非类型模版参数
模板参数分类类型形参与非类型形参 。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是 用一个常量作为类(函数)模板的一个参数 ,在类 ( 函数 ) 模板中可将该参数当成常量来使用 。
比如我们简单写一个静态栈,可以定义不同大小的栈,这里我们没有传默认参数,传也可以。
template<size_t N> class Stack { private: int _a[N]; int _top; }; int main() { Stack<5>s1; Stack<10>s2; return 0; }
其实非类型形参在大小定义方面用的比较多,把大小当做常量。对于传参都传的是整数。
注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的 。
2. 非类型的模板参数必须在编译期就能确认结果 。
在C++11版本的array用了 非类型的模板参数, 来代替静态数组。
#include <array> #include <iostream> using namespace std; int main(){ array<int, 10> a1; array<int, 100> a2; int a3[10]; return 0; }
那么用array定义数组的好处是什么呢,是数组初始化吗,当然不是。
相比较于int a3[10],其实array的好处是越界检查方面,array对于数组的读和写都会进行越界检查,而int a3[10],对于读不会检查,而写会检查,进行的是抽取,一般会在数组后面默认分配两个数据大小空间左右。
array<int, 10> a1; array<int, 100> a2; // a1[100] = 3; int a3[10]; cout << a3[100] << endl; a3[10] = 1; // a3[12] = 1;
array底层访问是调用了一个函数进行实现,都会调用assert语句来判断。
template<class T, size_t N = 10> class array { public: T& operator[](size_t index) { assert(index < N); return _array[index]; } private: T _array[N]; size_t _size; };
其实对于数组的定义,在C++中我们采用vector来定义,可以进行初始化。
vectorv(10,1);
但是如果要频繁的开设数组,其实array的效率可能要高些,因为array是在栈上开空间的,栈是向下生长的,vector是在堆上。
2.模版的特化
2.1概念
通常情况下, 使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果 ,需要特殊处理,比如:实现一个专门用来小于比较的函数模版
class Date { public: Date(int year = 1, int month = 1, int day = 1) :_year(year), _month(month), _day(day) {} bool operator<(const Date& other) const { if (_year != other._year) return _year < other._year; if (_month != other._month) return _month < other._month; return _day < other._day; } /* friend ostream&operator<<(ostream& out, const Date& d) { out << d._year << "-" << d._month << "-" << d._day; return out; }*/ private: int _year; int _month; int _day; }; template <class T> bool Less(const T &left,const T& right) { return left < right; } int main() { cout << Less(1, 2) << endl; // 可以比较,结果正确 Date d1(2024, 7, 9); Date d2(2024, 7, 8); cout << Less(d1, d2) << endl; // 可以比较,结果正确 Date* p1 = &d1; Date* p2 = &d2; cout << Less(p1, p2) << endl; // 可以比较,结果错误 return 0; }
可以看到, Less 绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中, p1 指向的d1 显然大于 p2 指向的 d2 对象,但是 Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2 指针的地址,这就无法达到预期而错误。
此时,就 需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 。模板特化中分为函数模板特化 与 类模板特化 。
2.2函数模版特化
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template <class T> bool Less(const T &left,const T& right) { return left < right; } template<> bool Less<Date*>(Date* const& left, Date* const& right) { return *left < *right; } /* template<> bool Less<const Date*>(const Date* const& left, const Date* const& right) { return *left < *right; } */
注意:const要放在*之后,修饰的才是引用变量本身,放在*之前是修饰指向内容
非const特化
template <class T> bool Less( T &left,T& right) { return left < right; } template<> bool Less<Date*>(Date* &left, Date* &right) { return *left < *right; }
所以对于下面这段代码
Date * p1 = & d1 ;
Date * p2 = & d2 ;
cout << Less ( p1 , p2 ) << endl ; // 调用特化之后的版本,而不走模板生成了
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给 出。
bool Less(Date* left, Date* right) { return *left < *right; }
该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。
C++之模版进阶篇(下)https://developer.aliyun.com/article/1625011