【C++】顺序容器

本文详细介绍了C++中的顺序容器,包括其特点、选择原则、容器库概览,重点阐述了迭代器的使用、操作及注意事项,并讨论了容器的大小、赋值和swap操作,以及向容器添加、访问、删除元素的方法。通过实例解析了如何在容器中使用emplace操作,并解释了容器大小调整可能使迭代器失效的情况。最后,文章探讨了vector对象的增长策略和容量管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  一个容器就是一些特定类型对象的集合。顺序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。

顺序容器的概述

下表列出了标准库中的顺序容器,所有顺序容器都提供了快速顺序访问元素的能力。但是不同容器在一下方面都有不同程度的性能折中。

  1. 向容器添加或删除元素的代价
  2. 非顺序访问容器元素的代价
顺序容器类型
vector ------ 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
deque ------双端队列。支持快速随机访问。在头尾位置插入删除速度很快
list --------双向链表。只支持双向顺序访问。在list中任何位置进行插入删除操作速度都很快
forward_list ----单向链表。只支持单向顺序访问。在链表任何位置进行插入删除速度都很快
array--------固定大小数组。支持快速随机访问。不能添加或删除元素
string--------与vector相似的容器,但专门用来保存字符。随机访问速度快。在尾部插入删除速度快

   
容器保存元素的策略对容器操作的效率有着固有的,有时是重大的影响。

string和vector将元素保存在连续的内存空间中,由于元素是连续存储的,所以通过下标来计算其地址是很快速的。但是在插入删除某元素时,其之后的元素都需要移动,非常耗时。

list和forward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持随机访问,如果想访问某个元素只能遍历整个容器。这两个容器的额外内存开销很大。

deque更为复杂。支持快速随机访问,在中间位置插入和删除元素的代价很高,但是在两端添加或删除元素是很快的。

forward_list和array是新C++标准增加的类型。array与内置数组相比,更加安全更易使用,array对象的大小是固定的。forward_list的设计目标是达到与手写单向链表结构相当的性能,所以为了节省开销,无size操作。

确定使用哪种容器

通常,使用vector是最好的选择,除非你有很好的理由选择其他容器。

基本原则:

如果程序有很多小元素,且空间的额外开销很重要,则不要使用list或者forward_list。

如果程序要求在中间位置插入或删除元素,应使用list或forward_list。

如果程序需要在头尾位置插入或删除元素,但不会在中间位置插入或删除操作,则用deque。

如果程序在输入时需要在容器中间位置插入元素,随后需要随机访问元素,则:
--首先,确定是否 真的需要在中间位置插入元素。当处理输入数据时,很容易地向vector尾部追加元素,然后在调用sort来重排容器中的元素,从而避免在中间位置插入元素。
--如果必须在中间位置插入元素,考虑在输入阶段用list,一旦输入完成后,将list中的内容拷贝到一个vector中。

例子:判断下列情况应该用哪种容器 
 
a.读取固定数量的单词,按字典序的方式插入容器中

需要频繁地在容器内部插入数据,vector在尾部之外插入元素很慢,deque在头尾之外很慢,所以最好用list。

b.从文件中读取未知数量的整数,并将这些数排序,然后将它们打印出来。

整数占用空间不大,而且快速的**排序算法需频繁的随机访问元素**,将list排除,无需在头部进行插入删除操作,所以不需用deque,故选择vector。

容器库概览

  一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。即,deque定义在头文件deque中,以此类推。容器均定义为模板类,当我们定义对象时,必须提供额外的信息来生成特定的容器类型。
比如定义一个list对象,其元素类型是int的deque:list<deque<int>> em;
  顺序容器几乎可以保存任意类型的元素,但某些容器操作对元素类型有其自己的特殊要求。

迭代器

迭代器介绍

  迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器不仅仅是指针,因此你不能认为他们一定具有地址值。例如,一个数组索引,也可以认为是一种迭代器。用的最多的还是容器迭代器。

使用迭代器

  和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如这些成员都有名为begin()和end()的成员,其中begin成员负责返回第一个元素的迭代器;end成员负责返回最后一个元素的下一个位置的迭代器,通常称为尾后迭代器
ps:如果容器为空,则begin和end返回的迭代器相同,且都是尾后迭代器。

迭代器运算符
标准容器迭代器的运算符 作用
*iter 返回迭代器iter所指元素的引用
iter->mem 解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
++iter 令iter指向容器的下一个元素
–iter 令iter指向元素的上一个元素
iter1 == iter2 判断两个迭代器是否相等,如果两个迭代器指的是同一个元素或者他们是同一个容器的尾后迭代器,则相等。
iter1 != iter2 判断两个迭代器是否不想等。判断条件同上

和指针类似,通过解引用来获取迭代器所指的的元素。举个例子

/*将 s 中的字母变成大写的*/
string s = "abcdef";
for(auto it = s.begin(); it != s.end(); ++it)
    *it = toupper(*it);
迭代器类型

  一般来说我们并不知道迭代器的类型(我们也无需知道),而实际上,那些拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型:
vector<int>::iterator it;  //it 能读写vector<int>的元素
string::iterator it2;    //it2能读写string对象中的字符
vector<int>::const_iterator it3;  //it3 只能读 不能写vector<int>的元素
string::const_iterator it4;    //it4只能读 不能写 string对象中的字符

  const_iterator和常量指针差不多,能读取但不能修改它所指向的元素值如果vector或string对象是常量,那么只能使用const_iterator。

迭代器和迭代器类型

  我们认定某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问容器的元素或者从某个元素移动到另外一个元素。
  每个容器定义了一个名为iterator的类型,该类型支持迭代器类型所规定的一系列操作。

迭代器范围

  一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置,即begin和end。这种元素范围被称为左闭合区间,其标准数学描述为[begin,end)
  
标准库之所以使用左闭合范围,是因为这种范围有三种方便的性质:
1. 如果begin和end相等,则范围为空。
2. 如果begin和end不想等,则范围至少包含一个元素,且begin指向该范围中的第一个元素。
3. 我们可以对begin递增若干次,使begin==end。

  begin和end有多个版本:带r的版本返回反向迭代器;以c开头的返回const迭代器。不以c开头的函数都是被重载过的,也就是说实际上有两个名为begin的成员,一个是const成员,返回容器的const_iterator类型;一个是非常量成员,返回容器的iterator类型。可以将一个普通的iterator转换为对应的const_iterator,但反之不行。以c开头的是C++新标准引入的,可以只读的访问容器,但不能写。
  

结合解引用和成员访问操作

  解引用迭代器可获得迭代器的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它的成员。
  例如:对于一个由字符串组成的vector对象来说,要想检查其元素是否为空,令it是该vector的迭代器,只需检查it所指的字符串是否为空就可以了。代码如下:

(*it).empty()  //先解引用it,调用结果对象的empty成员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值