前言:
目前学习阶段遇到最难的一部分,可能与我一开始思考方向不对有很大的关系。因此,从最开始就要纠正自己的错误。
认识迭代器:
👉:容器迭代器设计思路体现了C++三大特性中的封装,屏蔽底层实现细节和结构差异,从而提供统一的访问方式。当解引用和++操作无法正常访问数据时,这个时候就要改变底层的设计,使其能够进行解引用和++操作
认识封装:
👉:我们现实生活中各种各样的银行卡:工商、建商、农行......各个银行卡底层的支付方式都不一样,因此线上支付的逻辑就是将不同银行的支付方式进行一个封装,我们不用管他底层是怎么样实现的,但是访问方式都是统一的,即扫码付款。
一、list迭代器与string/vector之间的区别
这里说的区别主要从模拟实现的方式去考虑,而非库函数中自带的list、string以及vector。
先前学习string/vector时,迭代器的底层完全可以说成是指针。我们可以很轻松的写出如下代码:
int main() { jc::vector<int> lt1; //此vector 在命名空间jc中定义 vector.push_back(1); vector.push_back(1); vector.push_back(1); vector.push_back(1); jc::vector<int>::iterator it1 = lt1.begin(); while (it1 != lt1.end()) { cout << *it1 << " "; ++it1; } cout << endl; }
因为底层是指针:
👉:可以直接进行解引用得到数据
👉:可以直接通过++去访问到下一个数据的地址
★★★
但list底层模拟实现迭代器的方式与前两者完全不同!
因为当我们去创建一个链表的时候,解引用得到的是一个节点,而非我们所想要的数据。
而当我们对数据进行++处理时,因为链表中节点地址是不连续的,因此++操作会使得我们丢失原来的数据。
为了能继续使用解引用操作和++操作,我们需要通过运算符重载的方式去实现迭代器,因此需要在命名空间里定义新的对象,来封装节点指针,重载运算符,去模拟指针的行为
二、代码分析
通过下述代码来分析这一过程。
👉大致过程(从测试开始)
1:通过下列代码,创建了一个链表:lt1 。随后插入四个数据。
void empty_init() { _head = new Node(); _head->_next = _head; _head->_prev = _head; } list() { empty_init(); }
2:此处的迭代器不再是指针,而是struct类类型对象 it1,类型为:list_iterator<T>
bit::list<int>::iterator it1 = lt1.begin();
问:lt1.begin();实现了什么?
答:如图所示,匿名对象会调用相应自定义类的构造函数,此处临时构造一个 list_iterator<T> 类型的临时对象tmp,并返回给 it1,同时将_hear->next的指针(链表的头)传递给了成员变量_node,当此行结束时,匿名对象tmp结束生命周期
而这个_node是整个代码中,最核心的部分,因为他是链表的头节点,包含了所有信息,通过这个节点,我们可以访问所有的数据,“魔法”由此开始
3:循环过程
根据先前所学的运算符重载的知识,*it1会调用函数①,先前_node已经保存了链表的头节点,因此通过_node->_data便可直接访问数据。
而++it1则会调用函数②,通过_node->_next即可访问链表的下一个节点。
注:
1:时刻牢记此处 it1 不再是指针,而是类类型对象
2:&it1 为隐含的this指针