顺序容器为程序员提供了控制元素存储和访问顺序的能力,这种顺序不依赖与元素的值,而是与元素加入容器时的位置相对应。
1、顺序容器类型
向顺序容器添加元素的操作 |
操作会改变容器的大小;array不支持这些操作。 forward_list有自己专有版本的insert和emplace; forward_list不支持push_back和emplace_back vector和string不支持push_front和emplace_front c.push_back(t) 在c的尾部创建一个值为t或由args创建的元素,返回void c.emplace_back(args) c.push_front(t) 在c的头部创建一个值为t或由args创建的元素,返回void c.emplace_front(args) c.insert(p,t) 在迭代器p指向的元素之前创建一个值为t或由args创建的元素, 返回指向新添加的元素的迭代器 c.emplace(p,args) c.insert(p,n,t) 在迭代器p指向的元素之前插入n个值为t的元素。返回指向新添加的第一个元素的迭代器; 若n为0,则返回p c.insert(p,b,e) 将迭代器b和e指向的范围内的元素插入到迭代器p指向的元素之前。 b和e不能指向c中的元素,返回指向新添加 的第一个元素的迭代器;若范围为空,则返回p c.insert(p,il) il是一个花括号包围的元素值列表,将这些给定值插入到迭代器p指向的元素之前。 返回指向新添加第一个元素的迭代器:若列表为空,则返回p 向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效。 |
当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间,而这些策略直接影响性能。在一个vector或string的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。而且,向一个vector或string添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。
使用push_back
我们看到push_back将一个元素追加到一个vector的尾部。除array和forward_list之外,每个顺序容器(包括string类型)都支持push_back。
例如,下面的循环每次读取一个string到word中,然后追加到容器尾部:
//从标准输入读取数据,将每个单词放到容器末尾
string word;
while(cin>>word)
container.push_back(word);
关键概念:当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。就像我们将一个对象传递给非引用参数一样,容器中的元素与提供值的对象之间没有任何关联。随后对容器中元素的任何改变都不会影响到原始对象,反之亦然。
使用push_front
除了push_back,list、forward_list和deque容器还支持名为push_front的类似操作。此操作将元素插入到容器头部:
list
//将元素添加到Ilist开头
for(size_t ix=0;ix!=4;++ix)
ilist.push_front(ix);
注意:deque像vector一样提供了随机访问元素的能力,但它提供了vector所不支持的push_front。deque保证在容器首部进行插入和删除元素的操作都只花费常数时间。与vector一样,在deque首尾之外的位置插入元素会很耗时。
在容器中的特定位置添加元素
push_back和push_front操作提供了一种方便地在顺序容器尾部或头部插入单个元素的方法。insert成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素。vector、deque、list和string都支持insert成员。forward_list提供了特殊版本的insert成员。
每个insert函数都接受一个迭代器作为其一个参数。迭代器指出了在容器中什么位置放置新元素。它可以指向容器中任何位置,包括容器尾部之后的下一个位置。由于迭代器可能指向容器尾部之后不存在的元素的位置,而且在容器开始位置插入元素是很有用的功能,所有insert函数将元素插入到迭代器所指定的位置之前。例如,下面的语句
slist.insert(iter,"Hello!"); //将hello添加到iter之前的位置
虽然某些容器不支持push_front操作,但他们对于insert操作并无类似的限制(插入开始位置)。因此我们可以将元素插入到容器的开始位置,而不必担心容器是否支持push_front:
vector
list
//等价于调用slist.push_front("Hello!");
slist.insert(slist.begin(),"Hello!");
//vector不支持push_front,但我们可以插入到begin()之前
//警告:插入到vector末尾之外的任何位置都可能很慢
svec.insert(svec.begin(),"Hello!");
将元素插入到vector、deque和string中的任何位置都是合法的。然而,这样做可能很耗时。
插入范围内元素
除了第一个迭代器参数之外,insert函数还可以接受更多的参数,这与容器构造函数类似。其中一个版本接受一个元素数目和一个值,它将指定数量的元素添加到指定位置之前,这些元素够按给定值初始化:
svec.insert(svec.end(),10,"Anna");
接受一对迭代器或一个初始化列表的insert版本将给定范围中的元素插入到指定位置之前:
vector
slist.insert(slist.begin(),v.end()-2,v.end());
slist.insert(slist.end(),{"these","words","will","go","at","the","end"});
slist.insert(slist.begin(),slist.begin(),slist.end());//运行时错误:迭代器表示要拷贝的范围,不能指向与目的位置相同的容器
如果我们传递给insert一对迭代器,它们不能指向添加元素的目标容器。
使用emplace操作
新标准引入了三个成员——emplace_front、emplace和emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应push_front、insert和push_back,允许我们将元素放置在容器头部、一个指定的位置之前或容器尾部。
当调用push或insert成员函数时,我们将元素类型对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。例如,假定c保存Sales_data元素:
//在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));
其中对emplace_back的调用和第二个push_back调用都会创建新的Sales_data对象。在调用emplace_back时,会在容器管理的内存空间中直接创建对象。而调用push_back则会创建一个局部临时对象,并将其压入容器中。
emplace函数的参数根据元素类型而变化,参数必须与元素类型的构造函数相匹配:
//iter指向c中一个元素,其中保存了Sales_data元素
c.emplace_back();//使用Sales_data的默认构造函数
c.emplace(iter,"999-999999999"); //使用Sales_data(string)
//使用Sales_data的接受一个ISBN、一个count和一个price的构造函数
c.emplace_front("978-0590353403",25,15.99);
emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配。
访问元素
下表列出了我们可以用来在顺序容器值访问元素的操作。如果容器中没有元素,访问操作的结果是未定义的。
包括array在内的每个顺序容器都有一个front成员函数,而除了forward_list之外的所以顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用:
//在解引用一个迭代器或调用front或back之前检查是否有元素
if(!c.empty()){
//val和val2是c中第一个元素值的拷贝
auto val=*c.begin(),val2=c.front();
//val3和val4是c中最后一个元素值的拷贝
auto last=c.end();
auto val3=*(--last); //不能递减forward_list迭代器
auto val4=c.back(); //forward_list不支持
此程序用两种不同方式来获取c中的首元素和尾元素的引用。直接的方式hit调用front和back。而间接的方法是通过解引用begin返回的迭代器来获得首元素的引用,以及通过递减然后解引用end返回的迭代器来获取尾元素的引用。
在顺序容器中访问元素的操作 |
at和下标操作是适用于string、vector和array back不适用于forward_list。 c.back() 返回c中尾元素的引用。若c为空,函数行为未定义 c.front() 返回c中首元素的引用。若c为空,函数行为未定义 c[n] 返回c中下标为n的元素的引用,n是一个无符号整数。若n>c.size(),则函数的行为未定义 c.at[n] 返回下标为n的元素的引用。如果下标越界,则抛出一个out_of_range异常 对一个空容器调用front和back,就像使用一个越界的下标一样 |
访问成员函数返回的是引用
在容器中访问元素的成员函数(即,front、back、下标和at)返回的都是引用。如果容器是一个const对象,则返回值是const的引用。如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值。
if(!c.empty()){
c.front()=42; //将42赋予c中的第一个元素
auto &v=c.back(); //获得指向最后一个元素的引用
v=1024; //改变c中的元素
auto v2=c.back(); //v2不是一个引用,它是c.back()的一个拷贝
v2=0; //未改变c中的元素
}
与往常一样,如果我们使用auto变量来保存这些还是的返回值,并且希望使用此变量来改变元素的值,必须记得将变量定义为引用类型。
下标操作和安全的随机访问
提供快速随机访问的容器(string、vector、deque和array)也都提供下标运算符。就像我们已经看到的那样,下标运算符接受一个下标参数,返回容器中该位置的元素的引用。
我们希望确保下标是合法的,可以使用at成员函数。at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常:
vector
cout<
cout<
删除元素
与添加元素的多种方式类似,(非array)容器也有多种删除元素的方式。如下表所示:
顺序容器的删除操作 |
这些操作会改变容器的大小,所以不适用于array forward_list有特殊版本的erase forward_list不支持pop_back;vector和string不支持pop_front c.pop_back() 删除c中尾元素,若c为空,则函数行为未定义,函数返回void c.pop_front() 删除c中首元素,若c为空,则函数行为未定义,函数返回void c.erase(p) 删除迭代器p所指的元素,返回以指向被删除元素之后的迭代器, 若p指向尾元素,则返回尾后迭代器。若p是尾后迭代器,则函数的行为未定义 c.erase(b,e) 删除迭代器b和e所指定范围内的元素,返回一个指向最后一个被删除元素之后元素的迭代器, 若e本身就是尾后迭代器,则函数也返回尾后迭代器 c.clear() 删除c中的所以元素,返回void
删除deque中除首位元素之外的任何元素都会使所有迭代器、引用和指针失效。 指向vector或string中删除点之后位置的迭代器、引用和指针都会失效。 |
pop_front和pop_back成员函数
pop_front和pop_back成员函数分别删除首元素和尾元素。与vector和string不支持push_front一样,这些类型也不支持pop_front。类似的,forward_list不支持pop_back。与元素访问成员函数类似,不能对一个空容器执行弹出操作。
这些操作返回void,如果你需要弹出的元素的值,就必须在执行弹出操作之前保存它:
while(!ilist.empty()){
process(ilist.front()); //对ilist的首元素进行一些处理
ilist.pop_front(); //完成处理后删除首元素
}
从容器内部删除一个元素erase
成员函数erase从容器中指定位置删除元素,我们可以删除由一个迭代器指定的单个元素,也可以删除由一对迭代器指定的范围内的所有元素。两种形式的erase都返回指向删除的(最后一个)元素之后位置的迭代器。即,若j是i之后的元素,那么erase(i)将返回指向j的迭代器。
例如,下面的循环删除一个list中的所有奇数元素:
list
auto it=lst.begin();
while(it!=lst.end())
if(*it%2)
it=lst.erase(it); //删除此元素
else
++it;
每个循环步中,首先检查当前元素是否是奇数,如果是,就删除该元素,并将it设置为我们所删除的元素之后的元素。如果*it为偶数,我们将it递增,从而在下一步循环检查下一个元素。
删除多个元素
接受一对迭代器的erase版本允许我们删除一个范围内的元素:
//删除两个迭代器表示的范围内的元素
//返回指向最后一个被删除元素之后位置的迭代器
elem1=slist.erase(elem1,elem2); //调用后,elem1==elem2
迭代器elem1指向我们要删除的第一个元素,elem2指向我们要删除的最后一个元素之后的位置。
为了删除一个容器中的所有元素,我们既可以调用clear,也可以用begin和end获得的迭代器作为参数调用erase:
slist.clear() ;//删除容器中的所有元素
slist.erase(slist.begin(),slist.end()); //等价调用
为了理解forward_list为什么有特殊版本的添加和删除操作,考虑当我们从一个单向链表中删除一个元素时会发生什么。当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生变化。为了添加或删除一个元素,我们需要访问其前驱,以便改变前驱改变前驱的链接。但是,forward_list是单向链表。在一个单向链表中,没有简单的方法来获取一个元素的前驱,出于这个原因,在一个forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。这样,我们总是可以访问到被添加或删除元素所影响的元素。
由于这些操作与其他容器上的操作有实现方式不同,forward_list并未定义insert、emplace和erase,而是定义了名为insert_after、emplace_after和erase_after的操作。为了支持这些操作,forward_list也定义了before_begin,它返回一个首前迭代器。这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素(亦即在链表首元素之前添加删除元素)。
在forward_list中插入或删除元素的操作 |
lst.before_begin() 返回指向链表首元素之前并不存在的元素的迭代器,此迭代器不能解引用。 lst.cbefore_begin() cbefore_begin()返回一个const_iterator lst.insert_after(p,t) 在迭代器p之后的位置插入元素。t是一个对象,n是数量,b和e是 表示范围的一对迭代器(b和e不能指向lst内), il是一个花括号列表。返回一个指向最后一个插入lst.insert_after(p,n,t)元素的 迭代器。如果范围为空,则返回p。若p为尾后迭代器,则函数行为未定义。 lst.insert_after(p,b,e) lst.insert_after(p,il)
emplace_after(p,args) 使用args在p指定的位置之后创建一个元素,返回一个指向这个新元素的迭代器。 若p为尾后迭代器,则函数的行为未定义 lst.erase_after(p) 删除p指向的位置之后的元素,或删除从b之后直到(但不包含)e之间的元素。 返回一个指向被删除元素之后元素的迭代器, 若不存在这样的元素,则返回尾后迭代 lst.erase_after(b,e) 器,如果p指向lst的尾元素或者是一个尾后迭代器,则函数的行为未定义 |
当在forward_list中添加或删除元素时,我们必须关注两个迭代器——一个指向我们要处理的元素,另一个指向其前驱。例如,我们从list中删除奇数元素的循环程序,将其改为从forward_list中删除元素:
forward_list<int> flst={0,1,2,3,4,5,6,7,8,9};
auto prev=flst.before_begin(); //表示flst的“首前元素”
auto curr=flst.begin(); //表示flst中的第一个元素
while(curr!=flst.end())
{
if(*curr%2)
curr=flst.erase_after(prev);// 删除它并移动curr
else
{
prev=curr; //否则移动迭代器curr,指向下一个元素,prev指向curr之前的元素
++curr;
}
}
6、管理容器容量的成员函数
vector和string类型提供了一些成员函数,允许我们与它的实现中内存分配部分互动。
容器大小管理操作 |
shrink_to_fit只适用于vector、string、deque capacity和reserve只适用于vector、string c.shrink_to_fit() 将capacity()减少为size()相同大小,但是这是一个请求,并不保证一定退回内存空间 c.capacity() 不重新分配内存空间的话,c可以保存多少元素。size()是指已经保存的元素的数量 c.reserve(n) 分配至少能容纳n个元素的内存空间,它并不改变容器中元素的数量 |
7、额外的string操作
除了顺序容器共有的操作之外,string类型还提供了一些额外的操作。这些操作中的大部分要么是提供string类和C风格字符数组之间的相互转换,要么是增加了允许我们用下标代替迭代器的版本。
首先记录常用的string库文件中的方法:
cctype头文件中的函数 |
isalnum(c) 当c是字母或数字时为真 isalpha(c) 当c是字母时为真 iscntrl(c) 当c是控制字符时为真 isdigit(c) 当c是数字时为真 isgraph(c) 当c不是空格但可打印时为真 islower(c) 当c是小写字母时为真 isprint(c) 当c是可打印字符时为真(即c是空格或c具有可视形式) ispunch(c) 当c是标点符号时为真(即c不是控制字符、数字、字符,可打印空白中的一种) isspace(c) 当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) isupper(c) 当c是大写字母时为真 isxdigit(c) 当c是十六进制数字时为真 tolower(c) 如果c是大写字母,输出对应的小写字母:否则原样输出c toupper(c) 如果c是小写字母,输出对应的大写字母:否则原样输出c |
构造string的其他方法
构造string的其他方法 |
n、len2和pos2都是无符号值 string s(cp,n) s是cp指向的数组中的第n个字符的拷贝,此数组至少应该包含n个字符 string s(s2,pos2) s是string s2从下标pos2开始的字符的拷贝。若pos2>s2.size(),构造函数的行为未定义 string s(s2,pos2,len2) s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。 不管len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符 |
这些构造函数接受一个string或一个const char*参数,还接受指定拷贝多少个字符的参数。当我们传递给它们的是一个string时,还可以给定一个下标来指出从哪里开始拷贝:
const char *cp="Hello World!"; //以空字符结束的数组
char noNull[]={'H','i'}; //不是以空字符结束
string s1(cp); //拷贝cp中的字符直到遇到空字符
string s2(noNull,2); //从noNull拷贝两个字符
string s3(noNull); //未定义:noNull不是以空字符结束
string s4(cp+6,5); //从cp[6]开始拷贝5个字符
string s5(s1,6,5); //从s1[6]开始拷贝5个字符
string s6(s1,6); //从s1[6]开始直到s1的末尾
string s7(s1,6,20); //正确,只拷贝到s1的末尾
string s8(s1,16); //抛出一个out_of_range异常
通常当我们从一个const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到字符时停止。如果我们还传递给构造函数一个计数值,数组就不必以空字符结尾。如果我们未传递计数值且数组也未以空字符结尾,或者给定计数值大于数组大小,则构造函数的行为是未定义的。
当从一个string拷贝字符时,我们可以提供一个可选的开始位置和一个计数值。开始位置必须小于或等于给定的string的大小。如果位置大于size,则构造函数抛出一个out_of_range异常。如果我们传递了一个计数值,则从给定位置开始拷贝这么多个字符。不管我们要求拷贝多少个字符,标准库最多拷贝到string结尾,不会更多。
substr操作
substr操作返回一个string,它是原始string的一部分或全部的拷贝。可以传递给substr一个可选的开始位置和计数值:
string s("hello world");
string s2=s.substr(0,5); //s2=hello
string s3=s.substr(6); //s3=world
string s4=s.substr(6,11); //s4=world
string s5=s.substr(12); //抛出一个out_of_range异常
如果开始位置超过了string的大小,则substr函数抛出一个out_of_range异常。如果开始位置加上计数值大于string的大小,则substr会调整计数值,只拷贝到string的末尾。
子字符串的操作 |
s.substr(pos,n) 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。 n的默认值的s.size()-pos,即拷贝从pos开始的所以字符 |
改变string的其他方法
string类型支持顺序容器的赋值运算符以及assign、insert和erase操作。除此之外,它还定义了额外的insert和erase版本。
除了接受迭代器的insert和erase版本外,string还提供了接受下标的版本。下标指出了开始删除的位置,或是insert到给定值之前的位置:
s.insert(s.size(),5,'!'); //在s末尾插入5个感叹号
s.erase(s.size()-5,5); //从s删除最后5个字符
标准库string类型还提供了接受C风格字符数组的insert和assign版本。例如,我们可以将以空字符结尾的字符数组insert到或assign给一个string:
const char *cp="Stately,plump Buck";
s.assign(cp,7); //s="Stately"
s.insert(s.size(),cp+7); //s=="Stately,plump Buck"
此处我们首先通过调用assign替换s的内容。我们赋予s的是从cp指向的地址开始的7个字符。要求赋值的字符数必须小于或等于cp指向的数组中的字符数(不包括结尾的空字符)。
接下来在s上调用insert,我们的意图是将字符插入到s[size()]处(不存在的)元素之前的位置。此例中,我们将cp开始的7个字符(至多到结尾空字符之前)拷贝到s中。
我们也可以指定将来自其它string或字符串的字符插入到当前string中或赋予当前string:
string s="some string",s2="some other string";
s.insert(0,s2); //在s中位置0之前插入s2的拷贝
//在s[0]之前插入s2中开始的s2.size()个字符
s.insert(0,s2,0,s2.size());
append和replace函数
string类定义了两个额外的成员函数:append和replace,这两个函数可以改变string的内容。下表描述了这两个函数的功能。append操作是在string末尾进行插入操作的一种简写放形式:
string s("C++ primer"),s2=s;
s.insert(s.size()," 4th Ed.");
s2.append(" 4th Ed."); //s==s2
replace操作是调用erase和insert的一种简写形式:
//将“4th”替换成“5th”的等价方法
s.erase(11,3);
s.insert(11,"5th");
等价于:
s2.replace(11,3,"5th"); //等价方法:s==s2
此例中调用replace时,插入的文本恰好与删除的文本一样长。这不是必须的,可以插入一个更长或更短的string:
s.replace(11,3,"Fifth");
在此调用中,删除了3个字符,但在其位置插入了5个新字符。
修改string的操作 |
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可以是下列形式之一:append和assign可以使用所有形式: str不能与s相同,迭代器b和e不能指向s
str 字符串str str,pos,len str中从pos开始最多len个字符 cp,len 从cp指向的字符数组的前(最多)len个字符 cp cp指向的以空字符串结尾的字符数组 n,c n个字符c b,e 迭代器b和e指定的范围内的字符 初始化列表 花括号包围,以逗号分隔的字符列表
replace和insert所允许的args形式依赖于range和pos是如何指定的 |
改变string的多种重载函数
上表列出了append、assign、insert和replace函数有多个重载版本。根据我们如何指定要添加的字符和string中被替换的部分,这些函数的参数有不同的版本。幸运的是,这些函数有共同的接口。
assign和append函数无须指定要替换string中哪个部分:assign总是替换string中的所有内容,append总是将新字符追加到string末尾。
replace函数提供了两种指定删除元素范围的方式。可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定。insert函数允许我们用两种方式指定插入点:用一个下标或一个迭代器。在两种情况下,新元素都会插入到给定下标(或迭代器)之前的位置。
可以用好几种方式指定要添加到string中的字符。新字符可以来自于另一个string,来自于一个字符指针(指向的字符数组),来自于一个花括号包围的字符列表,或者一个字符和一个计数值。当字符来自于一个string或一个字符指针时,我们可以传递一个额外的参数来控制是拷贝部分还是全部字符。
并不是每个函数都支持所有形式的参数。例如,insert就不支持下标和初始化列表的参数。类似的,如果我们希望用迭代器指定插入点,就不能用字符指针指定新字符的来源。
string搜索操作
string类提供了6个不同的搜索函数,每个函数都有4个重载版本。下表描述了这些搜索成员函数及其参数。每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败,返回一个名为string::npos的static成员。标准库将npos定义为一个const string::size_type类型,并初始化为值-1。由于npos是一个unsigned类型,此初始化值意味着npos等于任何string最大的可能大小。
find函数完成最简单的搜索。它查找参数指定的字符串,若找到,则返回第一个匹配位置的下标,否则返回npos:
string name("AnnaBelle");
auto pos1=name.find("Anna"); //pos1==0
这段程序返回0,即子字符串"Anna"在"AnnaBelle"中第一次出现的下标
搜索(以及其他string操作)是大小写敏感的。当在string中查找子字符串时,要注意大小写。
一个更复杂的问题是查找与给定字符串中任何一个字符匹配的位置。例如,下面代码定位name中的第一个数字:
string numbers("0123456789"),name("r2d2");
//返回1,即,name中第一个数字的下标
auto pos=name.find_first_of(numbers);
如果是要搜索第一个不在参数中的字符,我们应该调用find_first_not_of,例如,为了搜索一个string中第一个非数字字符,可以这样做:
string dept("03714p3");
//返回5,字符'p'的下标
auto pos=dept.find_first_not_of(numbers);
string 搜索操作 |
搜索操作返回指定字符出现的下标,如果未找到则返回npos 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 从s中位置pos开始查找字符c,pos默认为0 s2,pos 从s中位置pos开始查找字符串s2,pos默认为0 cp,pos 从s中位置pos开始查找指针cp指向的空字符结尾的C风格字符串,pos默认为0 cp,pos,n 从s中位置pos开始查找指针cp指向的数组的前n个字符。pos和n无默认值 |
指定从哪里开始搜索
我们可以传递给find操作一个可选的开始位置。这个可选的参数指出从哪个位置开始搜索。默认情况下,此位置被置为0。一种常见的程序设计模式是用这个可选参数在字符串中循环地搜索子字符串出现的所有位置:
string::size_type pos=0; //每步循环查找name中下一个数 while((pos=name.find_first_of(numbers,pos))!=string::npos) { cout<<"found number at index:"<" element is "<endl; ++pos; //移动到下一个字符 }
逆向搜索
到目前为止,我们已经用过的find操作都是从左至右搜索。标准库还提供了类似的,但由右至左搜索的操作。rfind成员函数搜索最后一个匹配,即子字符串最靠右的出现位置:
string river("Mississippi");
auto first_pos=river.find("is"); //返回1
auto last_pos=river.rfind("is"); //返回4
find返回下标1,表示第一个"is"的位置,而rfind返回下标4.表示最后一个"is"的位置。
类似的,find_last函数的功能与find_first函数相似,只是它们返回最后一个而不是第一个匹配:
每个操作都接受一个可选的第二参数,可用来指出从什么位置开始搜索。
compare函数
除了关系运算符外,标准库string类型还提供了一组compare函数,这些函数与C标准库的strcmp函数很相似。类似strcmp,根据s是等于、大于还是小于参数指定的字符串,s.compare返回0,正数和负数。
如表所示,compare有6个版本,根据我们是要比较两个string还是一个string与一个字符数组,参数各不相同。在这两种情况下,都可以比较整个或一部分字符串。
s.compare的几种参数形式 |
s2 比较s和s2 pos1,n1,s2 将s中从pos1开始的n1个字符与s2进行比较 pos1,n1,s2,pos2,n2 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较 cp 比较s与cp指向的以空字符结尾的字符数组 pos1,n1,cp 将s中从pos1开始的n1个字符和cp指向的以空字符结尾的字符数组进行比较 pos1,n1,cp,n2 将s中从pos开始的n1个字符与指针cp指向的地址开始的n2个字符进行比较 |
数值转换
字符串中常常包含表示数值的字符。例如,我们用两个字符的string表示数值15——字符'1'后跟字符'5'。一般情况,一个数的字符表示不同于其数值。
新标准引入了多个函数,可以实现数值数据与标准库string之间的转换。
int i=42;
string s=to_string(i); //将整数i转换为字符表示形式
double d=stod(d); //将字符串s转换为浮点数
此例中我们调用to_string将42转换为对应的string形式,然后调用stod将此string转换为浮点值。
要转换为数值的string中第一非空白符必须是数值中可能出现的字符:
string s2="pi=3.14";
//转换s中以数字开始的第一个子串,结果d=3.14
d=stod(s2.substr(s2.find_first_of("+-.0123456789)));
在这个stod调用中,我们调用了find_first_of来获得s中第一个可能是数值的一部分的字符的位置。我们将s中从此位置开始的子串传递给stod。stod函数读取此参数,处理其中的字符,直至遇到不可能是数值的一部分的字符。然后它就将找到这个数值的字符串表示形式转换为对应的双精度浮点值。
string参数中第一个非空白字符必须是符号(+或-)或数字。
string和数值之间的转换 |
to_string(val) 一组重载函数,返回数值val的string表示。val可以是任何算术类型。 对每个浮点类型和int或更大的整型,都有相应版本的to_string。 stoi(s,p,b) 返回s的起始子串(表示整数内容)的数值。返回值类型是int、long、 unsigned long、long long、unsigned long long。b表示转换 所用的基数,默认值是10.p是size_t指针,用来stol(s,p,b)保存s中 第一个非数值字符的下标,p默认是0,即,函数不保存下标 stoul(s,p,b) stoll(s,p,b) stoull(s,p,b)
stof(s,p) 返回s的起始子串(表示浮点数内容)的数值,返回值类型分别是 float、double和long double,参数p的作用与整数转换中相同 stod(s,p) stold(s,p) |
除了顺序容器外,标准库还定义了三个顺序容器适配器:stack、queue和priority_queue。适配器是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制。能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如,stack适配器接受一个顺序容器(除array或forward_list外),并使其操作起来像一个stack一样。下表列出了所有适配器都支持的操作和类型:
所以容器适配器都支持的操作和类型 |
size_type 一种类型,足以保存当前类型的最大对象的大小 value_type 元素类型 container_type 实现适配器的底层容器类型 A a; 创建一个名为a的空适配器 A a(c); 创建一个名为a的适配器,带有容器c的一个拷贝 关系运算符 每个适配器都支持所有关系运算符:==、!=、<、<=、>和>=, 这些运算符返回底层容器的比较结果 a.empty() 若a包含任何元素,返回false,否则返回true a.size() 返回a中的元素数目 swap(a,b) 交换a和b的内容,a和b必须有相同的类型,包括底层容器类型也必须相同 a.swap(b) |
定义一个适配器
每个适配器都定义了两个构造函数:默认构造函数创建以空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。例如,假定deq是一个deque
stack
默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型:
//vector上实现的空栈
stack
//str_stk2在vector上实现,初始化时保存svec的拷贝
stack
对于一个给定的适配器,可以使用哪些容器是有限制的。所有适配器都要求容器具有添加和删除元素的能力。因此,适配器不能构造在array之上。类似的,我们也不能用forward_list来构造适配器,因为所有适配器要求容器具有添加、删除以及访问尾元素的能力。stack只要求push_back、pop_back和back操作,因此可以使用除array和forward_list之外的任何容器类型来构造stack。queue适配器要求back、push_back、front和push_front,因此它可以构造与list和deque之上,但不能基于vector构造。priority_queue除了front、push_back和pop_back操作之外还要求随机访问能力,因此它可以构造于vector和deque之上,但不能基于list构造。
栈适配器
stack类型定义在stack头文件中。下表列出了stack支持的操作。下面的程序展示了如何使用stack:
stack<int> intStack; //空栈
//填满栈
for(size_t ix=0;ix!=10;++ix)
intStack.push(ix);
while(!intStack.empty())
{
//使用栈顶的值
int value=intStack.top();
//弹出栈顶的元素,继续循环
intStack.pop();
}
栈操作 |
栈默认是基于deque实现,也可以在list或vector上实现 s.pop() 删除栈顶元素,但不返回该元素的值 s.push(item) 创建一个新元素压人栈顶,该元素通过拷贝或移动item而来,或者由args构造 s.emplace(args) s.top() 返回栈顶元素,但不将元素弹出栈 |
每个容器适配器都基于底层类型的操作定义了自己的特殊操作。我们只能使用适配器操作,而不能使用底层容器类型的操作。例如:
intStack.push(ix);
此语句试图在intStack的底层deque对象上调用push_back。虽然stack是基于deque实现的,但我们不能直接使用deque操作。不能在一个stack上调用push_back,而必须使用stack自己的操作——push。
队列适配器
queue和priority_queue适配器定义在queue头文件中。下表列出了它所支持的操作:
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) 在queue末尾或priority_queue中恰当的位置创建一个元素。其值为item,或者由args构造 q.emplace(args) |
标准库queue使用一种先进先出的存储和访问策略。进入队列的对象被放置在队尾,而离开队列的对象则从队首删除。
priority_queue允许我们为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。