《C++ Primer中文版》 第九章:顺序容器

目录

第九章:顺序容器

9.1顺序容器概述n

9.2容器库概览

9.2.1迭代器

​9.2.2容器类型成员

9.2.3begin和end成员

9.2.4容器定义和初始化​

9.2.5赋值和swap

9.2.6容器大小操作

9.2.7关系运算符

9.3顺序容器操作

9.3.1向顺序容器添加元素

9.3.2访问元素

9.3.3删除元素​

9.3.4特殊的 forward_list 操作​

9.3.5改变容器大小

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

9.4vector对象是如何增长的

9.5额外的string操作

9.5.1构造 string的其他方法​

9.5.2 改变string的其他方法

9.5.3 string 搜索操作

9.5.4compare函数

9.5.5数值转换

9.6容器适配器


第九章:顺序容器

顺序容器(sequential container)

9.1顺序容器概述n

列出了标准库中的顺序容器,所有顺序容器都提供了快速顺序访问元素的能力
但是,这些容器在以下方面都有不同的性能折中:

●向容器添加或从容器中删除元素的代价
●非顺序访问容器中元素的代价

1.除了固定大小的array外,其他容器都提供高效、灵活的内存管理。

string和vector将元素保存在连续的内存空间中。由于元素是连续存储的,由元素的下标来计算其地址是非常快速的。但是,两种容器的中间位置添加或删除元素就会非常耗时:在一次插入或删除操作后,需要移动插入/删除位置之后的所有元素,来保持连续存储。而且,添加一个元素有时可能还需要分配额外的存储空间。在这种情况下,每个元素都必须移动到新的存储空间中。

2.list和forward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持元素的随机访问:为了访问一个元素,我们只能遍历整个容器。而且,与vector、deque 和array,相比,这两个容器的额外内存开销也很大

3.deque是一个更为复杂的数据结构。与string和vector类似,deque 支持快速的随机访问与string和vector一样,在deque的中间位置添加或删除元素的代价(可能)很高。但是在deque的两端添加或删除元素都是很快的,与list或forward_ list 添加删除元素的速度相当。

4.forward_list 和array是新C++标准增加的类型。与内置数组相比,array是一种更安全、更容易使用的数组类型。与内置数组类似,array对象的大小是固定的。因此,array不支持添加和删除元素以及改变容器大小的操作。forward_list 的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list 没有size 操作,因为保存或计算其大小就会比手写链表多出额外的开销。对其他容器而言,size保证是一个快速的常量时间的操作。
 

确定使用哪种顺序容器

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

如果程序既需要随机访问元素,又需要在容器中间位置插入元素,那该怎么办?答案取决于在list或forward list中访问元素与vector或deque中插入/删除元素的相对性能。一般来说,应用中占主导地位的操作(执行的访问操作更多还是插入/删除更多)决定了容器类型的选择。在此情况下,对两种容器分别测试应用的性能可能就是必要的了

9.2容器库概览

某些操作是所有容器类型都提供的
●另外一些操作仅针对顺序容器、关联容器或无序容器
●还有一些操作只适用于一小部分容器
 

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

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

9.2.1迭代器

forward_list迭代器不支持递减运算符(--)。

这些运算只能应用于string、vector、deque和array。

迭代器范围(iterator range)

尾元素之后的位置(one past the last element )     左闭合区间(left-inclusive interval):[begin, end)

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

假定begin和end构成一个合法的迭代器范围,则

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

while (begin != end) {
    *begin = val; // 正确:范围非空,因此begin指向一个元素
    ++begin;      //移动迭代器,获取下一个元素
}
#include <iostream>
#include <vector>

using namespace std;

bool search_vec(vector<int>::iterator beg,
	               vector<int>::iterator end, int val)
{
	for (; beg != end; beg++) //遍历范围
		if (*beg== val) //检查是否与给定值相等
			 return true;
	return false;
}
int main()
{
	vector<int> ilist = { 1, 2,3,4,5,6,7 };
	cout << search_vec(ilist.begin() , ilist.end() ,3) << endl;
	cout << search_vec(ilist.begin() , ilist.end() ,8) << endl;
	return 0;
}


9.2.2容器类型成员

通过类型别名,我们可以在不了解容器中元素类型的情况下使用它。 这些元素相关的类型别名在泛型编程中非常有用.

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

9.2.3begin和end成员

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开头的函数都是被重载过的。也就是说,实际上有两个名为begin的成员。一个是const成员,返回容器的const_ iterator类型。另一个是非常量成员,返回容器的iterator类型。当我们对一个非常量对象调用这些成员时,得到的是返回iterator 的版本。只有在对一个const对象调用这些函数时,才会得到一个const版本。与const指针和引用类似,可以将一个普通的iterator转换为对应的const_ iterator,但反之不行。

以c开头的版本是C++新标准引入的,用以支持auto与begin和end函数结合使用。过去,没有其他选择,只能显式声明希望使用哪种类型的迭代器:

//显式指定类型
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

练习9.9: begin和cbegin两个函数有什么不同?
cbegin是C++新标准引入的,用来与auto结合使用。它返回指向容器第一一个元素的const迭代器,可以用来只读地访问容器元素,但不能对容器元素进行修改。因此,当不需要写访问时,应该使用cbegin.

begin 则是被重载过的,有两个版本:其中一个是const 成员函数也返回const迭代器:另一个则返回普通迭代器,可以对容器元素进行修改。
 

9.2.4容器定义和初始化

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

//每个容器有三个元素,用给定的初始化器进行初始化
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指向的元素
deque<string> authList (authors.begin(),it) ;

列表初始化c++11

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

对于除array之外的容器类型,初始化列表还隐含地指定了容器的大小:容器将包含与初始值一样多的元素。
 

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

顺序容器(array除外)

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<int, 42>     //类型为:保存42个int的数组
array<string, 10>  //类型为:保存10个string的数组
使用array类型,我们必须同时指定元素类型和大小:
array<int,10>::size_type i;//数组类型包括元素类型和大小
array<int>::size_type j;   //错误: array<int>不是一个类型

array大小固定的特性也影响了它所定义的构造函数的行为。与其他容器不同,一个默认构造的array是非空的:它包含了与其大小一样多的元素。这些元素都被默认初始化,就像一个内置数组中的元素那样。

如果我们对array进行列表初始化,初始值的数目必须等于或小于array的大小。

array<int,10> ial;                         // 10 个默认初始化的int
array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8,9}; // 列表初始化
array<int, 10> ia3 = {42}; // ia3[0]为 42,剩余元素为0
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; //正确:只要数组类型匹配即合法
#include <iostream>
#include <vector>
#include <list>

using namespace std;


int main()
{
	list<int> ilist = { 1,2,3,4,5,6,7 };
	vector<int> ivec = { 7, 6,5,4,3,2,1 };
	//容器类型不同,不能使用拷贝初始化
	// vector<double> ivec(ilist) ;
	//元素类型相容,因此可采用范围初始化
	vector <double> dvec(ilist.begin() ,ilist.end());
	//容器类型不同,不能使用拷贝初始化
	// vector<double> dvec1 (ivec) ;
	//元素类型相容,因此可采用范围初始化
	vector<double> dvecl(ivec.begin(),ivec.end());
	cout << dvec.capacity() << " " << dvec.size() << " "
		<< dvec[0] << " " << dvec[dvec.size() - 1] << endl;
	cout << dvecl.capacity() << " "<< dvecl.size() << " "
		<< dvecl[0] << " "<< dvecl[dvecl.size() - 1] << endl;

	return 0;
}

9.2.5赋值和swap

c1 = c2;      //将c1的内容替换为c2中元素的拷贝
cl = {a,b,c}; //赋值后,cl大小为3
第一个赋值运算后,左边容器将与右边容器相等。如果两个容器原来大小不同,赋值运算
后两者的大小都与右边容器的原大小相同。第二个赋值运算后,c1的size变为3,即花
括号列表中值的数目。

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

由于右边运算对象的大小可能与左边运算对象的大小不同,因此array类型不支持assign, 也不允许用花括号包围的值列表进行赋值。

使用assign (仅顺序容器)
顺序容器(array 除外)还定义了一个名为assign的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign 操作用参数所指定的元素(的拷贝)替换左边容器中的所有元素。例如,我们可以用assgin实现将一个vector中的一段char *值赋予一个list中的string:

list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误:容器类型不匹配

// 正确: 可以将const char*转换为 string
names.assign (oldstyle.cbegin(), oldstyle.cend());

assign的第二个版本:

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

使用swap

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

调用swap后,svec1将包含24个string元素,svec2将包含10个string。除array
外,交换两个容器内容的操作保证会很快————元素 本身并未交换,swap只是交换了两个
容器的内部数据结构。

元素不会被移动的事实意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。在swap之后,这些元素已经属于不同的容器了

swap 两个array会真正交换它们的元素。因此,交换两个array所需的时间与array中元素的数目成正比。
因此,对于array,在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个array中对应元素的值进行了交换。
在新标准库中,容器既提供成员函数版本的swap,也提供非成员版本的swap。而早期标准库版本只提供成员函数版本的swap.非成员版本的swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯。
 

#include <iostream>
#include <vector>
#include <list>

using namespace std;


int main()
{
	list<char *> slist = { "hello", "world", "!" };
	vector<string> svec;
	//容器类型不同,不能直接赋值
	// svec = slist;
	//元素类型相容,可以采用范围赋值
	svec.assign(slist.begin(),slist.end());
	cout << svec.capacity() << " " << svec.size() << endl;

	return 0;
}

9.2.6容器大小操作

除了一个例外,每个容器类型都有三个与大小相关的操作。成员函数size返回容器中元素的数目:

empty 当size为0时返回布尔值true,否则返回false。max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值。
forward_ list 支持max_ size和empty,但不支持size

9.2.7关系运算符

每个容器类型都支持相等运算符(==和!=); 除了无序关联容器外的所有容器都支持关系运算符(>、 >=、<、<=)关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素:

●如果两个容器具有相同大小且所有元素都两两对应相等,则这两个容器相等;否则两个容器不等
●如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器
●如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果

vector<int> v1={1,3,5,7,9,12};
vector<int> v2= {1,3, 9 };
vector<int> v3= { 1,3,5,7 };
vector<int> v4= { 1, 3, 5, 7, 9,12 };
v1 < v2 // true; v1和v2在元素[2]处不同: v1[2]小于等于v2[2]
v1 < v3 // false; 所有元素都相等,但v3中元素数目更少
v1 == v4 // true;每个元素都相等,且v1和v4大小相同
vl == v2 // false; v2元素数目比v1少

容器的关系运算符使用元素的关系运算符完成比较

练习:比较

bool l_v_equal (vector<int> &ivec, list<int> &ilist) {
    //比较list和vector元素个数
    if (ilist.size() != ivec.size() )
        return false;
    auto lb = ilist.cbegin() ;// list 首元素
    auto le = ilist.cend() ;// list 尾后位置
    auto vb = ivec.cbegin() ;// vector 首元素
    for (;lb != le; 1b++, vb++)
        if (*lb != *vb)// 元素不等, 容器不等
            return false;
    return true;//容器相等
}

9.3顺序容器操作

9.3.1向顺序容器添加元素

除array外

使用push_back

除array和forward_list之外,每个顺序容器(包括string类型)都支持push_back

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

push_ back在string末尾添加字符:

void pluralize (size_t cnt, string &word)
{
   if (cnt > 1)
      word.push_back('s'); //等价于word +=’s'
}


使用push_front

 list、 forward_ list和deque容器还支持名为push_ front的类似操作。此操作将元素插入到容器头部:

list<int> ilist;
//将元素添加到ilist开头
for (size_t ix = 0; ix != 4; ++ix)
  ilist.push_front(ix) ;


ilist保存序列3、2、1、0

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

insert 成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素,vector、deque.list和string都支持insert成员forward_ list提供了特殊版本的insert成员
每个insert函数都接受一个迭代器作为其第一个参数。迭代器指出了在容器中什么位置放置新元素。

由于迭代器可能指向容器尾部之后不存在的元素的位置,而且在容器开始位置插入元素是很有用的功能,所以insert函数将元素插入到迭代器所指定的位置之前。

虽然某些容器不支持push_front操作,但它们对于insert操作并无类似的限制(插入开始位置)。因此我们可以将元素插入到容器的开始位置,而不必担心容器是否支持push_front:

vector<string> svec;
list<string> slist;

//等价于调用slist.push_front ( "Hello!" );
slist.insert (slist.begin(), "Hello!" );

// vector 不支持push_ front, 但我们可以插入到begin()之前
//警告:插入到vector末尾之外的任何位置都可能很慢
svec.insert (svec.begin(),"Hello!");

插入范围内元素

svec. insert (svec.end() , 10 ,"Anna") ;


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版本返回指向第一个新加入元素的迭代器。(在旧版本的标准库中,这些操作返回void)如果范围为空,不插入任何元素,insert操作会将第一个参数返回。

使用insert的返回值

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

使用emplace操作

新标准引入了三个新成员一emplace_front、emplace和emplace_back,这些操作构造而不是拷贝元素

当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。例如:

//在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函数的参数根据元素类型而变化,参数必须与元素类型的构造函数相匹配:
// 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) ;
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> iv = { 1, 7,1,8 ,9,15,1 }; //int的vector
    int some_val = 1;

	vector<int>::iterator iter = iv.begin();
	int org_size = iv.size() ,new_ele = 0;   //原大小和新元素个数

	//每个循环步都重新计算“mid", 保证正确指向iv原中央元素
	while (iter != (iv.begin() + org_size / 2 + new_ele))
		if (*iter == some_val) {
			iter = iv.insert(iter , 2* some_val); // iter指向新元素
			new_ele++;
			iter++; iter++;		//将iter推进到旧元素的下一个位置.
		}
		else iter++;	//简单推进iter
	//用begin()获取vector首元素迭代器,遍历vector中的所有元素
	for (iter = iv.begin(); iter != iv.end(); iter++)
		cout << *iter << endl;

	return 0;
}
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> iv = { 1, 1,1,1, 1 };// int的vector
	int some_val = 1;
	vector<int>::iterator iter = iv.begin();
	int org_size = iv.size(), i = 0;//原大小

	//用循环变量控制循环次数
	while (i <= org_size / 2) {
		if (*iter == some_val) {
			iter = iv.insert(iter, 2 * some_val); // iter 指向新元素
			iter++; iter++; //将iter推进到旧元素的下一个位置
		}
		else iter++;  //简单推进iter
		i++;
	}

		//用begin()获取vector首元素迭代器,遍历vector中的所有元素
		for (iter = iv.begin(); iter != iv.end(); iter++)
			cout << *iter << endl;

	return 0;
}

9.3.2访问元素

包括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 不支持
}

另在调用front和back(或解引用begin和end返回的迭代器)之前,要确保c非空。如果容器为空,if中操作的行为将是未定义的。


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

在容器中访问元素的成员函数(即,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中的元素
}

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

提供快速随机访问的容器(string、 vector、 deque 和array)也都提供下标运算符。

下标运算符接受一个下标参数,返回容器中该位置的元素的引用。给定下标必须“在范围内”(即,大于等于0,且小于容器的大小)。保证下标有效是程序员的责任,下标运算符并不检查下标是否在合法范围内。使用越界的下标是一种严重的程序设计错误,而且编译器并不检查这种错误。
如果我们希望确保下标是合法的,可以使用at成员函数。at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_ range异常:

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

9.3.3删除元素

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> exam = { 0,1,2,3,4,5 };
	vector<int>::iterator it = exam.begin();
	it++;
	it++;
	cout << *exam.erase(it) << endl;
	cout << exam.size() << endl;

	for (auto it : exam)
		cout << it << "  ";
	return 0;
}

pop_front 和pop_back 成员函数

vector和string不支持push_ front 一样,这些类型也不支持pop_ front。

forwar_list不支持pop_ back。不能对一个空容器执行弹出操作。
这些操作返回void。如果你需要弹出的元素的值,就必须在执行弹出操作之前保存它:

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

从容器内部删除一个元素

list<int> 1st = {0,1,2,3,4,5,6,7,8,9};
auto it = lst.begin() ;
while (it != lst.end() )
    if (*it % 2)  //若元素为奇数
        it = lst.erase(it); //删除此元素
    else
        ++it;

删除多个元素

//删除两个迭代器表示的范围内的元素
//返回指向最后一个被删元素之后位置的迭代器
eleml = slist.erase (eleml,elem2); //调用后,eleml == elem2

slist.clear(); // 删除容器中所有元素
slist.erase (slist.begin(), slist.end()); //等价调用

9.3.4特殊的 forward_list 操作

为了添加或删除一个元素,我们需要访问其前驱,以便改变前驱的链接。但是,forward_ list是单向链表。在一个单向链表中,没有简单的方法来获取一个元素的前驱。出于这个原因,在一个forward_list 中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。这样,我们总是可以访问到被添加或删除操作所影响的元素。

为了删除elem3, 应该用指向elem2的迭代器调用erase_ after。 义了before_ begin, 它返回一个首前( off-the-beginning)迭代器。这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素(亦即在链表首元素之前添加删除元素)。

当在forward_ list中添加或删除元素时,我们必须关注两个迭代器一个指向
我们要处理的元素,另一个指向其前驱。例如,可以改写第312页中从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之前的元素
        }
}

#include <iostream>
#include <forward_list>
using namespace std;
int main()
{
	forward_list<int> iflst = { 1, 2, 3, 4,5,6,7,8 };
	auto prev = iflst.before_begin();//前驱元素
	auto curr = iflst.begin();	//当前元素
	while (curr != iflst.end())
		if (*curr & 1) //奇数
			curr = iflst.erase_after(prev); //删除,移动到下一元素
		else {
			prev = curr;//前驱和当前迭代器都向前推进
			curr++;
		}
	for (curr = iflst.begin(); curr != iflst.end(); curr++)
		cout << *curr << " ";
	cout << endl;

	return 0;
}

#include <iostream>
#include <string>
#include <forward_list>
using namespace std;

void test_and_insert(forward_list<string> &sflst, const string &s1, const string &s2)
{
	auto prev = sflst.before_begin();//前驱元素
    auto curr = sflst.begin();       //当前元素
    bool inserted = false;
	while (curr != sflst.end()) {
		if (*curr == s1) {
			//找到给定字符串
			curr = sflst.insert_after(curr, s2); // 插入新字符串,curr指向它
			inserted = true;
		}
		prev = curr;//前驱迭代器向前推进
		curr++;  //当前迭代器向前推进
		}
		if (!inserted)
			sflst.insert_after(prev, s2);//未找到给定字符串,插入尾后
}
int main()
{
	forward_list<string> sflst = { "Hello", "!", "world", "!" };
	test_and_insert(sflst, "Hello", "你好");
	for (forward_list<string>::const_iterator curr = sflst.cbegin(); curr != sflst.cend(); curr++)
		cout << *curr << " ";
	cout << endl;
	test_and_insert(sflst,"!", "?");
	for (forward_list<string>::const_iterator curr = sflst.cbegin(); curr != sflst.cend(); curr++)
		cout << *curr << " ";
	cout << endl;
	test_and_insert(sflst, "Bye", "再见");
	for (forward_list<string>::const_iterator curr = sflst.cbegin(); curr != sflst.cend(); curr++)
		cout << *curr << " ";
   cout << endl;

	return 0;
}

9.3.5改变容器大小

如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于新大小,会将新元素添加到容器后部:

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

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

在向容器添加元素后:
●如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。

●对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
●对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
当我们删除一个元素后: 
●对于list和forward_ list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
●对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。
●对于vector和string, 指向被删元素之前元素的迭代器、引用和指针仍有效。

 

注意:当我们删除元素时,尾后迭代器总是会失效。

编写改变容器的循环程序:使迭代器失效。

//傻瓜循环,删除偶数元素,复制每个奇数元素
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指向我们删除的元素之 后的元素
}

在调用erase后,不必递增迭代器,因为erase返回的迭代器已经指向序列中下一个元素调用insert后,需要递增迭代器两次。记住,insert 在给定位置之前插入新元素,然后返回指向新插入元素的迭代器。因此,在调用insert后,iter 指向新插入元素,位于我们正在处理的元素之前。我们将迭代器递增两次,恰好越过了新添加的元素和正在处理的元素,指向下--个未处理的元素。

不要保存end返回的迭代器

通常C++标准库的实现中end ()操作都很快

	//灾难:此循环的行为是未定义的
	auto begin = v.begin(),
		end = v.end(); //保存尾迭代器的值是一个坏主意
	while (begin != end) {
		//做一些处理
		//插入新值,对begin重新赋值,否则的话它就会失效
		++begin; //向前移动begin,因为我们想在此元素之后插入元素
		begin = v.insert(begin, 42); //插入新值
		++begin; // 向前移动begin跳过我们刚刚加入的元素
	}

此代码的行为是未定义的。在很多标准库实现上,此代码会导致无限循环。问题在于我们将end操作返回的迭代器保存在一个名为end的局部变量中。在循环体中,我们向容器中添加了一个元素,这个操作使保存在end中的迭代器失效了。这个迭代器不再指向v中任何元素,或是v中尾元素之后的位置。

9.4vector对象是如何增长的

标准库实现者采用了可以减少容器空间重新分配次数的策略
不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,可用来保存更多的新元素。就不需要每次添加新元素都重新分配容器的内存空间了。

这种分配策略比每次添加新元素时都重新分配容器内存空间的策略要高效得多。其实际性能也表现得足够好——虽然 vector在每次重新分配内存空间时都要移动所有元素,但使用此策略后,其扩张操作通常比list和deque还要快。
 

管理容量的成员函数

只有当需要的内存空间超过当前容量时reserve 调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大的内存空间(可能更大)。
如果需求大小小于或等于当前容量,reserve什么也不做。特别是,当需求大小小于当前容量时,容器不会退回内存空间。因此,在调用reserve之后,capacity 将会大于或等于传递给reserve的参数
调用reserve永远也不会减少容器占用的内存空间。类似的,resize 成员函数只改变容器中元素的数目,而不是容器的容量。我们同样不能使用resize来减少容器预留的内存空间。
 

在新标准库中,我们可以调用shrink_ to_ fit 来要求deque. vector或string 退回不需要的内存空间。此函数指出我们不再需要任何多余的内存空间。但是,具体的实现可以选择忽略此请求。也就是说,调用shrink_ to_ fit也并不保证一定退回内存空间

capacity和size
容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。
 

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> ivec;
	// size应该为0; capacity的值依赖于具体实现
	cout << "ivec: size:" << ivec.size()
		<< " capacity: " << ivec.capacity() << endl;
	//向ivec添加24个元素

	for (vector<int>::size_type ix = 0; ix != 24; ++ix)
		ivec.push_back(ix);

	// size应该为24; capacity 应该大于等于24,具体值依赖于标准库实现
	cout << "ivec: size:" << ivec.size()
		<< "capacity:" << ivec.capacity() << endl;

	return 0;
}

 

ivec.reserve(50); //将capacity至少设定为50,可能会更大
// size 应该为24; capacity应该大于等于50,具体值依赖于标准库实现
cout << "ivec: size: "<< ivec.size()
        <<"capacity: "<< ivec.capacity() << endl;

	//添加元素用光多余容量
	while (ivec.size() != ivec.capacity())
		ivec.push_back(0);
	// capacity 应该未改变,size 和capacity不相等
	cout << "ivec: size: " << ivec.size()
		<< "capacity:" << ivec.capacity() << endl;

	ivec.push_back(42); //再添加一个元素
    // size应该为51; capacity应该大于等于51,具体值依赖于标准库实现
	cout <<"ivec: size: " << ivec.size()
		<< "capacity: "<< ivec.capacity() << endl;

ivec.shrink_to_fit(); //要求归还内存
// size应该未改变; capacity的值依赖于具体实现

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> ivec;
	// size应该为0; capacity的值依赖于具体实现
	cout << "ivec: size:" << ivec.size()
		<< " capacity: " << ivec.capacity() << endl;
	//向ivec添加24个元素

	for (vector<int>::size_type ix = 0; ix != 24; ++ix)
		ivec.push_back(ix);

	// size应该为24; capacity 应该大于等于24,具体值依赖于标准库实现
	cout << "ivec: size:" << ivec.size()
		<< "capacity:" << ivec.capacity() << endl;


	ivec.reserve(50); //将capacity至少设定为50,可能会更大
    // size 应该为24; capacity应该大于等于50,具体值依赖于标准库实现
	cout << "ivec: size: " << ivec.size()
		<< "capacity: " << ivec.capacity() << endl;

	//添加元素用光多余容量
	while (ivec.size() != ivec.capacity())
		ivec.push_back(0);
	// capacity 应该未改变,size 和capacity不相等
	cout << "ivec: size: " << ivec.size()
		<< "capacity:" << ivec.capacity() << endl;

	ivec.push_back(42); //再添加一个元素
    // size应该为51; capacity应该大于等于51,具体值依赖于标准库实现
	cout <<"ivec: size: " << ivec.size()
		<< "capacity: "<< ivec.capacity() << endl;

	ivec.shrink_to_fit(); //要求归还内存
    // size应该未改变; capacity的值依赖于具体实现
	cout << "ivec: size: " << ivec.size()
		<< "capacity: " << ivec.capacity() << endl;

	return 0;
}

9.5额外的string操作

9.5.1构造 string的其他方法

const char *cp = "Hello World!! !"; // 以空字符结束的数组
char noNull[] = {'H','i' };//不是以空字符结束
string s1 (cp); //拷贝cp中的字符直到遇到空字符; sl == "Hello World!! !"
string s2 (noNull,2); // 从noNull拷贝两个字符; s2 == "Hi"
string s3 (noNull) ;//未定义: noNull不是以空字符结束
string s4(cp + 6,5);//从cp[6]开始拷贝5个字符; s4 == "World"
string s5(s1,6,5);//从s1[6]开始拷贝5个字符; s5 == "World"
string s6(s1,6) ;//从s1[6]开始拷贝,直至sl末尾; s6== "World!!!"
string s7(s1,6,20) ;//正确,只拷贝到s1末尾; s7 == "World!!!"
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); // s3 = world
string s5 = s.substr (12) ;   //抛出一个out_ of_ range 异常
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
	vector<char> vc =  { 'H','e','l','l','o'};
	string s(vc.data(),vc.size());
	cout << s << endl;
	return 0;
}

9.5.2 改变string的其他方法

s.insert(s.size(), 5  , '!'); //在s末尾插入5个感叹号
s.erase( s.size() - 5,5);//从s删除最后5个字符
const char *cp = "Stately, plump Buck";
s.assign(cp,7) ;              // s == "Stately"
s.insert (s.size(),cp + 7);   // s == "Stately, plump Buck"
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函数
string类定义了两个额外的成员函数: append和replace,这两个函数可以改变string的内容。append 操作是在string末尾进行
插入操作的一种简写形式:

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的一种简写形式:
//将"4th"替换为"5th"的等价方法
s.erase(11,3) ;       // 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:
s. replace(11,3,"Fifth") ; // s == "C++ Primer Fifth Ed."
在此调用中,删除了3个字符,但在其位置插入了5个新字符。

改变string的多种重载函数
assign和append函数无须指定要替换string 中哪个部分: assign总是替换string中的所有内容,append总是将新字符追加到string末尾。
replace函数提供了两种指定删除元素范围的方式。可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定。insert函数允许我们用两种方式指定插入点:用一个下标或一个迭代器。在两种情况下,新元素都会插入到给定下标(或迭代器)之前的位置。

 

练习9.43:编写函数,接受三个string参数s、oldVal和newVal。使用迭代器及insert和erase函数将s中所有oldVal替换为newVal .测试你的程序,用它替换通用的简写形式,如,将“tho”替换为“though”, 将“thru”替换为“through“

#include <iostream>
#include <vector>
#include <string>
using namespace std;

void replace_string2(string &s, const string &oldVal, const string &newVal)
{
	int p = 0;
	while ((p = s.find(oldVal, p)) != string::npos) { // 在s中查找oldVal
		s.replace(p, oldVal.size(), newVal);//将找到的子串替换为newVal的内容
		p += newVal.size();   //下标调整到新插入内容之后
	}

}

void replace_string(string &s, const string &oldVal, const string &newVal)
{
	auto l = oldVal.size();
	if (!l)   //要查找的字符串为空
		return;
	auto iter = s.begin();
	while (iter <= s.end() - 1) { //末尾少于oldVal长度的部分无须检查
		auto iter1 = iter;
		auto iter2 = oldVal.begin();
		// s中iter开始的子串必须每个字符都与oldVal相同
		while (iter2 != oldVal.end() && *iter1 == *iter2) {
			iter1++;
			iter2++;
		}
		if (iter2 == oldVal.end()) {     // oldVal耗尽一 字符串相等
			iter = s.erase(iter, iter1); // 删除s中与oldVal相等部分
			if (newVal.size()) {         //替换子串是否为空
				iter2 = newVal.end();    //由后至前逐个插入newVal中的字符
				do {
					iter2--;
					iter = s.insert(iter, *iter2);
				} while (iter2 > newVal.begin());
			}
			iter += newVal.size();      //迭代器移动到新插入内容之后
		}
		else iter++;
	}
}

int main()
{
	string s = "tho thru tho!";
	replace_string(s, "thru", "through");
	cout << s << endl;
	replace_string(s, "tho", "though");
	cout << s << endl;
	replace_string(s, "through","");
	cout << s << endl;

    string s2 = "tho thru tho! ";
	replace_string2(s2, "thru", "through");
	cout << s2 << endl;
	replace_string2(s2, "tho", "though");
	cout << s2 << endl;
	replace_string2(s2, "through", " ");
	cout << s2 << endl;


	return 0;
}
#include <iostream>
#include <vector>
#include <string>
using namespace std;

void  name_string(string &name, const string &prefix, const string &suffix)
{
	name.insert(name.begin(),1,' ');
	name.insert(name.begin(),prefix.begin() ,prefix.end());// 插入前缀.
	name.append(" ");
	name.append(suffix.begin(),suffix.end());// 插入后缀
}

int main()
{
	string s = "James Bond";
	name_string(s , "Mr .","III") ;
		cout << s << endl;
	s = "M";
	name_string(s , "Mrs.", "III");
	cout << s << endl;

	return 0;
}

9.5.3 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 posl = name.find ("Anna"); // pos1 == 0
这段程序返回0,即子字符串"Anna"在"AnnaBelle"中第一次出现的下标。
搜索(以及其他string操作)是大小写敏感的。当在string中查找子字符串时,
要注意大小写:
string lowercase ("annabelle") ;
pos1 = lowercase.find ("Anna"); // pos1 == npos
这段代码会将posl置为npos,因为Anna与anna不匹配。
一个更复杂一些的问题是查找与给定字符串中任何一个字符匹配的位置。例如,下面
代码定位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::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; // 移动到下一个字符
}
while的循环条件将pos重置为从pos开始遇到的第一个数字的下标。只要
find_ first_ of 返回一个合法下标,我们就打印当前结果并递增pos.
如果我们忽略了递增pos,循环就永远也不会终止。

逆向搜索

string river ("Mississippi") ;
auto first_ pos = river. find("is"); //返回1
auto last_ pos = river.rfind("is"); // 返回4

●find_ last_of搜索与给定string中任何一个字符匹配的最后一个字符。
●find_ last_not_of搜索最后一个不出现在给定string中的字符。

练习9.47: 编写程序,首先查找string " ab2c3d7R4E6 "中的每个数字字符,然后查找其中每个字母字符。编写两个版本的程序,第一个要使用find_first_of, 第二个要使用find_ first_not_of。

#include <iostream>
#include <string>
using namespace std;

void find_char(string &s, const string &chars)
{
	cout << "在" << s << "中查找" << chars << "中字符" << endl;
    string::size_type pos = 0;
	while ((pos = s.find_first_of(chars, pos)) != string::npos) { // 找到字符
		cout << "pos: " << pos << ",char: " << s[pos] << endl;
		pos++;
		//移动到下一字符
	}
}
int main()
{
	string s = "ab2c3d7R4E6";
	cout << "查找所有数字" << endl;
	find_char(s,"0123456789");
	cout << endl << "查找所有字母" << endl;
	find_char(s, "abcde fghij k lmnopqrstuvwxyz"\
		" ABCDEFGHI JKLMNOPQRSTUVWXYZ");

	return 0;
}


 

9.5.4compare函数

根据s是等于、大于还是小于参数指定的字符串,s.compare 返回0、正数或负数
compare有6个版本

9.5.5数值转换

字符串中常常包含表示数值的字符。

用两个字符的string 表示数值15字符'1'后跟字符'5’一般情况,一个数的字符表示不同于其数值。数值15如果保存为16 位的short类型,则其二进制位模式为000000000001111,而字符串"15"存为两个Latin-1编码的char,二进制位模式为0011000100110101。第一个字节表示字符'1’,其八进制值为061,第二个字节表示'5’,其Latin-1编码为八进制值065。

新标准引入了多个函数,可以实现数值数据与标准库string之间的转换:
int i = 42;
string s = to_string(i); //将整数i转换为字符表示形式
double d = stod(s);     //将字符串s转换为浮点数
要转换为数值的string中第-一个非空白符必须是数值中可能出现的字符:
string s2 = "pi = 3.14";
//转换s 中以数字开始的第一个子串,结果d = 3.14
d = stod(s2.substr(s2.find_first_ of ("+-.0123456789"))) ;


   string参数中第一个非空白符必须是符号(+或-)或数字。它可以以0x或0X
开头来表示十六进制数。对那些将字符串转换为浮点值的函数,string 参数也可以以小
数点(.)开头,并可以包含e或E来表示指数部分。对于那些将字符串转换为整型值的
函数,根据基数不同,string参数可以包含字母字符,对应大于数字9的数。

 

9.6容器适配器

除了顺序容器外,标准库还定义了三个顺序容器适配器: stack、 queue和priority_ queue

适配器(adaptor) 是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。

定义一个适配器

每个适配器都定义两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。

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

默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现。我们可以在创建一个适配器时将一个命名的顺序容器作为第二s 个类型参数,来重载默认容器类型。

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

对于一个给定的适配器,可以使用哪些容器是有限制的。所有适配器都要求容器具有添加和删除元素的能力。因此,适配器不能构造在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<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();              //弹出栈顶元素,继续循环
}
intStack.push(ix); // intStack 保存0到9十个数

此语句试图在intStack的底层deque对象上调用push_back。虽然stack是基于
deque实现的,但我们不能直接使用deque操作。不能在一个 stack上调用 push_back,
而必须使用stack自己的操作push.

队列适配器

queue和priority_queue适配器定义在queue头文件中。

标准库queue使用一种先进先出(first-in, first-out, FIFO) 的存储和访问策略。进入队列的对象被放置到队尾,而离开队列的对象则从队首删除。饭店按客人到达的顺序来为他们安排座位,就是一个先进先出队列的例子。


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

练习9.52:使用stack处理括号化的表达式。当你看到一个左括号,将其记录下来。当你在一个左括号之后看到一个右括号,从stack中pop对象,直至遇到左括号,将左括号也一起弹出栈。然后将一个值(括号内的运算结果) push到栈中,表示一个括号化的(子)表达式已经处理完毕,被其运算结果所替代。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值