list模拟实现

一、结点的定义

list模拟实现_第1张图片

有三个成员,2个指向前面和后面的指针,一个表示结点存储T类型的值。

对于_prev和_next,类型是  list_node*,不是list_node*,加上类型参数T之后,才是模板类的类型。

构造函数中,先把指针置空,然后val可以给一个默认构造的缺省值。

二、普通正向迭代器

list模拟实现_第2张图片

先将list_node typedef为Node,便于后续使用更方便。

一个成员变量为  Node* _node,表示迭代器当前指向的那个结点。

构造函数中,用传入的node,初始化_node,指针为内置类型,浅拷贝即可。

1、operator*()

对迭代器解引用,返回的是那个T类型的值val的引用。(无论T是内置类型还是自定义类型)

2、operator->()

返回的是val的地址。

val是结构体/自定义类型,val内部可能还有多个要访问的值

可以直接通过返回的地址/指针,用->找到其中要访问的值。

3、++/--/==

list模拟实现_第3张图片

 ++是让迭代器指向下一个结点,即让其成员_node变为_node->next。--同理。

前置++和--返回下一个/前一个迭代器位置,即改变后的*this的引用

后置++和--要先用tmp保存改变前的位置,最后传值返回tmp的拷贝

三、const迭代器

首先,对于所有迭代器,都是可以++,--修改的。

对于T*这种由原生指针(指针解引用就可以访问,且内存连续,++直接找到下一个结点)构成的迭代器,直接在 T*前加const,使得其无法解引用修改存储的值即可构成const_iterator。

但由于list中,内存不连续,且有时解引用得到的val为结构体(不方便)。

我们需要对++,--,*,->等进行运算符重载,因此就要定义一个__list_iterator的结构。

直接在迭代器类型前加const是错误的,这样使得const_iterator的类型变成了一个const对象,不能进行++,--等操作。const_iterator的const修饰的是对访问那个结点的值是否具有修改的权限

list模拟实现_第4张图片

如上图,返回的引用的值是否加const 修饰。

对于函数重载,我们只能通过参数类型/个数/顺序来区分,这里是仅通过返回值无法区分两个operator*()。

一种解决方法是重新定义一个const_iterator结构,仅在operator*()这里与iterator不同,但代码冗余度太大,重复部分太多。

还有一种方法是引入新的模板参数  Ref作为引用的返回值

list模拟实现_第5张图片

list模拟实现_第6张图片

iterator和const_iterator靠Ref和Ptr两个模板参数区分。看传入的T&和T*是否加const修饰。

实际上,两种迭代器仍然是两种结构,只不过我们用模板传入模板参数,让编译器生成了两个模板类,两个模板类通过对访问数据是否有修改来区分。

list模拟实现_第7张图片

先在ls内插入数据,然后用const引用接收,在Print函数内,*cit无法被修改 

四、insert/erase

list模拟实现_第8张图片

 list模拟实现_第9张图片

五、拷贝构造/operator= 

list模拟实现_第10张图片

list模拟实现_第11张图片

 空初始化为创建头节点,用的地方比较多,可以单独拿出来。

拷贝构造直接循环尾插即可。

赋值重载使用现代写法,交换得到_head指向拷贝得到的list。

注意:由于ls被const修饰,beign()返回的是const_iterator,需要用const迭代器接收

ls要完成交换,不能加const    std::swap需要两个参数

六、clear/析构

list模拟实现_第12张图片

循环删除时迭代器会失效,接收返回值更新一下。 

 list模拟实现_第13张图片

size()函数每次统计结点个数为O(N)

也可以增加一个成员变量,每次插入时++,删除时-- 

目录

一、结点的定义

二、普通正向迭代器

1、operator*()

2、operator->()

3、++/--/==

三、const迭代器

四、insert/erase

五、拷贝构造/operator= 

六、clear/析构


你可能感兴趣的:(C++人生,list,数据结构)