在上一文中我们完成了链表的多数基本接口,本文主要围绕构造函数进行补充
1. 链表的拷贝
在前文中我们没有手动实现拷贝构造,所以使用的就是编译器自动生成的浅拷贝
先使用一下编译器自动生成的浅拷贝:
我们在打印li2之前给li1加入一个数据:
现在还是完全的浅拷贝,拷贝构造过程中相当于只是将一个_head拷贝给另一个。两个链表有相同的_head。
插入一个会影响另外一个。
并且目前我们还没有实现析构函数,否则析构一个还会让另一个也无法使用。
1.1 clear
传统方法是一个一个释放节点。在实现析构之前我们来实现一个clear
每一个容器都有clear,也就是保留大的框架,但是删除里面的数据。
类比到链表中,就是删除数据节点,留下头结点。
请问这样写正确吗:
不对,it已经失效了。
复习:
在vector中,insert和erase都会让迭代器失效;
在list中,insert不会出现迭代器失效并且会返回插入的第一个节点,但是erase还是会迭代器失效的,返回的是被删除元素的下一个元素。
再次修改:
不对,it已经失效了,再++就加多了
正确写法:
void clear() {//iterator it(_head->_next);iterator it = begin();while (it != end()) {it = erase(it);}
1.2 析构
析构直接复用clear
析构和clear的区别就是前者会将大结构也清理干净。
1.3 拷贝构造(拷贝构造必须传引用)
综上所述,我们需要自己实现拷贝构造:
在创建一个全新的链表之后,一个一个的将被拷贝链表的内容全部push_back进去
但是给_head赋值这一段其实就是默认构造,我们希望在一开始就调用这个默认构造中的内容。
直接调用默认构造显然是不可以的,我们将里面的内容独立出来。
为什么要将empty_init单独提取出来?
因为拷贝构造在一开始需要使用默认构造中“建立头结点”的功能。
一般的范围for中,for后面都是(auto e: li)
为了应对T是较大的自定义类型的情况,我们将范围for中也传引用。
2. 赋值运算符的重载
因为我们已经实现好了拷贝构造,所以直接传值传参。
3.initializer_list
在initializer_list参与下实现的花括号构造:
实现和拷贝构造非常相似。
因为initializer_list的逻辑就是从initializer_list一个一个拷贝出来。
1、initializer_list不需要传引用传参,其本质就是两个指针,可以直接拷贝,代价不大
2、谨慎使用for循环,建议将auto都写成const auto&
小结
链表部分最重要的就是iterator
VS下的vector和string也没有用原生指针,而是封装过的,里面可能包含有标志等变量(比如erase之后不管是否真的失效都通过标志来表现该迭代器已经失效),但是只要观察早期版本,vector和string就是原生指针或者用原生指针换了个名字。