首先介绍顺序容器:
• 容器中的元素是有序的(ordered),但并未排序(sorted)。
顺序容器共有3种,分别是vector、list、deque。
1、vctor:
定义 :向量容器,一种动态数组,可取代C++语言本身提供的传统数组,提供随机存取的能力,操作尾端元素的速度最快。由于所有元素占用连续的空间,所以一旦进行插入或删除操作,有可能是原本的某些iterators失效.
算法:表示一段连续的内存区域,每个元素被顺序存储在这段内存中。对vector的随机访问效率很高,因为每次访问离vector起始处的位移都是固定的。但是,在任意位置,而不是在vector末尾插入元素,则效率很低,因为它需要把待插入元素右边的每个元素都拷贝一遍;类似,删除任意一个,而不是最后一个元素,效率同样很低,待删除元素右边的每个元素也都需要拷贝一遍。
这种代价对于大型的、复杂的类对象来说尤其大。
用法入门:
定义Vector:
vector
vector
vector
vector
赋值:
c1=c2; 把c2元素全部赋值给c1
c1.swap(c2) 把c1、c2所有元素互换
访问元素值:
c[n] 返回下标n所标识的元素
front() / back() 返回首/尾元素
常用操作:
insert(pos,e) 在pos位置插入元素e,并返回新元素的位置
insert(pos,n,e) 在pos位置插入n个元素e,不返回新元素的位置
push_back(e) 在尾部插入元素e
pop_back 删除最后一个元素
erase(pos) 删除指定位置元素
erase(begin,end) 删除区间元素,其中begin和 end都是迭代器
clear() 删除所有元素
begin() / end() 返回首/尾迭代器
size() 和 capacity()重点: capacity()返回其当前分配空间中所能容纳的元素的数目。如果capacity值发生变化,将会导致vector容器内存空间的再次分配。
reserve()预留一定的空间,避免内存空间的再次分配。
resize()调整容器中的元素的数量,缺少的采用默认或特定的元素填充。
2、 list
定义
双向链表。不提供随机存取的能力,在任何位置进行插入和删除动作都很快,不像vector只限于尾端而 deque 只局限与两端才有高效率。由于各元素并不占用连续空间,所以一旦进行插入或删除操作,原本的 iterators 仍然有效。注意,许多旨在「元素搬移」的 STL algorithms,用于 list上会有不佳的效率。所幸这些 STL algorithms 都有对应的list member functions 可以取代.
算法
表示非连续的内存区域,通过一对指向首尾元素的指针双向链接起来,从而允许向前和向后两个方向进行遍历。
在list任意位置插入和删除元素的效率都很高:指针必须被重新赋值,但是,不需要用拷贝元素来实现移动。另一方面,它对随即访问的支持并不好,因为访问一个元素需要从头开始遍历元素,而每个元素还有两个指针的额外空间开销。
List用法入门:
定义List:
list
list
list
list
赋值:
c1=c2; 把c2元素全部赋值给c1
c1.swap(c2) 把c1、c2所有元素互换
访问元素值:
front() / back() 返回首/尾元素
常见操作:
insert(pos,e) 在pos位置插入元素e,并返回新元素的位置
insert(pos,n,e) 在pos位置插入n个元素e,不返回新元素的位置
push_front / push_back(e) 在首/尾部插入元素e
pop_front / pop_back 删除首/尾部的元素
remove(val) 删除值为val的元素
erase(pos) 删除指定位置元素
erase(begin,end) 删除区间元素,其中begin和 end都是迭代器
clear() 删除所有元素
begin() / end() 返回首/尾迭代器
3、 deque
定义
双向队列。行为与特性很像vector。但是因为是两端都开口,所以操作两端元素的速度都很快,不像 vector只在操作尾端元素时才有高效率 。由于所有元素占用连续的空间(其实是分段的连续空间),所以一旦进行插入或删除操作,有可能是原本的某些iterators失效.
算法
一段连续的内存区域,它提供了与vector相同的行为,vector所支持的操作,deque也同样支持。
它与vector不同的是,对于首元素的有效插入和删除提供了特殊的支持。它通过两级数组结构来实现,一级表示实际的容器,第二级指向容器的首和尾
重点注意的地方:
内存动态增长:需要动态增长的vector必须分配一定内存以保存新的序列、按顺序拷贝旧的元素以及释放旧的内存,而且,如果它的元素是类对象,那么拷贝和释放内存需要对每个元素依次调用拷贝构造函数和析构函数。
list的每次增长,只是简单的链接新元素。
结论:所以,在动态增长的支持方面,list更有效。
vector机制剖析:
为了提高效率,实际上vector并不是随每一个元素的插入而增长自己,而是当vector需要增长自身时,它实际分配的空间比当前所需的空间要多一些,就是说,它分配了一些额外的内存容量,或者说它预留了存储区(分配的额外容量的确切数目由具体实现定义),这个策略使得容器的增长效率更高。
因此,对于小的对象,在实践中vector比list效率更高!
而对于大型的数据类型,因为初时分配容量较小,所以元素的频繁分配和拷贝成为使用vector的主要开销。
关联容器
• 容器中的元素都经过排序(sorted)
1、map
• 定义
由一对一的「键值(key)/值(value)」所组成的排序结构。键值独一无二。Map通常是以 balanced binary tree 来实现(但是并不强制规定)。事实上 map 的内部结构通常与是一样的。因此我们可以将 set视为一种特殊的 map:键值和值相同的 map。所以 map 和 set 几乎拥有完全相同的能力.
• 算法
以key/value数据对为元素;
以key的大小按照自动排序
常见操作:
count(key)
返回以key为键值的元素的个数。
find(key)
返回键值为key的第一个元素,或者返回end()
insert(elem)
插入元素elem的拷贝并返回新元素的索引位置。
erase(pos)
删除指定位置的元素。
2、set
经过排序的结构体,以某个可指定的排序方式来排序。每个元素独一无二。又已经排序,所以查找速度极快。但也因此不允许我们直接修改某个元素的内容,因为这可能会影响排序。修改元素内容正确的做法式,先将该元素删除,再加入(此时使用新值)。Set通常是使用 balanced binary tree 来实现(但是并不强制规定),甚至是以 red-black trees 完成。
4、Multiset
允许元素重复。
5、Multimap
允许键值重复。
6、hash table
这并不是 C++ Standard 规范内的一个container(因为标准委员会工作时间的关系),但是它对于大量数据的查找而言,很实用也很重要。有许多STL的实现品(例如 SGI)都涵盖了它。通常 STL产品厂商会提供四种hash tables:hash_set, hash_multiset, hash_map, hash_multimap 。
容器适配器
1、stack
特性是先进后出(FILO,First In, Last Out)。底部缺省是 deque 。
2、queue
特性是先进先出(FIFO,First In, First Out)。底部缺省是 deque 。
3、priority_queue
特性是依靠优先权来决定谁是「下一个」元素。底部缺省是 vector
迭代器
Input iterators 提供对数据的只读访问。
Output iterators 提供对数据的只写访问
Forward iterators 提供读写操作,并能向前推进迭代器。
Bidirectional iterators 提供读写操作,并能向前和向后操作。
Random access iterators 提供读写操作,并能在数据中随机移动。
定义
迭代器是一个对象,用来遍历容器,即在容器中实现“取得下一个元素”的操作。不管容器是否直接提供了访问其对象的方法,通过迭代器都能够访问该容器中的对象,一次访问一个元素。
算法
迭代器操作类似于指针,但它是更强大、更安全的指针,使开发者不必知道迭代器的实际类型,就可以用它去标识容器中的元素,给容器中的元素访问带来极大方便。
基本操作
定义迭代器: std::list
移动迭代器: ++iter表示向前移动迭代器,使其指向容器的下一个元素
返回数据的值: *iter返回迭代器指向元素的值
迭代器赋值: 每种容器类型都提供了一个being()和一个end()成员函数
begin() 返回一个iterator,指向容器的起点,返回第一个元素
end() 返回一个iterator,指向容器的终点,并不返回最后一个元素
STL大概有70多种算法:
accumulate() 元素累加
adjacent_difference() 相邻元素的差值
adjacent_find() 查找相邻的元素
binary_search() 二分查找
copy() 复制
copy_backward() 逆向复制
count() 计数
count_if() 在特定条件下计数
equal() 判断相等
equal_range()判断相等(传回一个有上下限的范围)
fill() 填充元素值
fill_n() 改填元素值,n 次
find() 查找
find_if() 在特定条件下查找
find_end() 查找某个子序列的中最后出现的节点
find_first_of() 查找某个元素首次出现的节点
for_each() 对范围内的每个元素进行某个操作
generate() 以指定动作的运算結果填充特定范围內的元素
generate_n() 以指定动作的运算结果填充 n 个元素內容
includes() 涵盖点
inner_product()
inplace_merge() 合并取代
iter_swap() 元素互换
lexicographical_compare() 以字典排列方式做比较
lower_bound() 下限
max() 最大值
max_element() 最大值所在位置
min() 最小值
min_element() 最小值所在位置
merge() 合并两个序列
mismatch() 找出不吻合点
next_permutation() 获得下一个排列组合
经典案例
迭代器越界:
【错误写法】
list
ProcessMsg(itor->pMsg);
【正确写法】
list
if (itor != m_MsgList.end()) {
if (itor->pMsg != NULL) {
ProcessMsg(itor->pMsg);
}
【分析】
由于消息列表可能为空,导致迭代器itor也可能为空,访问空指针
行为守则:使用迭代器之前,进行非空判断,避免操作空指针
Map的[]操作:
【错误写法】
std::map
m_mIsExist.insert(make_pair(“first”, true));
m_mIsExist.insert(make_pair(“second”, true));
bool isExist(const string& sFieldName)
{
return m_mIsExist[sFieldName];
}
【分析】
当频繁调用使用上述代码时,内存增长很快,因为当key不存在时,Operator[ ]会自动插入一个默认值。如果中间value是指针时,会引入空指针,因为指针类型的缺省构造函数会生成一个空指针。
行为守则:使用Map的[]之前,先进行判断是否元素存在
迭代器的有效时间:
【错误写法】
CEmfPerfIndiList* poIndiList = GetIndiList();
typedef EmfPerfIndiIDList::iterator LI_IndiId;
for (LI_IndiId item = listIndiID.begin(); item != listIndiID.end(); ++item)
{
int iRet = poIndiList->RegIndi(item->ulID, item->ulTmplID, item->strMibIndex, ulFmID);
if (iRet != 0)
{listIndiID.erase(item); //调用此函数之后,item已经失效,
continue; //之后会调用++item,但item已经失效,其结果?
}}
【分析】
STL容器中的插入、删除操作都可能影响以前得到的iterator,必须保证此iterator有效,才能继续使用,否则肯定出问题。
eg:
由于erase返回的是下一个迭代器,所以这样可以防止失效。而第二个明显是erase之后就不再用了,所以无所谓。
行为守则:在容器中删除、插入数据后,防止迭代器无效
STL的关键实际上是iterator。STL算法作为参数使用iterator,他们指出一个范围,有时是一个范围, 有时是两个。STL容器支持iterator,这就是为什么我们说 list
iterator有很好的定义继承性。它们非常有用。某些iterator仅支持对一个容器只读,某些 仅支持写,还有一些仅能向前指,有一些是双向的。有一些iterator支持对一个容器的随机存取。
STL算法需要某个iterator作为“动力” 如果一个容器不提供iterator作为“动力”,那么这个算法将无法编译。例如,list容器仅提供双向的 iterator。通常的sort()算法需要随机存取的iterator。这就是为什么我们需要一个特别的list成员函数sort()。
要合适的实际使用STL,你需要仔细学习各种不同的iterator。你需要知道每种容器都支持那类iterator。你还需要知道算法需要那种iterator,你当然也需要懂得你可以有那种iterator。