一、结点的定义
有三个成员,2个指向前面和后面的指针,一个表示结点存储T类型的值。
对于_prev和_next,类型是 list_node<T>*,不是list_node*,加上类型参数T之后,才是模板类的类型。
构造函数中,先把指针置空,然后val可以给一个默认构造的缺省值。
二、普通正向迭代器
先将list_node<T> typedef为Node,便于后续使用更方便。
一个成员变量为 Node* _node,表示迭代器当前指向的那个结点。
构造函数中,用传入的node,初始化_node,指针为内置类型,浅拷贝即可。
1、operator*()
对迭代器解引用,返回的是那个T类型的值val的引用。(无论T是内置类型还是自定义类型)
2、operator->()
返回的是val的地址。
当val是结构体/自定义类型,val内部可能还有多个要访问的值。
可以直接通过返回的地址/指针,用->找到其中要访问的值。
3、++/--/==
++是让迭代器指向下一个结点,即让其成员_node变为_node->next。--同理。
前置++和--返回下一个/前一个迭代器位置,即改变后的*this的引用。
后置++和--要先用tmp保存改变前的位置,最后传值返回tmp的拷贝。
三、const迭代器
首先,对于所有迭代器,都是可以++,--修改的。
对于T*这种由原生指针(指针解引用就可以访问,且内存连续,++直接找到下一个结点)构成的迭代器,直接在 T*前加const,使得其无法解引用修改存储的值即可构成const_iterator。
但由于list中,内存不连续,且有时解引用得到的val为结构体(不方便)。
我们需要对++,--,*,->等进行运算符重载,因此就要定义一个__list_iterator的结构。
直接在迭代器类型前加const是错误的,这样使得const_iterator的类型变成了一个const对象,不能进行++,--等操作。const_iterator的const修饰的是对访问那个结点的值是否具有修改的权限
如上图,返回的引用的值是否加const 修饰。
对于函数重载,我们只能通过参数类型/个数/顺序来区分,这里是仅通过返回值无法区分两个operator*()。
一种解决方法是重新定义一个const_iterator结构,仅在operator*()这里与iterator不同,但代码冗余度太大,重复部分太多。
还有一种方法是引入新的模板参数 Ref作为引用的返回值
iterator和const_iterator靠Ref和Ptr两个模板参数区分。看传入的T&和T*是否加const修饰。
实际上,两种迭代器仍然是两种结构,只不过我们用模板传入模板参数,让编译器生成了两个模板类,两个模板类通过对访问数据是否有修改来区分。
先在ls内插入数据,然后用const引用接收,在Print函数内,*cit无法被修改
四、insert/erase
五、拷贝构造/operator=
空初始化为创建头节点,用的地方比较多,可以单独拿出来。
拷贝构造直接循环尾插即可。
赋值重载使用现代写法,交换得到_head指向拷贝得到的list。
注意:由于ls被const修饰,beign()返回的是const_iterator,需要用const迭代器接收
ls要完成交换,不能加const std::swap需要两个参数
六、clear/析构
循环删除时迭代器会失效,接收返回值更新一下。
size()函数每次统计结点个数为O(N)
也可以增加一个成员变量,每次插入时++,删除时--
目录
一、结点的定义
二、普通正向迭代器
1、operator*()
2、operator->()
3、++/--/==
三、const迭代器
四、insert/erase
五、拷贝构造/operator=
六、clear/析构