0
点赞
收藏
分享

微信扫一扫

C++ Primer 学习笔记(第九章)

yundejia 2022-01-08 阅读 40

第九章 顺序容器

前言

       一个容器就是一些特定类型对象的集合顺序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。标准库还提供了三种容器适配器,分别为容器操作定义了不同的接口,来与容器类型匹配。

9.1 顺序容器概述

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

Ⅰ)除了固定大小的array外,其他容器都提供高效、灵活的内存管理,可以添加和删除元素,扩张和收缩容器的大小。容器保存元素的策略对容器操作的效率有着固定的,有时是重大的影响。

       1)string和vector将元素保存在连续的内存空间中,元素的下标来计算其地址是非常快速的,但是在两种容器的中间位置添加或删除元素非常耗时

       2)list和forward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持元素的随机访问。与vector、deque和array相比,这两个容器的额外内存开销也很大。

       3)deque是一个更为复杂的数据结构,元素可以从两端弹出。与string和vector类似,deque支持快速随机访问。与string和vector一样,在两种容器的中间位置添加或删除元素的代价(可能)很高。但是在deque的两端添加或删除元素很快,与list或forward_list添加/删除元素的速度相当。

       4)forward_list和array是新C++标准增加的类型。与内置数组相比,array更加安全、更容易使用。forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作,因为保存或计算其大小就会比手写链表多出额外的开销。对其他容器而言,size保证是一个快速的常量时间的操作。

 Ⅱ)选择容器的基本原则

        1)除非有很好的理由选择其他容器,否则使用vector是最好的选择。

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

        3)要求随机访问元素,应该使用vector或deque。

        4)要求中间插入或删除元素,应该使用list或forward_list。

        5)要求在头尾插入或删除元素,且中间不进行插入或删除,应该使用deque。

        6)如果只在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素。首先可以考虑在读取输入时使用vector,再调用sort函数重排容器中的元素,从而避免在中间位置添加元素。如果必须在中间位置插入元素,考虑在输入阶段使用list,输入完成将list拷贝到vector中。

9.2 容器库概览

      本节将介绍所有的容器都支持的操作。一般说来,每个容器都定义在一个头文件中,文件名和类型名相同,容器均定义为模板类。我们可以定义一个保存这种类型对象的容器,但我们在构造这种容器时不能只传递给它一个元素数目参数

vector<noDefault> v1(10, init);    // 正确:提供了元素初始化器
vector<noDefault> v2(10);          // 错误!必须提供一个元素初始化器
表 9.2:容器操作
类型别名
iterator此容器类型的迭代器类型
const_iterator可以读取元素,但不能修改元素的迭代器类型
size_type无符号整数类型,足够保存此种容器类型最大可能容器的大小
different_type带符号整数类型,足够保存两个迭代器之间的距离
value_type元素类型
reference元素的左值类型,与value_type&含义相同
const_reference元素的const左值类型,即constvalue_type&
构造函数
C c;默认构造函数,构造空容器
C c1(c2);构造 c2 的拷贝 c1
C c(b, e)构造 c,将迭代器 b 和 e 指定的范围内的元素拷贝到 c(array不支持)
C c{a, b, c...}列表初始化 c
赋值与swap
c1 = c2;将c1中的元素替换为c2中元素
c1 = {a, b, c...}将c1中的元素替换为列表中元素(不适用于array)
a.swap(b)交换a和b的元素
swap(a, b)与a.swap(b)等价
大小
c.size( )c中元素的数目(不支持forward_list)
c.max_size( )c可保存的最大元素数目
c.empty( )若c中存储了元素,返回false,否则返回true

添加/删除元素(不适用于array)

注意:在不同容器中,这些操作的接口都不同

c.insert(args)将args中的元素拷贝进c
c.emplace(inits)使用inits构造c中的一个元素
c.erase(args)删除args指定的元素
c.clear( )删除c中所有的元素,返回void
关系运算符
==, !=所有的容器都支持相等运算符
<, <=, >, >=关系运算符
获取迭代器
c.begin( ), c.end( )返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin( ), c.cend( )返回const_iterator
反向容器的额外成员(不支持forward_list)
reverse_iterator按逆序寻址元素的迭代器
const_reverse_iterator不能修改元素的逆序迭代器
c.rbegin( ), c.rend( )返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin( ), c.crend( )返回const_reverse_iterator

 Ⅰ)迭代器范围

1)一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。这两个迭代器通常被称为begin和end,或者是first和last,标记了容器中元素的一个范围,左闭合区间[begin, end)。

2)对构成范围迭代器的要求(编译器不会强制,但是程序员应该满足)

  • 它们指向同一个容器中的元素,或者是容器最后一个元素之后的位置。
  • 可以通过递增begin来到达end(即end不在begin之前)
while (begin != end) {
    *begin = val;        // 正确:范围非空,因此begin指向一个元素
    ++begin;             // 移动迭代器,获取下一个元素
}

auto it7 = a.begin();    // 仅当a是const时,it7是const_iterator
auto it8 = a.cbegin();   // it8是const_iterator

Ⅱ)容器类型成员

反向迭代器:反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义发生了颠倒。如对一个反向迭代器执行++操作,会得到上一个元素。

Ⅲ)容器定义和初始化

当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。并且,新容器和原容器中的元素类型也可以不同,只要能将拷贝的元素转换为要初始化的容器的元素类型即可。

// 将一个容器初始化为另一个容器的拷贝
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());  // 正确,可以将const char*转换为string

deque<string> authList(authors.begin(), it);                   // 拷贝元素,直到 it-1 指向的元素

// 与顺序容器大小相关的构造函数
vector<int> ivec(10, -1);                                      // 10个int元素每个都是-1
list<string> svec(10, "hi!");                                  // 10个string元素每个都是“hi!”
forward_list<int> ivec(10);                                    // 10个int元素每个都是0
deque<string> svec(10);                                        // 10个string元素每个都空字符串

注意:1)只有顺序容器的构造函数才接受大小参数,关联容器不支持。  2)标准库array具有固定大小。

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

array<int, 10>::size_type i;
array<int>::size_type j;        // 错误!array<int>不是一个类型

array<int, 10> ial;
array<int, 10> ia2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> ia3 = {42};

// 对内置数组类型不能进行拷贝或对象赋值操作
int digs[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int cpy[10] = digs; 
// 对array可以进行拷贝或对象赋值操作
array<int, 10> digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> copy = digits;

array<int, 10> a1 = {0};       // 正确
a1 = {0};                      // 错误!不能将一个花括号列表赋予数组
举报

相关推荐

0 条评论