【C++】《C++ Primer 5th》笔记-Chapter9-顺序容器

笔记:
一、顺序容器概述
1、元素在顺序容器中的顺序与其加入容器时的位置相对应。关联容器中元素的位置由元素相关联的关键字值决定。
2、顺序容器类型:
①vector:可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
②deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。
③list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。
④forward_list:单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。(不支持size()操作、forward_list迭代器不支持递减操作符)
⑤array:固定大小数组。支持快速随机访问。不能添加或者删除元素。
⑥string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。

3、现代C++程序应该使用标准库容器,而不是更原始的数据结构,如内置数组。
4、以下是一些选择容器的基本原则:
①除非你有很好的理由选择其他容器,否则应使用vector。
②如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list。
③如果程序要求随机访问元素,应使用vector或deque。
④如果程序要求在容器的中间插入或删除元素,应使用list或forward_list。
⑤如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque。
⑥如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则:
-首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector追加数据,然后再调用标准库的sort函数。
-如果必须在中间位置插入元素,考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中。

5、如果不确定应该使用哪种容器,那么可以在程序中只使用vector和list公共的操作:使用迭代器,不使用下标操作,避免随机访问。这样,在必要时选择使用vector或list都很方便。

二、容器库概览
1、容器均定义为模板类。
2、较旧的编译器可能需要在两个尖括号之间键入空格,例如,vector<vector<string> >.
3、顺序容器构造函数的一个版本接受容器大小参数,它使用了元素类型的默认构造函数。但某些类没有默认构造函数。我们可以定义一个保存这种类型对象的容器,但我们在构造这种容器时不能只传递给它一个元素数目参数:
// 假定noDefault是一个没有默认构造函数的类型
vector<noDefault> v1(10, init);    // 正确:提供了元素初始化器
vector<noDefault> v1(10);        // 错误:必须提供一个元素初始化器
4、《C++ Primer 5e》P295列出了容器支持的操作。
5、迭代器end可以与迭代器begin指向相同的位置,但不能指向begin之前的位置。
6、反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义也都发生了颠倒。例如,对一个反向迭代器执行++操作,会得到上一个元素。
7、begin和end操作生成指向容器中第一个元素和尾元素之后位置的迭代器。这两个迭代器最常见的用途是形成一个包含容器中所有元素的迭代器范围。
8、begin和end有多个版本:带r的版本返回反向迭代器;以c开头的版本则返回const迭代器。不以C开头的函数都是被重载过的,一个是const成员,另一个是非常量成员。
9、当不需要写访问时,应使用cbegin和cend。
10、每个容器类型都定义了一个默认构造函数。除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
11、只有顺序容器(不包括array)的构造函数才能接受大小参数,关联容器并不支持。
12、将一个新容器创建为另一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者(array除外)拷贝由一个迭代器对指定的元素范围。
13、当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且,新容器和原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。
14、如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。如果元素类型没有默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值。
15、我们不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制。
16、赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(容器类型为array和string的情况除外)。
17、顺序容器(array除外)定义了一个名为assign的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign操作不适用于关联容器和array。由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。
18、swap操作交换两个相同类型容器的内容。除array外,交换两个容器内容的操作保证会很快——元素本身并未交换,swap只是交换了两个容器的内部数据结构。
19、除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。元素不会被移动的事实意味着,除string外,指向容器的迭代器、引用或指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素,但是在swap之后,这些元素已经属于不同的容器了。
20、与其他容器不同,对一个string调用swap会导致迭代器、引用和指针失效。
21、与其他容器不同,swap两个array会真正交换它们的元素。因此,交换两个array所需的时间与array中元素的数目成正比。
22、统一使用非成员版本的swap是一个好习惯。
23、forward_list支持max_size和empty,但不支持size。
24、比较两个容器实际上是进行元素的逐对比较。这些运算符的工作方式与string的关系运算类似。
25、只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。

三、顺序容器操作
1、除array外,所有标准库容器在运行时都可以动态添加或删除元素来改变容器大小。
2、向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效。
3、除array和forward_list之外,每个顺序容器(包括string类型)都支持push_back.
4、当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。就像我们将一个对象传递给非引用参数一样,容器中的元素与提供值的对象之间没有任何关联。随后对容器中的元素的任何改变都不会影响到原始对象,反之亦然。
5、deque像vector一样提供了随机访问元素的能力,但它提供了vector所不支持的push_front.
6、insert函数将元素插入到迭代器所指定的位置之前。注意,将元素插入到vector、deque和string中的任何位置都是合法的,然而这样做可能很耗时。
7、在新标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器。所以,通过使用insert的返回值,可以在容器中一个特定位置反复插入元素。
8、新标准中引入了三个新成员——emplace_front、emplace和emplace_back,这些操作构造而不是拷贝元素。emplace函数在容器中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数相匹配。
9、如果容器中没有元素,访问操作的结果是未定义的。对一个空容器调用front和back,就像使用一个越界的下标一样,是一种严重的程序设计错误。
10、包括array在内的每个顺序容器都有一个front成员函数,而除forward_list之外的所有顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用。
11、在容器中访问元素的成员函数(即front、back、下标和at)返回的都是引用。如果容器是一个const对象,则返回值是const的引用。如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值。
与往常一样,如果我们使用auto变量来保存这些函数的返回值,并且希望使用此变量来改变元素的值,必须记得将变量定义为引用类型。
12、删除元素的成员函数并不检查其参数。在删除元素之前,程序员必须确保它(们)是存在的。
13、删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vector或string中删除点之后位置的迭代器、引用和指针都会失效。
14、成员函数erase从容器中指定位置删除元素,返回指向删除的(最后一个)元素之后位置的迭代器。
15、我们可以用resize来增大或缩小容器(array不支持resize)。如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于新大小,会将新元素添加到容器后部。
16、如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;对vector、string或deque进行resize可能导致迭代器、指针和引用失效。
17、由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vector、string和deque尤为重要。
18、添加/删除vector、string和deque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。程序必须保证每个循环步中都更新迭代器、引用或指针。
如果在一个循环中插入/删除deque、string或vector中的元素,不要缓存end返回的迭代器。

四、vector对象是如何增长的
1、capacity操作告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素。reserve操作允许我们通知容器它应该准备保存多少个元素。
2、reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。
只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。
调用reserve永远也不会减少容器占用的内存空间。
3、resize成员函数只改变容器中元素的数目,而不是容器的容量。我们同样不能使用resize来减少容器预留的内存空间。
4、在新标准库中,我们可以调用shrink_to_fit来要求deque、vector或string退回不需要的内存空间(只是一个请求,标准库并不保证一定退回内存空间)。
5、容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。
6、每个vector实现都可以选择自己的内存分配策略。但是必须遵守的一条原则是:只有当迫不得已时才可以分配新的内存空间。
7、只有当执行insert操作时size与capacity相等,或者调用resize或reserve时给定的大小超过当前capacity,vector才可能重新分配内存空间。会分配多少超过给定容量的额外空间,取决于具体实现。

五、额外的string操作
1、通常当我们从一个const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。
2、string搜索函数返回string::size_type值,该类型是一个unsigned类型。因此,用一个int或其他带符号类型来保存这些函数的返回值不是一个好主意。
3、如果string不能转换为一个数值,这些函数抛出一个invalid_argument异常。如果转换得到的数值无法用任何类型来表示,则抛出一个out_of_range异常。

六、容器适配器
1、除了顺序容器外,标准库还定义了三个顺序容器适配器:stack、queue和priority_queue。每个适配器都在其底层顺序容器类型之上定义了一个新的接口。
容器、迭代器和函数都有适配器。
本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如,stack适配器接受一个顺序容器(除array或forward_list外),并使其操作起来像一个stack一样。
2、默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。
3、对于一个给定的适配器,可以使用哪些容器是有限制的。所有适配器都要去容器具有添加和删除元素的能力。因此,适配器不能构造在array之上。类似的,我们也不能用forward_list来构造适配器,因为所有适配器都要求容器具有添加、删除以及访问尾元素的能力。stack只要求push_back、pop_back和back操作,因此可以使用除array和forward_list之外的任何容器类型来构造stack。queue适配器要求back、push_back、front和push_front,因此它可以构造于list或deque之上,但不能基于vector构造。priority_queue除了front、push_back和pop_back操作之外还要求随机访问能力,因此它可以构造于vector或deque之上,但不能基于list构造。

一些术语:
1、适配器:标准库类型、函数或迭代器,它们接受一个类型、函数或迭代器,使其行为像另外一个类型、函数或迭代器一样。
2、deque:顺序容器。deque中的元素可以通过位置下标来访问。支持快速的随机访问。deque各方面都与vector类似,唯一的差别是,deque支持在容器头尾位置的快速插入和删除,而且在两端插入或删除元素都不会导致重新分配空间。
3、forward_list:顺序容器,表示一个单向链表。forward_list中的元素只能顺序访问。从一个给定元素开始,为了访问另一个元素,我们只能遍历两者之间的所有元素。forward_list上的迭代器不支持递减运算。forward_list支持任意位置的快速插入(或删除)操作。与其他容器不同,插入和删除发生在一个给定的迭代器之后的位置。因此,除了通常的尾后迭代器之外,forward_list还有一个"首前"迭代器。在添加新元素后,原有的指向forward_list的迭代器仍有效。在删除元素后,只有原来指向被删元素的迭代器才会失效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值