🧶09_顺序容器 - 图1

9.1 顺序容器概述

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

9.2 容器库概览

常用操作:

image.png

迭代器

:::info 与容器一样,迭代器有着公共的接口:如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。 :::

迭代器范围

:::tips 一个迭代器范围(iterator range)由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置(one past the last element)这两个迭代器通常被称为beginend,或者是firstlast(可能有些误导),它们标记了容器中元素的一个范围。 ::: :::info 这种元素范围被称为左闭合区间(left-inclusiveinterval),其标准数学描述为[begin,end) ::: why? :::info 标准库使用左闭合范围是因为这种范围有三种方便的性质:

  • 如果beginend相等,则范围为空
  • 如果beginend不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素
  • 我们可以对begin递增若干次,使得begin==end :::

    容器定义与初始化

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

    拷贝初始化

  • 将一个容器拷贝给另一个容器;

  • 通过迭代器将一个容器的范围拷贝给另一个容器
    1. vector<int> a{1,2,3,4,5};
    2. vector<int> b(a);
    3. vector<int> c(a.begin(),a.end());

    列表初始化

    1. vector<int> a{1,2,3,4,5};
    2. vector<int> a={1,2,3,4,5};//包含隐式类型转换?

    顺序容器操作

    image.png

    插入元素

    :::tips 当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。就像我们将一个对象传递给非引用参数(参见3.2.2节,第79页)一样,容器中的元素与提供值的对象之间没有任何关联。随后对容器中元素的任何改变都不会影响到原始对象,反之亦然 :::

    insert

    push_backpush_front操作提供了一种方便地在顺序容器尾部或头部插入单个元素的方法,insert成员函数提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素vector、deque、list和string都支持insert成员
    1. vector<string> svec;
    2. svec.insert(svec.begin(),"hello");
    3. svec.insert(svec.begin(),10,"hi");//范围插入
    :::tips 在新标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器。(在旧版本的标准库中,这些操作返回void。可以通过这一点进行循环插入; :::

    emplace操作

    当调用pushinsert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素
    1. //在c的末尾构造一个Sales_data对象
    2. //使用三个参数的Sales_data构造函数
    3. c.emplace_back("978-0590353403",25,15.99);
    4. //错误:没有接受三个参数的push_back版本
    5. c.push_back("978-0590353403",25,15.99);
    6. //正确:创建一个临时的Sales_data对象传递给push_back
    7. c.push_back(Sales_data("978-0590353403",25,15.99));

访问元素

  • 对空容器使用front或者back会引起类似下标越界的错误;因此需要提前检测容器非空
  • 访问成员函数返回值是引用:在容器中访问元素的成员函数(即,front``,back、下标和at)返冋的都是引用。如果容器是一个const对象,则返冋值是const的引用。如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值。
  • 如果我们希望确保下标是合法的,可以使用at成员函数。at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常

    删除元素

    image.png :::danger 删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效,指向vectorstring中删除点之后位置的迭代器、引用和指针都会失效.

  • 删除元素的成员函数并不检查其参数。在删除元素之前,程序员必须确保它(们)是存在的。 :::

    *迭代器失效问题

    :::info 向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效。一个失效的指针、引用或迭代器将不再表示任何元素。 :::

  • 添加元素 :::info

  • 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。

  • 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
  • 对list和forward_list,指向弃器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。 :::

  • 删除元素 :::info

  • list不受影响

  • 对于deque,如果在首尾之外的仟何位置删除元素,那么指向被删除元素外其他元素的迭代器、弓I用或指针也会失效。如果是删除deque的尾元素,则尾后迭代器(end)也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。
  • 对于vector和string,指向被删元素之前元素的迭代器、引用和指针仍有效。注意:当我们删除元素时,尾后迭代器总是会失效。 ::: :::danger 当我们添加/删除vector或string的元素后,或在deque中首元素之外任何位置添加/删除元素后,原来end返冋的迭代器总是会失效。因此,添加或删除元素的循环程序必须反复调用end,而不能在循环之前保存end返回的迭代器,一直当作容器末尾使用. :::

    9.5 额外的string操作

    构造方法

    image.png

    子字符串操作

    substr操作(参见表9.12)返回一个string,它是原始string的一部分或全部的拷贝。可以传递给substr—个可选的幵始位置和计数值
    1. s.substr(pos,n);//返回一个string,包含s中从pos开始的n个字符的拷!A。pos的默
    2. //认值为0。n的默认值为s.sizeO-pos,即拷贝从pos开始的所有字符

    compare函数

    除了关系运算符外(参见3.2.2节,第79页),标准库string类型还提供了一组compare函数,这些函数与C标准库的strcmp函数(参见3.5.4节,第109页)很相似。类似strcmp,根据s是等于、大于还是小于参数指定的字符串,0、正数或负数。

    数值转换

    image.png

    9.6 容器适配器

    :::info 适配器(adaptor)是标准库中的一个通用概念。(stack,queue,priority_queue)
    本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如,stack适配器接受一个顺序容器(除array或forward_list外),并使其操作起来像一个stack一样。 :::

    小结