You Know Nothing
  • 主页
  • 分类
  • 标签
  • 归档

C++ Primer 第九章 顺序容器

  • 9.1 顺序容器概述
  • 9.2 容器库概览
    • 9.2.1 类型别名
    • 9.2.2 构造函数
    • 9.2.3 赋值与 swap
    • 9.2.4 容器大小操作
    • 9.2.5 添加元素
    • 9.2.6 访问元素
    • 9.2.7 删除元素
    • 9.2.8 迭代器
    • 9.2.9 特殊的 forward_list 操作
    • 9.2.10 改变容器大小
  • 9.3 迭代器失效
    • 9.3.1 添加元素
    • 9.3.2 删除元素
  • 9.4 额外的 string 操作
    • 9.4.1 构造 string 的其他方法
    • 9.4.2 substr 操作
    • 9.4.3 其他修改 string 的操作
    • 9.4.4 string 搜索操作
    • 9.4.5 compare 函数
    • 9.4.6 数值转换
  • 9.5 容器适配器
    • 9.5.1 所以适配器都支持的的操作和类型
    • 9.5.2 定义适配器
    • 9.5.3 栈适配器
    • 9.5.4 队列适配器

一个容器就是一些特定类型对象的集合。顺序容器(sequential container)为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。

9.1 顺序容器概述

类型 简介
vector 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
deque 双端队列。支持快速随机访问。在头尾位置插入或删除速度很快
list 双向链表。只支持双向顺序访问。在任何位置进行插入或删除都很快
forward_list 单向链表。只支持单向顺序访问。在任何位置进行插入或删除都很快
array 固定大小数组。支持快速随机访问。不能添加或删除元素
string 与vector相似的容器,专门用于保存字符。随机访问快。在尾部插入或删除快

string和vector保存在连续的内存空间中,因此由下标计算地址非常快速。

forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能,因此不提供size操作,因为保存和计算大小会增加开销。

通常,vector是最好的选择。

每个容器都定义在同名的头文件中,容器均是模板类。

9.2 容器库概览

9.2.1 类型别名

类型别名 意义
iterator 此容器类型的迭代器类型
const_iterator 常量迭代器类型
size_type 无符号整数,足够保存此种容器的最大大小
difference_type 带符号整数,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型,与value_type&含义相同
const_reference 常量左值类型

9.2.2 构造函数

构造函数 意义
C c; 默认构造函数,构造空容器。如果 c 是一个array,则元素按默认方式初始化
C c1(c2); 构造 c2 的拷贝 c1,必须为相同类型,且保存元素也相同
C c1 = c2; 同上
C c(b, e); 构造 c,将迭代器指定的范围内的元素拷贝到 c,类型要相容(array不支持)
C c 列表初始化,类型要相容,遗漏元素值初始化
C c = 同上
C seq(n); 包含 n 个元素并进行值初始化,是explicit构造函数(string不要求explicit)
C seq(n, t); 包含 n 个初始值为 t 的元素

只有顺序容器(不包括array的构造函数才能接受大小参数)。如果元素类型没有默认构造函数,除了大小参数外,还需要显式指定元素初始值。array支持拷贝和赋值(内置数组不行)。

9.2.3 赋值与 swap

赋值与 swap 意义
c1 = c2; 将 c1 中的元素替换成 c2 中的元素,类型要相同
c1 = 将 c1 中的元素替换成列表中的元素(array不支持)
a.swap(b); 交换 a 和 b 的元素,类型要相同,此操作通常比拷贝元素快得多
swap(a, b); 与上面等价
seq.assign(b, e) 将 seq 中的元素替换为迭代器范围中的元素,迭代器不能指向 seq 的元素
seq.assign(il) 用初始化列表替换
seq.assign(n, t) 用 n 个 t 替换

由于右边运算对象的大小可能与左边不同,因此array不支持assign,也不运行用花括号包围的值列表赋值。assign不适用于关联容器。assign仅要求类型相容。

赋值相关操作会导致指向左边容器内部的迭代器、引用和指针失效,而swap操作不会导致失效(array和string除外),它们仍指向交换之前的那些元素。

除array外,swap不对任何元素进行拷贝、删除和插入操作,因此可以保证在常数时间完成,它只是交换了两个容器的内部数据结构。

swap两个array会真正交换它们的元素。在操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经和另一个array中对应元素的值进行了交换。

统一使用非成员版本的swap是一个好习惯。

9.2.4 容器大小操作

大小 意义
c.size(); c 中元素的数目,forward_list不支持
c.max_size(); c 可保存的最大元素数目
c.empty(); 判空
关系运算符 意义
==、!= 所有容器都支持
<、<=、>、>= 无序关联容器不支持,类型相同,保存元素也要相同

9.2.5 添加元素

添加元素 意义
c.push_back(t) 在尾部创建值为 t 的元素,返回void
c.emplace_back(args) 在尾部用参数构造元素,返回void
c.push_front(t) 在头部创建值为 t 的元素,返回void
c.emplace_front(t) 在头部用参数构造元素,返回void
c.insert(p, t) 在迭代器 p 指向的元素之前创建值为 t 的元素,返回指向新添加元素迭代器
c.emplace(p, args) 在迭代器 p 指向的元素之前构造值为 t 的元素,返回指向新添加元素迭代器
c.insert(p, b, e) 在迭代器 p 指向的元素之前插入迭代器范围指定的元素,返回指向新添加的第一个元素的迭代器,若范围为空,返回 p
c.insert(p, n, t) n 个 t
c.insert(p, il) 列表

这些操作会改变容器大小,array不支持。

forward_list有自己版本的 insert 和 emplace。forward_list不支持 push_back 和 emplace_back。

vector和string不支持push_front 和 emplace_front。

emplace函数会在容器管理的内存空间中直接创建对象,而push函数会创建一个局部临时变量,并将其压入容器中。传递给emplace的参数必须与元素类型的构造函数相匹配。

9.2.6 访问元素

访问元素 意义
c.back() 返回尾元素引用。若 c 为空,行为未定义
c.front() 返回首元素引用。若 c 为空,行为未定义
c[n] 返回下标为 n 元素的引用,n 是一个无符号整数。若 n >= c.size(),行为未定义
c.at(n) 返回下标为 n 元素的引用,若 n 越界,抛出 out_of_range 异常

at和下标操作只适用于string、vector、deque和array。back不适用于forward_list。

9.2.7 删除元素

删除元素 意义
c.pop_back() 删除尾元素。若 c 为空,行为未定义。返回void
c.pop_front() 删除首元素。若 c 为空,行为未定义。返回void
c.erase(p); 删除迭代器所指元素,返回被删除元素之后元素的迭代器,若 p 为尾后迭代器,行为未定义
c.erase(b, e); 删除迭代器所指元素,返回最后一个被删除元素之后元素的迭代器,若 e 为尾后迭代器,函数返回尾后迭代器
c.clear(); 清空,返回void

这些操作会改变容器大小,array不支持。

forward_list有自己版本的 erase。forward_list不支持 pop_back。

vector和string不支持pop_front。

删除元素的成员函数并不检查其参数。在删除元素前,程序员必须确保它们是存在的。

9.2.8 迭代器

迭代器 意义
c.begin(), c.end() 首尾迭代器
c.cbegin(), c.cend() 首尾常量迭代器
反向容器额外成员 意义
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 按逆序寻址元素的常量迭代器
c.rbegin(), c.rend() 尾首迭代器
c.crbegin(), c.crend() 尾首常量迭代器

注:forward_list不支持

9.2.9 特殊的 forward_list 操作

为了理解forward_list为什么有特殊版本的添加和删除操作,考虑当我们从一个单向链表中删除一个元素时会发生什么。当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生变化。为了添加或删除一个元素,我们需要访问其前驱,以便改变前驱改变前驱的链接。但是,forward_list是单向链表。在一个单向链表中,没有简单的方法来获取一个元素的前驱,出于这个原因,在一个forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。这样,我们总是可以访问到被添加或删除元素所影响的元素。

由于这些操作与其他容器上的操作有实现方式不同,forward_list并未定义insert、emplace和erase,而是定义了名为insert_after、emplace_after和erase_after的操作。为了支持这些操作,forward_list也定义了before_begin,它返回一个首前迭代器。这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素(亦即在链表首元素之前添加删除元素)。

操作 意义
lst.before_begin() 返回指向链表首元素之前并不存在的元素的迭代器,此迭代器不能解引用
lst.cbefore_begin()  cbefore_begin() 返回一个 const_iterator
lst.insert_after(p, t) 在迭代器 p 之后的位置插入元素 t,若 p 为尾后迭代器,则函数的行为未定义。若范围为空,返回 p
lst.insert_after(p, n, t) 在迭代器 p 之后的位置插入 n 个 t
lst.insert_after(p, b, e) 在迭代器 p 之后的位置插入迭代器范围表示的元素
lst.insert_after(p,il) 在迭代器 p 之后的位置插入花括号列表
emplace_after(p,args)   使用 args 在 p 指定的位置之后构造一个元素,返回一个指向这个新元素的迭代器。若 p 为尾后迭代器,则函数的行为未定义
lst.erase_after(p) 删除 p 指向的位置之后的元素,返回一个指向被删除元素之后元素的迭代器,若不存在这样的元素,则返回尾后迭代器,如果 p 指向 lst 的尾元素或者是一个尾后迭代器,则函数的行为未定义
lst.erase_after(b, e) 删除从 b 之后直到(但不包含)e 之间的元素

9.2.10 改变容器大小

操作 含义
c.resize(n) 调整 c 的大小为 n 个元素。若 n < c.size(),多出的元素被丢弃。若必须添加新元素,则新元素采取值初始化
c.resize(n, t) 调整 c 的大小为 n 个元素。,多出的元素被丢弃。若必须添加新元素,则新元素初始化为 t
c.shrink_to_fit() 请求将capacity()减小为与size()相同,具体的实现可能忽略此请求
c.capacity() 不重新分配内存的话,c 可以保存多少元素
c.reserve(n) 分配至少能容纳 n 个元素的内存空间

shrink_to_fit()只适用于vector、string和deque。

capacity()和reserve(n)只适用于vector和string。

9.3 迭代器失效

9.3.1 添加元素

vector或string:

  • 存储空间重新分配:迭代器、指针、引用均失效
  • 未重新分配:插入位置之前的有效,之后的失效

deque:

  • 插入首尾之外:均失效
  • 插入首尾:迭代器失效,指针、引用不失效

list或forward_list:都有效(包括尾后和首前)

9.3.2 删除元素

list或forward_list:都有效(包括尾后和首前)

deque:

  • 删除首尾之外:均失效
  • 删除首:首前失效,其他有效
  • 删除尾:尾后失效,其他有效

vector或string:被删除元素之前的都有效

当我们删除元素时,尾后迭代器总是会失效(除了删除deque首元素外),所以不要保存end返回的迭代器。

9.4 额外的 string 操作

9.4.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)

这些构造函数接受一个string或const char*参数。从const char*拷贝时,指针指向的数组必须以空字符结尾,如果还传递了一个计数值,数组就不必以空字符结尾。

9.4.2 substr 操作

s.substr(pos, n)返回一个string,包含 s 中从 pos 开始的 n 个字符的拷贝。pos 的默认值是0,n 的默认值是 s.size()-pos,即拷贝从 pos 开始的所有字符。如果开始位置超出string的大小,抛出 out_of_range 异常,不管 n 值为多少,最多拷贝到string的末尾。

9.4.3 其他修改 string 的操作

修改 string 的操作

repalce 和 insert参数类型

9.4.4 string 搜索操作

string类提供了6个不同的搜索函数,每个函数有4个重载版本。每个搜索操作都返回string::size_type值,表示匹配发生的下标。如果搜索失败,则返回string::npos的static成员。标准库将string::npos定义成一个const string::size_type,并初始化为-1。由于 npos 是一个无符号数,此初始值意味着 npos 等于任何string最大的可能大小。

string 搜索操作

string 搜索操作参数

9.4.5 compare 函数

compare 函数

9.4.6 数值转换

string 数值转换

string s2 = "pi = 3.14";
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));

9.5 容器适配器

适配器(adaptors)是标准库中的一个通用概念,容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器(Container adaptors)接受一种已有的容器类型,使其行为看起来像一种不同的类型。标准库定义了三个序列容器适配器:stack、queue和priority_queue。

9.5.1 所以适配器都支持的的操作和类型

名称 意义
size_type 一种类型,足以保存当前类型的最大对象的大小
value_type 元素类型
container_type 实现适配器的底层容器类型
A a; 创建一个名为 a 的空适配器
A a(c); 创建一个名为 a 的适配器,带有容器 c 的一个拷贝
关系运算符 每个适配器都支持所有关系运算符。这些关系运算符返回底层容器的比较结果
a.empty() 判空
a.size() 元素数目
swap(a, b) 交换,类型要相同,包括底层容器类型也必须相同
a.swap(b) 同上

9.5.2 定义适配器

  • stack默认基于deque实现,要求push_back、pop_back和back操作,可使用除array和forward_list之外的任何容器构造

  • queue默认基于deque实现,要求back、push_back、front、push_front操作,可使用list和deque构造

  • priority_queue默认基于vector实现,要求front、push_back和pop_back操作,还需要随机访问能力,可使用vector和deque构造

两种构造方法:默认构造函数创建一个空对象;接受一个容器的构造函数拷贝该容器来初始化适配器。

//假设 deq 是一个 deque<int>
stack<int> stk(deq);    //从 deq 拷贝元素到 stk

我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型:

//在 vector 上实现的空栈
stack<string, vector<string>> str_stk;
//在 vector 上实现的空栈,初始化时保存 svec 的拷贝
stack<string, vector<string>> str_stk2(svec);

9.5.3 栈适配器

stack类型定义在同名头文件中。下面展示了如何使用:

stack<int> intStack;  //空栈
//填满栈
for (size_t ix = 0; ix != 10; ++ix)
    intStack.push(ix);  //栈保存0-9十个数
while (!intStack.empty()) { //栈中有值就继续循环
    int value = intStack.top();
    //使用栈顶值的代码
    intStack.pop(); //弹出栈顶元素,继续循环
}
操作 意义
s.pop() 删除栈顶元素,但不返回该元素值
s.push(item) 创建一个新元素压入栈顶,该元素通过拷贝或移动 item 而来
s.emplace(args) 构造一个新元素压入栈顶,该元素通过 args 构造
s.top() 返回栈顶元素,但不将元素弹出栈

每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作,我们只能使用适配器操作,而不能使用底层容器类型的操作。

9.5.4 队列适配器

queue和priority_queue定义在头文件 queue 中。操作方法如下:

操作 意义
q.pop() 删除 queue 的首元素或 priority_queue 的最高优先级的元素,但不返回该元素
q.front() 返回首元素,但不删除此元素,只适用于 queue
q.back() 返回尾元素,但不删除此元素,只适用于 queue
q.top() 返回优先级最高的元素,但不删除该元素,只适用于 priority_queue
q.push(item) 在 queue 末尾或 priority_queue 中恰当的位置创建一个元素,其值为 item
q.emplace(args) 在 queue 末尾或 priority_queue 中恰当的位置由 args 参数构造一个元素

RELATED

  • C++ 中的类型转换
  • C++ Primer 第十七章 标准库特殊设施
  • C++ Primer 第十六章 模板与泛型编程
  • C++ Primer 第十五章 面向对象程序设计
  • C++ Primer 第十四章 重载运算与类型转换

OLDER

  • Atom 快捷键
  • Perl 入门
  • 原码, 反码, 补码
  • eps 图像截切四周的空白
  • PyTroch 之 torch 包

NEWER

  • C++ Primer 第十章 泛型算法
  • C++ Primer 第十一章 关联容器
  • C++ Primer 第十二章 动态内存
  • C++ Primer 第十三章 拷贝控制
  • C++ Primer 第十四章 重载运算与类型转换

发布日期

2018-10-16 22:43:16

最后更新

2018-10-17 11:12:28

分类

C++

标签

  • C++ 18
  • Powered by Pelican. Theme: Elegant by Talha Mansoor