《c++ primer笔记》第九章 顺序容器

前言

知识点很多,这里只记录遗忘的。从这章开始会对前面章节的内容进行一个扩充,如果以前的忘了读起来会有点吃力。总的来说,本章节难度不大。

一、概述

​ 顺序容器一共有六种类型:vector、deque、list、forward_list、array、string。下图是它们的一些特性。除了固定大小的array外,其他容器都提供高效、灵活的内存管理。vector、string和array之所以支持快速随机访问,是因为它们的元素存储在连续的内存空间中,所以进行插入删除操作就会很慢。而forward_list和list正好相反,它们存储元素的内存空间都是离散的,因此访问元素的开销较大。deque相对来说访问速度和某些情况的插入删除操作都比较高效。

array和forward_list都是C++11增加的新特性。array相对内置数组,效果更高、更安全。forward_list设计是为了达到与手写单向链表的性能。新标准库的容器优于旧版本。

image-20230307203401211

二、容器库概览

2.1容器定义和初始化

​ 将一个新容器创建为另一个容器的拷贝的方法:1)直接拷贝整个容器(两个容器的类型必须相同);2)拷贝一个由迭代器指定的元素范围(array除外)(两个迭代器的类型可以不同,只要元素能够转换)

image-20230307210016181

与顺序容器大小相关的构造函数

​ 如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。如果元素没有默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值。

array

array与内置数组很相似,当定义一个array时,除了指定元素类型,还要指定容器大小。

array<int, 42> 
array<string, 10>

一个默认构造的array是非空的,如果我们对array进行列表初始化,初始值的数目必须等于或小于array的大小。内置数组不能对其进行拷贝或对象赋值操作,但是array没有限制

int d[3] = {1,2,3};
int c[3] = d[3]; // 错误
array<int, 3> d2 = {1,2,3};
array<int, 10> c2 = d2;

2.2赋值

​ 赋值就是把左边容器的元素全部换成右边容器中元素的拷贝。只有顺序容器可以使用assign,但是array不支持assign操作

image-20230307213436479

assign

​ 赋值运算符要求等号两边的运算对象具有相同的类型,assign允许从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。使用assign会使左边容器内部的迭代器、引用和指针失效

swap

swap交换两个相同类型容器的内容,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。swap两个array会正在交换它们的元素

三、顺序容器操作

3.1添加元素

​ 除了array,所有标准库容器都提供灵活的内存管理。下表是添加元素的的操作。前面提到,像vector、string等容器添加一个元素可能会导致整个对象的存储空间重新分配,所以当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间。

image-20230309192600649

当我们将一个对象插入到容器时,实际上放入的是对象的拷贝,容器的元素与提供值的对象之间没有任何关联。

emplace

C++11引入了三个新成员:emplace_front、emplace、emplace_back,前面说了以前的插入元素的方法是拷贝,而这些新成员会进行构造,不会拷贝。下面一段代码很容易理解它们之间的差别。加入有一个info对象,它含有三个数据成员,且对应的构造函数。在使用emplace_back,我们可以在A的末尾添加一个Info对象,但是push_back却不行,因为它是拷贝元素,对于push_back的函数设计里面没有接收三个参数的版本,所以只能写成最后一行的形式,先构造,再拷贝。总之,emplace_back直接进行构造简化了操作过程。当然,如果Info对象没有对应三个参数的构造函数,那么该操作也会失效

A.emplace_back("heyun",12,23);
A.push_back("heyun",12,23); // 错误
A.push_back(Info("heyun",12,23));

3.2删除元素

​ 下标是删除元素的操作。

image-20230309195451698

3.3forward_list

​ 有C语言基础的应该都记得链表,forward_list其实就是一个单向链表。它的特殊之处在于,进行添加和删除元素时,删除或添加的元素之前的那个元素的后继会发生改变。

image-20230309195942688

下表是forward_list特有的插入和删除元素操作。

image-20230309201352502

3.4容器操作使迭代器失效

添加元素

  1. vector和string,如果存储空间被重新分配,指向容器的迭代器、指针和引用都会失效。如果没有重新分配,则指向插入元素之间的有效,之后的全部失效。
  2. deque,除了在首尾之外的任何位置插入元素会让它们都失效,如果在首尾添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效
  3. list和forward_list,全部都有效

删除元素

  1. list和forward_list,指向容器其他位置的迭代器全部都有效
  2. deque,在首尾之外的任何位置删除元素,指向被删除元素之外其他元素的迭代器、引用或指针也会失效。如果删除的是尾元素,则尾后迭代器也会失效,但是其他迭代器、引用和指针不受影响。如果删除首元素,这些都不受影响
  3. vector和string,指向被删除元素之前的有效。
  4. 只要删除元素,尾后迭代器总是失效。

四、vector对象是如何增长的

​ 第一次接触vector时,就很好奇这个和数组类似的存储容器居然有和链表一样的动态增加容量的功能。我们知道vector为了支持快速随机访问,里面的元素是连续存储的,如果这时vector的空间不足以接纳新的元素,可以再开辟一段连续的空间,就旧元素和新元素一起拷贝进去,最后删除旧的空间。这样虽然能够达到目的,但是经常进行性能会非常之慢。

C++标准库为了避免这种代价,设法减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间

​ 我们可以人为的去管理容量的成员函数。对于reserve,只有当前需要的内存空间超过当前容量时才会改变vector的容量。

image-20230309204255024

五、string操作

​ 这一小节主要对第三章的string内容进行一个补充。

5.1构造string 的其他方法

image-20230309211748753

5.2改变string的其他方法

string类型同样支持顺序容器的赋值运算符及assign、insert和erase操作

s.insert(s.size(),5,'!'); // 在s末尾插入5个感叹号
s.erase(s.size() - 5,5); // 在s删除最后5个字符

const char *cp = "Stately, plump Buck";
s.assign(cp,7); // s == "Stately"
s.insert(s.size(), cp + 7); // s == "Stately, plump Buck"


image-20230309213033023

image-20230309213602962

assign总是替换string中的所有内容,append总是将新字符追加到string末尾

5.3string搜索操作

string类提供了6个不同的搜索函数,每个函数都有4个重载版本,如下表所示。每个搜索操作会返回一个string::size_type值,表示匹配发生位置的下标,如果搜索失败,会返回一个名为string::nposstatic成员。两个搜索操作的返回值类型都是一个unsigned类型,所以不要用带符号类型进行接收。

image-20230309213642657

image-20230309213650936

下面写几个书上的例子:

string name("AnnaBelle");
auto pos1 = name.find("Anna"); // pos1 = 0

// 查找给定字符串中任何一个字符匹配的位置
string numbers("0123456789"), name("r2d2");
auto pos = name.find_first_of(numbers);
string dept("1231p23");
auto pos = dept.find_first_not_of(number); // 返回p的下标

5.4compare函数

image-20230309214847872

5.5数值转换

C++11引入多个函数可以实现数值数据与标准库string之间的转换。

image-20230309214921668

六、容器适配器

stack、queue和priority_queue是三个顺序容器适配器。一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。

image-20230309215308928

定义一个适配器

​ 每个适配器都定义两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。stack和queue基于deque实现,priority_queue在vector的基础上实现

stack<int> stk(deq); // 从deq拷贝元素到stk
stack<string, vector<string>> str_stk; // 在vector上实现的空栈

所有适配器都要求容器具有添加和删除元素的能力,所以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构造。

栈适配器

image-20230309220158132

队列适配器

priority_queue允许为队列中的元素建立优先级,也就是每个元素含有一个权重,权重大的会优先排在前面。

image-20230309220245287

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

madkeyboard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值