0
点赞
收藏
分享

微信扫一扫

C++ Primer 0x09 学习笔记


📔 C++ Primer 0x09 学习笔记

​​更好的阅读体验​​

9.1 顺序容器概述

所有顺序容器都提供了快速访问元素的能力,但是这些容器在以下方面都有不同的性能折中

  • 向容器添加或从容器中删除元素的代价
  • 非顺序访问容器中元素的代价

C++ Primer 0x09 学习笔记_后端

  • 除了固定大小的​​array​​外,其他容器都提供高效、灵活的内存管理
  • 新标准库的容器比旧版本快很多,所以我们应该尽量使用标准库容器而不是原始的数据结构,如内置数组
  • 通常vector是最好的选择,除非你有很好的利用选择其他容器
  • 如果你的程序有很多小元素,而且空间的额外开心很重要,不要使用​​list​​​或​​forward_list​
  • 如果程序需要在头尾插入删除元素不需要在中间增添或删除,那么用​​deque​
  • 如果成功续只有读取输入是才需要在容器中间插入元素,随后需要随机访问元素
  • 先确定是否真的要在中间加入,可以vector+sort来避免
  • 如果必须加,那么考虑输入阶段使用​​list​​​,输入完成拷贝到​​vector​
  • 如果既要随机访问又要在容器中间插入元素,要看啥操作占主导地位,然后比较不同容器相对性能
  • 如果不确定,那么可以只在程序中用​​vector​​​和​​list​​的公共操作,使用迭代器不使用下标避免随机访问

9.2 容器库概览

  • 顺序容器几乎可以保存任意类型的元素

9.2.1 迭代器

  • 如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的
  • 一个迭代器范围由一对迭代器表示​​[begin,end)​​左闭右开
  • 当满足如下条件时,两个迭代器​​begin​​​和​​end​​构成一个迭代器范围
  • 指向同一个容器中的元素或者是容器元素最后一个元素的位置
  • end 不在 begin之前
  • ​begin​​​与​​end​​相等,则范围为空
  • 与 ​​vector​​​ 和 ​​deque​​​ 不同,​​list​​​ 的迭代器不支持 ​​<​​​ 运算,只支持递增、递减、​​=​​​ 以及 ​​!=​​ 运算。

9.2.2 容器类成员

  • ​size_type​​:通常作为索引
  • ​iterator​​:普通迭代器
  • ​const_iterator​​:常量迭代器
  • 反向迭代器
  • 通过类型别名,我们可以在不了解容器元素类型的情况下使用它

9.2.3 begin 和 end 成员

  • 不需要写访问时,应该使用​​cbegin​​​和​​cend​​,都是被重载过的
  • ​rbegin​​​和​​rend​​返回反向迭代器
  • 可以将一个​​iterator​​​转换为对应的​​const_iterator​​反过来不行

9.2.4 容器定义和初始化

  • 每个容器类型都定义了一个默认构造函数,除了​​array​​之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数
  • 只有顺序容器(不包括​​array​​)的构造函数才能接受大小参数
  • 将一个容器创建为另一个容器的拷贝方法有两种
  • 直接拷贝整个容器,两个容器的类型和元素类型必须匹配
  • 拷贝由一个迭代器对指定的元素范围(​​array​​除外),不要求容器类型相同,元素类型也可以不同,只要能将拷贝的元素转换
  • 我们可以对一个容器进行列表初始化
  • 除了与管理容器相关的构造函数外,顺序容器(​​array​​除外)还提供另一个构造函数,它接受一个容器大小和一个可选的元素初始值
  • 标准库​​array​​具有固定大小,允许拷贝或对象赋值操作

9.2.5 赋值和 swap

  • 赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效
  • swap 操作将容器内容交换,不会导致指向容器内部的迭代器、引用和指针失效(容器类型为​​array​​​和​​string​​的情况除外)
  • ​array​​​类型不支持​​assign​​也不允许用花括号包围的值列表进行赋值
  • 顺序容器(除​​array​​​外)定义了​​assign​​成员,允许我们从不同但相容的类型赋值,或从容器的一个子序列赋值
  • ​assign​​操作用参数指定的元素(的拷贝)替换左边容器中的所有元素
  • 由于旧元素被替换,因此传递给​​assign​​​的迭代器不能指向调用​​assign​​的容器
  • 除​​array​​​外,​​swap​​不对任何元素进行拷贝、插入或删除操作,因此可以保证在常数时间内完成
  • 对一个​​string​​​调用​​swap​​会导致迭代器、引用、指针失效,因为原来的元素已经不属于原来的容器了
  • ​swap​​​两个​​array​​​会真正交换它们的元素,因此交换两个​​array​​​所需时间和​​array​​中元素数量成正比
  • 使用非成员版本的​​swap​​是一个好习惯

9.2.6 容器大小操作

  • 除了一个例外,每个容器都有三个与大小相关的操作
  • ​size​​返回容器中元素的数目
  • ​empty​​​当​​size​​​为0时返回​​true​
  • ​max_size​​返回大于或等于该类型容器所能容纳的最大元素数的值
  • ​forward_list​​​支持​​max_size​​和​​empty​​但不支持​​size​

9.2.7 关系运算符

  • 每个容器类型都支持相等运算符,除了无序关联容器外的所有容器都支持关系运算符
  • 关系运算符两侧运算对象必须是类型相同的容器且必须保存相同类型的元素
  • 两个容器的比较是进行元素的逐对比较,和​​string​​关系运算类似
  • 只有当其中元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器
  • 容器的相等运算实际上是使用元素的​​==​​​运算实现的,其他关系运算符是使用元素的​​<​​运算符实现的

9.3 顺序容器操作

9.3.1 向顺序容器添加元素

  • 添加元素会改变容器的大小,​​array​​不支持这些操作
  • 向一个​​vector​​​、​​string​​​或​​deque​​插入元素会使所有指向容器的迭代器、引用和指针失效
  • 当我们用一个对象来初始化容器是,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝而不是本身。容器中的元素与提供值的对象之间没有任何关联
  • ​deque​​​像​​vector​​​一样提供了随机访问元素的能力,但它还提供了​​push_front​
  • ​insert​​​可以将元素(可以指定数量)插入到迭代器所指定位置之前,​​vector​​​、​​deque​​​、​​string​​使用会比较耗时间
  • ​insert​​​返回值指向插入的新元素,可以借助​​insert​​​实现​​push_front​​相同的功能
  • ​emplace​​​函数在容器中直接构造元素,传递给​​emplace​​的参数必须与元素类型的构造函数相匹配

9.3.2 访问元素

  • 对一个空容器调用​​front​​​和​​back​​就像使用一个越界的下标一样,是一种严重的程序设计错误
  • 访问成员函数返回的是引用,如果我们使用​​auto​​变量来保存这些函数返回值并希望通过此变量来改变元素的值,必须记得将变量定义为引用类型

9.3.3 删除元素

  • 删除​​deque​​中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效
  • 指向​​vector​​​或​​string​​中删除点之后位置的迭代器、引用和指针都会失效
  • 删除元素的成员函数并不检查其参数,在删除元素之间,必须确保它们是存在的
  • ​pop_front​​​和​​pop_back​​​操作返回​​void​
  • ​erase​​删除指定位置(可以是一个,也可以是迭代器对形成的范围内所有元素),返回删除的最后一个元素之后位置的迭代器
  • 为了删除一个元素中的所有元素,我们既可以调用​​clear​​​,也可以用​​begin​​​和​​end​​​获得的迭代器作为参数调用​​erase​

9.3.4 特殊的 forward_list 操作

  • 在一个​​forward_list​​中添加或删除元素的操作是通过改变给定元素之后的元素来完成的
  • 复合赋值语句不能用于​​forward_list​​​和​​list​

9.3.5 改变容器大小

  • 可以使用​​resize​​​来增大或缩小容器,​​array​​不支持。如果当前大小大于要求的,容器后部的元素会被删除;如果当前大小小于新大小,会将新元素添加到容器后部(可以指定初始值,如果容器保存的是类类型元素,则必须提供初值或提供默认构造函数)
  • 如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效
  • 接受单个参数的​​resize​​版本对元素类型必须有默认构造函数

9.3.6 容器操作可能使迭代器失效

  • 向容器添加元素后
  • 如果​​vector​​​或​​string​​,且存储空间被重新分配,那么迭代器、指针和引用失效,如果没有重新分配,那么插入位置之前的元素有效,插入位置之后的会失效
  • 对于​​deque​​,插入到首尾位置之外的任何位置都会导致迭代器、指针和引用失效;如果在首尾添加元素,迭代器会失效,但指向元素的引用和指针不会失效
  • 对于​​list​​​和​​forward_list​​,指向容器的迭代器、指针和引用仍然有效
  • 从容器中删除元素
  • 对于​​list​​​和​​forward_list​​,指向容器的迭代器、指针和引用仍然有效
  • 对于​​deque​​,删除首尾位置之外的任何位置都会导致迭代器、指针和引用失效;如果删除为元素,那么尾后迭代器会失效;如果删除首元素,不会有影响
  • 如果​​vector​​​或​​string​​,删除元素位置之前的元素迭代器、指针和引用有效,插入位置之后的会失效
  • 使用失效的迭代器、指针和引用都是严重的运行时错误
  • 使用迭代器(或者指向容器元素的指针、引用)时,最小化要求迭代器必须保持有效的程序片段是个好方法
  • 编写改变容器的循环程序,可以利用​​insert​​​、​​erase​​返回值更新迭代器
  • 不要保存​​end​​​返回的迭代器,每次用的时候就直接​​.end()​​重新获取

9.4 vector 对象是如何快速增长的

  • 当不得不获取新的内存空间时,​​vector​​​和​​string​​的实现通常会分配比新的空间需求更大的内存空间,用来备用,这样不用每次添加新元素都重新分配容器的内存空间了
  • ​capacity​​告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素
  • ​reserve​​​操作允许我们通知容器它应该准备保存多少个元素,它并不影响容器中元素的数量,只影响​​vector​​​预先分配多大的内存空间(​​capacity​​)
  • ​resize​​只改变容器中元素的数目而不是容器的容量
  • ​capacity​​​是在不分配新的内存空间的前提下最多可以保存多少元素,​​size​​是指已经保存的元素的数目
  • 每个​​vector​​的实现都可以选择自己的内存分配策略,但是必须遵循的一条原则是,只有当迫不得已时才可以分配新的内存空间(一般2倍或者1.5倍不至于太浪费内存空间,同时也不需要太多次的赋值数组内容)
  • ​vector​​​的内存空间只增不减,vector内存的回收只能靠vector调用析构函数的时候才被系统收回,当然也可以使用swap来帮你释放内存,具体方法:​​vec.swap(vec);​

9.5 额外的 string 操作

  • 通常当我们从一个 ​​const char*​​​创建​​string​​时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符截止。或者可以传一个计数值,那么就不用空字符结尾
  • ​substr(pos,n)​​获取从pos开始长度为n的拷贝
  • ​string​​​的​​insert​​​和​​eraser​​支持下标版本
  • ​assign​​替换整个字符串
  • ​append​​追加到末尾
  • ​repalce(pos,oldVal.size(),newVal)​​​从​​pos​​​开始,删除​​oldVal.size()​​​长度的字母,插入​​newVal​​​,返回指向​​s​​的一个引用
  • ​string​​​搜索函数返回​​string::size_type​​​值,是一个​​unsigned​​类型
  • ​find​​​查找参数指定的字符串,若找到,则返回第一个匹配位置的下标,否则返回​​npos​
  • ​find_first_of​​找参数指定的字符串任一字符匹配的第一个位置
  • ​find_first_not_of​​搜索第一个不在参数中的字符
  • ​to_string(i)​​把i转为字符表示形式
  • ​stod(s)​​​将字符串转为浮点数,​​stoi​​​转​​int​​,其他类型同理

9.6 容器适配器

  • 除了顺序容器之外,标准库还定义了三个容器适配器​​adaptor​
  • ​stack​
  • ​queue​
  • ​priority_queue​
  • 适配器是一种机制,使某种事物的行为看起来像另一种事物一样


举报

相关推荐

0 条评论