C++顺序容器

1. 顺序容器概述

1.1 顺序容器的定义

顺序容器是一些特定类型对象的集合,顺序容器为程序员提供了控制元素储存和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。所有顺序容器都提供了快速顺序访问元素的能力。但是这些容器在以下方面都有不同的性能折中:

容器 含义
vector 可变大小数组,支持快速访问,在尾部之外的位置插入元素很慢
deque 双端队列,支持快速随机访问,在头尾位置插入 / 删除很快
list 双向链表,只支持双向顺序访问,在任何位置插入 / 删除元素都很快
forward_list 单向链表,只支持单向顺序访问,在链表任何位置进行插入 / 删除都很快
array 固定数组大小,支持快速访问不能添加或删除元素
string 与 vector 相似,但专门用于保存字符,在尾部插入删除很快

容器保存元素的策略对容器操作的效率是有影响的,在某些情况下,储存策略还会影响特定容器是否支持某些特定操作。
stringvector将元素保存在连续的内存空间中。由元素的下标来计算其地址非常迅速,但是在这两种容器的中间位置添加或删除元素就会非常耗时。
listforward_list两个容器的设计目的是在容器的任何位置的添加和删除操作都很快速,但作为代价这两个容器不支持元素的随机访问,为了访问一个元素必须遍历整个容器。
deque与 string 和 vector 类似,deque 支持快速的随机访问,在 deque 的中间位置插入或删除元素的代价同样很高,但在 deque 的两端添加或删除元素的操作很快。
forward_listarray是C++新增加的类型。array 对象的大小是固定的,array 不支持添加或删除元素以及改变容器大小的操作。forward_list 的设计目标是达到与最好的手写的单向链表数据结构相当的性能,因此 forward_list 没有 size 操作。

1.2 选择使用哪种顺序容器

除非你有很好的理由,不然一律建议使用 vector;
如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用 list 或 forward_list;
如果程序要求随机访问元素,应使用 vector 或 deque;
如果在容器中间插入或删除元素,应使用 list 或 forward_list;
如果在头尾位置插入或删除元素,但不会在中间位置插入或删除元素,建议使用 deque;

1.3 对容器可以保存类型的限制

顺序容器几乎可以保存任意类型的元素。甚至我们可以定义一个容器,其元素的类型是另外一个容器。如下所示:

vector<vector<string>> lines;

2. 容器库概览

2.1 迭代器

如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。例如标准容器类型上的所有迭代器都允许我们访问容器中的元素,而所有迭代器都是通过解引用运算符来实现这个操作。

#include <iostream>
#include <vector>
#include <list>
int main() {
   
    // 使用 vector 容器
    std::vector<int> vectorContainer = {
   1, 2, 3, 4, 5};
    for (auto it = vectorContainer.begin(); it != vectorContainer.end(); ++it) {
   
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    // 使用 list 容器
    std::list<int> listContainer = {
   6, 7, 8, 9, 10};
    for (auto it = listContainer.begin(); it != listContainer.end(); ++it) {
   
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    return 0;
}

1.迭代器表示元素范围
两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。这两个迭代器通常被称为 begin 和 end,或者是 first 和 last。它们标记了容器中元素的一个范围。虽然第二个迭代器被称为 last,但这个迭代器从来不会指向范围中最后一个元素,而是指向尾元素之后的位置。迭代器范围中的元素包含 first 所表示的元素以及从 first 开始到 last 之间的所有元素,不包含 last。这种元素范围被称为左闭合区间。
2.使用左闭合范围蕴含的编程假定
假定 begin 和 end 构成一个合法的迭代器范围,如果 begin 和 end 相等,则范围为空。如果 begin 和 end 不等,则范围内至少包含一个元素,且 begin 指向该范围中的第一个元素,我们可以使 begin 递增若干次,使得begin == end
3. begin和end迭代器的版本
begin 和 end 有多个版本:带 r 的版本返回反向迭代器,以 c 开头的版本返回 const迭代器。

auto it1 = a.begin();
auto it2 = a.rbegin();
auto it3 = a.cbegin();

4.容器类型成员
每个容器都定义了多个类型,已经使用过其中三种,size_type、iteratorconst_iterator
size_type: size_type 是一个无符号整数类型,通常用于表示容器的大小。它是容器的一个成员类型,可以用来表示容器中元素的数量。

std::vector<int>::size_type size = myVector.size();

iterator: iterator 是容器的迭代器类型,用于遍历容器中的元素。通过使用迭代器,可以在容器中进行遍历、访问和修改元素。

std::vector<int>::iterator it = myVector.begin();

const_iterator: const_iterator 是常量迭代器类型,它限制了通过迭代器对容器元素的修改操作。常量迭代器可以用于遍历容器,但不能修改容器中的元素。

std::vector<int>::const_iterator constIt = myVector.cbegin();

2.2 容器定义和初始化

每个容器都定义了一个默认构造函数。除 array 之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。

forward_list<int> ivec(10);   // 10个元素,初始化为0
deque<string> svec(10);       // 10个元素,每个都是空string

如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。如果元素类型没有默认构造函数,除了大小参数外还必须指定一个显式的元素初始值。
1.将一个容器初始化为另一个容器的拷贝
将一个新容器创建为另一个容器的拷贝的方法有两种:直接拷贝整个容器或者拷贝由迭代器指定的元素范围。创建一个容器为另一个容器的拷贝要求两个容器的类型及其元素类型必须匹配。不过通过传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了,而且新容器和原始容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。

list<string>authors = {
   "A","B","C"};
list<string>list2(authors);
vector<const char*>articles = {
   "a","b","c"};
forward_list<string> words(articles.begin(), articles.end());

2.列表初始化
在新标准中我们可以对一个容器进行列表初始化,当这样做时我们就显式的指定了容器中每个元素的值。对于除 array 之外的容器类型,初始化列表还隐含地制定了容器的大小,容器将包含与初始值一样多的元素。

// 列表初始化向量
std::vector<int> myVector = {
   1, 2, 3};
// 遍历向量并输出元素
for (int elem : myVector) {
   
     std::cout << elem << " ";
}

3.标准库 array 具有固定大小
与内置数组一样,标准库 array 的大小也是类型的一部分。当定义一个 array 时,除了指定元素类型,还要指定容器大小:

array<int, 10> ia1;       // 保存42个int的数组
array<string, 10> ia2;    // 保存10个string的数组

2.3 赋值和swap

与内置数组不同,标准库 array 类型允许赋值。赋值号左右两边的运算对象必须具有相同的类型。

array<int,10>a1 = {
   0,1,2,3,4,5,6,7,8,9};
array<int,10>a2 = {
   0};
a1 = a2;
a2 = {
   0}; // 错误

由于右边运算对象的大小可能与左边运算对象的大小不同,因此 array 类型不支持 assign,也不允许用花括号包围的值列表进行赋值。
1.使用assign

函数 含义
seq.assign(b, e) 将 seq 中的元素替换为迭代器 b 和 e 所表示的范围中的元素。b 和 e 不能指向 seq 中的元素
seq.assign(i1) 将 seq 中的元素替换为初始化列表 i1 中的元素
seq.assign(n, t) 将 se
  • 45
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Litle_Pudding

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

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

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

打赏作者

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

抵扣说明:

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

余额充值