

本编介绍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有"差异"的接口~
| 对于单参数的尾插,push_back和emplace_back是一样的,都要构造再拷贝;但是emplace_back是支持多参数的,对于多参数为一个元素的尾插,emplace_back就直接构造了。 |
| 在链表中的 resize 就没有扩容的概念了。 |
| 有两个list,将一个list里面的一个数或一段数据剪切掉,再粘贴到另一个list上。 |
| 去重,但是是针对于有序的数据。 |
| 合并两个链表,被拿去合并的链表合并后变成空链表,合并成的链表保持升序。 |
| 对于小数据,list自带的 sort 快;对于大数据,algorithm里面的 sort 快。 |
4、模板的按需实例化
模拟实现List中的具体例子:
// 模板 可以实例化出下面两个 — 正常版/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在物理空间上是不连续的,而且迭代器是经过努力转化出来的,指向哪里就指向哪里,所以插入时,迭代器不失效。
测试代码
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删除时,迭代器是失效的。
测试代码
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之后支持了这样的初始化的方式:
// 直接构造
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":初始化列表。
auto il = { 10, 20, 30 };
initializer_list<int> il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
cout << sizeof(il) << endl;
实现方式是前后两个指针,仅作了解。
7、默认构造、拷贝构造、赋值运算符重载、析构函数的简便实现写法
void 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);
}
}





