文章目录

  • 第九章 顺序容器
  • 顺序容器概述
  • 这种顺序是不依赖于元素的值,而是与元素加入容器时的位置相对应,于之相对的是有序和无序关联容器,根据关键字的值存储元素
  • 顺序容器在以下两个方面的性能上做了不同侧重:
  • 顺序容器的选择
  • 容器库概览
  • 容器类型上的操作形成了一种层次
  • 虽然可以在容器中保存几乎所有类型,但是对某些容器操作对元素类型有其自己的特殊要求。我们可以为不支持特定操作需求的类型定义容器,但是这种情况下只能使用那些没有特殊要求的容器了。
  • 容器操作
  • 迭代器
  • 容器类型成员
  • begin end成员
  • 容器定义和初始化
  • 赋值和swap
  • 容器大小操作
  • 关系运算符
  • 顺序容器操作
  • 向顺序容器添加元素的操作
  • 访问元素
  • 删除元素
  • 特殊的forward_list操作
  • 改变容器的大小
  • 容器操作可能使迭代器失效
  • vector对象是如何增长的
  • 向vector和string添加元素时,如果没有空间保存新元素,则不能随便把新元素添加到其他内存空闲的位置,因为这两个容器的元素必须连续存储,所以就要重新分配足够大的空间并移动过去,释放旧的空间,为了避免这种代价,可以使用函数提前申请比较大的空间,避免重新分配空间的开销。
  • 管理容量的成员函数
  • 只有当需要的空间超过当前容量时,reserve调用才会改变容量,需求大于容量时会至少分配和需求一样大的空间(可能更大)。否则什么也不做
  • vector\ ivec;
  • 额外的string操作
  • 构造string的其他方法
  • 改变string的其他方法
  • string搜索操作
  • compare函数
  • 数值转换
  • 容器适配器
  • 适配器是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器配置器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如,stack适配器接受一个顺序容器,并使其操作看起来像一个stack一样
  • 所有容器适配器都支持的操作和类型
  • 定义一个适配器
  • 栈适配器
  • 队列适配器
  • 术语表
  • 1-10
  • 11-19



容器 副本 容器碎片位置图_stl

第九章 顺序容器

顺序容器概述

这种顺序是不依赖于元素的值,而是与元素加入容器时的位置相对应,于之相对的是有序和无序关联容器,根据关键字的值存储元素

顺序容器在以下两个方面的性能上做了不同侧重:

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

  • vector
    可变大小数组,支持快速随机访问,在尾部之外的位置增删元素可能很慢
  • string
    与vector相似的容器,专门用于保存字符,随机访问快,尾部增删快
  • deque
    双端队列,支持快速随机访问,在头尾位置增删元素速度很快
  • list
    双向链表,只支持双向顺序访问,在list任何位置增删元素都很快
  • forward_list
    单向链表,只支持单向顺序访问,在链表的任何位置增删元素的速度都很快,无size操作
  • array
    固定大小数组,支持快速随机访问,不能增删元素

顺序容器的选择

  • 除非有很好的理由,否则使用vector
  • 如果有很多元素,且额外空间开销很重要,不要使用list和forward_list
  • 如果程序要求随机访问元素,使用vector和deque
  • 如果程序在中间增删元素,使用list或forward_list
  • 如果程序需要在头尾增删,但是不需要中间操作,使用deque
  • 如果程序只有在读取输入时需要在中间插入元素,随后需要随机访问元素,
    首先,可以vector追加数据,随后sort
    如果必须中间位置插入,则使用list输入,拷贝到vector

容器库概览

容器类型上的操作形成了一种层次

所有容器都提供的操作
另外一些操作,仅针对顺序容器、关联容器或无序容器
还有一些操作只适用于一小部分容器

虽然可以在容器中保存几乎所有类型,但是对某些容器操作对元素类型有其自己的特殊要求。我们可以为不支持特定操作需求的类型定义容器,但是这种情况下只能使用那些没有特殊要求的容器了。

比如,顺序容器构造函数的一个版本支持接收容器的大小,使用元素的默认构造函数,但某些类没有默认构造函数,这就不能使用这种默认构造函数了

// 提供了元素初始化器,因为该类类型元素没有默认构造函数
  • vector v1(10, init);
    // 错误:必须提供一个元素初始化器
    vector v2(10);

容器操作

  • 类型别名
  • iterator
  • const_iterator
  • size_type
  • difference_type
    两个迭代器的距离
  • value_type
    元素类型
  • reference
    元素左值类型,与value_type&同义
  • const_reference
  • 构造函数
  • C c
  • C c1(c2)
  • C c(beg,end)
    array不支持
  • C c{a,b,c…)
  • 赋值与swap
  • c1=c2
  • c1={a,b,c…}
    不支持array
    但是我试验可以
  • a.swap(b)
  • swap(a,b)
  • 大小
  • c.size()
    不支持forward_list
  • c.max_size()
  • c.empty()
  • 增删元素(不适用于array)
  • c.insert()
  • c.emplace()
    构造一个元素
  • c.erase()
  • c.clear()
  • 关系运算符
  • == !=
    所有容器都支持
  • < <= > >= 比较的是容器
    除了无序关联容器之外,都支持
  • 获得迭代器
  • c.begin() c.end()
    c.cbegin(),c.cend()
  • 反向容器的额外成员
  • reverse_iterator
  • const_reverse_iterator
  • c.rbegin() c.rend()
  • c.crbegin() c.crend()

迭代器

  • 所有迭代器都支持
  • -> ++ – == !=
    特殊的是forward_list不支持–
  • 算术运算只能应用于string vector deque array的迭代器
    +n -n +=n -=n iter1-iter2 > >= < <=
  • 迭代器范围由一对迭代器表示,首元素和尾元素之后的位置

容器类型成员

  • 通过类型别名,我们可以在不了解容器元素的情况下使用它,如果需要元素的类型,可以使用容器的value_type。如果需要元素类型一个引用可以使用reference或const_reference ,这些在泛型编程中很有用
list<string>::iterator iter;

vector::difference_type count;

begin end成员

list<string> a = {"hi", "hello", "world"};
  • auto it1 = a.begin(); //list::iterator
    auto it2 = a.rbegin(); // list::reverse_iterator
    auto it3 = a.cbegin(); //list::const_iterator
    auto it4 = a.crbegin(); //list::const_reverse_iterator
  • 不以c开头的函数其实都是两个重载函数,一个版本是返回const_iterator类型,另一个是返回非常量。当使用一个非常量成员调用这些函数使用iterator版本,常量对象调用,调用const版本
// 显式指定类型
  • list::iterator it5 = a.begin();
    list::const_iterator it6 = a.begin();
    // 是iterator还是const_iterator取决于a的类型
    auto it7 = a.begin();
    auto it8 = a.cbegin();

容器定义和初始化

  • 每个容器类型都定义了一个默认构造函数,除array外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器的大小和初始值
  • 容器定义初始化操作
  • C c
    如果C是array,则c中元素执行默认方式初始化
  • C c1=c2
    C c1(c2)
    c1 c2必须是相同类型(容器类型相同,保存元素类型相同,array的大小相同)
  • C c{a,b,c…}
    C c = {a,b,c…}
  • C c(b,e)
    array不适用
  • C seq(n)
    C seq(n,t)
    只有顺序容器(不包括array)支持接受大小参数
    如果元素类型无默认构造函数,除了大小参数外还需要指定初始值
  • 为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配,不过当传递迭代器参数拷贝一个范围时,就不要求容器类型是相同的了,而且,新容器和原容器元素类型也可以不同,只要能将要拷贝的元素转换为初始化的容器元素类型就可以
list<string> authors = {"a", "b", "c"};

list<const char > articles = {“a”, “an”, “the”};
list list2(authors); // 正确 类型匹配
deque authlist(authors); // 错误,容器类型不匹配
vector words(articles); //错误,容器类型不匹配
// 正确,可以将const char
转换为string
forward_list words(articles.begin(), articles.end());

  • array大小固定的特性也影响了它定义的构造函数的行为,与其他容器不同,一个默认构造的array是非空的,它包含了与其大小一样多的元素,这些元素都被默认初始化,如果进行列表初始化,数量必须小于等于大小
array<int, 10> ia1;

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

// 不能对内置数组类型进行拷贝或赋值,但是可以对array操作
array<int, 10> copy = ia2;

赋值和swap

  • c1=c2如果两个容器原来大小相同,赋值运算后两者大小都与右边容器的大小相同
  • 容器赋值运算
  • c1=c2
  • c={a,b,c…}
  • swap(c1,c2)
  • c1.swap(c2)
  • seq.assign(b,e)
  • seq.assign(il)
  • seq.assign(n,t)
list<string> names;

vector<const char > oldstyle;
names = oldstyle; // 错误,容器类型不匹配
// 正确,可以将const char
转换为string
names.assign(oldstyle.cbegin(), oldstyle.cend());

// 相当于先清空,在插入元素
list slist1(1);
slist1.assign(10, “hi”);

  • 除array外,交换容器内容保证很快——元素本身未交换,swap只是交换两个容器的内部数据结构。
  • 元素不会被移动意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后不会失效,它们仍指向那些元素,但是属于另一个容器了。
    与其他容器不同的是,array会真正交换元素,所以所需时间与长度成正比

容器大小操作

关系运算符

  • 每个容器类型都支持相等运算符,除了无序关联容器外的所有容器都支持关系运算符,关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素
  • 只有当其元素类型定义了对应的比较运算符时,才能用关系运算符比较两个容器

顺序容器操作

向顺序容器添加元素的操作

  • 成员函数表
  • array不支持这些操作
  • forward_list 有自己专有版本的insert和emplace
    不支持push_back和emplace_back
  • vector和string不支持push_front和emplace_front
  • c.push_back(t)
    c.empalce_back(args)
    在c尾部创建一个值为t或由args创建的元素
  • c.push_front(t)
    c.emplace_front(args)
  • c.insert(p,t)
    c.emplace(p,args)
  • c.insert(p,n,t)
    c.insert(p,b,e)
    c.insert(p,il)
  • 向一个vector或string添加元素可能引起整个对象存储空间的重新分配,重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。
  • push_back 函数除了array和forward_list 之外,所有顺序容器都支持。
  • list forward_list deque容器支持push_front的操作,将元素添加到头部
  • vector deque list string都支持insert成员
  • insert的insert(p,b,e)版本,后面的一对迭代器不能使用本容器的迭代器,但是我试验是可以的
  • emplace是构造元素,而不是拷贝元素。
    当调用push或insert时,是把对象传递给他们,这些对象再被拷贝到容器中。
    而当我们使用emplace时,是把参数传递给元素类型的构造函数,emplace成员使用这些参数直接在容器管理的内存空间中构造出元素,效率更高
  • class Sales_data
    {
    public:
    Sales_data() = default;
    Sales_data(string a, double b) : ISBN(a), price(b) {}
    Sales_data(string a, double b, int c) : ISBN(a), price(b), count© {}
    private:
    string ISBN;
    double price;
    int count;
    };
    vector<Sales_data> v;
    // 调用类的默认函数,在容器管理的内存空间中直接创建对象,而push_back是创建临时对象,再进入容器
    v.emplace_back();
    v.emplace_back(“111”, 1.1);
    v.emplace_back(“111”, 1.1, 1);
    // push_back
    // v.push_back(“111”); 错误
    v.push_back(Sales_data(“111”, 1.2));

访问元素

  • 每个顺序容器中都有一个front成员函数,除了forward_list 之外都有一个back成员函数,返回的是首元素和尾元素的引用。但是调用之前要保证非空,否则行为未定义
  • 访问元素的操作
  • c.back()
  • c.front()
  • c[n]
  • c.at(n)
    与下标不同的是,当越界时会抛出异常
  • 下标操作和at只适用于string vector deque array

删除元素

  • 顺序容器的删除操作
  • 不适用于array
  • forward_list 有特殊的erase,不支持pop_back
  • vector string 不支持pop_front
  • c.pop_back()
  • c.pop_front()
  • c,erase§
    返回下一位置的迭代器,如果p是尾后迭代器,行为未定义
  • c.erase(b,e)
    返回最后一个被删除后一个元素的迭代器
  • c.clear()
  • list l{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    auto it = l.begin();
    while (it != l.end())
    {
    if (*it & 1)
    it = it = l.erase(it);
    else
    ++it;
    }

特殊的forward_list操作

  • l.before_begin()
    l.cbefore_begin()
    返回首元素之前的迭代器,称作首前迭代器,不指向任何元素
  • l.insert_after(p,t)
    l.insert_after(p,n,t)
    l.insert_after(p,b,e)
    l.insert_after(p,il)
    返回指向最后一个插入元素的迭代器,不同于之前的返回一个插入元素的迭代器,如果p是尾后迭代器,行为未定义
  • emplace_after(o,args)
    返回指向新元素的迭代器,p是尾后迭代器行为未定义
  • l.erase_after§
    l.erase_after(b,e)
    删除从b(不包含)到e之间元素,返回被删除的最后一个元素后的迭代器,若p是尾元素或尾后迭代器,行为未定义
forward_list<int> fl{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  auto prev = fl.before_begin(); // 表示第一个元素前的迭代器
  auto curr = fl.begin();
  while (curr != fl.end())
  {
      if (*curr & 1)
          curr = fl.erase_after(prev);
      else
      {
          prev = curr;
          ++curr;
      }
  }

改变容器的大小

  • 如果当前大小小于要求大小,则之后的会删除,否则,会执行初始化,加到后面
  • c.resize(n)
    c.resize(n,t)

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

  • 向容器增删元素可能会导致指向容器元素的指针,引用或迭代器失效。
  • 增加元素之后
  • vector或string,如果空间被重新分配,三者全部失效,如果没有重新分配空间的话,插入位置之前的元素有效,指向后面的元素三者全部失效
  • deque插入到收尾之外的任何位置都会导致三者全部失效,在首尾插入会导致迭代器失效,剩下的两者不会失效
  • list和forward_list都有效
  • 删除元素之后
  • list和forward_list三者都有效(除了被删除元素的)
  • 对于deque,如果在首尾之外的任何位置删除元素,三者全部失效。删除尾元素,尾后迭代器会失效,其他不受影响。删除首元素,这些也不会受影响
  • 对于vector和string,指向被删元素之前的有效。
  • 由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地正确的重新定位迭代器
  • 程序在每个循环步中都要更新迭代器,引用、指针。如果使用insert或erase,那么更新迭代器更容易
// 奇数复制增加,偶数删除
  • vector vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    auto iter = vi.begin();
    while (iter != vi.end())
    {
    if (*iter % 2)
    {
    // insert是在指定位置前面加,并返回增加的位置的迭代器,所以要+2
    iter = vi.insert(iter, *iter);
    iter += 2;
    }
    else
    // erase返回的是删除元素后的迭代器
    iter = vi.erase(iter);
    }
  • 当我们增删vector或string元素后,原来的end迭代器总会失效,因此在程序中不应在处理前保存end,并且一直当做尾后迭代器通用。应该每次使用都调用end成员函数

vector对象是如何增长的

向vector和string添加元素时,如果没有空间保存新元素,则不能随便把新元素添加到其他内存空闲的位置,因为这两个容器的元素必须连续存储,所以就要重新分配足够大的空间并移动过去,释放旧的空间,为了避免这种代价,可以使用函数提前申请比较大的空间,避免重新分配空间的开销。

管理容量的成员函数

  • shink_to_fit适用于vector string deque
  • capacity和reserve适用于vector和string
  • c,shink_to_fit()
    请求编译器收回多余分配的空间,让预留空间==当前元素占用的空间。这个请求可以不被编译器满足
  • c.capacity()
    当前已分配给该容器的空间可以容纳多少元素
  • c.reserve(n)
    分配至少能容纳n个元素的空间

只有当需要的空间超过当前容量时,reserve调用才会改变容量,需求大于容量时会至少分配和需求一样大的空间(可能更大)。否则什么也不做

vector<int> ivec;

cout << "size" << ivec.size() << " capacity:" << ivec.capacity() << endl;
    ivec.resize(24, 1);
    cout << "size" << ivec.size() << " capacity:" << ivec.capacity() << endl;
    ivec.reserve(50);
    // 分配给ivec的空间至少是50个元素的空间,具体是多少取决于标准库的实现
    cout << "size" << ivec.size() << " capacity:" << ivec.capacity() << endl;
    // 用光剩下的预留空间
    while (ivec.size() != ivec.capacity())
        ivec.push_back(0);
    cout << "size" << ivec.size() << " capacity:" << ivec.capacity() << endl;
    // 再添加一个元素,此时不得不重新分配空间,具体是多少根据标准库的实现不同而不同
    ivec.push_back(11);
    cout << "size" << ivec.size() << " capacity:" << ivec.capacity() << endl;
    // 退回不需要的空间,使得capacity==size,但是这个请求可以忽略
    ivec.shrink_to_fit();
    cout << "size" << ivec.size() << " capacity:" << ivec.capacity() << endl;

额外的string操作

构造string的其他方法

  • 其他方法
  • n len2 pos2都是无符号值
  • string s(cp,n)
    s是cp指向的数组中前n个字符的拷贝,此数组至少包含n个字符
  • string s(s2,pos2)
    s是string s2从下标pos2开始的字符拷贝,若开始位置大于s2的长度,行为未定义
  • string s(s2,pos2,len2)
    s是string s2从pos2开始的len2个字符的拷贝。不管len2值是多少,最多拷贝到末尾
const char *cp = "Hello World!!!";
  char noNull[] = {'H', 'i'};
  string s1(cp);        // 全部拷贝,直到遇到空字符
  string s2(noNull, 2); // 拷贝两个字符
  // string s3(noNull); // 未定义,不是以空字符结尾
  string s4(cp + 6, 5); // cp[6]开始拷贝,5个字符
  string s5(s1, 6, 5);  // s1[6]开始拷贝,拷贝5个字符
  string s6(s1, 6);     // 从s1[6]开始拷贝到末尾
  string s7(s1, 6, 20); // 正确,只拷贝到末尾
  // string s8(s1, 16); // 抛出异常,out_of_range,开始位置超出字符串
  • 通常当我们从const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止,如果还传递了一个计数值,数组就不必以空字符结尾。如果未传递计数值且未以空字符结尾,或者计数值大于数组大小,构造函数的行为是未定义的。
    当从一个string拷贝字符时,我们可以提供一个可选的开始位置和一个计数值,开始位置必须小于或等于给定的string的大小。如果位置大于size,则构造函数抛出out_of_range异常。如果传递了一个计数值,则从给定位置拷贝指定个数字符,最多拷贝到结尾,不会更多
  • substr操作
string s("hello world");
  • string s2 = s.substr(0, 5); //hello
    string s3 = s.substr(6); // world
    string s4 = s.substr(6, 11); //world
    // string s5 = s.substr(12); // out_of_range异常
  • s.substr(pos,n)
    返回一个string,包含s中从pos开始的n个字符的拷贝,pos的默认值为0,n的默认值是s.size()-pos,即拷贝从pos开始的所有字符
  • 如果pos位置大于string大小,则抛出out_of_range异常

改变string的其他方法

  • API列表
  • s.insert(pos,args)
    在pos之前插入args指定的字符。
    pos可以是一个下标或者一个迭代器。
    接受下标的版本返回一个指向s的引用。
    接受迭代器的版本返回指向第一个插入字符的迭代器。
  • s.erase(pos,len)
    删除从位置pos开始的len个字符。
    如果len被省略,则删除从pos开始直到s末尾的所有字符,返回指向s的引用
  • s.assign(args)
    将s中的字符替换为args指定的字符,返回一个指向s的引用
  • s.append(args)
    将args追加到s,返回一个指向s的引用
  • s.replace(range,args)
    删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。返回一个指向s的引用
  • args形式
  • 字符串str
  • str,pos,len
  • cp,len
  • cp
  • n,c
  • b,e
  • 初始化列表
string s = "Hello World!!!";
  s.insert(s.size(), 5, '!'); // 末尾插入5个感叹号
  s.erase(s.size() - 5, 5);   //删除最后5个字符
  const char *cp = "Stately, plump Buck";
  s.assign(cp, 7); // s="Stately"
  s.insert(s.size(), cp + 7);
  string s = "some string", s2 = "some other string";
  s.insert(0, s2);
  // s[0]位置插入s2[0]开始的s2.size()个字符
  s.insert(0, s2, 0, s2.size());
string s = "C++ Primer", s2 = s;
  s.insert(s.size(), " 4th Ed.");
  s2.append(" 4th Ed."); // 等价于插入方法

  s.erase(11, 3);
  s.insert(11, "5th");
  s2.replace(11, 3, "5th"); // 等价于上边的替换方法
  s.replace(11, 3, "Fifth");

string搜索操作

  • API列表
  • s.find(args)
    查找s中args第一次出现的位置
  • s.rfind(args)
    查找s中args最后一次出现的位置
  • s.find_first_of(args)
    在s中查找args中任何一个字符第一次出现的位置
  • s.find_last_of(args)
    在s中查找args中任何一个字符最后一次出现的位置
  • s.find_first_not_of(args)
    在s中查找第一个不在args中的字符
  • s.find_last_not_of(args)
    在s中查找最后一个不在args中的字符
  • args形式
  • c,pos
    查找字符c从pos开始
  • s2,pos
    从pos开始查找字符串s2,
    pos默认是0
  • cp,pos
    从pos开始查找cp指向的以空字符结尾的C风格字符串
  • cp,pos,n
    从pos开始查找指针cp指向的数组的前n个字符,pos n无默认值
  • 每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败,返回一个名为string::npos的static成员。标准库将npos定义为const string::size_type类型,并初始化值为-1.由于npos是一个unsigned类型,此初始值意味着npos等于任何string最大的可能大小
// 查找第一个与目标字符串的任一字符匹配的位置
  string numbers("0123456789"), name("r2d2");
  auto pos = name.find_first_of(numbers); // 1

  // 循环搜索字符串中出现数字的下标并显示
  string::size_type pos = 0;
  while ((pos = name.find_first_of(numbers, pos)) != string::npos)
  {
      cout << name[pos] << endl;
      ++pos;
  }
string river("Mississippi");
  auto first_pos = river.find("is");
  auto last_pos = river.rfind("is");

compare函数

  • compare几种参数形式
  • s2
  • pos1,n1,s2
  • pos1,n1,s2,pos2,n2
  • cp
  • pos1,n1,cp
  • pos1,n1,cp,n2

数值转换

  • API列表
  • to_string(val)
    一组重载函数,返回string表示,val可以是任何算数类型,对每个浮点类型和int或更大的整型,都有相应版本的to_string。与往常一样,小整型会提升。
  • stoi(s,p,b)
    stol(s,p,b)
    stoul(s,p,b)
    stoll(s,p,b)
    stoull(s,p,b)
    返回s的起始子串的数值,b是转换所用的基数,默认是10,p是size_t指针,用来保存s中第一个非数值字符的下标,p默认是0,即函数不保存下标
  • stof(s,p)
    stod(s,p)
    stold(s,p)
int i = 42;
  string s = to_string(i);
  double d = stod(s);
  string s2 = "pi = 3.14";
  d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
  • 如果string不能转换为一个数值,这些函数抛出一个invalid_argument异常,如果转换得到的数值无法用任何类型来表示,则抛出一个out_of_range异常

容器适配器

适配器是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器配置器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如,stack适配器接受一个顺序容器,并使其操作看起来像一个stack一样

所有容器适配器都支持的操作和类型

  • size_type
    一种类型,足以保存当前类型的最大对象的大小
  • value_type
    元素类型
  • container_type
    实现适配器的底层容器类型
  • A a;
    创建一个名为a的空适配器
  • A a©
    创建一个名为a的适配器,带有容器c的一个拷贝
  • 关系运算符
    每个适配器都支持所有关系运算符
  • a.empty()
  • a.size()
  • swap(a,b)
  • a.swap(b)

定义一个适配器

  • 默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。我们可以一个适配器时将一个命名的顺序容器作为第二个类型参数来重载默认容器类型。
  • stack<string, vector<string>> str;
  • 所有适配器都要求容器具有添加和删除元素的能力。因此,适配器不能构造在array上。类似的,我们也不能用forward_list来构造适配器,因为所有适配器都要求容器具有添加删除以及访问尾元素的能力。
    stack只要求push_back、pop_back和back操作,因此可以使用除了array和forward_list之外的任何容器类型来构造stack。
    queue适配器要求back push_back front和push_front,因此,可以构造在deque或list之上,但不能基于vector构造。
    priority_queue除了front push_back pop_back之外还要求随机访问的能力,因此可以构造于vector或deque之上,但不能基于list构造

栈适配器

  • 额外的栈操作
  • 默认基于deque实现,也可以在list或vector实现
  • s.pop()
  • s.push(item)
  • s,emplace(args)
  • s.top()

队列适配器

  • 额外的queue和priority_queue操作
  • queue默认基于deque实现,priority_queue默认基于vector实现
    queue也可以用list或vector实现,priority_queue也可以用deque实现
  • q.pop()
    返回queue的首元素或priority_queue的最高优先级的元素
  • q.front()
  • q.back()
    只适用于queue
  • q.top()
    返回最高优先级元素,不删除,只适用于priority_queue
  • q.push(item)
  • q.emplace(args)

术语表

1-10

  • 适配器
  • 数组
  • begin
  • cbegin
  • cend
  • 容器
  • deque
  • end
  • forward_list
  • 迭代器范围

11-19

  • 左闭合区间
  • list
  • 首前迭代器
  • 尾后迭代器
  • priority_queue
  • queue
  • 顺序容器
  • stack
  • vector

XMind: ZEN - Trial Version