C++Primer第五版【笔记】——第九章——顺序容器

1. 顺序容器概述


表一中的顺序容器,提供了对元素快速的顺序访问。但是其他操作的开销则不同:
  • 添加或删除元素的开销
  • 进行非顺序访问的开销
表一:顺序容器类型
 vector   可变长度数组。支持快速随机访问。在非尾部插入或删除元素速度很慢
 deque  双端队列。支持快速随机访问。快速在头或尾的插入和删除
 list  双向链表。只支持双向的顺序访问。可以在list的任何位置快速插入和删除
 forward_list   单向链表。只支持单向顺序访问。可在任何位置快速插入和删除
 array  定长数组。支持快速随机访问。不能添加或删除元素。
 string  专门用来保存字符的容器,类似vector。支持快速随机访问。快速在尾部的
  插入和删除。
除了array是定长容器之外,容器提供了高效的,灵活的内存管理。我们可以添加和删除元素,动态的增长或缩减容器的大小。
容器中元素的存储方法决定了它所支持的操作,以及操作的效率。比如,string和vector中的元素存储在连续的内存中。因此它们的元素是连续的,支持快速随机访问。但是,在元素序列中的插入或删除操作开销很大。list和forward_list则可以快速的在容器中的任意位置插入或删除元素。但是,不支持随机访问。deque是一个比较复杂的数据结构。它可以像string和vector那样,支持快速随机访问。同时可以在容器的头部和尾部快速的插入和删除元素。
【C++11】
forward_list和array是新标准中加入的。array相比内置数组,更加安全、易用。它具有固定长度,不支持插入和删除元素。forward_list可以与最高效的手写单链表相媲美。forward_list不支持size操作,因为该操作开销很大。
【注】
新版本的库容器要比之前版本的效率高的多。所以,尽可能的使用它们吧。

如何选择使用哪个顺序容器?


可以按照下面的规则:
  • 使用vector,除非你有理由使用其他容器
  • 如果你的程序有许多小元素,且空间开销很重要时,不要使用list或forward_list
  • 如果程序需要随机访问元素,使用vector或deque
  • 如果程序需要在容器中间插入或删除元素,使用list或forward_list
  • 如果程序需要在头部或尾部插入或删除元素,而不是中间,使用deque
  • 如果程序只在读入数据时需要在容器中间插入元素,随后需要随机访问元素:
    -- 首先,确定是否真的需要在容器中间插入元素。一般先为vector追加元素,然后调用sort函数排序会更容易。
    -- 如果真的需要在中间插入元素,考虑在输入阶段使用list。然后当输入完成后,将list复制到vector。
如果程序既需要随机访问,又需要在中间插入或删除元素。那么就需要比较list或forarad_list访问元素的开销,和vector,deque插入或删除元素的开销。同时还需要考虑程序本身是随机访问次数更多还是插入、删除操作更多。或者还可以考虑使用更复杂的数据结构来支持。

2. 容器库概述


容器的操作种类可以划分为一个层次:
  • 一些操作是通用的。
    Type Aliases 
     iterator 容器类的迭代器类型
     const_iterator 该迭代器只能读,不能改变元素
     size_type 无符号整型,足够保存最大可能的容器类的大小
     difference_type 有符号整型,足够保存两个迭代器的距离
     value_type 元素类型
     reference 元素左值类型;与value_type&等价
     const_reference 元素的const左值类型(const value_type &)
    Construction 
     C c; 默认构造函数,空容器
     C c1(c2); 创建c1为c2的一个副本
     C c(b, e); 复制迭代器b和e范围内的元素(不适用于array)
     C c{a, b, c...}; 列表初始化
    Assignment and swap 
     c1 = c2 用c2中的元素值代替c1中的元素
     c1 = {a,b,c...} 用列表中的元素代替c1中的元素 (不适用于array)
     a.swap(b) 交换a和b中的元素
     swap(a, b) 等价于a.swap(b)
    Size 
     c.size() c中元素的个数(不适用于forward_list)
     c.max_size() c最多可以保存的元素个数
     c.empty()  如果c为空,true
    Add/Remove Elements
    (not valid for array)
    Note: the interface to these operations varies by container type
     c.insert(args) 将args指代的元素插入c中
     c.emplace(inits) 使用inits构造一个c中的元素
     c.erase(args) 删除args指代的元素
     c.clear() 删除c中所有元素,返回void
    Equality and Relational Operators 
     ==, != 所有容器都支持
     <, <=, >, >= 关系(非有序关系容器不支持)
    Obtain Iterators 
     c.begin(), c.end() Return iterator to the first, one past the last element in c
     c.cbegin(), c.cend() Return const_iterator
    Additional Members of Reversible Containers (not valid for forward_list)
     reverse_iterator Iterator that addresses elements in reverse  order
     const_reverse_iterator Reverse iterator that cannot write the elements
     c.rbegin(), c.rend() Return iterator to the last, one past the first element in c
     c.crbegin(), c.crend() Return const_reverse_iterator

  • 顺序容器,关系容器,非顺序容器的其他操作
    Defining and Initializing Containers 
     C c; 默认构造函数。如果C是array,则c中的元素是默认初始化,否则c为空
     C c1(c2) c1 is a copy of c2. c1 and c2 must have the same type
    (必须是相同类型的容器,且其中的元素类型也相同;对于array,长度也需要相同)
     C c{a,b,c...} c is a copy of the elements in the initializer list.列表中元素的类型必须与C中元素的类型是相容的。对于array,列表中元素的个数必须小于或等于array的长度,缺少的部分用值初始化
     C c={a,b,c...} 
     C c(b,e)c is a copy of the elements in the range denoted by iterators b and e. 元素类型必须和C的元素类型相容。(不适用于array)
    Constructors that take a size are valid for
    sequential containers only
    (not including array)
     C seq(n) seq has n value-initialized elements; the constructor is explicit.(Not valid for string).
     C seq(n, t) seq has n elements with value t.
容器几乎可以保存任何的类型,包括容器本身。但是还是有一些限制的。比如一些容器需要其元素支持某个特定的操作,如果某类型不支持这种操作,那么就不能作为容器的元素。
vector<vector<int>> vvi;
vector<vector<int> > vv_i; // 旧版本需要加一个空格

2.1 迭代器


迭代器支持的操作和指针类似,包括自增、自减、解引用等。对于不同的容器,其迭代器支持的操作也会不同。比如forward_list就不支持--操作。
需要特别指出的是,迭代器的范围是一个前开后闭区间:[begin, end)

2.2 容器的类型成员


容器中定义了一些类型成员,比如size_type,iterator, const_iterator等。这些类型别名可以让我们在使用的时候不需要知道具体类型,在泛型编程中很有用。

2.3 begin和end


【C++11】
c版本的begin和end是新标准加入的,使用auto可以很方便的定义。
vector<string> vs;
const vector<string> c_vs;
auto beg = vs.begin(); // iterator
auto cbeg = c_vs.begin(); // const_iterator
auto cbeg1 = vs.cbegin(); // const_iterator 
不带c的版本在内部是重载函数。一个const版本的和一个非const版本的。如果是const对象,则调用const版本,如果是非const对象则调用非const版本。

2.4 定义和初始化容器


除了array之外,每个容器都定义了默认构造函数,以及一个指定容器大小和初始化元素值的构造函数。
根据一个容器来初始化另一个容器有两种方法:第一种是直接复制,容器类型和元素类型必须匹配;第二种是用迭代器指定一个范围,容器类型和元素类型都不需要一致,元素类型要保证是可以转换的。
vector<string> vs = {"hello", "world"};
list<const char*> lcc = {"ok", "I", "want"};
deque<string> ds(vs); // error
forward_list<string> fs(lcc.begin(), lcc.end()); // ok
顺序容器的初始化:
vector<int> vi(10, 1024); // 初始化为10个1024
list<string> ls(10, "ok"); // 初始化为10个"ok"
forward_list<int> fi(10); // 初始化为10个0
deque<string> ds(10); // 初始化为10个空string
需要指出的是,只提供一个元素个数的构造函数,需要元素本身(如果是类类型)具有默认构造函数。

array


array和内置数组类型类似,固定长度,数组大小是类型的一部分。
array<int, 50> a = {0, 1, 2 ,3}; // 其余元素为0

2.5 赋值和交换

assign operation
 assign operations not valid for associative containers or array
 seq.assign(b,e)  Replaces elements in seq with those in the range denoted by iterators
 b  and e. The iterators b and e must not refer to elements in seq.
 seq.assign(il) Replaces the elements in seq with those in the initializer list il.
 seq.assign(n,t) Replaces the elements in seq with n elements with value t.
【WARNING!】Assignment related operations invalidate iterators, references, and pointers into the left-hand container. Aside from string they remain valid after a swap, and (excepting arrays) the containers to which they refer are swapped.
与内置数组不同的是,array支持赋值。
array<int,5> arr1 = {1,2,3,4,5};
array<int,5> arr2 = {0};
arr1 = arr2;
【注】赋值操作的等号左右两边的类型必须一致

使用assign


顺序容器还支持assign操作,提供了更加灵活的赋值,只要类型是相容的(即可以相互转换)。
list<string> ls;
vector<const char*> vcc;
ls = vcc; // error
ls.assign(vcc.cbegin(), vcc.cend()); // ok

使用swap


swap可以用来交换两个相同类型的容器,该操作可以在常数时间内完成。 数据本身没有交换,交换的是内部数据结构,比如迭代器,引用,指针

2.6 size操作


size函数返回当前容器中包含的元素个数;如果容器为空, empty返回ture,否则返回false; max_size返回容器最大可以容纳的元素个数。

2.7 关系操作符


所以容器都支持相等操作(==和!=);所以容器除了 无序关联容器,都支持关系操作(>, >=, <, <=)。
【注】只有容器的元素支持相应的关系操作时,才能比较该容器。

3. 顺序容器操作


3.1 添加元素


除了array,所以的库容器都支持容器大小在运行时的动态改变。
c.push_back(t)
c.emplace_back(args)
在容器c的尾部,创建一个值为t的元素,或根据args构建一个元素。返回void
c.push_front(t)
c.emplace_front(args)
在容器c的头部,创建一个值为t的元素,或根据args构建一个元素。返回void
c.insert(p,t)
c.emplace(p,args)
在迭代器p所指元素的前面,插入一个值为t的元素,或根据args构建一个元素;
返回指向插入元素的迭代器
c.inesrt(p, n, t)在迭代器p所指元素的前面,插入n个值为t的元素;返回指向插入的第一个元素的迭代器,
如果n为0,返回p
c.insert(p, b, e)在迭代器p所指元素的前面,插入迭代器指向范围为[b,e)的元素,b,e不能指向c本身。
返回指向插入的第一个元素的迭代器,如果范围为空,返回p
c.insert(p, il)il是包含元素值的括号序列。在迭代器p指向的元素前面插入该序列。返回指向插入的
第一个元素的迭代器,如果序列为空,返回p
【注意】在vector, string, deque中插入元素,会使现有的迭代器,引用和指针无效。forward_list不支持push_back和emplace_back。vector和string不支持push_front和emplace_front.
不同容器的插入操作效率是不同的。比如在vector中插入(非尾部)元素,需要元素的移动操作,这会导致额外开销。
list<string> ls;
vector<string> vs = {"this", "is", "for", "test"};
ls.insert(ls.begin(), vs.end()-2, v.end());
ls.insert(ls.end(), {"love","coding"});
【c++11】
新标准中,接受一个计数参数或迭代器范围的insert版本会返回指向插入的第一个元素的迭代器(之前的版本返回void)。
【c++11】
emplace_front, emplace, emplace_back是新标准加入的三个操作,与push_front, insert, push_back不同的是,它们用args参数调用元素类型的构造函数在插入的位置创建新的元素,而不是根据传递的元素复制一个副本。
相当于根据参数类型,调用相应的构造函数,在容器所在的内存空间(插入的位置)创建新的元素。

3.2 访问元素


顺序容器的元素访问操作
 c.back()  返回容器c的最后一个元素的引用。如果c为空,则结果未定义
 c.front()  返回容器c的第一个元素的引用。如果c为空,则结果未定义
 c[n] 返回下标n所在位置的元素。如果n>=c.size(),则结果为定义
 c.at(n) 返回下标n所在位置的元素。如果n超出范围,则抛出out_of_range 异常
每个顺序容器包括array,都支持front操作,除了forward_list,都支持back操作。

3.3 删除元素


顺序容器的删除操作
c.pop_back()删除容器c的最后一个元素。如果c为空,则未定义。返回void
c.pop_front()删除容器c的第一个元素。如果c为空,则未定义。返回void
c.erase(p)删除迭代器p指向的元素,返回指向被删除元素后一位的迭代器。
c.erase(b,e)删除迭代器范围为[b,e)的元素,返回指向被删除元素后一位的迭代器。
c.clear()删除c的所有元素,返回void
【注】在deque中删除非开头或结尾的元素会导致其所有的迭代器、引用和指针失效。在vector或string中删除元素,指向被删除元素后面位置的迭代器、引用和指针会失效。

3.4 forward_list的专用操作


对于单向链表来说,插入或删除元素,会改变其前面一个元素中的指针。所以它的插入和删除操作不同于其他容器。
forward_list的插入和删除操作
 lst.before_begin()
 lst.cbefore_begin()
 指向链表开头前一个位置的迭代器,该迭代器指向的不是链表中的某个元素,
 不能解引用。cbefore_begin()返回const_iterator
 lst.insert_after(p,t)
 lst.insert_after(p,n,t)
 lst.insert_after(p,b,e)
 lst.insert_after(p,il)
 在迭代器p所指元素的后面插入相应的元素。
 emplace_after(p, args) 在迭代器p所指元素的后面,根据args构造一个新的元素
 lst.erase_after(p)
 lst.erase_after(b,e)
 删除在迭代器p所指的,或范围为[b,e)的元素。返回被删除元素的后继的迭代器。

3.5 改变容器大小


容器(除了array)可以使用resize来改变大小。
顺序容器size操作
 c.resize(n) 重新定义c的大小为n。如果n < c.size(),删除多余的元素。 
 否则添加新的元素(值初始化)
 c.resize(n,t)  重新定义c的大小为n。增加的新元素值为t
【注】当resize使容器缩小时,指向被删除元素的迭代器、引用和指针会失效;resize会使vector、string、deque的所以迭代器、引用和指针失效。
如果容器保存的是类类型元素,当resize使得增加元素时,会调用该类的默认构造函数,如果没有默认构造函数,则必须提供与构造函数相应的初始化值。

4. vector如何增长


vector的内存管理是,根据一定的策略事先分配一段内存空间,当元素个数增长到空间不够用时,再分配新的内存空间,然后将元素搬到新的空间,再释放旧的空间。
容器内存管理
 c.shrink_to_fit() 将capacity()缩小到与size()相等
 c.capacity() 容器c的容量
 c.reserve(n) 使c的容量至少可以保存n个元素
需要注意的是,resize不会改变容器的容量,其改变的是容器中元素的个数。reserve不会使容器的容量减少。
【c++11】
新标准加入的shrink_to_fit使得可以释放容器中多余的容量。适用于deque, vector和string。
增长测试:
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> vi;
	cout << "size: " << vi.size() << "; capacity: " << vi.capacity() << endl;
	for(vector<int>::size_type idx = 0; idx <= 50; ++idx) {		
		vi.push_back(idx);
		cout << "size: " << vi.size() << "; capacity: " << vi.capacity() << endl;
	}
	return 0;
}

5. string操作


略过。

6. 容器适配器


适配器是一个通用概念。包括容器适配器,迭代器适配器和函数适配器。 适配器是一种使一种对象按另一种对象的规则操作的机制
除了前面几节讲到的顺序容器,C++库还定义了三种顺序容器适配器:stack, queue和priority_queue。

定义一个适配器


默认情况下,stack和queue在deque上实现,priority_queue在vector上实现。
stack<int> stk;
也可以指定用来实现的容器:
stack<int, vector<int>> stk_vec
实现适配器的容器是有限制的。所有的适配器都要求有添加和删除元素的能力。所以array不能使用。所以的适配器都要求在容器的末尾添加和删除元素,forward_list也不能使用。stack要求push_back, pop_back和back操作,可以用vector, deque, list实现。queue要求back, push_back, front和push_front操作,可以用list, deque实现。priority_queue除了要求front, push_back和pop_back 操作,还要求随机访问,所以可以用vector 或 deque实现。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

superbin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值