【c++ Prime 学习笔记】第9章 顺序容器
9.1 顺序容器概述
所有容器都可快速访问元素,但在不同方面有折中:
- 添加/删除元素的代价
- 非顺序访问的代价
顺序容器的类型
vector
可变数组大小。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。deque
双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。list
双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。forward_list
单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。array
固定大小数组。支持快速随机访问。不能添加或删除元素。string
与vector相似的容器,但专门用于保存字符、随机访问快。在尾部插入/删除速度快。- 除固定大小的array外,其他容器都提供高效、灵活的内存管理。可以添加和删除,扩展和收缩容器的大小
string
和vector
将元素存储在连续空间中,故通过下标的随机访问很快,在中间和头部插入/删除很慢,在尾部添加元素很快,添加元素可能造成空间的重新分配和元素拷贝。list
(双向链表)和forward_list
(单向链表)的设计目的是让任何位置的插入/删除都快速高效且不需重新分配内存。但不支持随机访问,为访问一个元素需要遍历整个链表。由于要存储指针,故内存开销大。deque
(双端队列)支持快速随机访问,且在中间插入/删除元素很慢,但两端插入/删除很快。forward_list
和array
是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); //错误:必须提供一个元素初始化器
容器操作
9.2.1 迭代器
- 表3.6(p96)列出了所有容器迭代器都支持的操作(例外:forward_list不支持
-
) - 表3.7(p99)列出了
string
、vector
、deque
、array
支持的迭代器算术运算。它们不适用于其他容器
迭代器范围
由一对迭代器表示,两迭代器指向同一容器中的元素或尾后元素。迭代器范围包含它们之间(左闭右开 [ 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
元素的常量引用类型
- 2个
- 大多数容器提供
反向迭代器
,它可反向遍历容器。与正向迭代器相比,其各种操作含义被颠倒。例如对反向迭代器做++
得到上一个元素 - 要使用这些类型成员,需要用
::
指明作用域,即模板类的类名(含模板参数)
list<string>::iterator iter;
vector<int>::difference_type count;
9.2.3 begin 和 end 成员
begin
和end
成员函数得到指向容器首元素和尾后元素的迭代器,即形成包含容器中所有元素的迭代器范围- 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之外的容器默认构造函数都会创建指定类型的空容器,且都可接受指定容器大小和初始值的参数
将一个容器初始化为另一个容器的拷贝
方法有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
- 赋值前两容器大小可不同,赋值后大小都等于右边容器的大小
- 与内置数组不同,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前后解引用得到的值不一致(因为真的交换了值)
- 除string和array外,指向元素的迭代器/指针/引用,在swap后都指向原来的元素,但已经属于不同的容器了。例如:
- C++11同时提供swap的成员版本和非成员版本,但在旧标准中只有成员版本。在泛型编程中多用非成员版本,即用
swap(a,b)
而不是a.swap(b)
9.2.6 容器大小操作
- 3个关于大小的成员函数:
size
返回容器中元素的数目empty
当size为0时返回true,否则falsemax_size
返回一个大于等于该类型容器所能容纳的最大元素数量的值
forward_list
只支持empty
和max_size
,不支持size
9.2.7 关系运算符
- 任何容器都支持相等运算符
==
和!=
,除无序关联容器外都支持关系运算符<
、<=
、>
、>=
- 关系运算符两侧对象必须容器类型相同且元素类型相同。比较两个容器实际上是进行元素的逐对比较
- 容器大小相同且所有元素都两两对应相等,则两容器相等;否则不等
- 容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器<较大容器
- 两容器不是另一个容器的前缀子序列,则比较结果取决第一个不相等元素的比较结果
- 容器的相等是用元素的
==
实现,其他关系运算符是用元素的<
实现。如果元素类型不支持所需运算符,那么保存这种袁旭的容器就不能使用相应的关系运算
9.3 顺序容器操作
- 顺序容器和关联容器的不同之处在于它们组织元素的方式。顺序容器中元素的顺序与其加入容器的位置对应,关联容器中元素的顺序由其关联的关键字决定
9.3.1 向顺序容器添加元素
- 除array外,所有标准库容器都可在运行时动态添加/删除元素以改变容器大小
- 进行操作时,须记得不同容器使用不同策略来分配元素空间:
- 在vector、string的尾部之外,或deque的首位之外的任何位置田间元素,都需要移动元素
- 向vector、string添加元素可能引起整个对象存储空间的重新分配。
- 重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间
使用 push_back()
- 在尾部插入元素。除
array
和forward_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 的返回值
利用insert返回值,可以在容器中一个特定位置反复插入元素
list<string> lst; auto iter=lst.begin(); while(cin>>word) iter=lst.insert(iter,word); //等价于反复调用push_front
使用 emplace 操作
- C++11引入新成员
emplace_front
、emplace_back
、emplace
,分别对应push_front
、push_back
、insert
。区别是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 访问元素
- 若访问处没有元素,则结果未定义
- 顺序容器都有
front
成员函数,除forward_list之外的顺序容器都有back
成员函数。前者返回首元素的引用,后者返回尾元素的引用 front
/back
与begin
/end
的区别:- front/back返回引用,begin/end返回迭代器
- front/back返回首元素和尾元素,begin/end返回首元素和尾后元素
- 空元素求front/back是未定义,但可求begin/end且有begin==end
访问成员函数返回的是引用
- 表9.6中访问元素的成员函数(
front
、back
、[]
、at
)都返回引用
。若要用auto,记得将变量声明为引用,否则存在拷贝且不能修改容器。
下标操作和安全的随机访问
- 提供快速随机访问的容器(string、vector、deque、array)都支持下标运算符
[]
。 - 下标运算
[]
不检查下标是否在合法范围,但at
成员函数在下标越界时抛出out_of_range
异常
9.3.3 删除元素
pop_front和pop_back成员函数
pop_front
和pop_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操作
- 对forward_list(单向链表)的元素做插入/删除,需要知道其
前驱
。 - forward_list的插入/删除改变的不是指定元素,而是指定元素之后的一个元素
- forward_list未定义
insert
、emplace
、erase
,但定义了insert_after
、emplace_after
、erase_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 改变容器大小
- 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要求在每次需要分配新内存空间时,会分配比所需空间更大的空间,预留作备用。之后即使会拷贝元素,但尽量不会分配/释放空间
管理容量的成员函数
capacity
操作告诉我们容器在不扩张内存时最多还能容纳多少元素reserve
操作允许通知容器它至少需要容纳多少元素- reserve不改变元素的数量,即不改变
size
,只影响预分配的内存 - 传给reserve的值小于等于当前capacity时,reserve什么都不做。特别是,小于时不会退回空间
- 传给reserve的值大于当前capacity时,reserve扩张容量,至少分配与要求容量一样大的空间,可能更大
- 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 的其他方法
- 这些构造函数接受
string
或const char *
参数作为源,还接受(可选的)指定拷贝多少个字符的参数。当源为string时,还可给定下标来指定从哪里开始拷贝 - 使用
const char *
构造string时,字符数组必须以空字符\0
结尾,用于停止拷贝。但若给定拷贝大小的计数值,则只需不越界,不需空字符结尾。 - 使用
string
构造string时,可提供开始位置和计数值。开始位置必须小于等于源string大小,否则out_of_range异常。不管要求拷贝多少,最多拷到源string结尾。
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的其他方法
- 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搜索操作
- 每个搜索操作都返回
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、正数、负数
9.5.5 数值转换
- 要将string转为数值,必须保证string中的第一个非空白字符是该数值类型中可能出现的字符,例如正负号、数字等,也可是
0x
或0X
表示的十六进制数(此时string中可包含字母)。对于浮点类型,可以小数点.
开头,并可包含e
或E
指定指数部分。 - 如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个
顺序容器适配器
:stack
、queue
、priority_queue
定义一个适配器
-
每个适配器都有两个构造函数:
-
默认构造函数,创建空对象
- 接受一个容器,拷贝该容器来初始化适配器
deque<int> deq; stack<int> stk(deq); //从deq拷贝元素到stk
-
-
默认情况下,
stack
和queue
基于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
只要求back
、push_back
、pop_back
操作,故可构建于除array/forward_list外的所有容器queue
要求back
、push_back
、front
、push_front
,故可构建于list/dequepriority_queue
要求front
、push_back
、pop_back
、随机访问
,故可构建于vector/deque
栈适配器
- stack定义于
stack头文件
中,栈操作
```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允许为队列中的元素建立优先级,新加入的元素会排在所有优先级比它低的已有元素之前。默认情况下使用元素类型的<运算符来确定优先级