第九章 顺序容器

一、顺序容器概述

  • 顺序容器(sequential container):为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。
  • 不同容器在不同的方面都有不同的性能折中
    • 向容器添加或者删除元素的代价
    • 非顺序访问容器中元素的代价

1. 顺序容器类型

容器类型 解释
vector 可变大小数组。支持快速随机访问。在尾部之外的位置进行插入/删除元素操作可能很慢。
deque 双端队列。支持快速随机访问。在头尾位置进行插入/删除操作速度很快。
list 双向链表。只支持双向顺序访问。在任何位置进行插入/删除操作速度很快。
forward_list 单向链表。只支持单向顺序访问。在任何位置进行插入/删除操作速度很快。
array 固定大小数组。支持快速随机访问,不能进行添加/删除元素操作。
string 专门用来保存字符,与vector相似的容器。支持快速随机访问。在尾部进行插入/删除元素操作很块。
  • 除了固定大小的array外,其他容器都提供高效、灵活的内存管理。
    • forward_list和array是新C++标准增加的类型。
  • 通常使用vector是最好的选择,除非你有很好的理由选择其他容器。
  • 新标准库的容器比旧版的快得多

二、容器库概览

1. 容器操作

类型别名

操作 解释
iterator 此容器的迭代器类型
const_iterator 可以读取元素,但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型;和value_type &含义相同
const_reference 元素的const左值类型,即const value_type &

构造函数

操作 解释
C c; 默认构造函数,构造空容器
C c1(c2);或C c1 = c2; 构造c2的拷贝c1
C c(b, e) 构造c,将迭代器b和e指定范围内的所有元素拷贝到c(array不支持)
C c{a, b, c...} 列表初始化c
C c(n) 只支持顺序容器,且不包括array,包含n个元素,这些元素进行了值初始化
C c(n, t) 包含n个初始值为t的元素
  • 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
  • array具有固定大小。
  • 和其他容器不同,默认构造的array是非空的。
  • 直接复制:将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。
  • 使用迭代器复制:不要求容器类型相同,容器内的元素类型也可以不同。

赋值和swap

操作 解释
c1 = c2; 将c1中的元素替换成c2中的元素
c1 = {a, b, c...} 将c1中的元素替换成列表中的元素(不适用于array)
c1.swap(c2) 交换c1和c2的元素
swap(c1, c2) 等价于c1.swap(c2)
c.assign(b, e) 将c中的元素替换成迭代器b和e表示范围中的元素,b和e不能指向c中的元素
c.assign(il) 将c中的元素替换成初始化列表il中的元素
c.assign(n, r) 将c中的元素替换为n个值是t的元素
  • 使用非成员版本的swap是一个好习惯。
  • assign操作不适用于关联容器和array,仅适用于顺序容器。
  • 赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(array和string除外)

大小

操作 解释
c.size() c中元素的数目(不支持forward_list)
c.max_size() c中可保存的最大元素数目
c.empty() 若c中存储了元素,返回false,否则返回true

添加元素

操作 解释
c.push_back(t) 在c尾部创建一个值为t的元素,返回void
c.emplace_back(args) 同上
c.push_front(t) 在c头部创建一个值为t的元素,返回void
c.emplace_front(args) 同上
c.insert(p, t) 在迭代器p指向的元素之前创建一个值是t的元素,返回指向新元素的迭代器
c.emplace(p, args) 同上
c.inset(p, n, t) 在迭代器p指向的元素之前插入n个值为t的元素,返回指向第一个新元素的迭代器;如果n是0,则返回p
c.insert(p, b, e) 将迭代器b和e范围内的元素,插入到p指向的元素之前;如果范围为空,则返回p
c.insert(p, il) il是一个花括号包围中的元素值列表,将其插入到p指向的元素之前;如果il是空,则返回p
  • 因为这些操作会改变大小,因此不适合于array。
  • forward_list有自己专有版本的insert和emplace。
  • forward_list不支持push_back和emplace_back。
  • 当我们用一个对象去初始化容器或者将对象插入到容器时,实际上放入的是对象的拷贝。
  • emplace开头的函数是新标准引入的,这些操作是构造而不是拷贝元素。
  • 传递给emplace的参数必须和元素类型的构造函数相匹配。
  • 向一个vector、string或deque插入元素会使所有指向内容的迭代器、引用和指针失效

删除元素

操作 解释
c.pop_back() 删除c中尾元素,若c为空,则函数行为未定义。函数返回void
c.pop_front() 删除c中首元素,若c为空,则函数行为未定义。函数返回void
c.erase(p) 删除迭代器p指向的元素,返回一个指向被删除元素之后的元素的迭代器,若p本身是尾后迭代器,则函数行为未定义
c.erase(b, e) 删除迭代器b和e范围内的元素,返回指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器,则返回尾后迭代器
c.clear() 删除c中所有元素,返回void
  • 会改变容器大小,不适用于array。
  • forward_list有特殊版本的erase
  • forward_list不支持pop_back
  • vector和string不支持pop_front
  • 删除deque中除收尾位置之外的任何元素都会使所有迭代器、引用和指针都会失效。指向vector或string中删除点之后位置的迭代器、引用和指针都会失效

访问元素

操作 解释
c.back() 返回c中尾元素的引用。若c为空,函数行为未定义
c.front() 返回c中头元素的引用。若c为空,函数行为未定义
c[n] 返回c中下标是n的元素的引用,n时候一个无符号整数。若n>=c.size(),则函数行为未定义。
c.at(n) 返回下标为n的元素引用。如果下标越界,则抛出out_of_range异常
  • 访问成员函数返回的是引用。
  • at和下标操作只适用于string、vector、deque、array。
  • back不适用于forward_list。
  • 如果希望下标是合法的,可以使用at函数。

特殊的forward_list操作

操作 解释
lst.before_begin() 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用。
lst.cbefore_begin() 同上,但是返回的是常量迭代器。
lst.insert_after(p, t) 在迭代器p之后插入元素。t是一个对象
lst.insert_after(p, n, t) 在迭代器p之后插入元素。t是一个对象,n是数量。若n是0则函数行为未定义
lst.insert_after(p, b, e) 在迭代器p之后插入元素。由迭代器b和e指定范围。
lst.insert_after(p, il) 在迭代器p之后插入元素。由il指定初始化列表。
emplace_after(p, args) 使用args在p之后的位置,创建一个元素,返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义。
lst.erase_after(p) 删除p指向位置之后的元素,返回一个指向被删元素之后的元素的迭代器,若p指向lst的尾元素或者是一个尾后迭代器,则函数行为未定义。
lst.erase_after(b, e) 类似上面,删除对象换成从b到e指定的范围。
  • 链表在删除元素时需要修改前置节点的内容,双向链表会前驱的指针,但是单向链表没有保存,因此需要增加获取前置节点的方法。
  • forward_list定义了before_begin,即首前(off-the-begining)迭代器,允许我们再在首元素之前添加或删除元素。

改变容器大小

操作 解释
c.resize(n) 调整c的大小为n个元素,若n<c.size(),则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化。
c.resize(n, t) 调整c的大小为n个元素,任何新添加的元素都初始化为值t。
  • 果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;对vector、string或deque进行resize可能导致迭代器、引用和指针失效

获取迭代器

操作 解释
c.begin(), c.end() 返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(), c.cend() 返回const_iterator
  • 以c开头的版本是C++11新标准引入的
  • 当不需要写访问时,应该使用cbegin和cend。

反向容器的额外成员

操作 解释
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(), c.rend() 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend() 返回const_reverse_iterator
  • 不支持forward_list

2. 迭代器

  • 迭代器范围:begin到end,即第一个元素到最后一个元素的后面一个位置。
  • 左闭合区间:[begin, end)。
  • 使用左闭合蕴含的编程设定
    • 如果begin和end相等,则范围为空。
    • 如果二者不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素。
    • 可以对begin递增若干次,使得begin == end。

三、顺序容器操作

1. 容器操作可能使迭代器失效的情况

  • 在向容器添加元素后

    • 对于vector和string,如果存储空间被重新分配,则指向容器的迭代器、引用和指针都会失效。如果存储空间未被重新分配,则指向插入位置之前的元素的迭代器、引用和指针仍然有效,但是之后的都会无效,
    • 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、引用和指针失效。如果在首尾位置添加元素,那么迭代器会失效,但是指向存在的元素的引用和指针不会失效
    • 对于listforward_list,指向容器的迭代器、引用和指针仍然有效。
  • 在从容器删除元素后
    • 对于vector和string,指向被删元素之前元素的迭代器、引用和指针仍然有效。
    • 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用和指针都会失效。如果在尾位置删除元素,则尾后迭代器失效,但是其他迭代器、引用和指针仍然有效。如果在首位置删除元素,其他迭代器、引用和指针仍然有效。
    • 对于listforward_list,指向容器其他位置的迭代器、引用和指针仍然有效。
    • 注意:当我们删除元素时,尾后迭代器总会失效。使用失效的迭代器、指针、引用是严重的运行时错误!
    • 建议:将要求迭代器必须保持有效的程序片段最小化。并且不要保存end返回的迭代器。

2. 容器内元素的类型约束

  • 元素类型必须支持赋值运算;
  • 元素类型的对象必须可以复制。
  • 除了输入输出标准库类型外,其他所有标准库类型都是有效的容器元素类型。

四、vector对象是如何增长的

vector和string在内存中是连续保存的,如果原先分配的内存位置已经使用完,则需要重新分配新空间,将已有元素从就位置移动到新空间中,然后添加新元素。但是如果这样,性能会很差。

为了避免这种代价,标准库采用了可以减少容器空间重新分配次数的策略。,当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间。

管理容器的成员函数

操作 解释
c.shrink_to_fit 将capacity()减少到和size()相同大小
c.capacity() 不重新分配内存空间的话,c可以保存多少个元素
c.reserve(n) 分配至少能容纳n个元素的内存空间
  • 注意:reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。

五、额外的string操作

1. 构造string的其他方法

操作 解释
string s(cp, n) s是cp指向的数组中前n个字符的拷贝,此数组至少应该包含n个字符。
string s(s2, pos2) s是string s2从下标pos2开始的字符的拷贝。若pos2 > s2.size(),则构造函数的行为未定义。
string s(s2, pos2, len2) s是string s2从下标pos2开始的len2个字符的拷贝。

2. substr操作

操作 解释
s.substr(pos, n) 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size()-pos,即拷贝从pos开始的所有字符。

3. 改变string其他方法

操作 解释
s.insert(pos, args) 在pos之前插入args指定的字符。pos可以使是下标或者迭代器。接受下标的版本返回指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。
s.erase(pos, len) 删除从pos开始的len个字符,如果len被省略,则删除后面所有字符,返回指向s的引用。
s.assign(args) 将s中的字符替换成args指定的字符。返回一个指向s的引用。
s.append(args) 将args指定的字符追加到s,返回一个指向s的引用。
s.replace(range, args) 删除s中范围range中的字符,替换成args指定的字符。返回一个指向s的引用。

4. string搜索操作

  • string类提供了6个不同的搜索函数,每个函数都有4个重载版本。
  • 每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败则返回一个名为string::npos的static成员(类型是string::size_type,初始化值是-1,也就是string最大的可能大小)
操作 解释
s.find(args) 查找s中args第一次出现的位置
s.rfind(args) 查找s中args最后一次出现的位置
s.find_first_of(args) 在s中查找args中任何一个字符第一次出现的位置
s.find_last_of(args) 在s中查找args中任何一个字符最后一次出现的位置
s.find_first_not_of(args) 在s中查找第一个不在args中的字符
s.find_first_not_of(args) 在s中查找最后一个不在args中的字符

args必须是一下的形式之一:

args形式 解释
c, pos 从s中位置pos开始查找字符c。pos默认是0
s, pos 从s中位置pos开始查找字符串s。pos默认是0
cp, pos 从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认是0
cp, pos, n 从s中位置pos开始查找指针cp指向的前n个字符。pos和n无默认值。

5. compare函数

逻辑类似于C标准库的strcmp函数,根据s是等于、大于还是小于参数指定的字符串,s.compare返回0、正数或负数。

参数形式 解释
s2 比较s和s2
pos1, n1, s2 比较s从pos1开始的n1个字符和s2
pos1, n1, s2, pos2, n2 比较s从pos1开始的n1个字符和s2
cp 比较s和cp指向的以空字符结尾的字符数组
pos1, n1, cp 比较s从pos1开始的n1个字符和cp指向的以空字符结尾的字符数组
pos1, n1, cp, n2 比较s从pos1开始的n1个字符和cp指向的地址开始n2个字符

6. 数值转换

操作 解释
to_string(val) 一组重载函数,返回数值val的string表示。val可以使任何算术类型。对每个浮点类型和int或更大的整型,都有相应版本的to_string()。和往常一样,小整型会被提升。
stoi(s, p, b) 返回s起始子串(表示整数内容)的数值,p是s中第一个非数值字符的下标,默认是0,b是转换所用的基数。返回int
stol(s, p, b) 返回long
stoul(s, p, b) 返回unsigned long
stoll(s, p, b) 返回long long
stoull(s, p, b) 返回unsigned long long
stof(s, p) 返回s起始子串(表示浮点数内容)的数值,p是s中第一个非数值字符的下标,默认是0。返回float
stod(s, p) 返回double
stold(s, p) 返回long double

六、容器适配器

  • 除了顺序容器外,标准库还定义了三个顺序容器适配器:stackqueuepriority_queue
  • 适配器(adapter)是使一事物的行为类似于另一事物的行为的一种机制,例如 stack 可以使任何一种顺序容器(除array或forward_list)以栈的方式工作。
  • 默认情况下,stack和queue是基于deque实现的,priority_queue是基于vector实现的。
  • 注意:所有适配器都要求容器具有添加和删除元素以及访问尾元素的能力。
// 每个适配器都定义两种构造函数:1.默认构造函数创建一个空对象。2. 接受一个容器的构造函数拷贝该容器来初始化适配器。

// 1.
deque<int> deq;
stack<int> stk(deq); // 2.
stack<string, vector<string> > str_stk;

1. 适配器的通用操作和类型

操作 解释
size_type 一种类型,须以保存当前类型的最大对象的大小
value_type 元素类型
container_type 实现适配器的底层容器类型
A a; 创建一个名为a的空适配器
A a(c) 创建一个名为a的适配器,带有容器c的一个拷贝
关系运算符 每个适配器都支持所有关系运算符:==、!=、<、 <=、>、>=这些运算符返回底层容器的比较结果
a.empty() 若a包含任何元素,返回false;否则返回true
a.size() 返回a中的元素数目
swap(a, b) 交换a和b的内容,a和b必须有相同类型,包括底层容器类型也必须相同
a.swap(b) 同上

2. stack

操作 解释
s.pop() 删除栈顶元素,不返回。
s.top() 返回栈顶元素,不删除。
s.push(item) 创建一个新元素,压入栈顶,该元素通过拷贝或移动item而来。
s.emplace(args) 同上,但元素由args来构造。
  • 定义在stack头文件中。
  • stack默认基于deque实现,也可以在list或vector之上实现

3. queue和priority_queue

操作 解释
q.pop() 删除队首元素,但不返回。
q.front() 返回队首元素的值,不删除。
q.back() 返回队尾元素的值,不删除。只适用于queue
q.top() 返回具有最高优先级的元素值,不删除。
q.push(item) 在队尾压入一个新元素。
q.emplace(args) 同上,但元素由args来构造。
  • 定义在queue头文件中。
  • queue默认基于deque实现,priority_queue默认基于vector实现
  • queue可以在list或vector之上实现,priority_queue也可以用deque实现

【C++】《C++ Primer 》第九章的更多相关文章

  1. C++ Primer : 第九章 : vector变长、string的其他操作以及容器适配器

    vector变长机制.string的其他构造方法,添加.替换和搜索操作,string比较和数值转换,最后是容器适配器. vector对象是如何增长的 vector和string类型提供了一些成员函数, ...

  2. C++ Primer 第九章 顺序容器

    由于书籍上写的已经很经典了,故大部分用图片的形式来阐述概念,代码纯手打进行验证. 1.顺序容器类型:vector.deque.list.forword_list.array.string. 2.顺序容 ...

  3. C++Primer 第九章

    //1.vector:可变大小数组.支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢.注意点:不要在vector中存放bool类型,vector<bool>并不是一个容器,其实现方 ...

  4. C++ Primer : 第九章 : 顺序容器的操作以及迭代器失效问题

    顺序容器的添加.访问.删除操作以及forward_list的特殊操作,还有迭代器失效问题. 一.向容器添加元素 // array不支持这些操作 // forward_list有自己撰于的版本的inse ...

  5. C++ Primer : 第九章 : 顺序容器的定义、迭代器以及赋值与swap

    顺序容器属于C++ STL的一部分,也是非常重要的一部分. 顺序容器包括: std::vector,包含在头文件<vector>中 std::string, 包含在头文件<strin ...

  6. C++ Primer第九章课后编程问题

    1. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3V1Z2xlMjAxMA==/font/5a6L5L2T/fontsize/400/fill/I0J ...

  7. C++ Primer Plus学习:第九章

    C++第九章:内存模型与名称空间 C++在内存中存储数据方面提供了多种选择.可直接选择保留在内存中的时间长度(存储持续性)以及程序哪一部分可以访问数据(作用域和链接)等. 单独编译 程序分为三个部分: ...

  8. 精通Web Analytics 2.0 (11) 第九章: 新兴分析—社交,移动和视频

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第九章: 新兴分析-社交,移动和视频 网络在过去几年中发生了不可思议的发展变化:从单向对话到双向对话的转变; 由视频,Ajax和 ...

  9. 第九章:四大组件之Broadcast Receiver

    第九章:四大组件之Broadcast Receiver   一.广播的功能和特征 广播的生命周期很短,经过调用对象-->实现onReceive-->结束,整个过程就结束了.从实现的复杂度和 ...

随机推荐

  1. 重庆聚焦区块链应用,Panda Global觉得春天真的来了!

    近日,由2020中国智博会组委会主办.重庆市大数据应用发展管理局与渝中区人民政府联合承办.重庆市区块链应用创新产业联盟和四川省区块链行业协会联合执行的"2020线上智博会区块链应用创新大赛& ...

  2. Tomcat 知识点总结

    Tomcat 学习笔记.本文相关配置均为 tomcat8 下,其他版本可能略有不同.如有错误请多包涵. 架构 首先,看一下整个架构图 接下来简单解释一下. Server:服务器.Tomcat 就是一个 ...

  3. Linux修改系统时间为东八区北京时间(上海时间)

    1. Linux时间   Linux的时间分为 System Clock(系统时间)和 Real Time Clock(硬件时间,简称RTC).   系统时间:指系统内核中的时间.   硬件时间:指主 ...

  4. JavaSE03-运算符&分支语句

    1.运算符 1.1 算术运算符 1.1.1 运算符和表达式 运算符:对常量或者变量进行操作的符号 表达式:用运算符把常量或者变量连接起来符合java语法的式子就可以称为表达式. 不同运算符连接的表达式 ...

  5. Java NIO:FileChannel数据传输

    调用方式 FileChannel dstChannel; FileChannel srcChannel; dstChannel.transferFrom(srcChannel,0,srcChannel ...

  6. [水题日常]Luogu1113 杂务

    这几天又在颓. 我的blog难度目前还比较低,主要面向像我这样子的新手(当然内容也会尽量讲得具体清楚一些)x 如果有错误还请指出~ 写完随笔之后才去翻了一下这题题解之后才注意到这题是有序的 QAQ完全 ...

  7. Python-自动化测试面试

    1.以你做过的项目,举例来说一下你的自动化测试是怎么做的? 参考答案:就拿简历上的ecshop项目来说吧,在编写脚本前,我们会对系统进行评估,确认这个系统可不可以实现UI自动化,如果可以的话,就筛选出 ...

  8. CSS—— div+css

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. 图解HTTP权威指南 | HTTP报文

                                                                                    一.问题 1.报文流是如何流动的 2.H ...

  10. 作为Java新手,如何才能快速的看透一个Java项目?

    前言 技术学习是一个总结.纠错.触类旁通的过程,而不是单纯重复练习的过程,如果你问一个做过5年以上Java的老码农,他们很多人都会有很强的"搬砖感",这种"搬砖感&quo ...