反向迭代器的实现
这里以链表为例:
正向迭代器的end就是反向迭代器的rbegin,正向迭代器的begin是反向迭代器的rend
普通思维:拷贝一份正向迭代器,修改一下,使之成为反向迭代器
优化思维:既要考虑list的反向迭代器,也要考虑vector的反向迭代器
这里用复用的方法,使vector和list都能用这个反向迭代器
对于反向迭代器operator ++走到前一个位置
namespace myspace { //复用,迭代器适配器 template <class Iterator,class Ref,class Ptr> struct __reverse_iterator_ { Iterator cur;//通过正向迭代器实现反向迭代器 typedef __reverse_iterator_<Iterator,Ref,Ptr> RIterator; __reverse_iterator_(Iterator it)//it是正向迭代器 :cur(it) {} operator++() { --_cur; return *this; } operator--() { ++_cur; return *this; } Ref operator *() { Iterator tmp = _cur; --tmp; return *tmp; } Ptr operator ->() { return &(operator*()) } bool operator !=(const RIterator& it) { return_cur != it._cur; } }; }
在list.h添加这个迭代器
传参过程
并在list.h设置rend和rbegin
用这个反向迭代器适配vector
这是迭代器适配器,可适配出反向迭代器
如果类能++和--,就能通过迭代器适配器进行反向迭代
迭代器功能分类:单向迭代器,双向迭代器,随机迭代器
随机迭代器是一种特殊的双向迭代器
适配器真正的特点就是复用
非类型模板参数
我们可用模板创建一个数组,但是我们想让int数组开辟100个空间,double开辟1000个空间
,除过修改N之外,还能这样做,再加一个非类型模板参数,传的参数不是类型,是常量
注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。
库里面的array(静态数组),只支持[],不支持插入……
直接使用array越界,会被查到array不管越界读还是越界写都会被查到,因为array[]是函数调用,函数里面会进行检查是否越界
C语言中越界读检查不到,越界写能检查出来,越界写是抽查
此时又不会报错
函数模板特化 |类模板特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:写一个日期类,进行日期比较
特化不能单独存在,必须依赖于原函数模板或类模板
这一组比较的结果应该是0,但打印出来了1,这种结果不是我们想要的
我们进行模板特化
特化:对某些类型进行特殊化处理
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
如果传过来的数据是Date*类型,就走下面这个特化函数
对类模板也能特化
使用优先级队列和模板特化进行排序
// 类模板 namespace bit { template<class T> struct less { bool operator()(const T& x1, const T& x2) const { return x1 < x2; } }; //特化 template<> struct less<Date*> { bool operator()(Date* x1, Date* x2) const { return *x1 < *x2; } }; } int main() { std::priority_queue<Date, vector<Date>, bit::less<Date>> dq1; dq1.push(Date(2022, 9, 27)); dq1.push(Date(2022, 9, 25)); dq1.push(Date(2022, 9, 28)); dq1.push(Date(2022, 9, 29)); while (!dq1.empty()) { const Date& top = dq1.top(); cout << top._year << "/" << top._month << "/" << top._day << endl; dq1.pop(); } cout << endl; std::priority_queue<Date*, vector<Date*>, bit::less<Date*>> dq2; dq2.push(new Date(2022, 9, 27)); dq2.push(new Date(2022, 9, 25)); dq2.push(new Date(2022, 9, 28)); dq2.push(new Date(2022, 9, 29)); while (!dq2.empty()) { Date* top = dq2.top(); cout << top->_year << "/" << top->_month << "/" << top->_day << endl; dq2.pop(); } return 0; }
特化不能单独存在
全特化
全特化即是将模板参数列表中所有的参数都确定化。
template<class T1, class T2> class Data { public: Data() { cout << "Data<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; template<> class Data<int, char> { public: Data() { cout << "Data<int, char>" << endl; } private: int _d1; char _d2; };
有特化就走特化,没特化就走其它的
偏特化(半特化)
template<class T1, class T2> class Data { public: Data() { cout << "Data<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; template <class T1> class Data<T1, int> { public: Data() { cout << "Data<T1, int>" << endl; } private: T1 _d1; int _d2; };
还能这样写,只要是指针就执行以下程序
//两个参数偏特化为指针类型 template <typename T1, typename T2> class Data <T1*, T2*> { public: Data() { cout << "Data<T1*, T2*>" << endl; } private: T1 _d1; T2 _d2; };
两个参数偏特化为引用类型
//两个参数偏特化为引用类型 template <typename T1, typename T2> class Data <T1&, T2&> { public: Data(const T1& d1, const T2& d2) : _d1(d1) , _d2(d2) { cout << "Data<T1&, T2&>" << endl; } private: const T1& _d1; const T2& _d2; }; template <typename T1, typename T2> class Data <T1&, T2*> { public: Data(const T1& d1, const T2* d2) : _d1(d1) , _d2(d2) { cout << "Data<T1&, T2&>" << endl; } private: const T1& _d1; const T2& _d2; };
也可以这样写,只要满足某些特化条件就会进入满足条件的特化
模板分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式
模板不支持分离编译
我们用之前写过的vector,把push_back和insert进行分离编译
在vector.cpp里面实现这俩个函数
这样写会报错
加上typename这些错误消失
为了简短点,我们也可以把这俩个函数的实现放到和.h相同的域名中
加typename原因:模板参数里即可用typename也可用class,而这里的typename是告诉编译器这里是一个类型
Typename关键字 告诉编译把一个特殊的名字解释成一个类型,在下列情况下必须对一个name使用typename关键字:
一个唯一的name(可以作为类型理解),嵌套在另一个类型中;
依赖于一个模板参数,就是说模板参数在某种程度上包含这个name,当模板参数是编译器在指认一个类型时便会产生误解
为了保险起见,应该在所有编译可能错把一个type当成一个变量的地方使用typename,如果你的类型在模板参数中是有限制的,那就必须使用typename
这种情况也要加typename
之后去调用push_back,报了一个链接错误
这是因为push_back的声明和定义分离了
因为这里有声明,但找不到定义,调用函数都会call一个地址,但是没有找到push_bakc的地址,因为push_back没有被实例化,调用函数,调的不是模板,调的是对应的实例化,.h文件里面的函数不会去链接,因为.h里面有这些函数的定义,vector<int> v会让这些函数实例化,直接就有了定义,编译阶段会确定这些函数的地址了,而push_back和insert不一样,这俩个函数.h只有这俩个的声明,没有定义,地址就只能在链接阶段去确认。报错说明:链接阶段没有找到地址,没找到的原因:T没办法确定,所以没有实例化,push_back和insert就没有进符号表,所以没实例化
解决办法:
1.模板声明和定义不要分离到.h和.cpp
2.显示实例化(不太推荐)
在实现模板的地方显示实例化,如果要传double类型,又要写一个template vector<double>进行显示实例化,这种方法比较麻烦!
模板总结
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误