注:写本博客的目的接在记录学习过程,学习c++的新知识点,以及巩固以前学过的知识!!!
整篇文章都比较大部分都扣的很细节,重点看
/*
知识点概括:
1.当普通迭代器不能满足我们的需求时,需要对迭代器进行包装,包装迭代器需要对++、--、==、!=、*重载。关于对==的重载,是因为迭代器创建完成后不是指针,如果不重载==,那么使用==对比的则为迭代器的地址,而迭代器的地址和容器内存放的数据的地址是不相关的。
2.在使用引用时,如果被引用的值为临时变量(如:被引用的值为函数返回产生的临时变量),那么必须使用const修饰,否则会报错,如:没有与这些操作数匹配的 "!=" 运算符、表达式必须为左值或函数指示符。
3.struct和class为同种意思,只不过struct默认都为publc。
4.new Node、new Node(val) 、new Node[n]、Node tmp的区别
5.设计迭代器根据不同的调用,返回不同的值(const 与非const)
template<class T,class Ref>
此操作用来设计const迭代器
6.反向迭代器的适配器:设计以及使用
*/
程序结构:
list为双向头节点,在结构设计上,使用指针分别指向下一个节点与头节点,所以使用单独的struct、class来实现list的节点。
template<class T>
struct list_node {
list_node<T>* _prev;
list_node<T>* _next;
T _val;
list_node(const T& val = T()) :
_prev(nullptr),
_next(nullptr),
_val(val)
{}
};
在c++中struct也演变为了class,与class不同的是struct默认为public。
因为带有class属性,所以可以在struct中使用构造函数用于初始化。
有了struct list_node,再来个list来包含list_node的头节点以及个数
template<class T>
class list {
public:
typedef list_node<T> Node;
//code..
private:
Node* _head;
size_t size;
}
1.构造函数
list() {
_head = new Node; //question1
_head->_next = _head;
_head->_prev = _head;
_size=0;
}
思考:
new Node new Node(val) new Node[3]; 以及Node n的区别?
答:
new Node :创建单个Node,在堆上开辟空间
new Node(val):创建单个Node,使用val作为初始化
new Node[3]:创建3个Node,成为Node 数组
Node n:局部变量Node
2.基本的功能:
void push_back(const T& val) {
Node* newNode = new Node(val);
Node* tail = _head->_prev;
tail->_next = newNode;
newNode->_prev = tail;
newNode->_next = _head;
_head->_prev = newNode;
_size++;
}
3.迭代器的设计:
代码一:
template<class T>
class list {
public:
typedef list_node<T> Node;
typedef Node* iterator;
iterator begin(){
return _head->next;
}
iterator end(){
return _head;
}
//code..
private:
Node* _head;
size_t size;
}
代码一这种设计方式可以吗?
答:乍一看确实没有问题,但实际却不正确,由于list底层为链表,为不连续的空间,此时迭代器使用++,会使得迭代器找不到下一个Node,所以这种设计不正确。为此,需要包装迭代器,使得迭代器满足我们的需求。
template<class T, class Ref>
struct __iterator_list
{
typedef list_node<T> Node;
typedef __iterator_list<T> self;
Node* _node;
__iterator_list(Node* node) :
_node(node)
{
}
self& operator++() {
_node = _node->_next;
return *this;
}
self& operator++(int) {
self tmp(*this);
//self tmp(_node);//两者皆可
_node = _node->_next;
return tmp;
}
self& operator--() {
_node = _node->_prev;
return *this;
}
self& operator--(int) {
self tmp(*this);
//self tmp(_node);//两者皆可
_node = _node->_prev;
return tmp;
}
bool operator==(const self& val) { //question1
while (it != ret.end()) {
cout << *it << endl;
it++;
}
return _node == val._node;
}
bool operator!=(const self& val) {
return _node != val._node;
}
self& operator*() {
return _node->_val;
}
//如果在这里使用const T &operator*(){}行不?
};
template<class T>
class list {
public:
typedef list_node<T> Node;
//code..
private:
Node* _head;
size_t size;
}
迭代器本质上是指向数据的地址,也就是迭代器使用的是数据的地址,迭代器在进行++时,操作的是数据的地址。那么包装迭代器同样也是使用的数据的地址,所以我们在__list_iterator中使用构造函数,接收地址用于初始化迭代器。
代码解释:
既然传统的迭代器++无法实现++,那么我们在迭代器中,对迭代器++进行重载:
前置++
self& operator++() {
_node = _node->_next;
return *this;
}后置++
self& operator++(int) {
self tmp(*this); //self tmp(_node);//两者皆可
_node = _node->_next;
return tmp;
}重载操作符*
T& operator*() {
return _node->_val;
}
思考:
1.为什么返回值为*this,因为++本身就是对迭代器的++,++不能访问里面的值,所以返回的*this,即返回的iterator这个对象
2.self tmp(*this);涉及到了拷贝构造,这里我们需要实现拷贝构造函数吗?
答:不需要实现拷贝构造函数,c++默认的拷贝构造函数即可,tmp在这里主要是为了保存*this,即tmp就是*this,_node=_node->next后,*this会指向为下一个Node,而tmp仍然指向保存时的Node的值,此时返回tmp,即可实现后置++。
这里还请注意区分__iterator_list 与list的拷贝构造函数,不能混为一谈,list内实现的拷贝构造函数在__iterator_list不能使用。
迭代器还应可以取值操作,把迭代器指向的值返回,为了支持此操作,需要对*进行重载,返回值为T&。
3.关于对*操作符的重载
使用演示:
list<int>ret;
list<int>::iterator it = ret.begin();
cout<<*it;
还请看迭代器中的*this,迭代器中的*this属于调用*重载符吗?
不属于,因为this为指针,虽然this也是__iterator_list对象,但它终究为指针
*操作符只针对iterator对象,而非iterator的指针。
4.再来看第一点,为什么要返回*this,我们使用void可以吗?
使用void虽然不会报错,但会限制我们许多操作
现将其修改为
void operator++() {
_node = _node->_next;
}然后调用
list<int>::iterator it = ret.begin();
cout<<*++it;
"*" 的操作数必须是指针,但它具有类型 "void"
原因在于:cout的针对函数的使用,必须要有返回值,将返回值进行流输出,虽然*++it为int,但返回值为void,故报错
举例:
void fun(){
}
cout <<fun()<<endl;//这行代码必报错,通过这个试着去理解上面,假设换成void,是不是会报错?
在操作符重载这里,虽然我们没有使用一个变量去接收返回值,但仍需有返回值,为的是能够支持连续赋值,以及上述所说的cout流
5.为什么it.begin 与it.end()的比较不符合我们的预期呢?
list<int>::iterator it = ret.begin();声明出来的it不是指针,而是一个对象,所以使用iterator begin的地址与iterato end返回的iterator本身的地址去对比是不行的
//00000031B42FF838
//00000031B42FF858
//这里两个iterator的地址,不管里面存放多少个数据,他俩的地址之差总是不会改变,所以
//为了能使得iterator==iterator比较,我们得重载==运算符,使用Node的地址去比较bool operator==(const self& val) { return _node == val._node; } bool operator!=(const self& val) { return _node != val._node; }
重要
假设修改为bool operator==(self &val){}
调用如下:
list<int>::iterator it = ret.begin();
while (it != ret.end()) { //而这个比较却提示没有可用的!=重载符号?
cout << *it << endl;
it++;}
目前高老师给出的答案是编译器强制报错,要求比较类的重载必须使用const修饰参数---错错错
list<int>::iterator it = ret.begin();
it ==ret.end;//知道为什么这样会报错了吗?
我们直接使用it==ret.end()进行比较。
因为是直接使用ret.end(),这个函数肯定会有一个返回值,这个返回值由于我们没有用东西去接收,所以返回值为一个临时变量,这个临时变量由于我们不可能会对其进行修改,所以临时变量为const类型,那么iterator it,这个it是非const类型 ret.end()的返回值创建的临时变量为const类型 非const 与 const类型进行"!="操作,由于我们没有实现"!="的非const 与const 类型比较,所以会产生错误。
关于产生临时变量,这个是被大部分人认可的,可以通过一个测试来测试
double a = 10.8;
int &b = (int)a;//报错这里,(int)a 会产生一个临时变量,用这个临时变量去初始化b,会报错为常引用的变量b不能作为左值,也就是产生的这个临时变量,是一个const类型,所以我们const int &b=(int )a便可以编译通过。而不是编译器强制报错。。。。。。。。。。
那么const 迭代器应该怎么实现?
先来思考const应该修饰什么?修饰iterator本身吗?还是修饰iterator的返回值?
typedef __iterator_list<T> const_iterator;
template<class T>
struct const__iterator_list {
typedef list_node<T> Node;
typedef const__iterator_list self;
}
template<class T>
class list {
public:
typedef list_node<T> Node;
typedef const const__iterator_list<T> const_iterator;
}
这样写对吗?
这里的const修饰的是iterator ,既然const修饰iterator,所以 const_iterator it; it++操作还能实现吗?
不能,因为it为const类型,不能被修改,所以我们只能使用const修饰返回值。
template<class T>
struct const__iterator_list {
typedef list_node<T> Node;
typedef const__iterator_list<T> self;
Node* _node;
__iterator_list(Node *node):
_node(node)
{
}
self& operator++() {
_node = _node->_next;
return *this;
}
self& operator++(int) {
self tmp(_node);
_node = _node->_next;
return *this;
}
const T& operator*() const{ 仅仅将这里修改
return _node->_val;
}
bool operator==(const self &val) {
return _node == val._node;
}
bool operator!=(const self &val) {
return _node != val._node;
}
};
由于其他操作都不会影响到里面的内容,仅仅对*的重载会影响,所以这里也就仅仅修改
T& operator*() const为const T& operator*() const ,返回值用const修饰。
所以现在的代码为:
template<class T>
struct const__iterator_list {
typedef list_node<T> Node;
typedef const__iterator_list<T> self;
Node* _node;
__iterator_list(Node *node):
_node(node)
{
}
self& operator++() {
_node = _node->_next;
return *this;
}
self& operator++(int) {
self tmp(_node);
_node = _node->_next;
return *this;
}
const T& operator*() const{ 仅仅将这里修改
return _node->_val;
}
bool operator==(const self &val) {
return _node == val._node;
}
bool operator!=(const self &val) {
return _node != val._node;
}
};
template<class T>
struct __iterator_list {
typedef list_node<T> Node;
typedef __iterator_list<T> self;
Node* _node;
__iterator_list(Node *node):
_node(node)
{
}
self& operator++() {
_node = _node->_next;
return *this;
}
self& operator++(int) {
self tmp(_node);
_node = _node->_next;
return *this;
}
T& operator*() const{
return _node->_val;
}
bool operator==(const self &val) {
return _node == val._node;
}
bool operator!=(const self &val) {
return _node != val._node;
}
};
有没有发现这样很繁琐?
仅仅就一行代码不同,就需要将整段代码全部整下来。
有!
template<class T,class Ref>
struct __iterator_list {
typedef list_node<T> Node;
typedef __iterator_list<T,Ref> self;
Node* _node;
__iterator_list(Node *node):
_node(node)
{
}
self& operator++() {
_node = _node->_next;
return *this;
}
self& operator++(int) {
self tmp(_node);
_node = _node->_next;
return *this;
}
Ref operator*(){ 仅仅将这里修改
return _node->_val;
}
bool operator==(const self &val) {
return _node == val._node;
}
bool operator!=(const self &val) {
return _node != val._node;
}
};
模板参数里面再添加一个参数为class Ref,使用Ref当作*的返回值,那么Ref应该怎么使用?或者说怎么定义?
template<class T>
class list {
public:
typedef list_node<T> Node;
typedef __iterator_list<T,T&> iterator;
typedef __iterator_list<T, const T&> const_iterator;
//code...
}
在list里面,我们分别给T 与 Ref参数为T T&,这样在使用iterator初始化迭代器时,上面的模板参数会初始化为 typedef __iterator_list<T,T&> self;
返回值T& operator*(){
return _node->_val;
}
对于const
我们分别给T 与 Ref参数为T, const T&,这样在使用iterator初始化迭代器时,上面的模板参数会初始化为 typedef __iterator_list<T,const T&> self;
返回值const T& operator*(){
return _node->_val;
}
反向迭代器:
代码一:
template<class T, class Ref>
struct reserve__iterator_list {
typedef list_node<T> Node;
typedef reserve__iterator_list<T, Ref> self;
Node* _node;
reserve__iterator_list(Node* node) :
_node(node)
{
}
self& operator++() {
_node = _node->_prev;
return *this;
}
self& operator++(int) {
self tmp(_node);
_node = _node->_prev;
return *this;
}
Ref operator*() const {
return _node->_val;
}
bool operator==(self& val) {
return _node == val._node;
}
bool operator!=(self& val) {
return _node != val._node;
}
};
这样写也是没有问题的,但还是存在代码冗长。
优化:通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭 代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行 包装即可。
template<class Iterator,class Ref>
class reversIterator {
public:
typedef reversIterator self;
Iterator _con;
reversIterator(Iterator it)://question1
_con(it)
{
}
Ref& operator*() { //question2
Iterator tmp = _con;
tmp--;
return *tmp;
}
self& operator++() {
_con--;
return *this;
}
self& operator--() {
_con++;
return *this;
}
bool operator==(const self& s) {
return _con == s._con;
}
bool operator!=(const self& s) {
return _con != s._con;
}
};
}
注解:question1处的 --。
因为tmp为iterator迭代器,所以tmp--实际上是调用的iterator的--。
虽然优化后的迭代器代码长度并没有改变多少,但是这个反向迭代器不仅仅可以让list使用,还可以让string 、vector等使用,只要正向迭代器支持++ 与--即可。
思考:question1处,这里要使用const吗?要使用引用吗?
答: 1.不用使用const ,如果接收const_iterator,那么就为const_iterator iterator, 如果接收为iterator ,那么就为iterator iterator。
2.不用const也可以,return reverse_iterator(end()),调用处为end()返回的是临时变量,为const修饰,但我们这里不是引用,而是局部变量,所以这个const会去初始化局部变量okok3.不能使用引用,因为return reverse_iterator(end());调用处为end()返回的是临时变量,这个临时变量不能被引用。
4.综上只有引用才会考虑要引用的值是否为临时变量。
question2处 ,解释*重载
答:1.反向迭代器里面包含的是正向迭代器,正向迭代器已经实现了*的重载,所以可以直接使用正向迭代器的重载。
2.由上图可知,反向迭代器的rbegin实际上指向的是整个数据的结尾+1个位置,
那么为了使*重载能有效返回值,就需要进行--操作,但是这个--操作还不能影响反向迭代器,即不能使反向迭代器移动,所以只能创建一个tmp,拷贝一份迭代器,将拷贝的迭代器--,并且返回。