【Cpp】第七章-STL_list类

list类

基础介绍

  list类是STL中封装的链表模板类,并且底层实现是以双向链表作为基础进行封装的。在数据结构中,线性存储结构中主要分为顺序表和链表,前者在物理结构上拥有连续的内存空间和地址,在STL中vectorstring都是使用了这种结构,其最大的特点就是方便进行随机访问并且尾插和尾删都能达到O1的时间复杂度并且使用方便,而链表作为物理结构上内存空间不连续的数据结构,其最大的特点就是方便在任何位置就行插入删除,list就是建立在此数据结构上封装出的模板类。

常用接口

构造函数

1
2
3
4
list();                                                  //构造空的list
list(size_type n, const value_type &val = value_type()); //构造的list中包含n个值为val的元素
list(const list &x); //拷贝构造函数
list(InputIterator first, InputIterator last); //用[first, last)区间中的元素构造list

迭代器相关

1
2
3
4
5
6
7
8
begin();   //返回第一个元素的迭代器
end(); //返回最后一个元素下一个位置的迭代器
rbegin(); //返回第一个元素的reverse_iterator,即end位置
rend(); //返回最后一个元素下一个位置的reverse_iterator,即begin位置
cbegin(); //(C++11) 返回第一个元素的cosnt_iterator
cend(); //(C++11) 返回最后一个元素下一个位置的const_iterator
crbegin(); //(C++11) 即crend()位置
crend(); //(C++11) 即crbegin()位置

容量相关

1
2
bool empty() const;  //检测list是否为空,是返回true,否则返回false
size_t size() const; //返回list中有效节点的个数

增删查改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
reference front();                      //返回list的第一个节点中值的引用
const_reference front() const; //返回list的第一个节点中值的const引用
reference back(); //返回list的最后一个节点中值的引用
const_reference back() const; //返回list的最后一个节点中值的const引用
void push_front(const value_type &val); //在list首元素前插入值为val的元素
void pop_front(); //删除list中第一个元素
void push_back; //(const value_type& val)在list尾部插入值为val的元素
void pop_back(); //删除list中最后一个元素
template <class... Args>
void emplace_front(Args && ... args); //(C++11)在list第一个元素前根据参数直接构造元素
template <class... Args>
void emplace_back(Args && ... args); //(C++11)在list最后一个元素后根据参数直接构造元素
template <class... Args>
iterator emplace(const_iterator position, Args && ... args); //(C++11)在链表的任意位置根据参数直接构造元素
iterator insert(iterator position, const value_type &val); //在list position 位置中插入值为val的元素
void insert(iterator position, size_type n, const value_type &val); //在list position位置插入n个值为val的元素
void insert(iterator position, InputIterator first, InputIterator last); //在list position位置插入[first, last)区间中元素
iterator erase(iterator position); //删除list position位置的元素
iterator erase(iterator first, iterator last); //删除list中[first, last)区间中的元素
void swap(list & x); //交换两个list中的元素
void resize(size_type n, value_type val = value_type()); //将list中有效元素个数改变到n个,多出的元素用val填充
void clear(); //清空list中的有效元素

emplace与insert

  在STL很多接口中我们都发现有一个emplace()的接口也是用来进行插入的,那么它与insert()push_back()有什么区别呢?以下用vector举个例子,从接口调用上就能看出区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <vector>
using namespace std;
int main()
{
struct Foo
{
Foo(int n, double x)
:_n(n)
,_x(x)
{

}
int _n;
double _x;
};
vector<Foo> v;
v.emplace(v.begin(), 42, 3.1416); // 没有临时变量产生
v.insert(v.begin(), Foo(42, 3.1416)); // 需要产生一个临时变量
v.insert(v.begin(), {42, 3.1416}); // 需要产生一个临时变量
for(int i = 0; i < v.size(); i++)
{
cout << v[i]._n << " " << v[i]._x << endl;
}
}


42 3.1416
42 3.1416
42 3.1416

  在进行内置类型和拥有单参构造函数的类型的插入时我们只传入一个参数往往很难发现区别,但在我们必须传入多个参数才能进行插入时我们就会发现在使用insert()这些接口时我们必须插入与容器中元素类型相同的元素才能完成接口调用,这让我们不得不构造一个临时匿名对象,而emplace则不需要,我们只要按照构造一个对象那样给构造元素的参数即可省去了构造匿名对象的过程。而这其中需要用到C++11中的新标准变参模板完美转发,因此emplace使用的时候要求编译器支持C++11标准。
  emplace不光是调用接口上有所不同,在有的时候也可以提高我们的效率。例如在list中如果利用insert传入一个元素的时候我们就需要先调用元素的构造函数构造元素,然后再调用Node的构造函数以及该元素的拷贝构造函数构造一个拥有该元素值的结点,才能进行插入,而emplace则可以在创建结点时直接利用原本要构造临时元素对象的参数来直接构造结点,并且直接构造结点中的元素对象,相当于少了一次元素的拷贝构造,并且省去了构造临时对象,效率更高

list迭代器失效

  对于list这种链式结构,在插入后是不会迭代器失效的,因为原先位置的迭代器依旧指向原来的结点,不会因为添加结点导致指向位置的变动。而在删除后迭代器原本指向的结点内存被释放,因此删除后删除结点的迭代器失效,但是其他结点迭代器指向不变因此不会发生迭代器失效。
  总结:list只有在删除节点后才会发生迭代器失效,并且只有删除结点的迭代器失效

实现

  list采用链式结构存储,因此实现起来要稍微麻烦一些,迭代器也需要额外进行封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include <assert.h>
//定义结点类
template<class T>
struct ListNode
{
ListNode<T>* _prev;
ListNode<T>* _next;
T _data;
ListNode(const T& data = T())
:_prev(nullptr)
,_next(nullptr)
,_data(data)
{}
};
//由于const_iterator与iterator除了在迭代器返回值上不一样外其他要求完全一样
//因此这里要进行实现时可以考虑定义两个类
//但是这里使用一种取巧的方法我们将返回值类型当作模板参数传入模板中
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
//构造函数
ListIterator(Node* node)
:_node(node)
{}
//operator* 为了让其可以和指针一样使用,返回引用
Ref operator*()
{
return _node->_data;
}
//operator-> 为了让其可以和指针一样使用,返回指针
//这里实际调用it->只能取到数据的指针,所以正常来说得写成it->->
//但是经过编译器优化,只用写it->就可以取到值了
Ptr operator->()
{
return &(_node->_data);
}
Self operator++()
{
_node = _node->_next;
return _node;
}
Self operator++(int)
{
Self temp(_node);
_node = _node->_next;
return temp;
}
Self operator--()
{
_node = _node->_prev;
return _node;
}
Self operator--(int)
{
Self temp(_node);
_node = _node->_prev;
return temp;
}
bool operator!=(Self it)
{
return _node != it._node;
}
Node* _node;
};
//构建一个带头结点双向循环链表来模拟实现list
template<class T>
class List
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
//构造函数j
List()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
//拷贝构造
List(const List& list)
:_head(new Node)
{
_head->_next = _head;
_head->_prev = _head;
const_iterator it = list.begin();
while(it != list.end())
{
Push_back(*it);
it++;
}
}
//operator=重载
List& operator=(const List& list)
{
List listTemp = list;
Swap(listTemp);
}
//交换
void Swap(List& list)
{
std::swap(_head, list._head);
}
//析构函数
~List()
{

while(_head->_next != _head)
{
Pop_back();
}
}
iterator begin()
{
return _head->_next;
}
const_iterator begin() const
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator end() const
{
return _head;
}
iterator Erase(iterator& it)
{
assert(it._node != nullptr);
Node* pTemp = it._node;
Node* pNew = pTemp->_next;
//it->_node = pTemp->_next;
pTemp->_prev->_next = pTemp->_next;
pTemp->_next->_prev = pTemp->_prev;
delete pTemp;
return pNew;
}
//插入
iterator Insert(iterator& it, const T& data = T())
{
assert(it._node != nullptr);
Node* newNode = new Node(data);
newNode->_next = it._node;
newNode->_prev = it._node->_prev;
it._node->_prev->_next = newNode;
it._node->_prev = newNode;
return newNode;
}
//尾插
void Push_back(const T& data)
{
assert(_head != nullptr);
Node* tail = _head->_prev;
Node* newNode = new Node(data);
newNode->_next = _head;
newNode->_prev = tail;
tail->_next = newNode;
_head->_prev = newNode;
}
//尾删
void Pop_back()
{
assert(_head != nullptr);
if(_head->_next == _head)
{
return;
}
Node* tail = _head->_prev;
tail->_prev->_next = _head;
_head->_prev = tail->_prev;
delete tail;
}
private:
Node* _head;
};

-------------本文结束感谢您的阅读!-------------
记录学习每一分,感谢您的赞助