后台开发工程师技术能力体系之编程语言4——顺序容器

顺序容器

1.标准库类型string

  标准库类型string表示可变长的字符序列,使用string类型必须首先包括string头文件,作为标准库的一部分,string定义在命名空间std中。
  初始化string对象有多种方式,如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去;如果不使用等号,则执行的是直接初始化

string s1; //默认初始化,s1是一个空串
string s2(s1); //s2是s1的副本
string s2 = s1; //等价于s2(s1),s2是s1的副本
string s3("value"); //s3是字面值“value”的副本,除了字面值最后的那个空字符外
string s3 = "value"; //等价于s3("value"),s3是字面值"value"的副本
string s4(n,'c'); //把s4初始化为由连续n个字符c组成的串

  string类及其他大多数标准库类型都定义了几种配套的类型,这些配套类型体现了标准库类型与机器无关的特性,类型 size_type即是其中的一种。尽管我们不清楚string::size_type类型的细节,但是可以肯定的是:size_type是一个无符号类型的值而且能足够存放下任何string对象的大小。size函数返回的是string::size_type类型也就是一个无符号类型,因此切记,如果在表达式中混用了带符号和无符号数将可能产生意想不到的结果。例如,假设n是一个具有负值的int,则表达式s.size() < n的判断结果几乎肯定是true,这是因为负数n会自动转换成一个比较大的无符号值。因此如果一条表达式中已经有了size()函数就不要再使用int了,这样可以避免混用int和unsigned可能带来的问题。

2.标准库类型vector

  标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。vector是一个类模板。
  定义和初始化vector对象有多种方法,包括默认初始化列表初始化以及值初始化等:

vector<T> v1; //v1是一个空vector,默认初始化
vector<T> v2(v1); //v2中包含v1所有元素的副本,直接初始化
vector<T> v2 = v1; //等价于v2(v1),拷贝初始化
vector<T> v3(n,val); //v3包含n个重复的元素,每个元素的值都是val,值初始化
vector<T> v4(n); //v4包含了n个重复执行值初始化的对象,值初始化 
vector<T> v5{a,b,c,......}; //v5包含了初始值个数的元素,每个元素被赋予相应的初始值,列表初始化
vector<T> v5 = {a,b,c,......}; //等价于v5{a,b,c,......},列表初始化

  默认初始化vector对象创建的是一个空的vector,看起来空vector好像没什么用,但实际上程序在运行时可以很高效地往vector对象中添加元素。事实上,最常见的方式就是先定义一个空vector,然后当运行时获取到元素的值后再逐一添加。
  向vector对象添加元素蕴含的编程假定:范围for语句体内不应该改变其所遍历序列的大小
  vector对象(以及string对象)的下标运算符可用于访问已存在的元素,但是不能用于添加元素

3.迭代器

  除了可以使用下标运算符来访问string对象的字符或vector对象的元素,还有一种更加通用的机制来实现同样的目的,这就是迭代器。所有标准库容器都可以使用迭代器,但是其中只有少数几种才同时支持下标运算符
  类似指针类型,迭代器也提供了对对象的间接访问。迭代器的对象是容器中的元素或string对象中的字符。使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另一个元素。迭代器有有效和无效之分,有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其他所有情况都属于无效
  和指针不一样,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员:begin成员负责返回指向第一个元素的的迭代器,end成员则负责返回指向容器尾后元素的迭代器,如果容器为空,则begin和end返回的都是同一个迭代器,都是尾后迭代器。
  在C++中,所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符。与此类似,所有的容器都定义了迭代器,但是它们大多数都没有定义下标运算符。因此,基于泛型编程的思想,只要我们养成使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型
  就像不知道size_type到底是什么类型一样,我们也不知道迭代器的准确类型。而实际上,那些拥有迭代器的标准库类型使用iteratorconst_iterator来表示迭代器的类型:iterator类似于普通指针,其对象可读可写;const_iterator类似于常量指针,其对象只能读取不能修改;如果容器是一个常量,则只能使用const_iterator,如果容器不是常量,则既能使用iteraotr又能使用const_iterator。begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,则返回const_iterator,如果对象不是常量,则返回iterator。为了便于专门得到const_iterator类型的返回值,C++11新标准引入两个新函数,分别是cbegincend,不论容器对象是否为常量,返回值都是const_iterator。
  指向数组元素的指针也拥有类似迭代器的功能,为了得到数组的头指针和尾后指针,C++11引入了两个名为begin和end的函数,这两个函数与容器中的两个同名成员函数功能类似,分别返回指向数组的首元素和尾后元素的指针

4.顺序容器

  顺序容器提供了快速顺序访问元素的能力,但不同容器各有侧重点:向容器添加或删除元素的代价;非顺序访问容器中元素的代价

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

  除了固定大小的array之外,其他容器都提供高效灵活的内存管理,我们可以添加和删除元素,扩张和收缩容器的大小。例如string和vector将元素保存在连续的内存空间中,因此由元素的下标来计算其地址是非常快速的,但是在非尾部位置添加或删除元素就会非常耗时,此外,添加一个元素还有可能需要分配额外的存储空间,在这种情况下,每个元素都必须移动到新的存储空间中;list和forward_Listl两个容器设计的目的是令容器任何位置的添加和删除操作都很快速,但这两个容器不支持元素的随机访问:为了访问一个元素,我们只能遍历整个容器,此外与vector、deque和array相比,这两个容器的额外内存开销也很大;deque是一个更为复杂的数据结构,与string和vector类似,deque支持快速的随机访问,在deque的中间位置添加或删除元素的代价很高,但在deque的两端添加或删除都是很快的,与list或forward_list添加或删除元素的速度相当;forward_list和array是新增加的类型,与内置数组类似,array对象的大小是固定的,不支持添加或删除元素以及改变容器大小的操作,但array是一种更安全、更容易使用的数组类型;forward_list的设计目标是达到性能最好的单向链表数据结构,因此forward_list没有size操作,因为保存或计算其大小会多出额外的开销
  选择容器的基本原则:1、除非有很好的理由使用其他容器,否则应使用vector;2、如果有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list;3、如果要求随机访问元素,应使用vector或deque;4、如果要求在容器的中间插入或删除元素,应使用list或forward_list;5、如果要求在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque;6、如果只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则考虑在输入阶段使用list,一输入完成,将list中的内容拷贝到一个vector中。

5.顺序容器和关联容器都支持的操作

  每个容器都定义了多个类型,前面已经介绍过size_typeiteratorconst_iterator,除了已经使用过的正向迭代器,大多数容器(除了forward_list)还提供了 反向迭代器(reverse_iterator),简单地说,反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义也都发生了颠倒。此外还有类型别名(value_type、reference和const_reference),通过类型别名,我们可以在不了解容器中元素类型的情况下使用它们,如果需要元素类型,可以使用容器的value_type如果需要元素类型的一个引用,可以使用reference或const_reference
  begin和end函数有多个版本:带r的版本返回反向迭代器以c开头的版本则返回const迭代器当auto与begin或end(rbegin或rend)结合使用时,获得的迭代器类型依赖于容器是否为常量,但以c开头的版本不论容器是否为常量都可以获得const_iterator(const_reverse_iterator)
  标准库容器有多种初始化的方式:

初始化方式 作用
C c; 默认构造函数,如果C是一个array,则c中元素按默认方式初始化,否则c为空
C c1(c2); c1初始化为c2的拷贝,c1和c2必须严格是相同类型,即它们必须是相同的容器类型,且保存的是相同的元素类型(元素类型相容也不行);对于array类型,两者还必须具有相同大小
C c{a,b,c,…}或C c={a,b,c,…} c初始化为初始化列表中元素的拷贝,列表中元素的类型必须与c的元素类型相容,对于array类型来说,列表中元素数目必须小于等于array的大小,任何遗漏的元素都进行值初始化
C c(b,e) c初始化为迭代器b和e指定范围中的元素的拷贝,范围中元素的类型必须与c的元素类型相容(array不适用) |
只有顺序容器(不包括array)的构造函数才能接受大小参数 ,关联容器并不支持|
C seq(n) seq包含n个元素,这些元素进行值初始化(string不适用)
C seq(n,t) seq包含n个初始化为值t的元素

  将一个新容器创建为另一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者(array除外)拷贝由一个迭代器对指定的元素范围,前者要求容器类型和元素类型必须严格匹配,后者不要求容器类型相同且元素类型相容即可。对于除array之外的容器类型,列表初始化还隐含地指定了容器的大小:容器将包含与初始值一样多的元素。与内置数组一样,标准库array的大小也是类型的一部分,因此当定义一个array时,除了指定元素类型,还要指定容器大小。值得注意的是,虽然我们不能对内置数组进行拷贝或对象赋值操作,但是array并无此限制。

int digs[5] = {1,2,3,4,5};
int cpy[5] = digs; //错误,内置数组不支持拷贝或赋值
array<int,10> digits = {1,2,3,4,5};
array<int,5> copy = digits; //正确,只要数组类型匹配即合法

  标准库容器有多种赋值的方式:

赋值方式 作用
c1 = c2 将c1中的元素替换为c2中的元素的拷贝,c1和c2必须具有相同的类型(array还必须具有相同的大小)
c = {a,b,c,…} 将c1中元素替换为初始化列表中元素的拷贝(array不适用)
swap(c1,c2) 交换c1和c2中的元素,c1和c2必须具有相同的类型,swap通常比从c2向c1拷贝元素快得多|
assign操作不适用于关联容器和array |
seq.assign(b,e) 将seq中的元素替换为迭代器b和e所表示的范围中的元素,迭代器b和e不能指向seq中的元素
seq.assign(il) 将seq中的元素替换为初始化列表il中的元素
seq.assign(n,t) 将seq中的元素替换为n个值为t的元素

  赋值运算符会导致指向左边容器内部的迭代器、引用和指针失效,而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(容器类型为array和string的情况除外)
  array类型允许赋值,但是赋值号左右两边的运算对象必须具有相同的类型,由于右边运算对象的大小可能与左边运算对象的大小不同,因此array类型不支持assign,也不允许用花括号包围的值列表进行赋值

array<int,10> a1 = {0,1,2,3,4,5,6,7,8,9};
array<int,10> a2 = {0}; //所有元素值均为0
a1 = a2; //将a1中的元素全部替换成a2中的元素
a2 = {0}; //错误,不能将一个花括号列表赋值给array

  赋值运算符要求左边和右边的运算对象具有相同的类型,它将右边运算对象中所有元素拷贝到左边运算对象中。顺序容器(array除外)还定义了一个名为assign的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个迭代器子序列赋值,assign操作用参数所指定的元素替换左边容器中的所有元素。由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。例如,我们可以用assgin实现将一个vector中的一端char*值赋予一个list中的string。

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

  swap操作交换两个相同类型容器的内容,调用swap之后,两个容器中的元素将会交换:

vector<string> sevc1(10); //10个元素的vector
vector<string> sev2(24); //24个元素的vector
swap(sevc1,sevc2);

  调用swap后,sevc1将包含24个string元素,sevc2将包含10个string元素,除array外,交换两个容器内容的操作保证会很快,因为元素本身并未交换,swap只是交换了两个容器的内部数据结构。元素不会被交换的事实意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效,它们仍指向swap操作之前所指向的那些元素,只是在swap操作之后,这些元素已经属于不同的容器了。与其他容器不同,对一个string调用swap会导致迭代器、引用和指针失效
  与其他容器不同,swap两个srray会真正交换它们的元素,因此,交换两个array所需的时间与array中元素的数目成正比。此外,array在swap操作后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个array中对应元素的值进行了交换
  在新标准库中,容器既提供成员函数版本的swap,也提供非成员版本的swap。早期标准库版本只提供成员函数版本的swap,非成员函数版本的swap在泛型编程中是非常重要的,统一使用非成员版本的swap是一个好习惯
  除了forward_list,其他每个容器类型都有三个与大小相关的操作:成员函数size返回容器中元素的数目;empty当size为0时返回true,否则返回false;max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值。forward_list只支持max_size和empty,但不支持size。
  每个容器类型都支持相等运算符(==和!=);除了无序关联容器外的所有容器都支持关系运算符(>,<,>=,<=)。关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素

6.顺序容器特有的操作

  向顺序容器(非array)添加元素有多种方式:

添加方式 作用
添加元素的操作会改变容器的大小,array不支持这些操作
forward_list 有自己专用版本的insert和emplace,此外不支持push_back和emplace_back
vector和string 不支持push_front和emplace_front
c.push_back(t) c.emplace_back(args) 在c的尾部创建一个值为t或由args创建的元素
c.push_front(t) c.emplace_front(args) 在c的头部创建一个值为t或由args创建的元素
c.insert(p,t) c.emplace(p,args) 在迭代器P指向的元素之前插入/创建一个值为t的元素,返回指向新添加的元素的迭代器
c.insert(p,n,t) c.insert(p.b,e) c.insert(p,il) 分别在迭代器P指向的元素之前插入 n个值为t的元素/迭代器b和e范围内的元素/花括号包围的元素,返回指向新添加的第一个元素的迭代器 |

  在一个vector或string的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。而且向一个vector或string添加元素可能引起整个对象存储空间的重新分配,重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用或指针失效
  每个顺序容器都有一个front成员,除forward_list之外的所有顺序容器都有一个back成员,分别用于返回首元素和尾元素的引用在调用front或back之前需要检查容器是否有元素,否则操作的结果是未定义的。在顺序容器中访问元素更加普遍的操作:at下标操作(只适用于string、vector和array)

c[n] //返回c中下标为n的元素的引用,n是一个无符号整数,若n>=c.size(),则函数行为未定义
c.at(n) //返回下标为n的元素的引用,如果下标越界,则抛出一个out_of_range异常

  在容器中访问元素的成员函数:front、back、下标和at,返回的都是引用如果容器是一个const对象,则返回值是const的引用,如果容器不是const,则返回值是一个普通的引用
  从顺序容器中删除元素有多种方式:

删除方式 作用
forward_list 有自己特殊版本的erase
c.pop_back() 删除c中的尾元素(forward_list不适用)
c.pop_front() 删除c中的首元素(vector和string不适用)
c.erase( p ) 删除迭代器p指向的元素,返回一个指向被删元素之后元素的迭代器
c.rease(b,e) 删除迭代器b和e范围内的元素,返回一个指向最后一个被删除元素之后元素的迭代器
c.clear() 删除c中所有的元素

  删除deque除首尾元素之外的任何元素都会使所有迭代器、引用和指针失效。指向vector或string删除点之后位置的迭代器、引用和指针都会失效。删除元素的成员函数并不检查其参数,在删除元素之前,程序员必须确保它(们)是存在的,否则会产生未定义的行为
  forward_list是单向链表,不能直接获取一个元素的前驱,因此添加或删除元素的操作都是通过改变给定元素之后的元素来完成的。forward_list并未定义insert、emplace和erase,而是定义了名为insert_afteremplace_aftererase_after的操作。为了支持这些操作,forward_list定义了一个before_begin,它返回一个首前迭代器,便于对首元素进行操作。
  我们可以通过resize成员函数来增大或缩小容器,同样,array不支持resize。如果当前容器大小大于所要求的大小,则容器后部的元素会被删除;如果当前容器大小小于新大小,会将新元素添加到容器后部。如果resize缩小了容器,则指向被删除元素的迭代器、引用和指针都会失效;对vector、string或deque进行resize可能导致迭代器、引用和指针失效。

c.reaise(n); //若n<c.size(),则多出的元素被丢弃,若添加新元素,对新元素进行值初始化
c.resize(n,t); //任何新添加的元素都初始化为值t

   向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效。
  在向容器添加元素后:1、如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。2、对于deque插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效,如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。3、对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
  从容器中删除一个元素后:1、指向被删除元素的迭代器、指针和引用会失效。2、对于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。3、对于deque,如果在首尾之外的任何位置删除元素,则所有元素的迭代器、引用或指针都会失效;如果删除的是首/尾元素,则首、尾后迭代器会失效,但其他迭代器、引用和指针不受影响。4、对于vector和string,指向被删除之前的元素的迭代器、引用和指针仍有效。5、当我们删除元素时,尾后迭代器总是会失效
  由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器,这个建议对vector、string和deque尤为重要。程序必须保证每个循环部中都要更新迭代器、引用或指针,如果循环中调用的是insert或erase,那么更新迭代器很容易,因为这些操作都返回迭代器,我们可以用来更新。

// 删除偶数元素,复制每个奇数元素
vector<int> vi = {0,1,2,3,4,5,6,7,8,9};
auto iter = vi.begin(); //调用begin而不是cbegin,因为我们要改变vi
while(iter != vi.end()) {
	if (*iter % 2){
		iter = vi.insert(iter,*iter); //复制当前元素,返回的迭代器指向新添加的元素
		iter += 2; //向前移动迭代器,跳过当前元素以及插入到它之前的元素
	}else
		iter = vi.erase(iter); //删除偶数元素,返回的迭代器指向删除的元素之后的元素
}

  在调用erase后,不必递增迭代器,因为erase返回的迭代器已经指向序列中下一个元素调用insert后,需要递增迭代器两次。insert是在给定的位置之前插入新元素,然后返回指向新插入元素的迭代器,因此我们将迭代器递增两次,恰好越过了新添加的元素和正在处理的元素,指向下一个未处理的元素。
  当我们添加/删除vector或string的元素后,或在deque中首元素之外任何位置添加/删除元素后,原来end返回的迭代器总是会失效。因此,添加/删除元素的循环程序必须反复调用end,而不能在循环之前保存end返回的迭代器,一直当做容器末尾使用。通常C++标准库的实现中end()操作都很快,部分就是因为这个原因。

7.vector对象的增长方式

  vector和string类型提供了一些成员函数,允许我们与它的实现中内存分配部分互动。capacity操作告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素。reserve操作允许我们通知容器它应该准备保存多少个元素,它并不改变容器中元素的数量,仅仅影响vector预先分配多大的内存空间,如果需求大小小于或等于当前容量,reserve什么也不做;如果需求大小大于当前容量,reserve至少分配与需求一样大的内存空间(可能更大)。reserve永远不会减少占用的内存空间,类似的,resize成员函数只会改变容器中元素的数目,并不会改变容器的容量。在新标准库中,我们可以调用shrink_to_fit来要求deque、vector或string退回不需要的内存空间,但具体的实现可以选择忽略此请求,也就是说,调用shrink_to_fit也并不保证一定退回内存空间
  容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。

8.额外的string操作

  substr操作返回一个string,它是原始string的一部分或全部的拷贝。可以传递给substr一个开始位置和计数值。如果开始位置超过了string的大小,则substr抛出一个out_of_range异常。如果开始位置加上计数值大于string的大小,则substr会调整计数值,只拷贝到string的末尾。

s.substr(pos,n) //从s的pos开始拷贝n个字符,pos的默认值为0,n的默认值为s.size()-pos,即拷贝从pos开始的所有字符

  string除了定义迭代器版本的insert和erase版本外,还提供了接收下标的版本的insert和erase。下标指出了开始删除的位置,或是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 = "hello world";
s.assign(cp,5); // s="hello"
s.insert(s.size(),cp+5); //s="hello world"

  我们也可以将来自其他string或子字符串的字符插入到当前string中:

 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中s2[0]开始的s2.size()个字符

  string类定义了两个额外的成员函数:appendreplace,这两个函数可以改变string的内容。append在string末尾进行插入操作的一种简写形式,replace操作是调用erase和insert的一种简写形式。

string s("C++ Primer"),s2 = s;
s.insert(s.size()," 4th Ed."); //s=="C++ Primer 4th Ed."
s2.append(" 4th Ed."); //等价方法,将" 4th Ed."追加到s2; s == s2

// 将“4th”替换为“5th”
s.erase(11,3); //s == "C++ primer Ed."
s.insert(11,"5th"); //s == "C++ Primer 5th Ed."
//等价方法
s2.replace(11,3,"5th"); //从位置11开始,删除3个字符并插入"5th",插入的文本不一定恰好与删除的文本一样长,可以插入一个更长或更短的string

  string类提供了6个不同的搜索函数,每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败,则返回一个名为string::npos的static成员,标准库将npos定义为一个const string::size_type类型,并初始化为值-1,由于npos是一个unsigned类型,此初始值意味着npos等于任何string最大的可能大小

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无默认值

  string还提供了一系列和数值之间的转换函数,如果string不能转换为一个数值,这些函数抛出一个invalid_argument异常,如果转换得到的数字无法用任何类型来表示,则抛出一个out_of_range异常

to_string(val); //一组重载函数,返回val的string表示,val可以是任何算术类型
//返回s的起始子串的数字,返回值类型分别是int、long、unsigned long、long long、unsigned long long,
//b表示转换所用的基数,默认为10;p表示子串的起始位置
stoi(s,p,b),stol(s,p,b),stoul(s,p,b),stoll(s,p,b),stoull(s,p,b); 
//返回s的起始子串的数字,返回值类型分别是float、double、long double;p表示子串的起始位置
stof(s,p),stod(s,p),stold(s,p); 

9.容器适配器

  除了顺序容器外,标准库还定义了三个顺序容器适配器:stackqueuepriority_queue。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如stack适配器接受一个顺序容器(除array和forward_list外),并使其操作起来像一个stack一样。每个适配器都定义两个构造函数:默认构造函数创建一个空对象接受一个容器的构造函数拷贝该容器来初始化适配器

stack<int> stk(deq); //从deq拷贝元素到stk

  默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。此外所有适配器要求容器具有添加和删除元素的能力,因此适配器不能构造在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_front和pop_back操作之外还要求随机访问能力,因此它可以构造于vector或deque之上,但是不能基于list构造。

// vector上实现的空栈
stack<string,vector<string>> str_stk;
// str_stk2在vector上实现,初始化时保存sevc的拷贝
stack<string,vector<string>> str_stk2(sevc); 

  我们不能直接使用适配器底层对象的操作,必须使用适配器自己定义的操作:

// stack默认基于deque实现,也可以在list或vector上实现
s.pop() //删除栈顶元素,但不返回该元素
s.push(item)
s.emplace(ags)
s.top() //返回栈顶元素,但不将元素弹出栈
// queue默认基于deque实现,priority默认基于vector实现
// queue也可以用list或vector实现,priority_queue也可以用deque实现
q.pop() //删除queue的首元素或priority_queue的最高优先级的元素,但不返回此元素
q.push(item)
q.emplace(args)
q.front(),q.back() //返回首元素或尾元素,但不删除此元素,只适用于queue
q.top() //返回最高优先级元素,但不删除,只适用于priority_queue
发布了19 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hjc132/article/details/105649968