Loading

【C++】《C++ Primer 》第九章

第九章 顺序容器

一、顺序容器概述

  • 顺序容器(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 列表初始化c
C c(n) 只支持顺序容器,且不包括array,包含n个元素,这些元素进行了值初始化
C c(n, t) 包含n个初始值为t的元素
  • 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
  • array具有固定大小。
  • 和其他容器不同,默认构造的array是非空的。
  • 直接复制:将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。
  • 使用迭代器复制:不要求容器类型相同,容器内的元素类型也可以不同。

赋值和swap

操作 解释
c1 = c2; 将c1中的元素替换成c2中的元素
c1 = 将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实现
posted @ 2020-07-29 10:39  Parzulpan  阅读(170)  评论(0编辑  收藏  举报