第六章标准库类型

本文介绍了C++标准库中的顺序容器,如vector、deque、list和forward_list,分析了它们的性能特点,并给出了选择和使用顺序容器的基本原则。重点讲解了添加、删除元素、改变大小以及特殊操作如forward_list的技巧。
摘要由CSDN通过智能技术生成

6.5 标准库类型:顺序容器

概述

除我们介绍过的string和vector这两种外,C++标准库还包含其他的顺序容器,他们在下述两种方面存在不同的性能折中:

  • 向容器添加或从容器中删除元素的代价
  • 非顺序访问容器中元素的代价
顺序容器介绍
vector可变大小数组支持快速随机访问,在尾部之外的位置插入或者删除元素可能很慢
deque双端队列支持快速随机访问,在头尾位置插入/删除速度很快
list双向链表只支持双向顺序访问,在list中任何位置进行插入/删除操作速度都很快
forward_list单向链表只支持单向顺序访问,在链表任何位置进行插入/删除操作速度都很快
array固定大小数组支持快速随机访问,不能添加或者删除元素
string字符串和vector相似的容器,但专门用于保存字符,支持快速随机访问,在尾部插入/删除速度快

选择顺序容器的基本原则

  • 除非你有很好的理由选择其他容器,否则优先用vector
  • 如果你的程序有很多小元素,且空间的额外开销很重要,那么不要使用list或者forward_list
  • 如果程序要求随机访问元素,应使用vector或者deque
  • 如果程序要求在容器的中间插入或删除元素,应使用list或者forward_list;如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque
  • 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素
    • 先判断是否真的需要在容器中间位置添加元素,如果仅仅是为了保证容器元素有序的话,可以在输入数据时直接向vector尾部追加数据,然后调用标准库中的sort函数重排容器中元素,从而避免在中间位置添加元素
    • 如果必须在中间位置插入元素,那么可以考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中

容器库概览

以下列举所有容器(顺序容器和关联容器)都支持的操作。

1. 容器类型别名

通过类型别名,我们可以在不了解容器汇总元素类型的情况下使用它:

类型别名含义
iterator此容器类型的迭代器
const_iterator此容器类型的const迭代器
reverse_iterator此容器类型的反向迭代器
const_reverse_iterator此容器类型的const反向迭代器
size_type无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type带符号整数类型,足够保存两个迭代器之间的距离
value_type元素类型
reference元素的左值类型,与value_type &含义相同
const_reference元素的const左值类型,与const value_type&含义相同
2. 容器的begin和end成员

begin和end操作生成指向容器中第一个元素和尾元素之后位置的迭代器,它们包含多个版本:

  • r的版本返回反向迭代器
  • c的版本返回const迭代器
3. 容器定义和初始化

容器定义和初始化的操作包括:

操作含义备注
C c默认构造函数如果C是一个array,则c中元素按默认方式初始化;否则c为空
C c1(c2)
C c1 = c2
c1初始化为c2的拷贝c1和c2必须是相同的容器类型,且保存相同的元素类型(对于array而言,两者还必须具有相同大小)
C c{a, b, c...}
C c = {a, b, c...}
c初始化为初始化列表中元素的拷贝对于array类型列表中元素数目必须小于等于array的大小,任何遗漏的元素都进行值初始化
C c(b, e)c初始化为迭代器b和e指定范围中元素的拷贝
C seq(n)seq包含n个元素,这些元素进行了值初始化此构造函数是explicit的,array和string不适用
C seq(n, t)seq包含n个初始化为值t的元素array不适用
4. 容器赋值和swap

Tips:assign操作不适用于关联容器和array

操作含义备注
c1 = c2将c1中的元素替换为c2中元素的拷贝c1和c2必须具有相同的类型
c = {a, b, c}将c1中元素替换为初始化列表中元素的拷贝array不适用
swap(c1, c2)
c1.swap(c2)
交换c1和c2中的元素c1和c2必须具有相同的类型,swap通常比从c2向c1拷贝元素快得多
seq.assign(b, e)将seq中的元素替换为迭代器b和e所表示的范围中的元素迭代器b和e不能指向seq中的元素
seq.assign(il)将seq中的元素替换为初始化列表il中的元素
seq.assign(n, t)将seq中的元素替换为n个值为t的元素
  • 除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成
  • 与其他容器不同,swap两个array会真正交换它们的元素,因此交换两个array所需的时间与array中元素的数目成正比
  • 赋值相关操作会导致左边容器内部的迭代器、引用和指针失效,而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(容器类型为array和string的情况除外)
5. 容器大小操作
操作含义备注
c.size()c中元素的数目不支持forward_list
c.max_size()c中可保存的最大元素数目
c.empty()若c中存储了元素,返回false,否则返回true
6. 容器的关系运算符

Tips:无序关联容器不支持关系运算符。

每个容器类型都支持相等运算符(==!=),除了无序关联容器外所有容器都支持关系运算符(>>=<<=)。关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。

Tips:比较两个容器实际上是进行元素的逐对比较:

  • 如果两个容器具有相同大小且所有元素都两两对应相同,则这两个容器相等;否则两个容器不等
  • 如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器
  • 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等元素的比较结果

顺序容器操作

1. 向顺序容器添加元素

Tips:除array外,所有标准库容器都提供灵活的内存管理,运行时可以动态添加或者删除元素来改变容器大小。

  • array不支持添加元素
  • forward_list有自己专有版本的insertemplae,不支持push_backemplace_back
  • vectorstring不支持push_frontemmplace_front
操作含义备注
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.insert(p, n, t)在迭代器p指向的元素之前插入n个值为t的元素,返回指向新添加的第一个元素的迭代器,如果n为0则返回p
c.insert(p, b, e)将迭代器b和e指定范围内的元素插入到迭代器p指向的元素之前,b和e不能指向c中的元素,返回指向新添加的第一个元素的迭代器,如果范围为空则返回p
c.insert(p, il)il是一个花括号包围的元素值列表,将这些给定值插入到迭代器p指向的元素之前,返回指向新添加的第一个元素的迭代器,若列表为空则返回p

Tips:向vector、string或者deque插入元素会使得所有指向容器的迭代器、引用和指针失效。

emplace_back()C++11加入的新特性,都是用于将一个临时对象push到容器的末尾。不同在于push_back()需要先临时构造对象,再将这个对象拷贝到容器的末尾;而emplace_back()直接在容器的末尾构造对象,省去了拷贝的过程。

  • push_back()会先使用构造函数构造临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中,最后释放临时对象。这样造成的问题就是临时对象申请资源的浪费。
  • emplact_back()在容器尾部添加一个元素,不需要触发拷贝构造和转移构造
2. 访问元素

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

需要注意:

  • 对一个空容器调用front或者back,就像使用一个越界的下标一样,是一种严重的程序设计错误
  • 在容器中访问元素的成员函数(front、back、下标和at)返回的都是引用,如果容器是一个const对象则返回const的引用
  • 如果我们希望下包是合法的,可以使用at成员函数(at成员函数类似于下标运算符,但是在下标越界时会抛出一个out_of_range异常)
操作含义备注
c.back()返回c中尾元素的引用若c为空,则函数行为未定义
c.front()返回c中首元素的引用若c为空,则函数行为未定义
c[n]返回c中下标为n的元素的引用n是一个无符号整数,若n>=c.size()则函数行为未定义
c.at(n)返回下标为n的元素的引用如果下标越界,则抛出out_of_range异常
3. 删除元素

Tips:删除deque除首尾元素之外的任何元素都会使所有迭代器、引用和指针失效;指向vector或string中删除点之后位置的迭代器、引用和指针都会失效。

需要注意:

  • array不支持删除元素
  • forward_list有特殊版本的erase
  • forward_list不支持pop_backvectorstring不支持pop_front
操作含义备注
c.pop_back()删除c中尾元素,返回void若c为空,则函数行为未定义
c.pop_front删除c中首元素,返回void若c为空,则函数行为未定义
c.erase(p)删除迭代器p所指向的元素,返回一个指向被删元素之后元素的迭代器若p是尾后迭代器,则函数行为未定义
c.erase(b, e)删除迭代器b和e所指定范围的元素,返回一个指向最后一个被删元素之后的元素的迭代器若e本身就是尾后迭代器,则函数也返回尾后迭代器
c.clear()删除c中的所有元素,返回void
4. 改变容器大小

Tips:如果resize缩小元素,那么指向被删除元素的迭代器、引用和指针都会失效;对vector、string或者deque进行resize可能导致迭代器、指针和引用失效。

操作含义备注
c.resize(n)调整c的大小为n个元素,若n < c.size()则多出的元素被丢弃,若必须添加新元素则对新元素进行值初始化不适用于array
c.resize(n, t)调整c的大小为n个元素,任何新添加的元素都初始化为值t不适用于array
5. 容器操作可能使得迭代器失效

向容器中添加元素和从容器中删除元素的操作可能使得指向容器元素的指针、引用或者迭代器失效。

Tips:使用失效的指针、引用或者迭代器是一种严重的程序设计错误,很可能引用与使用未初始化指针一样的问题。

向容器添加元素后:

  • vector和string:
    • 存储空间被重新分配:指向容器的迭代器、指针和引用都会失效
    • 存储空间未被重新分配:指向插入位置之前的元素的迭代器、指针和引用仍然有效,但指向插入位置之后元素的迭代器、指针和引用将会失效
  • deque:
    • 插入到除首尾位置之外的任何位置:迭代器、指针和引用都会失效
    • 在首尾位置添加元素:迭代器会失效,但指向存在的元素的引用和指针不会失效
  • list和forward_list:指向容器的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍然有效

当我们从容器删除一个元素后:

  • list和forward_list:指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍然有效
  • deque:
    • 在首尾之外的任何位置删除元素:指向被删除元素外其他元素的迭代器、引用或指针也会失效
    • 删除deque的尾元素:尾后迭代器失效,但其他迭代器、引用和指针不受影响
    • 删除首元素:其他迭代器、引用和指针不受影响
  • vector和string:指向被删元素之前元素的迭代器、引用和指针仍然有效

特殊的forward_list操作

从一个单向链表中添加或删除元素时,还会改变其前一个元素指向的下一个元素。由于这些操作与其他容器上的操作实现方式不同,因此forward_list并未定义insertemplaceerase,而是定义了名为insert_afteremplace_aftererase_after的操作。> Tips:为了支持这些操作,forward_list定义了before_begin首前迭代器。

操作含义备注
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之后的位置插入元素,其中t是一个对象,n是数量,b和e是表示范围的一对迭代器(b和e不能指向lst内),il是一个花括号列表返回一个指向最后一个插入元素的迭代器,如果范围为空则返回p,若p为尾后迭代器则函数行为未定义
emplace_after(p, args)使用args在p指定的位置之后创建一个元素返回一个指向这个新元素的迭代器,若p为尾后迭代器则函数行为未定义
lst.erase_after(p)
lst.erase_after(b, e)
删除p指向的位置之后的元素,或删除从b之后直到e之间的元素返回一个指向被删元素之后的迭代器,若不存在这样的元素则返回尾后迭代器,如果p指向lst的尾元素或者一个尾后迭代器则函数行为未定义
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值