【c++ Prime 学习笔记】第9章 顺序容器

9.1 顺序容器概述

所有容器都可快速访问元素,但在不同方面有折中:

  • 添加/删除元素的代价
  • 非顺序访问的代价

顺序容器的类型

  • vector 可变数组大小。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
  • deque 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。
  • list 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。
  • forward_list 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。
  • array 固定大小数组。支持快速随机访问。不能添加或删除元素。
  • string 与vector相似的容器,但专门用于保存字符、随机访问快。在尾部插入/删除速度快。
  • 除固定大小的array外,其他容器都提供高效、灵活的内存管理。可以添加和删除,扩展和收缩容器的大小
    • stringvector将元素存储在连续空间中,故通过下标的随机访问很快,在中间和头部插入/删除很慢,在尾部添加元素很快,添加元素可能造成空间的重新分配和元素拷贝。
    • list(双向链表)和forward_list(单向链表)的设计目的是让任何位置的插入/删除都快速高效且不需重新分配内存。但不支持随机访问,为访问一个元素需要遍历整个链表。由于要存储指针,故内存开销大。
    • deque(双端队列)支持快速随机访问,且在中间插入/删除元素很慢,但两端插入/删除很快。
    • forward_listarray是C++11新增的类型。array和内置数组一样大小固定,但操作更安全。forward_list的设计目标是达到与最快的手写单向链表相当的性能,故没有size操作(计算和保存都要开销)

C++新标准容器的性能比旧版本快很多,其性能与最精心优化过的同类数据结构一样好。现代C++程序应该使用标准库容器,而不是更原始的数据结构,如内置函数。

确定使用哪种顺序容器

  • 选择容器的基本原则
    • 般用vector,除非有理由选其他容器
    • 如果元素小而多,且空间开销重要,则不要用list或forward_list
    • 若要求随机访问,则用vector或deque
    • 若要在中间插入/删除,则用list或forward_list
    • 若要在头尾插入/删除但不在中间插入/删除,则用deque
    • 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则:
      • 尽量避免在中间插入。例如可用vector存储,再用标准库算法做排序等操作来改变顺序
      • 如程序分为几阶段,只有前半段必须在中间插入,则前半段用list,再拷贝到vector做后半段
    • 若必须同时使用随机访问和中间插入,则看哪个占主导地位。可用实验来判断用list/forward_list还是vector/deque
    • 若不确定用哪种容器,最好在程序中只用vector和list的公共操作:使用迭代器而不是下标,使用++/-而不是下标访问

9.2 容器库概览

容器上的操作形成层次:

  • 某些操作对所有容器都提供
  • 某些操作仅针对顺序容器,某些操作仅针对关联容器,某些操作仅针对无序容器
  • 某些操作仅适用于特定容器
  • 每个容器都定义于一个头文件中,文件名与容器名相同。
  • 容器均定义为模板类 。对大多数容器,需要提供额外提供元素类型信息
list<Sales_data>
deque<double>

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

  • 顺序容器几乎可以保存任意类型的元素,特别是,可以容器中保存容器。
vector<vector<string>> lines;
vector<vector<string> > lines; //在C++11之前的版本中需要多写一个空格
  • 某些类没有默认构造函数,可以定义一个保存这种类型对象的容器,但在构造这种容器时不能只传给他一个元素数目的参数
//假定noDefault 是一个没有默认构造函数的类型
vector<noDefault> v1(10,init); //正确:提供元素初始化器
vector<noDefault> v1(10);      //错误:必须提供一个元素初始化器

容器操作

image
image

9.2.1 迭代器

  • 表3.6(p96)列出了所有容器迭代器都支持的操作(例外:forward_list不支持-
  • 表3.7(p99)列出了stringvectordequearray支持的迭代器算术运算。它们不适用于其他容器

迭代器范围

由一对迭代器表示,两迭代器指向同一容器中的元素或尾后元素。迭代器范围包含它们之间(左闭右开 [ begin, end ))的所有元素。

使用左闭右开范围蕴含的编程假定

  • 若begin与end相等则范围为空
  • 若begin与end相不等,则begin指向范围中的第一个元素
  • 可使begin递增直到begin==end。以此条件做循环,可保证迭代器有效
while(begin != end){
	*begin = val;
	++begin;
}

9.2.2 容器类型成员

  • 表9.2定义了容器通用的类型成员:
    • 2个迭代器类型iterator迭代器类型,const_iterator常量迭代器类型
    • 2个整型size_type无符号,difference_type有符号
    • 3个类型别名value_type元素类型,reference元素的左值(引用)类型,const_reference元素的常量引用类型
  • 大多数容器提供反向迭代器,它可反向遍历容器。与正向迭代器相比,其各种操作含义被颠倒。例如对反向迭代器做++得到上一个元素
  • 要使用这些类型成员,需要用::指明作用域,即模板类的类名(含模板参数)
list<string>::iterator iter;
vector<int>::difference_type count;

9.2.3 begin 和 end 成员

  • beginend成员函数得到指向容器首元素和尾后元素的迭代器,即形成包含容器中所有元素的迭代器范围
  • begin和end有多个版本:rbegin返回反向迭代器,cbegin返回常量迭代器,crbegin返回常量反向迭代器
  • 不以c开头的(即非常量)迭代器都是被重载过的,即返回类型都可以是const/非const。因为考虑到begin可能被const/非const成员调用。
  • 以c开头的(即常量)迭代器类型是C++11定义的,用于支持auto。例如有时候想用auto得到非常量对象的常量迭代器时,用auto it=a.cbegin();
  • 不需要写访问时,尽量都用cbegin和cend

9.2.4 容器定义和初始化

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

image

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

方法有2种:

  • 直接拷贝整个容器:两容器的类型和元素类型都必须匹配
  • 拷贝一对迭代器指定的范围:不要求容器类型相同,也不要求元素类型相同,只要求元素类型可转换。但不可用于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外,初始化列表还隐式指定了容器的大小。
vector<string> articles={"a","an","the"};

与顺序容器大小相关的构造函数

  • 顺序容器(除array外)还可指定容器大小和给定初值,如。如果不给初值,则进行值初始化(内置类型初始化为0,类类型调用默认构造函数)。这种对关联容器不适用。
  • 如果元素类型是内置类型或者具有默认构造函数的类类型,可以只为构造函数提供一个容器大小的参数。
list<string> svec(10,"hi!");
deque<string> svec(10);

标准库array具有固定大小

  • array的大小也是类型的一部分,定义时模板参数包含元素类型和大小
array<int, 42>
array<string, 10>

array<int, 42>::size::type i;
array<int>::size::type j;  //错误,array<int>不是一个类型
  • array不可用普通的容器构造函数,因为它们都隐式确定大小。但可使用指定大小的构造函数
  • 默认构造的array非空,它被填满元素,元素都被默认初始化。
  • 可对array做列表初始化,列表长度须小于等于array大小,如果小于,则初始化靠前元素,剩下的被值初始化。
  • 与内置数组不同的是,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};                     //剩下元素被初始化为0

int digs[10]={0,1,2,3,4,5,6,7,8,9};
int cpy[10]=digs;                           //错,内置数组不可拷贝/赋值。digs被转为指针
array<int,10> digits={0,1,2,3,4,5,6,7,8,9};
array<int,10> copy=digits;                  //对,只要大小和元素类型相同即可

9.2.5 赋值和swap

image

  • 赋值前两容器大小可不同,赋值后大小都等于右边容器的大小
  • 与内置数组不同,array允许赋值,只要两array大小和元素类型都一样
  • 由于array大小固定,故只能用=赋值,不可用assign,也不可用花括号列表赋值。
array<int,10> a1={0,1,2,3,4,5,6,7,8,9};
array<int,10> a2={0}
a1=a2;  //对
a2={0}; //错

使用 assign (仅顺序容器)

  • ssign用参数指定的元素替换该容器的所有元素。其参数可为:
    • 一对迭代器范围
    • 一个大小和一个元素值
  • 由于旧元素被替换,故传递给assign的迭代器不能指向调用assign的容器
list<string> names;
vector<const char *> oldstyle;
names=oldstyle;                                     //错,容器类型和元素类型不匹配
names.assign(oldstyle.cbegin(),oldstyle.cend());    //对,只要元素类型可转换

list<string> slist1(1); //一个元素,为空
slist1.assign(10, "Hiya!");  //10个元素,每个都是"hiya!"

使用swap

  • swap交换两个相同类型容器的内容
vector<string> svec1(10);
vector<string> svec2(10);
swap(svec1, svec2);
  • 除array外,swap不对任何元素进行拷贝、删除和插入操作。因此都是O(1)时间
  • swap前后迭代器/指针/引用的变化:
    • 除string和array外,指向元素的迭代器/指针/引用,在swap后都指向原来的元素,但已经属于不同的容器了。例如:it指向svec1[3],在进行swap(svec1,svec2);后,it指向svec2[3],对it解引用得到的结果前后一致。
    • 对string使用swap导致之前的迭代器/指针/引用都失效
    • 对array做swap会真正交换元素,所需时间与array中的元素数目成正比
    • 对array使用swap导致之前的迭代器/指针/引用指向的元素不变,但值发生改变,即swap前后解引用得到的值不一致(因为真的交换了值)
  • C++11同时提供swap的成员版本和非成员版本,但在旧标准中只有成员版本。在泛型编程中多用非成员版本,即用swap(a,b)而不是a.swap(b)

9.2.6 容器大小操作

  • 3个关于大小的成员函数:
    • size返回容器中元素的数目
    • empty当size为0时返回true,否则false
    • max_size返回一个大于等于该类型容器所能容纳的最大元素数量的值
  • forward_list只支持emptymax_size,不支持size

9.2.7 关系运算符

  • 任何容器都支持相等运算符==!=,除无序关联容器外都支持关系运算符<<=>>=
  • 关系运算符两侧对象必须容器类型相同且元素类型相同。比较两个容器实际上是进行元素的逐对比较
    • 容器大小相同且所有元素都两两对应相等,则两容器相等;否则不等
    • 容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器<较大容器
    • 两容器不是另一个容器的前缀子序列,则比较结果取决第一个不相等元素的比较结果
  • 容器的相等是用元素的==实现,其他关系运算符是用元素的<实现。如果元素类型不支持所需运算符,那么保存这种袁旭的容器就不能使用相应的关系运算

9.3 顺序容器操作

  • 顺序容器和关联容器的不同之处在于它们组织元素的方式。顺序容器中元素的顺序与其加入容器的位置对应,关联容器中元素的顺序由其关联的关键字决定

9.3.1 向顺序容器添加元素

  • 除array外,所有标准库容器都可在运行时动态添加/删除元素以改变容器大小
  • 进行操作时,须记得不同容器使用不同策略来分配元素空间:
    • 在vector、string的尾部之外,或deque的首位之外的任何位置田间元素,都需要移动元素
    • 向vector、string添加元素可能引起整个对象存储空间的重新分配。
    • 重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间

image

使用 push_back()

  • 在尾部插入元素。除arrayforward_list外,每个顺序容器都支持push_back
  • string也可以使用push_back在末尾添加字符

使用push_front

  • 在头部插入元素。list、forward_list、deque容器支持push_front。vector、string不支持

使用 insert()

  • 在容器中的特定位置添加元素

    • 在任意位置插入0个/多个元素。vector、string、deque、list都支持insert。forward_list提供了特殊的insert。
    • insert 函数将元素插入到迭代器所指的位置之前,考虑左闭右开
    • vector、string虽不可用push_front,但可用insert在头部插入
    vector<string> svec;
    list<string> slist;
    slist.insert(slist.begin(),"hello");
    svec.insert(svec.begin(),"hello"); //会比较耗时
    
  • 插入范围内元素

    • insert(迭代器)
    • insert(迭代器个数)
    • insert(迭代器初始化列表{})
    • insert(迭代器迭代器范围start迭代器范围end)
      • 如果我们传递给insert一对迭代器,他们不能指向添加元素的目标容器
    svec.instert(svec.end(), 10, "Anna");
    vector<string> v={"quasi", "simba", "frollo", "scar"};
    slist.insert(slist.begin(), v.end()-2, v.end());
    slist.insert(slist.end(),{"these", "word", "will", "go", "at", "the", "end" });
    
    //错误,
    slist.insert(slist.begin(), slist.begin(), slist.end());
    
    

    C++11中,接受元素个数或迭代器范围的insert可返回指向第一个新加入元素的迭代器。如范围为空,则不插入,返回insert的第一个参数。

  • 使用insert 的返回值

    利用insert返回值,可以在容器中一个特定位置反复插入元素

    list<string> lst;
    auto iter=lst.begin();
    while(cin>>word)
        iter=lst.insert(iter,word); //等价于反复调用push_front
    

使用 emplace 操作

  • C++11引入新成员emplace_frontemplace_backemplace,分别对应push_frontpush_backinsert。区别是emplace是在原地构造元素,而push/insert是拷贝元素
  • push/insert可能会创建局部的临时量,再将临时量拷贝到容器
  • 调用emplace时,它将参数传递给元素类型的构造函数,使用它们在容器的内存空间中直接构造元素。故emplace的参数需对应到元素的构造函数参数
c.emplace_back("978-0590353403", 25, 15.99);//使用三个参数的构造函数
c.push_back("978-0590353403", 25, 15.99); //错误,没有接收三个参数的构造函数
c.push_back(Sales_data("978-0590353403", 25, 15.99));//创建临时对象,传递给push_back

c.emplace_back(); //使用默认构造函数
c.emplace_back(iter, "978-0590353403");//使用Sales_data(string)
c.emplace_front("978-0590353403", 25, 15.99);

9.3.2 访问元素

image

  • 若访问处没有元素,则结果未定义
  • 顺序容器都有front成员函数,除forward_list之外的顺序容器都有back成员函数。前者返回首元素的引用,后者返回尾元素的引用
  • front/backbegin/end的区别:
    • front/back返回引用,begin/end返回迭代器
    • front/back返回首元素和尾元素,begin/end返回首元素和尾后元素
    • 空元素求front/back是未定义,但可求begin/end且有begin==end

访问成员函数返回的是引用

  • 表9.6中访问元素的成员函数(frontback[]at)都返回引用。若要用auto,记得将变量声明为引用,否则存在拷贝且不能修改容器。

下标操作和安全的随机访问

  • 提供快速随机访问的容器(string、vector、deque、array)都支持下标运算符[]
  • 下标运算[]不检查下标是否在合法范围,但at成员函数在下标越界时抛出out_of_range异常

9.3.3 删除元素

image

pop_front和pop_back成员函数

  • pop_frontpop_back成员函数分别删除首元素和尾元素
  • vector/string不支持push_front/pop_front,forward_list不支持push_front/pop_front
  • pop_front/pop_back返回void,若需要值,需在pop之前保存

从容器内部删除一个或多个元素

  • 不能对空容器做删除操作
  • erase可从指定的任意位置删除元素,它有两个版本:
    • 接受一个迭代器,删除它指向的元素,返回它之后位置的迭代器
    • 接受一个迭代器范围,删除左闭右开区间内的元素,返回删除列表最后元素之后位置的迭代器

删除所有元素

  • 要删除容器的所有元素,可用clear,也可用begin/end调用erase
slist.clear();
slist.erase(slist..begin(), slist.end());

9.3.4 特殊的forward_list操作

image

  • 对forward_list(单向链表)的元素做插入/删除,需要知道其前驱
  • forward_list的插入/删除改变的不是指定元素,而是指定元素之后的一个元素
  • forward_list未定义insertemplaceerase,但定义了insert_afteremplace_aftererase_after来提供类似操作
  • forward_list定义了before_begin迭代器,它指向首元素之前,称为首前迭代器
forward_list<int> flst={0,1,2,3,4,5,6,7,8,9};
auto prev=flst.before_begin();          //要处理的元素的前驱
auto curr=flst.begin();                 //要处理的元素
while(curr!=flst.end()){
    if(*curr%2)
				//删除curr,返回要处理的元素的下一个迭代器,作为下一轮循环要处理的元素
        curr=flst.erase_after(prev);    
    else{
        prev=curr;                      //更新前驱
        ++curr;                         //更新要处理的元素的迭代器
    }
}

9.3.5 改变容器大小

image

  • resize改变大小的操作,不支持array
  • 对于给定的目标大小,若比当前大小更小,则容器后面的元素都被删除,若比当前大小更大,则将值初始化的新元素添加到容器尾部。可以指定值初始化的初始值

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

  • 向容器中添加/删除元素可能使指向元素的指针/引用/迭代器失效
  • 添加元素后:
    • vector/string:若空间被重新分配,则所有指针/引用/迭代器失效。若空间未重新分配,则插入位置的之后的指针/引用/迭代器都失效
    • deque:插入首尾之外的任何位置都使所有指针/引用/迭代器失效。在首尾插入时,迭代器失效,指向元素的指针/引用不失效
    • list/forward_list:所有指针/引用/迭代器仍有效
  • 删除元素后:
    • 指向被删除元素的指针/引用/迭代器一定失效
    • vector/string:删除位置的之后的指针/引用/迭代器都失效。特别是,删除任何元素时,尾后迭代器一定失效
    • deque:删除首尾之外的任何位置都使所有指针/引用/迭代器失效。删除首元素无影响,删除尾元素使尾后迭代器失效
    • list/forward_list:除被删除元素之外的所有指针/引用/迭代器仍有效
  • 最佳实践:
    • 最小化要求迭代器有效的程序片段
    • 保证每次改变容器的操作后都更新迭代器
    • 不要保存尾后迭代器,每次需要时都用end重新取

编写改变容器的循环程序

  • 添加/删除vector/string/deque中的元素,必须考虑指针/引用/迭代器的失效问题。用insert/erase可由返回值直接更新
vector<int> vi={0,1,2,3,4,5,6,7,8,9};
auto iter=vi.begin();
while(iter!=vi.end()){
    if(*iter%2){
        iter=vi.insert(iter,*iter); //复制奇数元素,迭代器实时更新
        iter+=2;
    }
    else
        iter=vi.erase(iter);        //删除偶数元素,迭代器实时更新
}

不要保存end 返回的迭代器

  • 添加/删除vector/string中的元素后,或在deque中首元素之外任何位置添加/删除元素后,原来end返回的迭代器总是会失效。
  • 因此,添加和删除元素的循环程序必须反复调用end
//灾难:此循环的行为是未定义的
auto begin = v.begin(), 
			end = v.end(); //保存为迭代器的值是一个坏主意
while(begin != end){
	++begin;
	begin = v.insert(begin, 42);
	++begin;
}

//更安全的方法:在每个循环步添加/删除元素后都重新计算end
while(begin != v.end()){
	++begin;
	begin = v.insert(begin, 42);
	++begin;
}

9.4 vector 对象是如何增长的

  • 为支持快速随机访问,vector将元素连续存储
  • 为减少分配/释放空间的次数,vector/string要求在每次需要分配新内存空间时,会分配比所需空间更大的空间,预留作备用。之后即使会拷贝元素,但尽量不会分配/释放空间

管理容量的成员函数

image

  • capacity操作告诉我们容器在不扩张内存时最多还能容纳多少元素
  • reserve操作允许通知容器它至少需要容纳多少元素
    • reserve不改变元素的数量,即不改变size,只影响预分配的内存
    • 传给reserve的值小于等于当前capacity时,reserve什么都不做。特别是,小于时不会退回空间
    • 传给reserve的值大于当前capacity时,reserve扩张容量,至少分配与要求容量一样大的空间,可能更大
  • 改变容器大小的resize方法只改变元素数量,不影响capacity
  • shrink_to_fit是C++11的方法,它可要求vector/string/deque退回多余的空间,但具体实现可忽略此要求。即,不保证能退回。

capacity 和 size

  • apacity至少与size一样大,具体多大取决于实现
  • 只要没有超出vector的capacity,vector就不会自动扩张(不会重新分配内存)
  • vector采用的内存扩张策略一般是:在每次需要分配新空间时,将当前容量翻倍。但具体实现可使用不同策略
  • 所有扩张策略都应遵循的原则:确保用push_back添加元素有高效率。即,在初始为空的vector上调用n次push_back,花费时间不应超过n的常数倍

9.5 额外的string操作

9.5.1 构造 string 的其他方法

image

  • 这些构造函数接受stringconst char *参数作为源,还接受(可选的)指定拷贝多少个字符的参数。当源为string时,还可给定下标来指定从哪里开始拷贝
  • 使用const char *构造string时,字符数组必须以空字符\0结尾,用于停止拷贝。但若给定拷贝大小的计数值,则只需不越界,不需空字符结尾。
  • 使用string构造string时,可提供开始位置和计数值。开始位置必须小于等于源string大小,否则out_of_range异常。不管要求拷贝多少,最多拷到源string结尾。

image

substr操作

  • substr操作返回一个string,它是原始string的一部分或全部的拷贝。
  • s.substr(pos, n) 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size() - pos, 即拷贝从pos开始的所有字符
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 异常

9.5.2 改变string的其他方法

image

image

  • assign 替换赋值,总是替换string中的所有内容
s.insert(s.size(), 5, '!'); //在s末尾插入5个感叹号
s.erase(s.size() - 5, 5);  //从s删除最后5个字符

const char *cp="Stately,plump Buck";
a.assign(cp, 7);     //"Stately"
a.insert(s.size(), cp+7) //"Stately,plump Buck"

string s="some string", s2="some other string";
s.insert(0,s2);             //s位置0之前插入s2的拷贝
s.insert(0,s2,0,s2.size()); //s位置0之前插入s2位置0开始的s2.size()个字符

append 和 replace 函数

  • append 末尾插入,总是将新字符追加到string末尾
  • replace 删除再插入
string s("C++ Primer"), s2=s;
s.insert(s.size()," 4th Ed.");  //s=="C++ Primer 4th Ed."
s2.append(" 4th Ed.");          //等价于上一行
s.erase(11,3);                  //s=="C++ Primer  Ed."
s.insert(11,"5th");             //s=="C++ Primer 5th Ed."
s2.replace(11,3,"5th");         //s2=="C++ Primer 5th Ed.",等价于上两行
s.replace(11,3,"Fifth");        //s=="C++ Primer Fifth Ed."

改变 string 的多种重载函数

  • append、assign、insert、replace都有多个重载版本,根据如何指定要添加的字符和string中被替换的部分
  • 指定要被替换的部分
    • assign/append无需指定替换哪部分:assign总是替换所有内容,append总将新字符追加到末尾
    • replace可用两种方式指定删除范围:可以用位置和长度,也可用迭代器范围
    • insert可用两种方式指定插入点:下标或迭代器
  • 指定要添加的字符
    • string,可以额外参数来控制拷贝局部还是全部字符
    • 字符指针,可以额外参数来控制拷贝局部还是全部字符
    • 花括号包围的字符列表
    • 一个字符和一个计数值

9.5.3 string搜索操作

image
image

  • 每个搜索操作都返回string::size_type类型值,表示匹配位置的下标。
  • 若搜索失败,即无匹配,则返回名为string::npos的static成员,它是string::size_type类型且初始化为1,即string最大的可能大小
  • string::size_type是无符号类型,不可与int等有符号混用
  • 搜索操作都是大小写敏感
  • find查找参数字符串第一次出现的位置,rfind查找参数字符串最后一次出现的位置
  • find_first_of查找参数中任何一个字符第一次出现的位置,find_last_of查找参数中任何一个字符最后一次出现的位置
  • find_first_not_of查找第一个不在参数中的字符,find_last_not_of查找最后一个不在参数中的字符
  • 可给find们指定可选的开始位置,指出从哪里开始搜索。可利用此机制循环查找所有匹配的位置
string numbers("0123456789"), name("r2d2");
string::size_type pos=0;
while((pos=name.find_first_of(numbers,pos))!=string::npos){ //每次查找一个子串
    cout<<"found number at index: "
        <<pos
        <<" element is "
        <<name[pos]
        <<endl;
    ++pos;    //移动到下一个字符,准备从后面的子串中查找
}

9.5.4 compare 函数

  • compare函数类似C语言中的strcmp,根据源字符串等于、大于、小于给定的字符串,compare成员函数返回0、正数、负数

image

9.5.5 数值转换

image

  • 要将string转为数值,必须保证string中的第一个非空白字符是该数值类型中可能出现的字符,例如正负号、数字等,也可是0x0X表示的十六进制数(此时string中可包含字母)。对于浮点类型,可以小数点.开头,并可包含eE指定指数部分。
  • 如string不能转为指定的数值类型,这些函数抛出invalid_argument异常
  • 如转换得到的数值无法用任何类型表示,则抛出out_of_range异常
string s2 = "pi = 3.14";
d = stod(s2.substr(s2.fingd_first_of("+_.0123456789"))); //得到3.14

9.6 容器适配器

  • 适配器是一种机制,能使某种事物的行为看起来像另一种事物。一个容器适配器接受一种已有的容器类型,使其看起来像另一种不同类型
  • 标准库定义了3个顺序容器适配器stackqueuepriority_queue

image

定义一个适配器

  • 每个适配器都有两个构造函数:

    • 默认构造函数,创建空对象

      • 接受一个容器,拷贝该容器来初始化适配器
      deque<int> deq;
      stack<int> stk(deq); //从deq拷贝元素到stk
      
  • 默认情况下,stackqueue基于deque实现,priority_queue基于vector实现。也可在创建时在模板参数里指定一个顺序容器来重载默认容器类型

    //在vector上实现的空栈
    stack<string, vector<string>> str_stk;
    //str_stk2在vector上实现,初始化时保存svec的拷贝
    stack<string, vector<string>> str_stk2(svec);
    
  • 对适配器的容器类型有限制:

    • 不能基于array,因为要添加/删除元素
    • 不能基于forward_list,因为要访问尾元素
    • stack只要求backpush_backpop_back操作,故可构建于除array/forward_list外的所有容器
    • queue要求backpush_backfrontpush_front,故可构建于list/deque
    • priority_queue要求frontpush_backpop_back随机访问,故可构建于vector/deque

栈适配器

  • stack定义于stack头文件中,栈操作

image

```cpp
stack<int> intStack; //空栈
for(size_t ix =0 ; ix != 10; ++ix )
	intStack.push(ix);
while(!intStack.empty()){
	int value = intStack.top(); //返回栈顶元素
	intStack.pop(); //弹出栈顶元素

}
```

队列适配器

  • queue和priority_queue定义于queue头文件
  • queue使用先进先出的存储和访问策略
  • priority_queue允许为队列中的元素建立优先级,新加入的元素会排在所有优先级比它低的已有元素之前。默认情况下使用元素类型的<运算符来确定优先级

image

posted @ 2021-04-22 16:21  砥才人  阅读(328)  评论(0编辑  收藏  举报