

本编介绍STL容器之List~
++1、迭代器的功能&性质++++2、辨别模板中应使用哪种迭代器++
++3、list接口的介绍和使用++
++4、模板的按需实例化++
++5、List中迭代器失效的问题++
++6、C++11统一初始化++
++7、默认构造、拷贝构造、赋值运算符重载、析构函数的简便实现写法++
1、迭代器的功能&性质区分迭代器,可从功能与性质两方面区分。
📌功能:
|------------------------|
| iterator |
| reverse_iterator |
| const_iterator |
| const_reverse_iterator |📌性质:
|----|-------------------------------|--------------|
| 单向 | forward_list/unordered_map... | 支持:++ |
| 双向 | list/map/set... | 支持:++/-- |
| 随机 | vector/string/deque... | 支持:++/--/+/- |💡迭代器性质的不同,其支持的运算就不同,这种差异源自于底层结构的区别。
💡对于这三个性质,可以将其归类:
|-----------------------------|
| 随机迭代器可以看作是特殊的双向迭代器,特殊的单向迭代器 |
| 双向迭代器可以看作是特殊的单向迭代器 |
| 他们之间可以特殊化的归为其一类~ |2、辨别模板中应使用哪种迭代器
|----------|---------------|
| 随机迭代器 | Random Access |
| 双向迭代器 | Bidirectional |
| 单向迭代器 | Forward |
| 包含这三个的~ | Input/Output |📌在模板中:
看到 "Random Access",说明:可用迭代器范围只能用随机迭代器;
看到 "Bidirectional",说明:可用迭代器范围能用 随机/双向 迭代器;
看到 "Forward",说明:可用迭代器范围能用 随机/双向/单向 迭代器;
看到 "Input/Output",说明:可用迭代器范围能用 随机/双向/单向 迭代器。
3、list接口的介绍和使用📌此处只介绍与vector有"差异"的接口~
| emplace_back | 对于单参数的尾插,push_back和emplace_back是一样的,都要构造再拷贝;但是emplace_back是支持多参数的,对于多参数为一个元素的尾插,emplace_back就直接构造了。 |
| resize | 在链表中的 resize 就没有扩容的概念了。 |
| splice | 有两个list,将一个list里面的一个数或一段数据剪切掉,再粘贴到另一个list上。 |
| unique | 去重,但是是针对于有序的数据。 |
| merge | 合并两个链表,被拿去合并的链表合并后变成空链表,合并成的链表保持升序。 |
sort 对于小数据,list自带的 sort 快;对于大数据,algorithm里面的 sort 快。
4、模板的按需实例化
模拟实现List中的具体例子:
cpp// 模板 可以实例化出下面两个 -- 正常版/const版 template <class T, class Ref, class Ptr> struct list_iterator { typedef list_node<T> Node; typedef list_iterator<T, Ref, Ptr> Self; Node* _node; list_iterator(Node* node) :_node(node) { } Ref operator* () { return _node->_data; } Ptr operator-> () // 强制语法规定:返回指针 { return &_node->_data; } Self& operator++ () { _node = _node->_next; return *this; } Self& operator--() { _node = _node->_prev; return *this; } bool operator!= (const Self& s) const { return _node != s._node; } bool operator== (const Self& s) const { return _node == s._node; } };📌该模板在没有具体需求时,编译器不会细致扫描,就像你知道你记得你有某样东西一样。当有具体需求时,模板根据具体需求产生对应的实例化类,供具体需求使用。
5、List中迭代器失效的问题📌List在物理空间上是不连续的,而且迭代器是经过努力转化出来的,指向哪里就指向哪里,所以插入时,迭代器不失效。
cpp测试代码 void test_list2() { list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); list<int>::iterator it = lt.begin(); // 插入迭代器不会失效 lt.insert(it, 10); *it += 100; list<int>::iterator it1 = lt.begin(); while (it1 != lt.end()) { cout << *it1 << " "; ++it1; } cout << endl; }📌List在删除结点时,当前迭代器就像是野指针一样,所以List删除时,迭代器是失效的。
cpp测试代码 void test_list2() { list<int> lt; lt.push_back(1); lt.push_back(2); lt.push_back(3); lt.push_back(4); // 删除所有偶数 // 迭代器失效 list<int>::iterator it = lt.begin(); while (it != lt.end()) { if (*it % 2 == 0) { lt.erase(it); } else { ++it; } } list<int>::iterator it1 = lt.begin(); while (it1 != lt.end()) { cout << *it1 << " "; ++it1; } cout << endl; }
6、C++11统一初始化
C++11之后支持了这样的初始化的方式:
cpp// 直接构造 list<int> lt0({ 1,2,3,4,5,6 }); // 隐式类型转换 list<int> lt1 = { 1,2,3,4,5,6,7,8 }; const list<int>& lt3 = { 1,2,3,4,5,6,7,8 }; func(lt0); func({ 1,2,3,4,5,6 });其中隐式类型转换:大括号一串的数字会转换成 "initializer_list<int>" 类型,"initializer_list":初始化列表。
cppauto il = { 10, 20, 30 }; initializer_list<int> il = { 10, 20, 30 }; cout << typeid(il).name() << endl; cout << sizeof(il) << endl;实现方式是前后两个指针,仅作了解。
7、默认构造、拷贝构造、赋值运算符重载、析构函数的简便实现写法
cppvoid empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; _size = 0; } // 默认构造 list() { empty_init(); } // l2(l1) 拷贝构造 list(const list<T>& lt) { empty_init(); for (auto& e : lt) { push_back(e); } } // C++11统一初始化的隐式类型转换 list(initializer_list<T>& lt) { empty_init(); for (auto& e : lt) { push_back(e); } } // l2 = l1 赋值运算符重载 list<int>& operator= (list<T> lt) { swap(lt); } void swap(list<T>& lt) { std::swap(_head, lt._head); std::swap(_size, lt._size); } // 析构 ~list() { clear(); delete _head; _head = nullptr; } void clear() { auto it = begin(); while (it != end()) { it = earse(it); } }