0%

C++ Primer 第九章

顺序容器

顺序容器概述

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

确定使用那种容器

  • 除非你有很好的理由选择其他容器,否则应使用vector。
  • 如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list。
  • 如果程序要求随机访问元素,应使用vector或deque。
  • 如果程序要求在容器的中间插入或删除元素,应使用list或forward_list。如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque。
  • 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
    • 首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector追加数据,然后再调用标准库的sort函数来重排容器中的元素,从而避免在中间位置添加元素。
    • 如果必须在中间位置插入元素,考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector 中。

如果程序兼具插入和访问,那么取决于哪一种操作更多。

容器库概览

首先定义时需要确定容器中的元素类型:

1
2
list<sales_data>					//保存Sales_data对象的list
deque<double> //保存double的deque

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

顺序几乎可以保存任意类型的元素,但若类型没有默认构造函数,虚特殊处理:

1
2
3
//假定noDefault是一个没有默认构造函数的类型
vector<noDefault> v1 (10, init); //正确:提供了元素初始化器
vector<noDefault> v2(10) ; //错误:必须提供一个元素初始化器

类型别名

iterator 此容器类型的迭代器类型
const_iterator 可以读取元素,但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型;与value_type&含义相同
const_reference 元素的const左值类型(即,const value_type&)

构造函数

c c; 默认构造函数,构造空容器(array,参见第301页)
c c1(c2); 构造c2的拷贝c1
cc(b,e); 构造c,将迭代器b 和e指定的范围内的元素拷贝到c(array不支持)
c c{a,b,c…}; 列表初始化c

赋值与swap

c1= c2 将c1中的元素替换为c2中元素
c1= {a,b,c… } 将c1中的元素替换为列表中元素(不适用于array)
a.swap(b) 交换a和 b的元素
swap(a,b) 与a.swap(b)等价

**大小 **

c.size () c中元素的数目(不支持forward_list)
c.max_size () c可保存的最大元素数目
c.empty() 若c中存储了元素,返回false,否则返回true

添加/删除元素(不适用于array)
注:在不同容器中,这些操作的接口都不同

c.insert(args) 将args 中的元素拷贝进c
c.emplace (inits) 使用inits构造c中的一个元素
c.erase (args) 删除args指定的元素
c.clear () 删除c中的所有元素,返回void

关系运算符

==,!= 所有容器都支持相等(不等)运算符
<,<=,>,>= 关系运算符(无序关联容器不支持)

获取迭代器

c.begin () , c.end () 返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(), c.cend () 返回const iterator

反向容器的额外成员(不支持forward_list)

reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin () , c.rend () 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin (), c.crend () 返回const_reverse_iterator

迭代器

一个迭代器范围有一对迭代器表示,通常是begin和end,[begin, end)。

他们应指向同一个容器中的元素或这最后一个容器的下一个位置,且begin<=end

左闭右合的范围

  • 如果begin 与 end相等,则范围为空
  • 如果begin与 end不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素
  • 我们可以对 begin递增若干次,使得begin==end

使用方法:

1
2
3
4
while (begin != end){
*begin = val; //正确:范围非空,因此begin指向一个元素
++begin; //移动迭代器,获取下一个元素
}

容器类型成员

如果需要元素类型,可以使用容器的value_type。如果需要元素类型的一个引用,可以使用reference或const_reference。这些元素相关的类型别名在泛型编程中非常有用。为了使用这些类型,我们必须显式使用其类名:

1
2
3
4
// iter是通过list<string>定义的一个迭代器类型
list<string>::iterator iter;
// count是通过vector<int>定义的一个difference_type类型
vector<int>::difference_type count;

begin和end成员

1
2
3
4
5
list<string> a ={ "Milton","Shakespeare","Austen" };
auto it1 = a.begin (); //list<string>::iterator
auto it2 = a.rbegin (); //list<string>::reverse_iterator
auto it3 = a.cbegin (); //list<string>::const_iterator
auto it4 = a.crbegin(); //list<string>::const_reverse_iterator

C++新版本可以使用auto来定义类型:

1
2
3
4
5
6
//显式指定类型
list<string> : :iterator it5 = a.begin () ;
list<string> : :const_iterator it6 = a.begin ();
//是iterator还是const_iterator依赖于a的类型
auto it7 = a.begin(); //仅当a是const时, it7是const_iterator
auto it8 = a.cbegin (); //it8是const_iterator

当auto与begin或end结合使用时,获得的迭代器类型依赖于容器类型,与我们想要如何使用迭代器毫不相干。但以c开头的版本还是可以获得const_iterator的,而不管容器的类型是什么。

容器的定义和初始化

image.png

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

拷贝时容器类型及元素类型必须匹配。使用迭代器拷贝不需要容器类型相同,且元素类型只需要可以类型转换。

1
2
3
4
5
6
7
8
9
//每个容器有三个元素,用给定的初始化器进行初始化
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); //错误:容器类型必须匹配
//正确:可以将const char*元素转换为string
forward_list<string> words (articles.begin ( ), articles.end ( ) );

初始化拷贝容器类型和元素类型都必须相同。

由于两个迭代器表示一个范围,因此可以使用这种构造函数来拷贝一个容器中的子序列。例如,假定迭代器it表示authors中的一个元素:

1
2
//拷贝元素,直到(但不包括)it指向的元素
deque<string> authList (authors.begin () , it);

列表初始化

1
2
3
//每个容器有三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milton","Shakespeare""Austen"};
vector<const char*> articles = { "a", "an", "the" };

构造函数

接受一个容器大小,及初始值来创建容器:

1
2
3
4
vector<int> ivec ( 10,-1);				// 10个int元素,每个都初始化为-1
list<string> svec (10"hi! "); // 10个strings;每个都初始化为"hi !"
forward_list<int> ivec (10) ; // 10个元素,每个都初始化为0
deque<string> svec (10) ; // 10个元素,每个都是空string

如果元素类型具有没有默认构造函数,则必须提供初始值。

标准库array具有固定的大小

定义array,必须指定类型和容器大小:

1
2
3
4
array<int, 42>						//类型为:保存42个int的数组
array<string,10> //类型为:保存10个string的数组
array<int, 10>: :size_type i; //数组类型包括元素类型和大小
array<int> : :size_type j; //错误:array<int>不是一个类型

与其他容器不同点在于,默认构造的array是非空的,因为指定大小后,其中的元素都被默认初始化了。其次使用列表初始化元素数目必须小于等于容器大小,剩余部分用0填充:

1
2
3
array<int, 10> ia1;									//10个默认初始化的int
array<int,10> ia2 = {0,1,2,3,4,5,6,7,8,9}; //列表初始化
array<int,10> ia3 = {42}; // ia3[0]为42,剩余元素为О

与内置数组不同,array可以进行拷贝赋值操作:

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

但初始值类型,元素类型,大小都必须相同。

赋值和swap

1
2
c1 =c2;						//将c1的内容替换为c2中元素的拷贝
c1 ={ a,b,c}; //赋值后,c1大小为3

与内置数组不同,array类型允许赋值,但对象类型需要相等:

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

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

注意:赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(容器类型为array和string的情况除外)。

assign

在顺序容器(除array外)中可以assign成员赋值:

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

由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。

第二种用法接受整型值和一个元素值:

1
2
3
4
//等价于slist1.clear ();
//后跟slist1.insert(slist1.begin (),10,"Hiya ! ");
list<string> slist1 (1); // 1个元素,为空string
slist1.assign (10,"Hiya ! "); // 10个元素,每个都是“Hiya !”

swap

1
2
3
vector<string> svec1 (10); 		// 10个元素的vector
vector<string> svec2 (24); // 24个元素的vector
swap (svec1, svec2 );

除arraay,swap操作不对任何元素进行拷贝、删除、和插入,所以可以在常熟时间内完成,它只是交换了容器内部的数据结构(人话就是交换了头指针)。所以迭代器、引用、指针并不会失效,且所指向的元素也不会变,原本指向s1[1]的迭代器与s2交换后会指向s2[1]。但string调用swap会使迭代器、引用、指针失效。

array容器中使用swap会真正交换元素,所以时间会取决于元素数量,此外指针、引用、迭代器绑定的s1[1]现在还使s1[1],

容器大小操作

与大小相关的操作,size、empty、和max_size:返回容器所能容纳的最大元素数目。

关系运算符

除了无需关联容器外都支持关系运算符,但容器中的元素类型必须一样。比较容器实际上是逐对比较:

  • 如果两个容器具有相同大小且所有元素都两两对应相等,则这两个容器相等;否则两个容器不等。
  • 如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
  • 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。
1
2
3
4
5
6
7
8
vector<int> v1 = { 1357,912 };
vector<int> v2 = { 1,39 };
vector<int> v3 = { 1,35,7 };
vector<int> v4 = { 1,357912 };
vl < v2 // true; v1和v2在元素[2]处不同: v1[2]小于等于v2[2]
vl < v3 // false;所有元素都相等,但v3中元素数目更少
vl == v4 // true;每个元素都相等,且v1和v4大小相同
vl == v2 // false; v2元素数目比v1少

容器的关系运算符使用元素的关系运算符

容器的相等运算符实际上是使用元素的==运算符实现比较的,而其他关系运算符是使用元素的<运算符。如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算。例如,我们在第7章中定义sales_data类型并未定义==-和<运算。

顺序容器操作

接下来介绍顺序容器的特有操作:

向顺序容器添加元素

image.png

当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间,而这些策略直接影响性能。在一个vector或string 的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。而且,向一个vector或string添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。

使用push_back

1
2
3
//从标准输入读取数据,将每个单词放到容器末尾string word;
while (cin >> word)
container.push back (word);

tip:当我们用对象初始化、或插入到容器时,实际上会先拷贝这个对象生成临时对象,再初始化或插入它。

在特定的位置添加元素

insert成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素。vector.deque、list和string都支持insert成员。forward_list提供了特殊版本的insert成员。

slist.insert(iter,"Hello! ");//将"Hello ! "添加到iter之前的位置

有些容器(如vector)不支持push_front但可以使用insert来插入到开始的位置。

svec.insert (svec.end(), 10,"Anna" ); //将10个Anna插入到尾部

同时还有接受迭代器的版本:

1
2
3
4
5
6
vector<string> v = { "quasi","simba","frollo", "scar"};		
//将v的最后两个元素添加到slist的开始位置
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。

insert返回值

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

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

insert返回的是第一个新加入元素的迭代器,如果不插入(即只有第一个参数),就返回传入的第一个参数。

使用emplace

新标准引入了三个新成员——emplace_front、emplace和 emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应push_front、insert和push_back,允许我们将元素放置在容器头部、一个指定位置之前或容器尾部。

1
2
3
4
5
6
7
//在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));

它与push或insert的区别在于它可以直接调用元素类型的构造函数在容器中直接创建,而push需要创建临时的对象,然后压入容器,但传入参数必须匹配。

1
2
3
4
5
// 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"2515.99) ;

访问元素

包括array在内的每个顺序容器都有一个front成员函数,而除forward_list之外的所有顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用:

1
2
3
4
5
6
7
8
//在解引用一个迭代器或调用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不支持

访问成员函数返回引用

const容器返回const引用,如果使用auto保存返回值,并希望改变元素的值,必须定义为引用类型:

auto &v = c.back();

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

为保证使用下标访问不会越界,可以使用at,它会在下标越界时抛出异常:

1
2
3
vector<string> svec;					//空vector
cout << svec[0] ; //运行时错误: svec中没有元素!
cout << svec.at(0) ; //抛出一个out_ of_ range异常

删除元素

image.png

pop函数

这些操作返回void,如果需要弹出元素的值,就必须在执行弹出前保存它:

1
2
3
4
while (!ilist. empty()) {
process (ilist. front()) ; //对ilist的首元素进行一些处理
ilist.pop_ front() ; //完成处理后删除首元素.
}

特殊的forward_list

因为单向链表删除或者添加一个元素会改变前一个元素,但单向链表没办法访问前驱,所以我们可以添加或者删除给定元素之后的元素,

image.png

1
2
3
4
5
6
7
8
9
10
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 之前的元素
}

改变容器大小

image.png

1
2
3
4
list<int> ilist(1042) ;					// 10个int:每个的值都是42
ilist. resize(15) ; //将5个值为0的元素添加到ilist的末尾.
ilist. resize(25-1) ; //将10个值为-1的元素添加到ilist的末尾
ilist. resize(5) ; //从ilist末尾删除20个元素

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

由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vector、string和deque尤为重要。

编写改变容器的循环程序

添加/删除vector、string 或deque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。程序必须保证每个循环步中都更新迭代器、引用或指针。如果循环中调用的是insert或erase,那么更新迭代器很容易。这些操作都返回迭代器,我们可以用来更新:

1
2
3
4
5
6
7
8
9
10
11
//傻瓜循环,删除偶数元素,复制每个奇数元素
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) ; //删除偶数元素
//不应向前移动迭代器,iter 指向我们删除的元素之后的元素
}

不要保存end返回的迭代器

因为插入删除操作会使end迭代器失效。

vector对象如何增长

vector和string在添加元素时如果大小其容量,会开辟一块比预定容量更大的地方。

管理容量的成员函数

image.png

reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间,不过只有当需要的内存超过当前容量时,才会改变容量。一旦调用,会至少分配与需求一样大或更大的空间。

capacity和size

image.png

每个 vector实现都可以选择自己的内存分配策略。但是必须遵守的一条原则是:只有当迫不得已时才可以分配新的内存空间。

额外的string操作

构造string的其他方法

image.png

1
2
3
4
5
6
7
8
9
10
const char *cp = "Hello world!!! ";		//以空字符结束的数组
char noNull[]={'H’, 'i'}; //不是以空字符结束
string s1(cp); // 拷贝cp中的字符直到遇到空字符;s1 == "Helloworld!!!"
string s2(noNull,2); //从noNull拷贝两个字符;s2 == "Hi"
string s3 (noNull); //未定义:noNull不是以空字符结束
string s4(cp + 65); //从cp [ 6]开始拷贝5个字符;s4 =="world"
string s5(s1,65) ; //从s1 [ 6]开始拷贝5个字符;s5 == "world"
string s6(s1,6); //从s1[ 6]开始拷贝,直至s1末尾;s6== "world! ! !"
string s7(s1,6,20); //正确,只拷贝到s1末尾;s7 == "world! ! ! "
string s8(s1,16); //抛出一个out_of_range异常

substr操作

1
2
3
4
5
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); // s3 = world
string s5 = s.substr (12); //抛出一个out_of_range异常

返回一个string,包含s中从pos 开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size () - pos,即拷贝从pos开始的所有字符,开始位置超过大小,会抛出一个异常,结束位置超出大小,会默认拷贝string的末尾。

改变string的其他方法

除了接受迭代器的insert和 erase版本外,string还提供了接受下标的版本。下标指出了开始删除的位置,或是insert到给定值之前的位置:

1
2
s.insert(s.size(), 5'!');				//在s末尾插入5个感叹号
s.erase(s.size()- 55); //从s删除最后5个字符

标准库string类型还提供了接受C风格字符数组的insert和 assign版本。例如,我们可以将以空字符结尾的字符数组insert 到或assign给一个string:

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

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

1
2
3
4
string s = "some string", s2 = "some other string";
s.insert (0, s2); //在s中位置0之前插入s2的拷贝
//在s [0]之前插入s2中s2[0]开始的s2.size()个字符
s.insert(0,s2,0, s2.size ());

append和replace函数

append操作是在string末尾进行插入操作的一种简写形式:

1
2
3
string s ("C++ Primer"),s2 = s;				//将s 和s2初始化为"C++ Primer"
s.insert (s.size()," 4th Ed."); //s == "C++ Primer 4th Ed."
s2.append(" 4th Ed." ); //等价方法:将”4th Ed."追加到s2;s == s2

replace操作是调用erase和insert的一种简写形式:

1
2
3
4
5
//将"4th"替换为"5th"的等价方法
s.erase (113); // s == "C++ Primer Ed . "
s.insert (11"5th" ) ; // s == "C++ Primer 5th Ed . "
//从位置11开始,删除3个字符并插入"5th"
s2.replace(11,3,"5th"); //等价方法:s == s2

此例中调用replace时,插入的文本恰好与删除的文本一样长。这不是必须的,可以插入一个更长或更短的string:

1
s.replace(11,3"Fifth");					// s == "C++ Primer Fifth Ed."

image.png

image.png

改变string的多种重载函数

assign和 append函数无须指定要替换string中哪个部分: assign总是替换string中的所有内容,append总是将新字符追加到string末尾。
replace函数提供了两种指定删除元素范围的方式。可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定。insert函数允许我们用两种方式指定插入点:用一个下标或一个迭代器。在两种情况下,新元素都会插入到给定下标(或迭代器)之前的位置。
可以用好几种方式来指定要添加到string 中的字符。新字符可以来自于另一个string,来自于一个字符指针(指向的字符数组),来自于一个花括号包围的字符列表,或者是一个字符和一个计数值。当字符来自于一个string或一个字符指针时,我们可以传递一个额外的参数来控制是拷贝部分还是全部字符。

string搜索操作

搜索操作:成功返回一个无符号的string::size_type值,失败返回npos(cont string::size_type)的static成员(他们应该是同一种类型,但变量名字不同),因此不建议使用int储存。

1
2
3
4
string name ("AnnaBelle");
auto posl = name.find ( "Anna"); // pos1 ==o
string lowercase ( "annabelle" );
pos1 = lowercase.find ( "Anna"); // posl ==npos

find操作对大小写有区分A与a会区分开来

image.png

image.png

1
2
3
4
5
6
string numbers ( "0123456789"), name ( "r2d2") ;
//返回1,即,name中第一个数字的下标
auto pos = name . find_first_of (numbers) ;
string dept ( "03714p3");
//返回5——字符'p'的下标
auto pos = dept.find_ first_not_of (numbers) ;

指定可选位置

find函数第二个参数为从哪里开始,默认为0,使用它可以在字符串中循环搜索子字符串的所有位置:

1
2
3
4
5
6
string : : size_type pos = 0;
//每步循环查找name中下一个数
while ((pos = name.find_first_of (numbers, pos)) != string::npos) {
cout <<"found number at index : " << pos <<" element is " <<name [pos] << endl;
++pos; //移动到下一个字符
}

逆向搜索

1
2
3
string river ( "Mississippi" );
auto first_pos = river.find ( "is"); //返回1
auto last_pos - river.rfind ( "is"); //返回4

compare函数

string类型提供的与C标准库 的strcmp函数相似的函数,根据s等于、大小、小于返回0、整数、负数。

image.png

数值转换

一些函数以可实现string与数值的转换:

1
2
3
int i = 42;
string s = to_string(i); //将整数i转换为字符表示形式
double d = stod (s) ; //将字符串s转换为浮点数

要转换为数值的string中第一个非空白符必须是数值中可能出现的字符:string s2 = “pi = 3.14” ;

1
2
//转换s中以数字开始的第一个子串,结果d = 3.14
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));

这里将字符串种第一个出现数字的地方的子串传入函数,直到遇到不可能是数值的字符,然后将其转换。参数中第一个非空白符号必须是+、0、数字或是是小数点,且可以包含e、E、x、X等其他进制需要的字母。

如果string不能转换为一个数值,这些函数抛出一个invalid_argument异常(参见5.6节,第173页)。如果转换得到的数值无法用任何类型来表示,则抛出一个 out_of range异常。

image.png

容器适配器

本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如,stack适配器接受一个顺序容器(除array或forward_list外),并使其操作起来像一个stack一样。

image.png

定义一个适配器

首先我们可以用一个容器初始化另一个容器:

1
2
deque<int> deq = {1,2,3,4};
stack<int> stk (deq); //从deq考贝元素到stk

我们还可以尖括号内重载构造函数,就可以创建一个适配器:

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

所有适配器都要求有添加和删除以及访问尾元素能力,所以不能用array、forward_list构造。

栈适配器

image.png

栈默认基于deque实现,所以可以省略第二个参数:

1
2
3
4
5
6
7
8
9
stack<int> intstack; //空栈
//填满栈
for (size_t ix = 0; ix != 10; ++ix)
intStack.push (ix); // intStack保存0到9十个数
while ( !intstack.empty()) { // intStack中有值就继续循环
int value = intStack.top ( ) ;
//使用栈顶值的代码
intStack.pop() ; //弹出栈顶元素,继续循环
}

虽然是基于deque实现,但我们不能使用底层容器的操作,所以只可以用push,而不可以使用push_back。

队列适配器

image.png

image.png

priority_queue 允许我们为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。饭店按照客人预定时间而不是到来时间的早晚来为他们安排座位,就是一个优先队列的例子。默认情况下,标准库在元素类型上使用<运算符来确定相对优先级。后面会学习如何重载这个默认设置。