第九章 顺序容器

版权声明:转载请注明出处 https://blog.csdn.net/weixin_39918693/article/details/86564737


所有容器类都共享公共的接口,同时不同容器按不同方式进行相应的扩展,形成一些特有的接口。公共接口使容器的学习更加容易

容器适配器分别为容器操作定义了不同的接口,来与容器类型适配


一、顺序容器概述

所有的顺序容器都提供了快速顺序访问容器元素的能力。但是,这些容器在一些方面都有不同的性能折中:向容器添加或从容器中删除元素的代价、非顺序访问(随机访问)容器中元素的代价

快速顺序访问容器中元素的能力

  • 1、vector 可变大小数组
  • 2、deque 双端队列
  • 3、list 双向链表
  • 4、forward_list 单向链表
  • 5、array 固定大小数组(标准库数组)
  • 6、string 与vector相似,但是只能用来存储字符(不含\0符)

*string可以说是C++风格的字符串,其以类对象的形式存在

这些顺序容器的结构特点和性能优势和劣势需要了然于心

forward_list没有size操作,对于其他的容器来说,size保证是一个O(1)操作

建议使用迭代器(不要使用下标运算符)、不相等(或相等)运算符来书写程序,这样不容易出错。因为有的顺序容器没有下标运算符、没有定义大于或小于号


二、容器库概览

所有容器都适用的操作、仅适用于顺序容器的操作

扫描二维码关注公众号,回复: 5006582 查看本文章

一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。容器都定义为模板

较旧的编译器可能需要在两个尖括号之间键入空格

容器中可以存很多种类型,但是上层能够进行的操作是以底部能够支持为基础的(你懂我意思)

顺序容器的构造函数可以只接受一个指定容器大小的参数,但是前提是被存储的类型具有默认构造函数(是通过圆括号实现的)

??????上述情况下,如果存储的是内置类型会怎么样???????

类型别名:

  • 1、iterator:
  • 2、const_iterator:
  • 3、size_type:无符号整数,可以存储下最大元素个数
  • 4、difference_type:带符号整数,迭代器之差类型
  • 5、value_type:元素类型(右值吗?????)
  • 6、reference:元素的左值类型
  • 7、const_reference:元素的const左值类型

构造函数有很多种:注意拷贝构造函数的使用形式

赋值与swap函数:a.swap(b)和swap(a,b)等价

大小:

  • 1、c.size():c中元素的数目,不支持forward_list
  • 2、c.max_size():c可保存的最大元素数目
  • 3、c.empty():容器是否为空

添加/删除元素(不适用于array):

  • 1、c.insert(args):将args拷贝进入容器
  • 2、c.emplace(inits):用inits构造一个元素
  • 3、c.erase(args):删除args元素
  • 4、c.clear():删除所有元素,返回void

关系运算符:所有容器都支持相等或不相等运算符,无序关联容器不支持其他四种关系运算符

获取迭代器:begin、end、cbegin、cend

反向容器的额外成员(不支持forward_list):

  • 1、reverse_iterator:按逆序寻址元素的迭代器
  • 2、const_reverse_iterator:
  • 3、rbegin、rend:
  • 4、crbegin、crend:

如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的

forward_list的迭代器不支持递减运算符。算术运算只能应用于string、vector、deque、array的迭代器。(因为这些是顺序结构,不是链式结构)。array有迭代器???????

所有容器的迭代器都支持相等和不相等运算符(很重要)

迭代器范围的概念是标准库的基础

迭代器的使用:左闭合区间

容器的类型成员:对于泛型编程,意义深刻

我们可以将一个普通的iterator转换为const_iterator,反之不行。(底层const)

容器定义和初始化

  • 1、C c
  • 2、C c1(c2):这两种对于explicit构造函数是有区别。不一定???
  • 3、C c1 = c2:注意与复制构造函数的区别??????

C c1(c2):显式调用explicit构造函数,用c2作为参数构造了一个名字为c1的C类型的对象

C c1 = c2:现在还不会将其与复制构造函数区别开来????????

C c{a, b, c, …}:两者好像没区别。拷贝。
C c = {a, b, c, …}
C c(b, e):接受迭代器序列。拷贝。array不适用。注意元素相容

C seq(n):这两个只适用于顺序容器。这种不适用于string
C seq(n, t):可以用于string

当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同

只有顺序容器的构造函数才接受大小参数,关联容器不支持

标准库array具有固定大小,其大小是类型的一部分。例如std::array<int, 21>
array不支持普通的容器构造函数 很是疑惑???????????
int类型的值初始化为0,非内置类型的值初始化需要使用默认构造函数
内置类型的数组不能拷贝和赋值,但是标准库array可以(注意类型要完全相等,大小包括在内)

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

  • 1、seq.assign(b, e):替换。但是b和e不能指向seq中的元素
  • 2、seq.assign(il):
  • 3、seq.assign(n, t):替换为n个值为t的元素

赋值相关运算符会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(容器类型为array和string的情况除外)

**上面需要回去看书

由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器

除了array外,交换两个容器内容的操作保证会很快(元素本身未交换),swap只是交换了两个容器的内部数据结构(保证在常数时间内完成)

对于array来说,在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个array中对应元素的值进行了交换(存在失效的可能)

在新标准库中,容器既提供成员函数版本的swap,也提供非成员版本的swap。而早期的标准只提供成员函数版本的。非成员版本的swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯

forward_list支持max_size和empty,但是不支持size。
max_size:返回一个大于或等于该类型容器所能容纳的最大元素个数的值

每个容器类型都支持相等性运算符(==和!=):除了无序关联容器外的所有容器都支持关系运算符(>、>=、<、<=)。关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。比较两个容器实际上是进行元素的逐对比较(你懂的)

只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器(某个高层操作需要有底层的实现作为基础)

string和array很特殊哇


三、顺序容器操作

除了标准库array外,所有标准库容器都提供灵活的内存管理。

forward_list有自己专有版本的insert和emplace
forward_list不支持push_back和emplace_back
vector和string不支持push_front和emplace_front

array不支持以下操作:

  • 1、push_back(t):在c的尾部创建一个值为t或由args创建的元素,返回void
  • 2、emplace_back(args):
  • 3、push_front(t):在c的头部创建一个值为t或由args创建的元素,返回void
  • 4、emplace_front(args):
  • 5、insert(p, t):在迭代器p指向的元素之前创建一个值为t或由args创建的元素。返回指向新元素的迭代器
  • 6、emplace(p, args):
  • 7、insert(p, n, t):在p指向的元素之前插入各种形式的元素集合。如果元素集合非空,返回指向新插入的第一个元素的迭代器。否则返回p迭代器
  • 8、insert(p, b, e):
  • 9、insert(p, il):

向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效

当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间,而这些策略直接影响到性能。向一个vector或string添加元素可能引起整个对象存储空间的重新分配

除了array和forward_list之外,每个顺序容器都支持push_back

在使用对象初始化容器或使用元素进行插入时,其实插入的是元素的拷贝

list、forward_list和deque支持push_front操作

vector、deque、list和string都支持insert,forward_list提供了特殊版本的insert成员

通过使用insert的返回值,可以在容器中一个特定位置反复插入元素

emplace是将参数传递给元素类型的构造函数,参数必须与元素类型的某个构造函数相匹配

如果容器中没有元素,访问操作的结果是未定义的

包括array在内的每个顺序容器都有一个front成员函数,而除了forward_list之外的所有顺序容器都有一个back成员函数。这两个操作返回首元素和尾元素的引用

front和back成员函数*

at和下标操作只适用于string、vector、deque、array:

  • 1、c[n]:返回c中下标为n的元素的引用,n是一个无符号整数。n超出范围,函数行为未定义
  • 2、c.at(n):返回下标为n的元素的引用。如果下标越界,则抛出一个out_of_range异常

对const对象使用这些函数,返回的是对const的引用。如果使用auto自动推断,记得加&,因为auto默认将引用忽略

下标的合法性,需要由程序员来保证。如果越界,将会抛出out_of_range异常

forward_list有特殊版本的erase
forward_list不支持pop_back(它到底是什么样的结构?)
vector和string不支持pop_front

  • 1、pop_back():删除首或尾元素,返回void。若容器为空,函数行为未定义
  • 2、pop_front():
  • 3、erase§:删除迭代器指定的元素,返回一个指向被删除元素后一个元素的迭代器
  • 4、erase(b, e):注意特殊情况
  • 5、clear():删除所有元素,返回void

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

在删除元素之前,程序员必须确保它是存在的

在一个单向链表中,没有简单的方法来获取一个元素的前驱。出于这个原因,在一个forward_list中添加或删除元素的操作都是通过改变给定元素之后的元素来完成的。这样我们总可以访问到被添加或删除操作所影响的元素

forward_list定义了before_begin,它返回一个首前迭代器。相关操作见p313

除array以外,我们可以通过resize()来调整容器大小。被调整过大小的容器,其迭代器、引用、指针可能都会失效

在使用迭代器的时候,最小化要求迭代器必须保持有效的程序片段是一个好办法。建议在每次改变容器的操作之后都正确的重新定位迭代器

不要保存end返回的迭代器,每次用到时都重新调用来产生(end的实现是很快的)


四、vector对象是如何增长的

标准库的实现者为了减少容器空间重新分配的次数,vector和string的实现通常会分配比新的空间需求更大的内存空间(你懂的),只有当迫不得已时才可以分配新的内存空间

vector和string类型提供了一些成员函数,允许我们与它的实现中的内存分配部分互动:

  • 1、shrink_to_fit只适用于vector、string和deque
  • 2、capacity和reserve只适用于vector和string
  • 3、shrink_to_fit:将capacity()减少为与size()相同大小
  • 4、capacity():不重新分配内存,可以保存多少元素
  • 5、reserve():要求分配存储多少个元素的空间

调用shrink_to_fit只是一个请求,标准库并不保证退还内存


五、额外的string操作

提供了string类和C风格字符串之间的相互转换。用到时再详细看


六、容器适配器

适配器是标准库中的一个通用的概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样

标准库定义了三个顺序容器的适配器:stack、queue和priority_queue。

所有容器适配器都支持的操作和类型:

  • 1、size_type:足以保存当前类型的最大对象的大小
  • 2、value_type:元素类型
  • 3、container_type:实现适配器的底层容器的类型
  • 4、A a:创建一个名为a的空适配器
  • 5、A a©:创建一个名为a的适配器,带有容器c的一个拷贝
  • 6、关系运算符:每个适配器都支持所有关系运算符,只是返回底层容器的比较结果
  • 7、a.empty()
  • 8、a.size()
  • 9、swap(a, b):交换内容。a和b必须有相同的类型,包括底层容器类型
  • 10、a.swap(b)

每个适配器都定义两个构造函数:默认构造函数和接受一个容器的构造函数

默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。

我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型

***我需要例子

对于一个给定的适配器,可以使用哪些容器是有限制的。所有容器适配器都要求容器具有添加和删除元素和访问尾元素的能力

我们不能用array和forward_list来构造适配器

我们可以用除了array和forward_list以外的其他的(顺序)容器类型来构造stack。

queue可以构造在list、deque或vector之上

priority_queue可以构造在vector或deque之上

根据结构分析来分析不同的适配器该支持什么样的操作

每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作,而不能使用底层容器类型的操作

stack定义在stack头文件中

queue和priority_queue适配器定义在queue头文件中

默认情况下,标准库在元素类型上使用<运算符来确定相对优先级

???上面的话是什么意思??????

各个适配器支持的操作在使用到时再仔细的阅读

猜你喜欢

转载自blog.csdn.net/weixin_39918693/article/details/86564737
今日推荐