C++ 学习笔记之(9)-顺序容器及适配器

C++ 学习笔记之(9)-顺序容器及适配器

顺序容器就是元素的顺序与其加入容器时的位置相对应,而 关联容器中元素的位置由元素相关联的关键字值决定。所有容器类都共享公共的借口,只是不同的容器会按不同方式对其进行扩展。

顺序容器概述

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

  • 向容器添加或从容器中删除元素的代价
  • 非顺序访问容器中元素的代价
    sequence_containers

  • 除了固定大小的array外,其他容器都提供高效、灵活的内存管理

  • stringvector将元素保存在连续的内存空间中,故支持随机访问,但中间位置添加和删除操作非常耗时
  • listforward_list在任何位置添加和删除操作都很快速,但不支持随机访问
  • deque支持随机访问,但中间位置添加和删除元素代价很高,但两端添加或删除元素很快
  • forward_listarray是C++11增加的类型,array大小固定,不支持添加和删除以及其他改变容器大小的操作。

容器库概览

迭代器

  • 迭代器范围(iterator range):由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置,通常被称为beingend 范围是左闭合区间[begin, end)

 容器类型成员

容器定义了多个类型,如图所示

container_type_alias

beginend成员

list<string> a = {"Milton", "Shakespeare", "Austen"};
auto it1 = a.begin();  // list<string>::iterator
auto it2 = a.rbegin();  // list<string>::reverse_iterator
auto it3 = a.cbegin();  // list<string>::const_iterator
auto it4 = a.crbegin();  // list<string>::const_reverse_iterator

容器定义和初始化

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

container_define_and_initializer

将一个容器初始化为另一个容器的拷贝

两种方法

  • 直接拷贝整个容器,要求两个容器的类别及其元素类型必须相同

  • 拷贝由一个迭代器对指定的元素范围,array除外, 不要求容器类别相同,元素类型也可以不用,但要能互相转换

    list<string> authors = {"Milton", "Shakespeare", "Austen"};
    vector<const char *> articles = {"a", "an", "the"};
    
    list<string> list2(authors);  // 正确:类型匹配
    deque<string> authList(authors);  // 错误:容器类型不匹配
    vector<string> words(articles);  // 错误:容器类型必须匹配
    forward_list<string> words(articles.begin(), articles.end());  // 正确:元素类型可以转换
  • 标准库array的大小是类型的一部分,定义array时,除了指定元素类型,还要指定容器大小

    array<int, 42>;  // 类型为:保存 42 个 int 的数组
    array<int, 10>::site_type i;  // 数组类型包括元素类型和大小
    array<int>::sizt_type j;  // 错误: array<int> 不是一个类型
  • 内置数组类型无法进行拷贝或对象赋值操作,但array可以

赋值和 swap

下表列出的与赋值相关的运算符可用于所有容器

container_assignment_operations

  • swap操作交换两个相同类型容器的内容, 元素本身并为交换, 只是交换了两个容器的内部数据结构,故可在常数时间内完成,且指向容器的迭代器、引用和指针在swap操作后不会失效
  • swap两个array会真正交换他们的元素,所需时间与array中元素数目成正比,且虽然指针、引用和迭代器绑定的元素保持不变,但元素值已经交换了。

容器大小操作

每个容器类型都有三个大小相关操作

  • 成员函数 size:返回容器中元素的数目, 但forward_list不支持size
  • empty:当size0时,返回true
  • max_size:返回一个大于或等于该类型容器所能容纳的最大元素数的值

关系运算符

每个容器类型都支持相等运算符==!=, 除了无序关联容器外的所有容器都支持关系运算符>、>=、<、<=

  • 若两个容器大小相同且元素相等,则相等
  • 若大小不同,但较小容器每个元素都等于较大容器的对应元素时,较小容器大于较大容器
  • 若两个容器都不是另一个容器的前缀子序列,结果取决于第一个不相等的元素的比较结果

容器的关系运算符使用元素的关系运算符完成比较

顺序容器操作

顺序容器和关联容器的不同之处在于组织元素的方式,所以导致元素的存储、访问、添加和删除的不同。下面介绍顺序容器特有的操作

向顺序容器中添加元素

sequence_container_append_item

  • 对于vectorstring的尾部之外的位置添加元素,都需要移动元素,可能还会导致整个对象存储空间的重新分配。重新分配对象的存储空间需要分配新的内存,并将元素从旧空间移动到新空间中

  • 对于deque 首尾之外的任何位置添加元素,都要移动元素,

  • 当用对象初始化容器或插入到容器中时,实际上放入到容器中的是对象值的拷贝,并不是对象本身。

  • 记住,insert插入在元素之前,并且返回指向新插入元素的迭代器

  • emplace操作将参数传递给元素类型的构造函数,直接在容器管理的内存空间中构造函数。而pushinsert操作是传递元素类型对象,然后拷贝到容器中

    // 在 c 的末尾构造一个 Sales_data 对象,使用 Sales_data  的三个参数的构造函数
    c.emplace_back("978-0590353403", 25, 15.99);
    //错误:没有接受三个参数的 push_back 版本
    c.push_back("978-0590353403", 25, 15.99);
    // 正确:创建一个临时的 Sales_data 对象传递给 push_back
    c.push_back(Sales_data("978-0590353403", 25, 15.99));

访问元素

sequence_container_access_item

  • 访问成员函数(即frontback、下标和at)返回的都是引用。若容器是const对象,则返回值是const引用
  • 下标运算符不检查下标是否合法,若想确保下标合法,可使用at成员函数,越界会抛出out_of_range异常

删除元素

sequence_container_delete_item

特殊的forward_list操作

由于在forward_list中,添加或删除元素都会改变后继元素,所以添加和删除的操作都是通过改变给定元素之后的元素完成的。

forward_list_insert_and_delete_item

改变容器大小

如图所示,可使用resize增大或缩小容器。array不支持

sequence_container_resize_operation

容器操作可能是迭代器失效

对容器进行添加或删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效

添加元素

  • vectorstring
    • 重新分配存储空间:迭代器、指针和引用都会失效
    • 未重新分配存储空间:指向插入位置之前的元素的迭代器、指针和引用仍有效,指向之后的将失效
  • deque
    • 插入到除首尾位置之外的任何位置:都会导致迭代器、指针和引用失效
    • 若在首尾位置添加元素:迭代器失效,但指针和引用有效
  • listforward_list
    • 指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效

删除元素(指向被删除元素的迭代器、指针和引用都会失效)

  • vectorstring
    • 指向被删除元素之前的迭代器、引用和指针:仍有效
  • deque
    • 若在首尾之外的任何位置删除元素:都会失效
    • 若删除尾元素:尾后迭代器也会失效,其他不受影响
    • 删除首元素:都有效
  • listforward_list
    • 都有效

vector对象是如何增长的

vector为了支持快速随机访问,故将元素连续存储。当空间不足时,就会重新分配空间,将元素从旧空间移动到新空间,然后添加新元素,释放旧存储空间。 vectorstring通常会分配比新空间需求更大的内存空间,预留些空间做备用,可以减少容器空间重新分配的次数

sequence_container_size_management_operations
* vector的实现策略是每次分配新内存空间时将当前容量翻倍
* shrink_to_fit请求vector将超出当前大小的多余内存退回给系统,但标准库并不保证

额外的string操作

构造string的其他方法

C++ 学习笔记之(3)-字符串、向量和数组 介绍过string的构造函数,以及与其他顺序容器相同的构造函数外,string还有其他三个构造函数,如表所示

string_other_constructors

  • 若用const char *创建string时,指针执行的数组必须以空字符结尾,拷贝操作遇到空字符时停止

substr 操作

string_substr_operation

改变string的其他方法

  • replace草市是调用eraseinsert的一种简写形式

string_modification_operations

string_modification_operations_2

string搜索操作

  • string::nposstatic成员,类型为const string::size_type类型,初始值为-1,无符号类型
    string_search_operations

compare函数

除了关系运算符外,string类型还提供了一组compare函数,与C标准库的strcmp函数很相似

string_compare_functions

数值转换

新标准引入了多个函数,用来实现数值数据与标准库string之间的转换。

  • 转换为数值的string中第一个非空白字符必须是数值中可能出现的字符

    string s2 = "pi = 3.14";
    // 转换 s2 中以数字开始的第一个子串,结果 d = 3.14
    d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
  • string不能转换为一个数值,则函数抛出invalid_argument异常。若转换得到的数值无法用任何类型表示,则抛出out_of_range异常
    string_to_number_functions

容器适配器

除顺序容器外,标准库还定义了三个容器适配器:stackqueuepriority_queue

  • 适配器(adaptor):标准库的通用概念。容器、迭代器和函数都有适配器,其本质上是一种机制,可以是某种事物的行为表现成另一种事物。

adaptor_operations_and_types

  • 默认情况下, stackqueue基于deque实现的, priority_queue是在vector之上实现的

  • 所有适配器都要求容器具有添加和删除元素的能力,故适配器不能构造在array之上

栈适配器

stack只要求push_backpop_backback操作,故可使用除arrayforward_list之外的任何容器构造

stack_adaptor_operations

队列适配器

  • queue适配器要求backpush_backfrontpush_front, 故可构造于listqueue之上,但不能基于vector构造
  • priority_queue除了frontpush_backpop_back操作外,还要求随机访问能力, 故可构造于vectordeque之上,但不能基于list构造
    queue_and_priority_queue_operations

结语

  • 标准库容器是模板类型,用来保存给定类型的对象。顺序容器中元素是顺序存放的,通过位置来访问
  • 所有容器(除array外)都提供高效的动态内存管理
  • 在容器中执行添加和删除操作后,要注意这些操作可能是其迭代器、引用和指针失效
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章