C++顺序容器

C++顺序容器,第1张

1. 顺序容器

后文的“前”即首部的方向,“后”为尾部的方向。



forward_list只能递增。


从前向后遍历。




允许随机访问的,插入和删除一般不快。


插入和删除快的,一般不支持随机访问。


deque

forward_list的目的是达到与最好的手写的单向链表数据结构相当的性能,所以不支持size *** 作,因为保存和计算size会带来开销。


list和forward_list是唯一删除效率高的顺序容器。


没有必需用其他容器的理由,请使用vector。


如果需要在中间插入元素且需要随机访问,就比较vector和list的缺点谁带来的损失小。


2. 容器库概览

容器上 *** 作的层次:1. 所有容器类型都提供。


2. 仅针对顺序容器、关联容器、无序容器。


3. 只适用于小部分容器。



所有容器都提供的 *** 作:

容器都定义为模板类。


大部分容器需要额外提供元素类型信息。



容器类元素类型可能会影响可以进行的容器 *** 作。


如:

这种容器就不能使用只接受一个参数的构造函数版本。


2.1 迭代器

迭代器有公共接口,所有类型的迭代器对某个 *** 作的实现方式都是一样的(前提是提供该 *** 作)。



迭代器范围是指从begin到end的一个左闭右开区间。



迭代器声明:所访问对象名::iterator 迭代器名。


begin和end有多个版本,带r的版本返回反向迭代器,带c的版本返回const迭代器。


如下:


当用容器初始化另一个容器时,两个容器的类型和元素类型必须匹配。


当传递迭代器参数只拷贝一个范围时,不要求容器类型和容器元素类型相同,只要元素类型可相互转换即可。


新容器大小和范围中元素数目相同,新元素用范围中对应元素的值来初始化。



如下:

顺序容器具有和关联容器不同的构造函数:使用值初始化。


只有顺序容器才可接受容器大小参数,关联容器不可。


值初始化示例如下:

2.1.1 标准库array

元素类型和容器大小是array类型的一部分,不管是在声明还是在使用中都需要带上:


默认构造的array是非空的。


包含和大小一样多的默认初始化的元素。



虽然不能对内置数组完成数组拷贝和赋值,但对array没有这个限制,只要求双方类型和大小一致。



array可用列表初始化,但是不可用列表赋值。



2.2 赋值和swap

赋值运算将左边容器的全部元素替换为右边容器元素的拷贝。


要求左边和右边的运算对象具有相同的类型。



赋值运算会导致左边容器的内部迭代器、引用和指针失效。


swap *** 作不会,string除外。


assign *** 作:assign用参数所指定的元素替换左边容器中的所有元素。


且允许从一个不同但是相容的类型拷贝。


可以说assign是用于补充赋值运算符的。



assign指定元素有两种方式:1. 迭代器范围。


2. 类似值初始化方式,传递数目以及初始值作为参数。


swap *** 作:交换两个容器的内容。


元素本身并未交换,只交换了容器的内部数据结构。



除了array外,swap不对任何元素进行拷贝、删除、插入 *** 作,所以可以保证在常数时间内完成。



swap后,指向容器元素的迭代器、指针和引用仍然指向之前的位置,但是值已经被交换过了。


swap的两个特例,string和array:

swap会真正交换array的元素。


交换array的时间和数组长度成正比。



swap两个string对象会导致迭代器、引用和指针失效

2.3 容器大小 *** 作

有三个:size,empty,max_size。



每个容器都支持==!= *** 作,除了无序容器外的所有容器都支持关系运算符(>,>=,<,<=)。



关系运算符两侧要求容器类型和容器元素类型相同。



容器的比较和string类似。


容器的相等运算符实际上是对元素使用==运算符实现的,容器的其他运算符是基于元素的<运算符实现的,所以容器要进行该类关系运算,要求元素能进行对应的运算。


3. 顺序容器 *** 作 3.1 插入元素

顺序容器支持的 *** 作:

只有list,forward_list,deque既能往头部插入/删除,也能往尾部插入/删除。



vector,string只能往尾部插入/删除。



array不能用这些东西。


向vector,string的非尾部插入元素会导致元素移动。


且无论往哪里插入元素,都导致重新分配对象空间,并将元素从旧空间移动到新空间。




insert主要两部分,第一部分是插入位置,传入一个迭代器指针,后面是插入内容,有各式各样的表达方式。


要注意,当使用迭代器指针来表示范围时,传入的迭代器不能指向添加元素的目标容器。


如下:

除了array以外的顺序容器基本都能使用insert。


其中forward_list提供了一个特殊版本的insert.

为什么Insert要设置为在迭代器p之前插入?
因为p有可能是尾后迭代器,而且会有在第一个元素之前插入数据的需求,在p之前插入可以保证所有地方都能插入。


insert返回第一个新加元素的迭代器。


如果插入范围为空,不插入元素,则会返回第一个参数。


示例如下,要理解为什么等价于push_front。




使用insert和push时,需要传入一个创建好的对象,然后将其拷贝到容器内。


emplace只需传入参数,然后该成员函数会在容器管理的内存空间内直接构造元素。




第一和第三种方法是不同的,使用push_back不能自动构造新对象,需要显式构造一个临时局部对象然后才能拷贝到容器内。


使用emplace会在容器管理的内存空间内直接创建对象。


3.2 访问元素


除了forward_list没有back成员函数外。


所有容器包括array都有front和back成员函数。



at和下标运算符区别在于使用at会抛出异常,而不是产生未定义行为。


可以通过捕捉异常更好的确定错误的位置。


3.3 删除元素

3.4 forward_list(单向链表) *** 作

因为单向链表单向访问的特性,想要在某个位置删除或者添加元素,需要获得它的前驱节点。


为此,引入了获取容器首元素之前迭代器的成员函数before_begin(),返回一个首前迭代器。



鉴于自身实现特点,forward_list版本的insert和erase都是插入/删除**之后的元素。




实际使用时,需要关注两个位置:要处理的位置和其前驱。


示例:

3.5 改变容器大小

array不支持。



3.6 插入或者删除元素对迭代器的影响



不要保存end处的迭代器。


当添加/删除vector,string元素,或者在deque尾部添加/删除元素,或者在deque首部添加元素,都会导致尾后指针end失效。


因此添加/删除元素的循环程序必须不断调用end。


所以C++的end *** 作被设计的很快,部分也是这个原因。


4. vector是如何增长的

为了支持连续访问—>
vector将元素连续存储—>
空间不足不能简单的随便找块空间分配给容器—>
需要开辟一块新空间,把旧元素和新元素全部复制过去,并释放旧空间—>
每次添加新元素都要完成一次分配和复制,开销太大—>
当空间不足时,vector分配比所需的空间大得多的空间,作为备用,这样不用每次都分配和复制。


具体多分配多少随标准库具体实现而定,C++是两倍。


4.1 管理容量的成员函数

容量指的是容器最大能存储的元素数目,不是空余空间数。




reserve函数,只有当当前容量小于所需容量n时才起作用,分配大于等于n的空间。


否则,reserve什么也不做,也不会缩减当前容量。



shrink_to_fit请求将空闲空间退还给内存,但只是请求,标准库不保证退还。


5. 额外string *** 作 5.1 创建string


s可以是string也可以是const char*。



如果参数没有给定长度和起始位置,在使用char初始化时,拷贝 *** 作会遇到空字符才停止,所以char结尾最好是"\0";

5.2 修改string

string提供了下标版本的插入和删除,以下标方式指明插入/删除的位置。




除此以外,string还提供了C风格的insert和assign(用char* 代替string):

5.3 额外的append和replace


string重载函数参数的说明:

5.4 string搜索

搜索 *** 作返回一个string::size_type的值,如果搜索失败,返回一个string::nops的static成员,nops被设置为const string::size_type类型,初始化为-1,由于size_type是unsigned的,所以nops事实上是最大的size_type值。




5.5 compare函数

根据s是大于、等于、还是小于参数指定的字符串,返回整数、0、负数。



5.6 string转数值

6. 容器适配器

适配器是一种机制,使得某个事物的行为看起来像另一种事物。



容器、函数、迭代器都有适配器。



容器适配器接受一种已有容器类型,使他看起来向另一种类型。


使用容器适配器后不能使用底层容器的 *** 作,只能用适配器的 *** 作管理内存。




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


但是可以在创建适配器时指定底层实现所用的容器:

这种指定也并非毫无限制。


如下:
stack适配器:要求在尾部插入,d出,和访问尾首元素。


底层可用除了array和forward_list。



queue适配器:要求要求能访问首尾元素,不能基于vector构造,可使用list和deque。



priority_queue适配器:要求随机访问能力。


不可基于list构造。


和队列queue的区别在于,queue是先进先出的,priority是优先级高的先出,什么时候进不重要,可以理解为常规队列,但是优先级高的能插队。



priority_queue无法访问尾元素。




欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/562275.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-01
下一篇 2022-04-01

发表评论

登录后才能评论

评论列表(0条)

保存