1. 非类型模板参数
对于函数模板和类模板,模板参数并不局限于类型,普通值也可以作为模板参数。
STL 的 array 就有一个非类型模板参数。
T 是类型,而 N 这里并不是类型,而是一个常量。
类型模板参数定义的是虚拟类型,注重的是你要传什么,而非类型模板参数定义的是常量。
1.1 array
array是一个固定大小的顺序容器(静态数组),是 C++11 新增的,它有什么独特的地方吗?
很可惜,基本没有,并且 vector 可以完全碾压 array,这就是为什么只在这里简单地讲讲 array。
看段代码:
#include <iostream> #include <array> #include <vector> using namespace std; int main() { vector<int> v(100, 0); array<int, 100> arr; cout << "v : " << sizeof(v) << endl; //这里sizeof算的是成员变量的大小,VS2022下vector应该有四个成员变量,32位平台每个指针是4个字节,因此16字节 cout << "arr : " << sizeof(arr) << endl; return 0; }
vector 是开在空间大的堆上的而 array 是开在栈上的,堆可比栈的空间大太多太多了。
array 能做的操作几乎 vector 都能做,因为 vector 的存在 array 显得有些一无是处。
所以我们拿 array 去对标 vector 是不对的,拿去和原生数组比还是可以对比的。
但是 array 也只是封装过的原生数组罢了,就是有了接口函数,
比起原生数组,array 的最大优势也只是有一个越界的检查,读和写都可以检查到是否越界。
原生数组的读检查不到,写只能检查到后面几个数,
#include <iostream> #include <array> #include <vector> using namespace std; int main() { int a[10]; array<int, 10> arr; // array也不会初始化 int x = a[15]; // 没报错 a[10] = 2; // 报错 a[11] = 2; // 没报错 int y = arr[15]; // 报错 arr[10] = 2; // 报错 arr[11] = 2; // 报错 return 0; }
在 C++11 增加完 array 后备受吐槽,从简化的角度来说完全可以不增加 array。
并且现在大多数人都习惯了用原生数组,基本没人用array。
1.2 非类型模板参数的使用场景
假设我们要定义一个静态栈:
#define N 100 template<class T> class Stack { private: int _arr[N]; int _top; };
如果定义两个容量不一样的栈,一个容量是100 另一个是 500,能做到吗?
这就像 typedef 做不到一个存 int 一个存 double,而使用模板可以做到 st1 存 int,st2 存 double。
这里你的 #define 无论是改 100 还是改 500 都没办法解决这里的问题,
对应的,这里使用非类型模板参数就可以做到 s1 存 100,s2 存 500。
#include <iostream> using namespace std; template<class T, size_t N> class Stack { private: int _arr[N]; int _top; }; int main() { Stack<int, 100> st1; // 大小是100 Stack<int, 500> st2; // 大小是500 return 0; }
在模板这定义一个常量 N,派遣它去做数组的大小。
于是我们就可以在实例化 Stack 的时候指定其实例化对象的大小了,分别传 100 和 500。
1.3 注意事项
注意事项 ①:非类型模板参数是是常量,是不能修改的。
#include <iostream> using namespace std; template<class T, size_t N> class Stack { public: void modify() { N = 10; // 错误 C2106 “ = ”: 左操作数必须为左值 } private: int _arr[N]; int _top; }; int main() { Stack<int, 100> st1; st1.modify(); return 0; }
注意事项 ②:有些类型是不能作为非类型模板参数的,比如浮点数、类对象以及字符串。
非类型模板参数基本上都是整型家族,char也是整形家族,也只有整型家族是有意义和价值的
#include <iostream> using namespace std; template<class T, double N> // 错误 C2058 常量表达式不是整型 class Stack { private: int _arr[N]; int _top; }; int main() { Stack<int, 100> st1; return 0; }
注意事项 ③:非类型的模板参数必须在编译期就能确认结果。
即非类型模板参数的实参只能是常量。
#include <iostream> using namespace std; template<class T, size_t N> // 错误 C2058 常量表达式不是整型 class Stack { private: int _arr[N]; int _top; }; int main() { size_t N; cin >> N; Stack<int, N> st1; // 错误 C2971 “Stack” : 模板参数“N”:“N”: 包含非静态存储持续时间的变量不能用作非类型参数 return 0; }
2. 模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码
但是,对于一些特殊类型,可能我们就要对其进行一些 "特殊化的处理" 。
举例:如果不对特殊类型进行特殊处理就可能会出现一些问题,比如:
#include <iostream> using namespace std; class Date // 简化的日期类 { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} bool operator<(const Date& d) const { if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day)) { return true; } else { return false; } } private: int _year; int _month; int _day; }; template<class T> // 函数模板 -- 参数匹配 bool Less(T left, T right) { return left < right; } int main() { cout << Less(1, 2) << endl; // 可以比较,结果正确 Date d1(2023, 1, 1); Date d2(2023, 1, 2); cout << Less(d1, d2) << endl; // 可以比较,结果正确 Date* p2 = &d2; Date* p1 = &d1; cout << Less(p1, p2) << endl; // 可以比较,结果错误 return 0; }
这里我们想比较的是指针指向的内容,而不是指针本身,怎么解决?
2.1 函数模板的特化
首先,必须要先有一个基础的函数模板。
其次,关键字 template 后面接上一对空的 <> 尖括号。
然后,函数名后跟一对尖括号,尖括号中指定需要特化的内容。
最后,函数形参表必须要和模板函数的基础参数类型完全相同。
template<class T> // 函数模板 -- 参数匹配 bool Less(T left, T right) { return left < right; } template<> // 针对某些类型要特殊化处理 ———— 使用模板的特化解决 bool Less<Date*>(Date* left, Date* right) { return *left < *right; }
代码演示:
#include <iostream> using namespace std; class Date // 简化的日期类 { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} bool operator<(const Date& d) const { if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day)) { return true; } else { return false; } } private: int _year; int _month; int _day; }; template<class T> // 函数模板 -- 参数匹配 bool Less(T left, T right) { return left < right; } template<> // 针对某些类型要特殊化处理 ———— 使用模板的特化解决 bool Less<Date*>(Date* left, Date* right) { return *left < *right; } int main() { cout << Less(1, 2) << endl; // 可以比较,结果正确 Date d1(2023, 1, 1); Date d2(2023, 1, 2); cout << Less(d1, d2) << endl; // 可以比较,结果正确 Date* p2 = &d2; Date* p1 = &d1; cout << Less(p1, p2) << endl; // 可以比较,结果正确 return 0; }
解读:对于普通类型,它还是会调正常的模板。对于 Date* 编译器就会发现这里有个
专门为 Date* 而准备的特化版本,编译器会优先选择该特化版本。这就是函数模板的特化。
思考:现在我们加一个普通Less函数的函数重载,Date* 会走哪个版本?
bool Less(Date* left, Date* right) { return *left < *right; }
答案:函数重载,会走直接匹配的普通函数版本,因为是现成的,不用实例化。
你可以这么理解:原模板是生肉,模板特化是半生不熟的肉,直接匹配的普通函数是熟肉。
所以:函数模板不一定非要特化,因为在参数里面就可以处理,
写一个匹配参数的普通函数也更容易理解。
从C语言到C++_21(模板进阶+array)+相关笔试题(下):https://developer.aliyun.com/article/1521899?spm=a2c6h.13148508.setting.20.712b4f0eDngT44