本系列文章的目录在这里:目录. 通过目录里可以对STL总体有个大概了解
#前言
本文介绍了STL中的序列式容器std::forward_list
, 从它的名字就能推测出来,它是单向的,只能前进(forward). 确实如此,它其实就是对C语言风格的单链表的封装,仅提供有限的接口,相对于std::list(双向链表,并且定义很多接口)来说它节省了内存,同时又有比list更好的运行时性能;相对于自己实现的C风格的单链表(hand-written c-style linked list)而言,forward_list
也有与其不相上下的效率表现.
正如标准库所说的那样:
“
forward_list
设计的目标是, 使用forward_list
不会比使用自己手写的C风格单链表有更多的时间和空间的开销, 任何有悖于这个目标的接口和特性都会被标准拒之门外”
先看个例子吧
constructor
构造方式 | 说明 |
---|---|
forward_list<T> c |
默认构造函数,构造一个空的列表。 |
forward_list<T> c(c2) |
使用c2来拷贝构造c,c2中所有元素都被拷贝 |
forward_list<T> c = c2 |
使用c2来拷贝构造c,c2中所有元素都被拷贝 |
forward_list<T> c(rValue) |
移动构造函数,使用右值rValue的内容来构造c。 |
forward_list<T> c = rValue |
移动构造函数,使用右值rValue的内容来构造c。 |
forward_list<T> c(n) |
构造一个含有n个元素的列表,每个元素使用默认构造函数创建 |
forward_list<T> c(n, elem) |
构造一个含有n个elem元素的列表 |
forward_list<T> c(begin, end) |
使用[begin, end)之间的元素初始化c |
forward_list<T> c(initiallist) |
使用初始化列表initiallist来初始化c |
forward_list<T> c = initiallist |
使用初始化列表initiallist来初始化c |
非修改类接口 nonmodifying operations.
API | 说明 |
---|---|
c.empty() |
是否是空列表 |
c.max_size() |
最大可能的容量 |
c1 == c2 |
是否相等,内部对所有elems调用== |
c1 != c2 |
!(c1 == c2) |
c1 < c2 |
小于 |
c1 > c2 |
c2 < c1 |
c1 <= c2 |
!(c2 < c1) |
c1 >= c2 |
!(c1 < c2) |
注意:因为性能方面的考虑,forward_list
不提供size()接口,这正是标准所说的“不比手写的C风格单链表有更多的时间和空间上的开销”的设计目标的结果·
赋值操作 assignments
API | 说明 |
---|---|
c1 = c2 |
c2所有元素赋值给c1 |
c1 = rValue |
move rValue所有元素给c1 |
c1 = initiallist |
initiallist所有元素赋值给c1 |
c1.assign(initiallist) |
分配initiallist给c1 |
c1.assign(n, elem) |
分配n个elem |
c1.assign(begin, end) |
分配[begin, end)给c1 |
c1.swap(c2) |
交换c1, c2 |
swap(c1, c2) |
交换c1, c2 |
元素访问 element access
同样是出于简单高效的设计目标,forward_list
只有一个直接访问元素的接口:c.front()
.
c.front()返回第一个元素,这个接口内部并不会检查是不是存在第一个元素。如果调用了,但是元素不存在,那么导致未定义行为。
既然只有一个接口返回第一个元素,那么怎么来遍历所有元素呢?
有两种方式:
-
iterators.
-
range-based for loops. (其本质依然是使用iterators)
迭代器函数 iterator functions.
API | 说明 |
---|---|
c.begin() |
指向第一个元素的迭代器 |
c.end() |
最后一个元素的下一个位置 |
c.cbegin() |
const begin() |
c.cend() |
const end() |
c.before_begin() |
第一个元素前一个位置 |
c.cbefore_begin() |
const before_begin() |
使用迭代器是遍历forward_list
所有元素的方式之一, 这一点和其他类型的容器类似,不必细说,待会看代码的例子就明了了。
注意: 对end()和before_begin()
迭代器的直接操作都是未定义的行为,有可能造成运行时错误:
一般没人会像上面这样写,但是有时候连我自己都不知道我会对这两个位置进行解引用,比如我从一个函数的返回值得到了一个pos,然后我没有对pos进行合法性检查就直接*pos。
插入、移除元素 modifying operations
API | 说明 |
---|---|
c.push_front() |
在开头插入元素 |
c.pop_front() |
删掉开头元素 |
c.insert_after(pos, elem) |
在pos之后插入元素, 返回新元素的位置 |
c.insert_after(pos, n, elem) |
在pos之后插入n个元素elem, 返回第一个新元素的位置 |
c.insert_after(pos, begin, end) |
在pos之后插入元素[begin, end), 返回第一个新元素的位置 |
c.insert_after(pos, initiallist) |
在pos之后插入initiallist, 返回第一个新元素的位置 |
c.emplace_after(pos, args...) |
在pos之后插入args…元素,返回第一个新元素的位置 |
c.emplace_front(args...) |
在开头插入元素args…, 无返回值 |
c.erase_after(pos) |
删掉pos后面那个元素 |
c.erase_after(begin, end) |
删掉(begin, end)之间的元素 |
c.remove(val) |
移除所有值为val的元素 |
c.remove_if(op) |
删掉所使得op(elem)为true的元素 |
c.resize(n) |
调整个数为n,如果变大了,那多出来的用默认构造函数来创建 |
c.resize(n, elem) |
调整个数为n,如果变大了,那多出来的用elem的来拷贝构造 |
c.clear() |
清除所有元素 |
手写过单链表这种数据结构的人都会知道,对单链表的插入和删除操作都需要指定一个位置,并且这个位置要是被改动元素之前的位置,这是因为单链表不能够“回头”,比如我要删除第N个元素,我不仅要删掉第N个,还需要修改第N-1个元素,使它的跟第N+1个连在一起。
这个道理在std::forward_list
上的体现就是,插入和删除forward_list
的操作跟其他的标准容器的接口不太一样:
-
1.
forward_list
的接口总是需要一个“位置”参数pos, 要处理的元素就是在pos的后面; -
2.
forward_list
的接口总是以_after
结尾,意思是在某个参数之后进行操作。
在实际的应用中,我通常要先搜索要被处理的元素,比如,在一个列表里,我要删除第一个偶数, 如果我这样来写:
使用next来找要删除或插入的位置的代码还勉强能够接受,如果要频繁重复这操作,那建议像标准库那本书里自己定义两个算法:
-
find_before(begin, end, val)
: 返回[begin, end)之间,elem == val的元素的前一个位置 -
find_before_if(begin, end, op)
: 返回[begin, end)之间,op(elem) == true 的元素的前一个位置
本文最后会给出这俩算法的实现。
连接(splice)和变序类操作
API | 说明 |
---|---|
c.unique() |
删除连续相同的元素 |
c.unique(op) |
删除连续使得op(elem) == true的元素 |
dst.splice_after(dstPos, src) |
把src里的全部元素移动到dst中的dstPos之后 |
dst.splice_after(dstPos, src, srcPos) |
把src里的srcPos后面的一个元素移动到dst中的dstPos之后 |
dst.splice_after(dstPos, src, srcBegin, srcEnd) |
把src里(srcBegin, srcEnd)之间的元素移动到dst中的dstPos之后 |
c.sort() |
使用默认的< 来给c中的元素排序 |
c.sort(op) |
使用op来给c中的元素排序 |
c.merge(c2) |
移动c2中的元素到c中,假定二者原来都已序,那么移动之后c仍然有序 |
c.merge(c2, op) |
移动c2中的元素到c中,假定二者原来都按op已序,那么移动之后c仍然按op有序 |
c.reverse() |
所有元素反序 |
注意:splice_after
中涉及到的dst, src可以是同一个forward_list
, 此时,这个操作就是在列表内部来移动元素。splice_after
的位置参数不要使用end(), 这将导致未定义行为。
下面给出《c++标准库》上关于splice_after
的一个例子,书中的代码有一句是有错误的,下面给出正确的代码:
find_before
和 find_before_if
的实现
使用示例:
源码和参考链接
欢迎访问github博客,与本站同步更新