C++Primer 第五版——《第九章》“ 顺序容器 ”

目录

顺序容器概述

迭代器(范围)

容器类型成员

begin 和 end  成员

容器定义和初始化

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

列表初始化

标准库 array 具有固定的大小

赋值 和  swap 、assign( 仅顺序容器 使用)操作( 302P)

使用 swap

关系运算符

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

使用push_back

使用push_front

使用 insert 在容器中的特定位置添加元素(307P)

使用 insert 操作插入范围内的元素

使用 insert 的返回值

使用 emplace_front, emplace, emplace_back 操作 (C++11)308P

使用back、front 在顺序容器中访问元素 ( 309P )

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

使用at 运算符 安全的访问 数组元素

删除元素

pop_front 和 pop_back、erase 成员函数 来删除元素( 312P)

forward_list 的操作

改变容器大小操作(resize 操作)( 314Page)

容器操作可能会改变迭代器失效 ( 315P)

编写改变容器的循环程序

不要保存end 返回的迭代器

Vector 对象是如何增长的( 318Page)

使用shrink_to_fit 、capacity、reserve 、resize 成员函数来管理容器的容量(318Page)

capacity 和 size 的区别(318Page)

创建 string 对象的方法

使用 substr 操作来拷贝 string 一部分或者全部字符 ( 321P)

string 的 insert  和 erase 操作(322P)

append 和  replace 操作(323P)

string 的 append 、 assign、insert 和 replace 操作具有多个重载版本( 324P)

string 搜索( find )操作 ( 325P)

使用 string 类 的 rfind 操作从右往左搜索( 326P)

string 类 的 compare 操作( 327P)

使用 to_string 和 stod 操作 实现数值数据 和 string之间的转换( 327P)

顺序容器的适配器 —— stack、queue、priority_queue( 329P)


顺序容器概述


C++Primer 第五版——《第九章》“ 顺序容器 ”_第1张图片

C++Primer 第五版——《第九章》“ 顺序容器 ”_第2张图片

 

●  string 和  vector 元素都是连续存储的,所以可以按下标运算符来快速访问。 它们在容器的中间位置添加 或删除元素就会非常耗时, 因为每次执行 插入 或 删除操作后, 都需要移动 插入 / 删除位置之后的所有元素来保持连续存储。有时可能因为添加了一个元素还需要分配额外的内存空间, 在这种情况下, 每个元素都必须移动到新的存储空间中。 

deque两端添加或删除元素与 list 或 forward_list 添加删除的速度相当。该容器支持快速的随机访问。

array 不支持哪些操作:

  • 不支持添加删除元素以及改变容器大小的操作。
  • array 也不像其他容器那样支持高效、灵活的内存管理。
  • array 不支持 resize 操作来改变容器大小。
  • array 不支持添加删除元素
  •  由于右边运算对象的大小可能与左边运算对象的大小不同, 因此array类型不支持assign 操作。
  • 容器类型 array和string 对 swap操作将容器内容交换会导致指向容器的迭代器、引用和指针失效。(302page)
  • 也不允许使用 assign 操作。
array ia1 = { 0,1 };
array iv2(ia1.begin(),ia1.end()); // array不支持这样的拷贝操作( 295Page )

forward_list 不支持哪些操作: 

  • forward_list 没有 size 操作,因为保存或计算其大小就会比手写链表多出额外的开销。
  • forward_list 还不支持“反向容器的成员”
  •  forward_list 迭代器不支持递减运算符
  • forward_list 不支持递减迭代器. 309Page
  • forward_list 不支持  push_back、emplace_back、 pop_back、insert、emplace、 erase 、back 操作。

forward_list 支持  empty、max_size。

vector 和 string 不支持哪些操作:

  • vector 和 string 不支持  push_front、 和 emplace_front、 pop_front 操作.
string v1(10); // string 不能这样作初始化
string v1(10,"hi"); // 同样

string v1(10, 'hi' ); // 正确

list 不支持哪些操作:

  • 与vector和deque不同 , list的 迭代器不支持 < 运算, 只支持递增、递减、以及!=运算。(  原因在于这几种数据结构实现上的不同。vector和deque将元素在内存中连续保存, 而 list 则是将元素以链表方式存储, 因此前者可以方便地实现迭代器的大小比较(类似指针的大小比较) 来体现元素的前后关系。而在list中,两个指针的大小关系与它们指向的元素的前后关系并不一定是吻合的,实现 < 运算将会非常困难和低效。

 

  • 每个容器类型都有三个与大小相关的操作: size、empty、max_size,但是 forward_list 没有 size 操作。
  • 每个容器类型都支持相等运算符( == 和 != ); 除了无序关联容器外的所有容器都支持关系运算符——关系运算符将左右两边的运算对象必须是容器类型相同和元素类型相同。
  • 包括 array 在内的每个顺序容器都有一个 front 成员函数,  而除 forward_list之外 的所有顺序容器都有一个back成员函数。 这两个操作分别返回首元素和尾元素的引用。309Page
  • at  和 下标 操作只适合用于 string、vector、deque、 array.  310Page
  • 如果 resize 缩小容器, 则指向被删除元素的迭代器、引用和指针都会失 效;  对vector, string 或 deque 进行resize 操作可能导致迭代器、指针和引用失效。314Page
  • shrink_to_fit 只适用于 vector、string、deque。318P
  • capacity 和 reserve 只适用于 vector 和 string。318P
  • string 类型 和 vector 支持顺序容器的 赋值运算符 以及 assign, insert 和 erase操作。 除此之外,它们还定义了自己的 insert 和 erase 版本 ( 接受下标的版本, 顺序容器的版本是 支持 迭代器的版本)。( 322P ).
  • string 类型 还提供了接受  C 风格字符数组的 insert 和 assign 版本。 (322P)
  • string 类定义了两个额外的成员函数: append 和 replace, 这两个函数可以改变 string的内容。该操作是在 string 的末尾进行插入操作的一种简写形式。( 323P)

迭代器(范围)


下图是容器迭代器支持的所有操作:

C++Primer 第五版——《第九章》“ 顺序容器 ”_第3张图片

下表中列出的迭代器算术运算仅适用于 string,vector,deque 和 array 的迭代器。 我们不能对任何其他容器类型的迭代器使用这些操作:

C++Primer 第五版——《第九章》“ 顺序容器 ”_第4张图片

 


迭代器的范围由一对迭代器表示, 两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。 通常被称为begin 和 end, 它们标记了容器中元素的一个范围。

 注意: begin 和 end 必须指向相同的容器。 end 可以与begin 指向相同的位置, 但不能指向begin 之前的位置 。

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

  • 如果begin 与 end相等, 则范围为空
  • 如果begin 与 end不等, 则范围至少包含一个元素,  且 begin指向该范围中的第一个元素
  • 我们可以对begin递增若干次,使得begin == end
int main()
{
	vector ival = { 0,1,2,3 };
	auto _begin = ival.begin();
	auto _end =  ival.end();
	while (_begin != _end)
	{
		*_begin = 42; // ok: range isn't empty so begin denotes an element
		++_begin; // advance the iterator to get the next element
	}
	for_each(ival.cbegin(), ival.cend(), [](int s) {cout << s << " "; });
	system("pause");
	return 0;
}

输出结果为:42 42 42 42

构成迭代器范围的迭代器有何限制 ?

  • 两个迭代器 begin 和 end 必须指向同一个容器中的元素, 或者是容器最后一个元素之后的位置; 而且, 对begin反复进行递增操作, 可保证到达end,即end不在begin之前。

容器类型成员


容器类型成员中有哪些类型:

  • iterator
  • const_iterator
  • size_type
  • difference_type
  • value_type
  • reference
  • const_reference

我们可以在不了解容器中元素类型的情况下使用类型别名

如果需要对该元素类型的引用,则使用 reference 或 const_reference 。

如果需要元素类型,则使用容器的 value_type 。

// iter is the iterator type defined by list
	list::iterator iter;

	// count is the difference_type type defined by vector
	vector::difference_type count;
	system("pause");

这些声明语句使用了作用域运算符来说明我们希望使用  list类的iterator成员及vectorkint>类定义的difference_type 成员.


begin 和 end  成员


int main()
{
	list a = { "Milton", "Shakespeare", "Austen" };

	list::iterator it5 = a.begin(); // 普通迭代器,可读可写
	list::const_iterator it6 = a.begin(); // 能读取但不能修改它所指的元素值
	const list::iterator it7 = a.begin(); // 迭代器不可指向别的元素,但是可以修改它的元素值
	
	
	// 是 iterator 还是const_iterator 依赖于 a的类型
	auto it7 = a.begin(); // 仅当a是 const时, it7 是个 const_iterator
	auto it8 = a.cbegin(); // it8 是 const_iterator
	system("pause");
	return 0;
}

begin 和 end 各个版本中, 不以 c 开头的都是被重载过的。 意识就是说,实际上有两个名为 begin  成员:

  • 一个是 const 成员, 并返回容器的 const_iterator 类型。
  • 另一个是 非const 成员, 并返回容器的 iterator 类型。

到底是是 iterator 还是 const_iterator 依赖于 对象的类型

rbegin,end和rend的情况类似。当我们对一个非const 对象调用这些成员时, 得到的是返回 iterator 的版本 ( 比如上述代码中的 it5)。只有在对一个 const 对象调用这些函数时, 才会得到一个 const版本( 比如上述代码中的 it6)。与const指针和引用类似,可以将一个普通的iterator转换为对应的 const_iterator, 但反之则不行。

begin 和 cbegin 两个函数有什么不同?

返回的迭代器的类型不同;

  • 当begin与auto结合使用时,cbegin 返回的是容器 const_iterator 类型,可以用来只读地访问容器元素, 但不能对容器元素进行修改。因此,当不需要写访问时, 应该使用cbegin。
  • begin 是被重载过的, 当begin与auto结合使用时,如果对一个 非 const对象 调用 begin,返回该容器的iterator 类型; 如果对一个 const对象调用begin, 返回的是容器 const_iterator 类型

容器定义和初始化


每个容器类型都定义一个默认构造函数。array 之外,其他容器的默认构造函数都会创建一个指定类型的空容器。 array 之外,,其他容器的构造函数都接受指定容器大小和元素初始值的参数。


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


将一个新容器创建为另一个容器的拷贝的方法有两种:

  • 可以直接拷贝整个容器或者
  • (array除外)拷贝由一个迭代器对指定的元素范围。

对于第一种方法:

  • 当创建一个容器为另一个容器的拷贝, 两个容器的类型及其元素类型必须匹配。

对于第二种方法:

  • 当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了(也可以相同),而且, 元素类型也可以不同(也可以相同), 只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。
int main()
{
	list authors = { "huang","cheng","tao" };
	vectorarticles = { "a","an","the" };
 
	list list2(authors); // 正确,容器类型、元素类型都匹配
	//deque authList(authors); //错误,容器类型不匹配
	//list words(authors);  //元素类型不匹配,错误

  //正确:可以将const char*元素转换为string 。 这样拷贝元素就不需要容器和元素类型相同
 
	forward_list ft(articles.begin(), articles.end());
	auto it1 = authors.cbegin();
	auto it2 = authors.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	cout << endl;
 
	auto it3 = ft.cbegin();
	auto it4 = ft.cend();
	while (it3 != it4)
	{
		cout << *it3++ << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
 
 
输出结果为:
 
huang cheng tao
a an the

我们还可以使用接受两个迭代器表示一个范围的构造函数来拷贝一个容器中的子序列:

int main()
{
	vector authors = { "Milton","Shakespeare","Austen","huang","chengt"};
	auto it = authors.begin() + 2;// 现在 it 指向第三个位置
	deque authVector(authors.begin(), it); //拷贝元素,直到但不包括it指向的元素
 
	auto it1 = authVector.cbegin();
	auto it2 = authVector.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
 
输出结果为:
 
Milton Shakespeare

 


列表初始化


int main()
{
	list authors = { "Milton", "Shakespeare", "Austen" };
	vector articles = { "a", "an", "the" };

	vector ivec(10, -1); // ten int elements, each initialized to -1
	list svec(10, "hi!"); // ten strings; each element is "hi!"
	forward_list ivec(10); // ten elements, each initialized to 0
	deque svec(10); // ten elements, each an empty string
	system("pause");
	return 0;
}

标准库 array 具有固定的大小


在定义 array 时, 需要指定其元素类型,还要指定元素的个数。 与其它容器不同的是,一个默认构造的 array 是非空的 ( 其它的容器都是空的 );它包含了与其大小一样多的元素。这些元素都被默认初始化( 一般来说是一些垃圾值)。

  • 如果我们对 array 进行列表初始化, 初始值的数目必须等于或小于array的大小。
  • 如果初始值数目小于array的大小, 则它们被用来初始化array中靠前的元素, 所有剩余元素都会进行值初始化。
  • 在这两种情况下, 如果元素类型是一个类类型,那么该类必须有一个默认构造函数, 以使值初始化能够进行
array ia1; // ten default-initialized ints

array ia2 = {0,1,2,3,4,5,6,7,8,9}; // list initialization

array ia3 = {42}; // ia3[0] is 42, remaining elements are 0

值得注意的是:虽然我们不能对内置数组类型进行拷贝或对象赋值操作 ,  但 array 可以这样做 ( 只要 赋值号左右两边的运算对象必须具有相同的元素类型):

int main()
{
	int digs[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int cpy[10] = digs; // 错误, 不允许使用一个数组初始化另一个数组
	cpy = digs; // 错误,不能把一个数组赋值给另一个数组

	array digits = { 0,1,2,3,4,5,6,7,8,9 };
	array copy = digits; // ok:只要数组类型匹配即合法
	array copy11 = digits; //  数组类型不匹配

	system("pause");
	return 0;
}

对 6 种创建和初始化vector对象的方法, 每一种都给出一个实例。

int main()
{
	vector iva1; // 适合于元素个数和值未知,需要在程序运行中动态添加的情况。
	vectoriva2(10); // 当程序运行初期元素大致数量可预知,而元素的值需动态获取时,可采用这种初始化方式。
	vectoriva3(5, 3);
	vectoriva4 = { 0,1,2,3,33}; // 适合元素数量和值预先可知的情况。
	vector iva5{ 0,1,2,344 };
	vector iva6(iva5); // iva6 和 iva5 中的值相同, 初始化为 iva5 的拷贝
	list iva7(iva4.begin() + 1, iva4.end() - 1); // 该方法适合获取一个序列的子序列。
	system("pause");
	return 0;
}

对于接受一个容器创建其拷贝的构造函数, 和接受两个迭代器创建拷贝的构造函数有什么不同?

  • 对于接受一个容器创建其拷贝的构造函数,要求两个容器的容器类型和元素类型必须相同。
  • 接受两个迭代器创建拷贝的构造函数, 两个容器类型和元素类型相不相同都可以,只要将其拷贝的元素转换为要初始化的容器的元素类型即可。

赋值 和  swap 、assign( 仅顺序容器 使用)操作( 302P)


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

 

assign 操作允许我们从一个不同但相容的类型赋值, 或者从容器的一个子序列赋值 。

 assign 操作用其实参所指定的元素(的拷贝)替换左边容器中的所有元素。assign的实参决定了容器中将有多少个元素以及它们的值都是什么。

将一个容器创建为另一个容器的拷贝的方法还有两种:

  • 使用赋值运算符,但是要求左边和右边的运算对象具有相同的容器和元素类型。 它将右边运算对象中的所有元素拷贝到左边运算对象中。
  • (array除外)还可以使用assign 操作,此时就不要求容器类型是相同的了(也可以相同),而且, 元素类型也可以不同(也可以相同), 只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。
int main()
{
	
	list authors = { "huang","cheng","tao" };
	vectorarticles = { "a","an","the" };
 
	list list2(authors); // 正确,容器类型、元素类型都匹配
	authors.assign(articles.cbegin(), articles.cend()); //正确:可以将const char*元素转换为string
 
	auto it1 = articles.cbegin();
	auto it2 = articles.cend();
	while (it1 != it2)
	{
		cout << *it1++ << " ";
	}
	system("pause");
	return 0;
}
 
输出结果为:
 
a an the 

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

int main()
{
	// 等价于 slist1.clear();
    // 后跟 slist1.insert(slist1.begin(), 10, "Hiya!"); 表示在begin 指向的位置添加10个 Hiya

	list slist1(1); // one element, which is the empty string
	slist1.assign(10, "Hiya!"); // ten elements; each one is Hiya !

	system("pause");
	return 0;
}

使用 swap


  •  注意: 除array 外,交换两个容器中的内容的操作保证会很快——元素本身并未交换,swap只是交换了两个容器的内部数据结构。
  • 注意: 除array外, swap 不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
  • 元素不会被移动的事实意味着, 使用 swap, 除了array 和 string 外,指向容器的迭代器、引用和指针在swap之后,都不会失效。 它们仍指向swap 操作之前所指向的位置。但是,在swap之后,这些元素已经属于不同的容器了。

 

  • 注意: 与其它容器不同, swap 两个array会真正交换它们的元素。 交换两个array所需的时间与array 中元素的数目成正比。
  • 对于array,在swap操作之后 ,指针、引用和迭代器仍然绑定到它们在交换之前表示的元素。当然,该元素的值已与另一个数组中的相应元素交换 (  意思就是说本来该指针指向array1 中的 “ 1 ” 元素, swap 后,该指针现在指向 array2 中的 “ 1 ” 元素)。

关系运算符


 每个容器类型都支持相等运算符( == 和 != ); 除了无序关联容器外的所有容器都支持关系运算符——关系运算符 和 相等运算符 左右两边的运算对象必须是容器和元素类型相同即,我们可以将vector 仅与另一个vector 进行比较。 我们无法将vector 与list 或vector 进行比较。

比较两个容器实际上是进行元素的逐对比较。


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


  •  注意: 只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符比较两个容器。
  • 容器的相等运算符是使用元素的“ == ”运算符实现比较的, 而其他关系运算符是使用元素的“ < ” 运算符实现比较的。
  • 如果元素类型不支持所需运算符, 那么保存这种元素的容器就不能使用相应的关系运算符。

使用push_back


void pluralize(size_t cnt, string  &word)
{
	if (cnt > 0)
	{
		word.push_back('e'); //由于string是一个字符容器,我们也可以用push_back在string末尾添加字符
	//等价于 word+='e';
	}
}
int main()
{
	vector vec;
	string word;
	while (cin >> word)
	{
		if (word == "e")
		{
			break;
		}
		vec.push_back(word); //容器类型可以是 vector  list deque string
		
	}
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	string  sd = "huang";
	pluralize(1, sd);
	cout << "result :";
	for (auto tt : sd)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
输出结果为:
 
huang
e
huang result :h u a n g e

当我们用一个对象来初始化容器时, 或将 一个对象插入到容器中时, 实际上放入到容器中的是对象值的一个拷贝, 而不是对象本身。就像我们将一个对象传递给非引用参数一样, 容器中的元素与提供值的对象之间没有任何关联。随后对容器中元素的任何改变都不会影响到原始对象, 反之亦然。


使用push_front


int main()
{
	/*注意, deque像vector一样提供了随机访问元素的能力,但它提供了vector所
	//不支持的push_front 操作, deque保证在容器首尾进行插入和删除元素的操作都只花费常数时间。
	与vector一样,在deque首尾之外的位置插入元素会很耗时。*/
	deque vec;
	for (size_t i = 0; i != 4; ++i)
	{
		vec.push_front(i);
	}
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
 

使用 insert 在容器中的特定位置添加元素(307P)


每个insert函数的第一个参数都接受一个迭代器。 该迭代器指出了在什么位置放置新元素。 它可以指向容器中的任何位置,包括容器末尾之后的下一个位置。

虽然某些容器不支持 push_front  操作,但它们对于insert 插入元素的开始位置的操作并没有限制 。因此我们可以将元素插入到容器的开始位置,  而不必担心容器是否支持push_front 。意思就是说 vector 和 string 可以使用 insert 操作将元素插入到头部,然而这样做可能会很耗时。


使用 insert 操作插入范围内的元素


int main()
{
	vector v = { "huang","cheng","tao","taotao" };
	list  slist;
	slist.insert(slist.end(), 3, "Anna");
	for (auto tt : slist)
	{
		cout << tt << " ";
	}
	cout << endl;
 
 
	slist.insert(slist.begin(), v.end() - 2, v.end());
	for (auto tt : slist)
	{
		cout << tt << " ";
	}
	cout << endl;
 
 
	slist.insert(slist.end(), { "these","words","will","go","at" });
	for (auto tt : slist)
	{
		cout << tt << " ";
	}
 
	//运行时错误:迭代器表示要拷贝的范围,不能指向与目的位置相同的容器
	slist.insert(slist.begin(), slist.begin(), slist.end());

 // 如果我们传递给 insert 一对迭代器,它们不能指向添加元素的目标容器。
 
	cout << endl;
	system("pause");
	return 0;
}
 
输出结果为:
Anna Anna Anna
tao taotao Anna Anna Anna
tao taotao Anna Anna Anna these words will go at

使用 insert 的返回值


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

int main()
{
	
	string word;
	list  lst;
	auto iter = lst.begin();
	while (cin >> word)
	{
		if (word == "e")
		{
			break;
		}
		iter = lst.insert(iter, word); //等价于调用 push_front,
 // 在迭代器 iter 之前插入元素 word, 然后返回新添加元素的迭代器,即该迭代器始终指向第一个元素。
	}
	for (auto tt : lst)
	{
		cout << tt << " ";
	}
	system("pause");
	return 0;
}
 
 
输出结果为:
huang
cheng
e
cheng huang 

向 list 插入元素不会使所有指向容器的迭代器、引用、指针失效。但是vector、string、deque 会。


使用 emplace_front, emplace, emplace_back 操作 (C++11)308P


当我们调用 push 或 insert 的成员时,我们传递的是元素类型的对象,这些对象被拷贝到容器中。

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

而调用 push_back 、insert、push_front、这样的操作时,则会创建一个局部临时对象,并将其压入容器中。
 

 注意: emplace 函数的参数根据元素类型而变化 , 参数类型与元素类型的构造函数相匹配。 emplace 函数是在容器中直接构造(创建)元素。 说白了就是使用 emplace 的相关操作时 , 操作的元素类型必须有相应的构造函数,不管是显式的还是隐式的。


使用back、front 在顺序容器中访问元素 ( 309P )


如果我们需要访问容器中的一个元素( 通过解引用迭代器),那么我们必须检查是否有元素。 如果容器中没有元素,访问的操作是未定义的。

int main()
{
	
	vector c = { "huang","cheng","tao","taotao" };
	if (!c.empty())//在解引用一个迭代器或调用front或back之前检查是否有元素
	{
		auto va1 = *c.begin(), val2 = c.front(); //val和va12是c中第一个元素值的拷贝
		cout << "输出va1的值:" << va1 << ",输出va2的值:" << val2 << endl;
 
		auto last = c.end();
		//forward_list 不支持递减运算符,也不支持递减迭代器,还不支持 back  操作
		auto va3 = *(--last), val4 = c.back(); //val和va12是c中最后一个元素值的拷贝
		cout << "输出va1的值:" << va3 << ",输出va2的值:" << val4 << endl;
	}
	for (auto tt : c)
	{
		cout << tt << " ";
	}
	system("pause");
	return 0;
}
 
输出结果为:
 
输出va1的值:huang,输出va2的值:huang
输出va1的值:taotao,输出va2的值:taotao
huang cheng tao taotao 

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


 在容器中访问元素的成员函数 (即, front, back、下标 和 at )返回的都是引用。如果容器是一个const对象, 则返回值是const的引用。如果容器不是const的,则返回值是普通引用,  可以使用返回的引用改变元素的值:

int main()
{

	vector c = { "huang","cheng","tao","taotao" };
	if (!c.empty())
	{
		c.front() = "taotaotao"; //改变第一个元素的值
		auto &v = c.back(); // c指向最后一个元素的引用,然后修改它的值
		v = "tohuang";

		//v2不是一个引用,它是c.back()的一个拷贝, 所以未改变c中的元素
		auto v2 = c.back();
		v2 = "ttttt";
	}
	for (auto tt : c)
	{
		cout << tt << " ";
	}

	const vector v = {0,1,2,5,6 };
	v.front() = 55; // 错误, 因为容器是一个 const对象,则返回值是const的引用,不能给常量赋值
	system("pause");
	return 0;
}

输出结果为:taotaotao cheng tao tohuang

使用at 运算符 安全的访问 数组元素


如果我们希望确保下标是合法的,可以使用at 成员函数。 at 成员函数类似下标运算符, 但如果下标越界, at 会抛出一个 out_of_range异常。

int main()
{
	vector svec; // empty vector

	cout << svec[0]; // run-time error: there are no elements in svec!

	cout << svec.at(0); // throws an out_of_range exception

	system("pause");
	return 0;
}

删除元素


  • 删除 deque 中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。 指向 vector 或 string 中删除点之后位置的迭代器、引用和指针都会失效。
  • 删除元素的成员函数并不检查其参数。在删除元素之前, 程序员必须确保它 (们)是存在的。使用 empty 操作来检查。

pop_front 和 pop_back、erase 成员函数 来删除元素( 312P)


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


int main()
{

	list  slist = { "huang","cheng","tao","taotao" };
	if (!slist.empty())
	{
		slist.pop_front(); // 删除头部元素
		slist.pop_back();
	}
	for (auto tt : slist)
	{
		cout << tt << " ";
	}
	cout << endl;

	list  lst = { 0,1,2,3,4,5,6,7,8,9 };
	auto it = lst.begin();
	while (it != lst.end())
	{
		if (*it % 2)
		{//表示删除迭代器it指向的元素,然后该迭代器指向删除元素下一个元素
			it = lst.erase(it); 
		}
		else
			++it;
	}
	for (auto s : lst)
	{
		cout << s << " ";
	}
	cout << endl;


	vector  lst2 = { 0,1,2,3,4,5,6,7,8,9 };
	auto elem1 = lst2.begin();
	auto elem2 = (lst2.end() - 2);
	if (!lst2.empty())
	{
		elem1 = lst2.erase(elem1, elem2); // 迭代器 elem1 指向我们要删除的第一个元素, elem2 指向我们要删除的最后一个元素之后的位置。

	}
	for (auto s : lst2)
	{
		cout << s << " ";
	}

	cout << "\nlst2 中的元素个数为:" << lst2.size() << endl;
	lst2.erase(lst2.begin(), lst2.end()); //删除全部元素
	lst2.clear(); // 等价调用

	cout << endl;
	system("pause");
	return 0;
}



输出结果为:  

cheng tao
0 2 4 6 8
8 9
lst2 中的元素个数为:2

forward_list 的操作


、
 
int main()
{
	
	forward_list 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 = flst.erase_after(prev); // 删除该元素,并把迭代器移动下一位
		}
		else
		{
			// 把prev移动到curr指向的位置,然后curr指向下一个元素,此时prev指向的是curr指向的之前的元素
			// prev 永远指向 要删除元素的前一个位置的元素
			prev = curr;  
			++curr;
		}
	}
	cout << "输出flst 中的所有元素:";
	for (auto s : flst)
	{
		cout << s << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
 
 

改变容器大小操作(resize 操作)( 314Page)


  • 如果当前大小大于所请求的新大小, 容器后部的元素会被删除; 如果当前大小小于新大小,会将新元素添加到容器后部。
  •  resize 操作接受一个可选的元素值参数, 用来初始化添加到容器中的元素。 如果调用者未提供此参数, 新元素进行值初始化。
  •  如果容器保存的是类类型元素, 且 resize 向容器添加新元素, 则我们必须提供初始值, 或者元素类型必须提供一个默认的构造函数。
int main()
{
	
	list ilist(10, 42);
	ilist.resize(15); //将5个值为0的元素添加到ilist的末尾
	cout << "输出结果为:";
	for (auto s : ilist)
	{
		cout << s << " ";
	}
	cout << endl;
	ilist.resize(25, -1);//将10个值为-1的元素添加到ilist的末尾
	cout << "输出结果为:";
	for (auto s : ilist)
	{
		cout << s << " ";
	}
	cout << endl;
	ilist.resize(5); //从ilist末尾删除20个元素
	cout << "输出结果为:";
	for (auto s : ilist)
	{
		cout << s << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
 
 
输出结果为:
输出结果为:42 42 42 42 42 42 42 42 42 42 0 0 0 0 0
输出结果为:42 42 42 42 42 42 42 42 42 42 0 0 0 0 0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
输出结果为:42 42 42 42 42

接受单个参数的 resize版本对元素类型有什么限制?

  • 对于元素是类类型, 则单参数 resize 版本要求该类型必须提供一个默认构造函数。不管是隐式的还是显式的。

接受两个参数的 resize版本对元素类型有什么限制?

  • resize 的第二个参数初始值的类型要与容器的元素类型相容。 意思就是说初始值的类型是可以转换为元素类型的。

容器操作可能会改变迭代器失效 ( 315P)


指向容器元素的指针、引用、迭代器可能会失效,发生这样的情况一般来说是向容器中添加元素和删除元素了。 我们不应该使用失效的指针、引用或迭代器,因为失效的将不再引用任何元素, 我们有时候可以在使用时使用 empty 操作来检查看是否失效。

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

编写改变容器的循环程序


 注意: 添加删除vector、string、或者deque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。 程序必须保证每个循环中都需要更新迭代器、引用或指针的正确位置。

如果循环中调用的是inserterase 的相应版本, 那么更新迭代器很容易, 因为这些操作都返回迭代器。

int main()
{
	
	vector vi = { 0,1,2,34,5,6,7,8,9 };
	auto iter = vi.begin();
	// 删除偶数元素,复制奇数元素
	while (iter != vi.end())
	{
		if (*iter % 2)
		{
			iter = vi.insert(iter, *iter); //复制当前的奇数元素
			++++iter; //向前移动迭代器,跳过当前元素以及插入到它之前的元素
			//iter += 2;
		}
		else
			iter = vi.erase(iter); //删除偶数元素
		// 此时不应该向前移动迭代器, iter指向我们删除的元素之后的元素
	}
	cout << "输出结果为:";
	for (auto s : vi)
	{
		cout << s << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
 
 
输出结果为:1 1 5 5 7 7 9 9

不要保存end 返回的迭代器


注意:当我们添加 / 删除vector或string的元素后,或在deque中首元素之外的任何位置添加删除元素后, 原来end 返回的迭代器总是会失效。

● 注意:如果在一个循环程序中插入删除 deque、vector、string 中的元素,必须在每次插入删除操作后重新更新 end()的新位置,而 不能使用在循环之前保存的end 返回的迭代器,然后一直当做容器的末尾迭代器使用。
 

int main()
{
	vector vi = { 0,1,2 };
	auto iter = vi.begin();
	
	while (iter != vi.end())
	{
		++iter;  //向后移动一位,因为我们想在该元素之后插入元素
		iter = vi.insert(iter, 422);
		++iter; // 插入新值后 ,向后移动一位
	}
	cout << "输出结果为:";
	for (auto s : vi)
	{
		cout << s << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
 
输出结果为:0 422 1 422 2 422

练习9.31:

list 和 forward_list 与其他容器的一个不同是:

迭代器不支持加减运算。 因为链表中元素并非在内存中连续存储, 因此无法通过地址的加减在元素间远距离移动。因此,应多次调用++来实现与迭代器加法相同的效果。

int main()
{

	list vi = { 0,1,2,34,5,6,7,8,9 };
	auto iter = vi.begin();
	// 删除偶数元素,复制奇数元素
	while (iter != vi.end())
	{
		if (*iter % 2)
		{
			 vi.insert(iter, *iter); //复制当前的奇数元素
			++iter; 
			
		}
		else
		{
			 iter = vi.erase(iter); //删除偶数元素
		}
		
	}
	cout << "输出结果为:";
	for (auto s : vi)
	{
		cout << s << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

因为 forward_list 是单向链表结构, 添加或删除元素时, 需将被删元素之前的那个元素的前驱指针指向为被删元素之后的那个元素, 因此需维护“前驱”、“后继”两个迭代器。

int main()
{

	forward_list vi = { 0,1,2,34,5,6,7,8,9 };
	auto curr = vi.begin(); // 首节点
	auto prev = vi.before_begin(); // 首节点的前驱节点
	// 删除偶数元素,复制奇数元素
	while (curr != vi.end())
	{
		if (*curr % 2)
		{
			curr = vi.insert_after(curr, *curr); //插入到当前元素之后
			prev = curr; // prev 始终指向新插入的新元素
			++curr; // 移到下一个未处理的第一个元素
		}
		else
		{
			 curr = vi.erase_after(prev); //删除偶数元素
		}
		
	}
	cout << "输出结果为:";
	for (auto s : vi)
	{
		cout << s << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

练习9.32:

  • 不合法,一般来说很多编译器对实参求值、然后向形参传递的处理顺序都是由右至左的。  这意味着, 会首先对 *iter++ 求值, 由于 后置的 ++ 运算符 比 解引用 * 运算符的优先级高,所以先把iter 递增移动下一个元素,然后解引用该元素。 然而这样会改变iter 的位置,但是该代码并不能正确的更新迭代器的位置。 但是在对第一个参数求值时,该迭代器指向的是错误的位置, 程序会发生异常崩溃。

练习9.33: 

向vector中插入新元素后, 原有迭代器都会失效。因此,不将insert ()返回的迭代器赋予begin, 会使begin失效。继续使用begin会导致程序崩溃。


Vector 对象是如何增长的( 318Page)


如果向 vector 和 string添加新元素,但此时并没有内存空间存放新元素。 容器是不可能在其它内存位置容纳新元素, 因为vector 和 string 的存储的元素必须是连续的。

此时是如果想要容纳新的元素,容器必须分配新的内存空间来保存已有元素和新元素, 将已有元素从旧位置移动到新空间中, 然后添加新元素, 释放旧存储空间。但这并不是一个好的方法。

有一种更好的方法是:

当不得不获取新的内存空间来存放新元素时( 指的是:只有当需要的内存空间超过当前容量时),vector 和 string 的实现通常会分配 比新的内存空间需求更大的内存空间。这样,就不需要每次添加新元素都重新分配容器的内存空间了。

这种分配策略比每次添加新元素时都重新分配容器内存空间的策略要高效得多。事实上,它的性能足够好,在实践中,vector 和 string 通常比 list 或deque更有效添加元素,即使vector 在每次重新分配内存时都必须移动它的所有元素。


使用shrink_to_fit 、capacity、reserve 、resize 成员函数来管理容器的容量(318Page)


注意:reserve 操作并不改变容器中元素的数量, 它仅影响 vector 预先分配多大的内存空间。

只有当需要的内存空间超过当前容器的容量时, reserve调用才会改变vector的容量。如果请求的空间大小大于当前容器的容量, reserve至少分配与请求一样大的内存空间(可能更大)。

 

如果请求的大小小于或等于当前容量,则reverse不做任何事。特别是, 当请求的大小 小于当前容量时, 容器是不会退回请求的内存空间。因此, 在调用reserve之后, 该容器的 capacity 将会大于或等于传递给 reserve 的参数。意思就是说如果传递给 reverse 的参数是小于当前容器的容量时,那么当调用之后。 该容器的容量将会大于或等于传递给 reserve 的参数。 所以说如果不是所需要,千万不要给reserve 传递 小于当前容器容量的参数。

类似地,resize成员只会改变容器中的元素数量,而不改变容器的容量。我们不能使用 resize 来减少容器预留的内存空间。

如果想退回 deque,vector 或 string 中不需要的内存空间,可以使用 shrink_to_fit 。该表示我们不再需要任何多余的容量。但是,具体的实现可以忽略这个请求。也就是说, 调用shrinkto-fit也并不保证一定退回内存空间。


capacity 和 size 的区别(318Page)


capacit 与 size的区别是:

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

举个例子: 

vector ivec = { 0,1,2,3};

size 的个数是4

capacity 的值依赖于具体的实现

int main()
{
	vector ivec;
	// size应该为0; capacity的值依赖于具体实现
	cout << " ivec 的 size :" << ivec.size()
		<< " ,capacity 的 size : " << ivec.capacity() << endl;

	//添加元素
	for (vector::size_type ix = 0; ix != 24; ++ix)
	{
		ivec.push_back(ix);
	}
	//'size应该为24; capacity应该大于等于24,capacity至少与size一样大,具体值依赖于标准库实现
	cout << "添加元素之后的";
	cout << " ivec 的 size :" << ivec.size()
		<< " ,capacity 的 size : " << ivec.capacity() << endl;
	cout << "输出结果为:";
	for (auto s : ivec)
	{
		cout << s << " ";
	}

	cout << "\n\n下面分配一些额外的内存空间!" << endl;
	ivec.reserve(50);
	cout << "\n输出分配空间后 ivec 的 size 的大小:" << ivec.size()
		<< " , ivec 的 capacity 的大小:" << ivec.capacity() << endl;


	cout << "\n接下来用光这些内存!" << endl;
		while (ivec.size() != ivec.capacity())
		{
			ivec.push_back(2);
		}
	cout << "输出 ivec 的 size 的大小:" << ivec.size()
		<< " , ivec 的 capacity 的大小:" << ivec.capacity() << endl;

	// 只要没有操作需求超出vector的容量, vector就不能重新分配内存空间。
	// 现在在添加一个元素,vector 就需要重新分配内存了
	cout << "\n 现在在添加一个元素 到 vector中:";
	ivec.push_back(44555);
	cout << "\n输出 ivec 的 size 的大小:" << ivec.size()
		<< " , ivec 的 capacity 的大小:" << ivec.capacity() << endl;

	//可以调用shrink_to_fit来要求vector将超出当前大小的多余内存退回给系统
	cout << "\n调用shrink_to_fit 来请求退回多余的内存: ";
	ivec.shrink_to_fit(); //调用shrink-to-fit只是一个请求,标准库并不保证退还内存
	cout << "\n输出 ivec 的 size 的大小:" << ivec.size()
		<< " , ivec 的 capacity 的大小:" << ivec.capacity() << endl << endl;
	system("pause");
	return 0;
}

输出结果为:

 ivec 的 size :0 ,capacity 的 size : 0
添加元素之后的 ivec 的 size :24 ,capacity 的 size : 28
输出结果为:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

下面分配一些额外的内存空间!

输出分配空间后 ivec 的 size 的大小:24 , ivec 的 capacity 的大小:50

接下来用光这些内存!
输出 ivec 的 size 的大小:50 , ivec 的 capacity 的大小:50

 现在在添加一个元素 到 vector中:
输出 ivec 的 size 的大小:51 , ivec 的 capacity 的大小:75

调用shrink_to_fit 来请求退回多余的内存:
输出 ivec 的 size 的大小:51 , ivec 的 capacity 的大小:51
  • 一个空的 vector 的size 和 capacity 的大小都是0.
  • 注意: 只要没有操作需求超出vector的容量, vector 就不会重新分配内存空间。vector只有当迫不得已时才分配新的内存空间。

那么 vector 何时可能会重新分配内存空间?

  • 当在执行 insert 操作时,该容器的 size 和 capacity 的大小正好相等时
  • 当执行 resize 或 reserve 操作时, 该容器的 size 超过了 capacity 时

那么会分配多少当前容器大小额外的空间,取决于具体实现。

不管怎样,当分配新的空间后,vector 会将已有元素从旧位置移动到新空间中, 然后添加新元素, 最后释放旧存储空间。


创建 string 对象的方法



int main()
{
	const char *cp = "Hello World!!!";  // 以空字符结束
	char noNull[] = { 'H', 'i' , 'o','v'}; // 不以空字符结束
	char toNull[] = { 'H','v','\0' }; // 以空字符结束

	// 以 const char* 创建 string时,指针指向的数组必须以空字符结尾,因为这样拷贝操作就会在遇到空字符时停止
	string s1(cp); 
	cout << "输出 s1的结果:" << s1 << endl;

	// 如果 还为构造函数传递了一个计数值,那么数组就不需要以空字符结尾
	string s2(noNull, 2); 
	cout << "输出 s2的结果:" << s2 << endl;


	// 如果我们没有给构造函数传递一个计数值,数组也没有以空字符结尾,行为未定义
	//string s3(noNull); // 错误,noNull不是以空字符结束
	// cout << "输出 s3的结果:" << s3 << endl;

	// 如果我们传递的计数值大于数组大小,但是数组也没有以空字符结尾,行为未定义
	string  s99(noNull, 16); // 错误, 计数值超过数组大小
	cout << "输出 s99的结果:" << s99 << endl;

	
	string s4(toNull);
	cout << "输出 s4的结果:" << s4 << endl; // 正确, toNull 是以空字符结束


	string s5(cp + 6, 5);
	cout << "输出 s5的结果:" << s5 << endl;


	// 当从一个 string 拷贝字符时,可以提供一个可选的开始位置和一个计数值。开始位置必须小于或等于给定的string的大小。比如下面的 6 

使用 substr 操作来拷贝 string 一部分或者全部字符 ( 321P)


 substr 操作它返回一个 string,该操作的功能是从目标 string 对象中拷贝一部分或者全部字符。  我们还可以传递 substr 两个可选的参数: 分别是开始位置和计数值。

int main()
{
	string s("hello world");

	string s2 = s.substr(0, 5); // s2 = hello
	cout << "输出s2的结果:" << s2 << endl;

	string s6 = s.substr();  // s6 = hello world
	cout << "输出s6的结果:" << s6 << endl;

	string s3 = s.substr(6); // s3 = world
	cout << "输出s3的结果:" << s3 << endl;


	string s4 = s.substr(6, 11); // s4 = world
	cout << "输出s4的结果:" << s4 << endl;

	//string s5 = s.substr(12); // throws an out_of_range exception

	system("pause");
	return 0;
}

输出结果为:

输出s2的结果:hello
输出s6的结果:hello world

输出s3的结果:world
输出s4的结果:world

如果 第一个可选参数 —— 开始位置超过了 string 的大小, 则substr函数抛出一个outofrange异常 —— 比如上述的s5。如果开始位置加上计数值大于 string 的大小, 则substr会调整计数值, 只拷贝到string的末尾 —— 比如上述程序的 s4.


string 的 insert  和 erase 操作(322P)


使用下标的方式实现:

int main()
{
	string s("hello world");
	s.insert(s.size(), 5, '!'); // 在s 的末尾插入5个 !
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	s.erase(s.size() - 5, 5); // 删除 s的最后5个字符
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

hello world!!!!!
hello world
  • s.insert( pos , n , t) —— 在 pos 指向的位置之前插入 n (不能为负数)个值为t的元素, pos 可以是一个下标或者 迭代器。返回一个指向 s 的引用。

使用迭代器版本实现:

int main()
{
	string s("hello world");
	s.insert(s.end(), 5, '!'); // 在s 的末尾插入5个 !
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	s.erase(s.end() -5, s.end()); // 删除 s的最后5个字符
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	system("pause");
	return 0;
}

将以空字符结尾的字符数组 insert 或 assign 给一个 string :

int main()
{
	string s("hello world");
	const char *cp = "Stately, plump Buck";
	s.assign(cp, 7); // s == Stately
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;

	
	s.insert(s.size(), cp + 7); // s == Stately, plump Buck
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	system("pause");
	return 0;
}
  • ​​​​​​ 当 insert  和 assign 操作一个 const char* 字符数组时, 数组中最后的那个空字符不会被赋值 或者 插入到 string 中。
  • 字符数组必须是 const char*   char cp [], const char[] 其中的一个, 不能是 char*

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

int main()
{
	string s = "some string", s2 = "some other string";
	s.insert(0, s2); // 在s 中位置0之前插入 s2 的拷贝
	
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;

	s.insert(0, s2, 0, s.size()); // 在 s[0] 之前插入 s2 中 从s2[0]开始的 s2.size ()个字符
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:
some other stringsome string
some other stringsome other stringsome string

append 和  replace 操作(323P)


int main()
{
	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
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;

	for (auto tt : s2)
	{
		cout << tt;
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

C++ Primer 4th Ed.
C++ Primer 4th Ed.

replace 操作是调用 erase 和 insert 的一种简写方法:


int main()
{
	string s("C++ Primer 4th Ed."), s2 = s; //  s 和 s2 == "C++ Primer"
	
	s.erase(11, 3);
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	s.insert(11, "5th");
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;

	s2.replace(11, 3, "5th");
	for (auto tt : s2)
	{
		cout << tt;
	}
	cout << endl;

	s.replace(11, 3, "Fifth");
	for (auto tt : s)
	{
		cout << tt;
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

C++ Primer  Ed.
C++ Primer 5th Ed.
C++ Primer 5th Ed.
C++ Primer Fifth Ed.

string 的 append 、 assign、insert 和 replace 操作具有多个重载版本( 324P)


assign 和 append 函数不需要指定string 的哪个部分被改变:

  • assign 总是替换字符串的整个内容
  • append 总是添加到字符串的末尾。

string 搜索( find )操作 ( 325P)


  •  string 类 有 6个 不同的搜索函数,每个函数都有 4 个 重载版本。如果搜索成功,每个搜索操作都返回一个 string :: size_type值,该值返回对应位置的下标。
  • 如果搜索失败,则该函数返回一个名为 string :: npos 的static 成员。标准库将 npos 定义为一个 const string::size_type 类型的值 , 该值初始化为 -1 。
  • 因为 npos 是 unsigned 类型,所以此初始值意味着 npos 等于任何string 可能具有的最大可能大小。因此, 用一个 int 或 其他带符号类型来保存这些函数的返回值不是一个好主意
int main()
{
	string name("AnnaBelle");
	auto pos1 = name.find("Anna"); // 在 name 中 查找"Anna" 第一次出现的下标
	cout << pos1 << endl; // pos1 == 0
	system("pause");
	return 0;
}

注意: 搜索 ( 以及其他string操作 ) 操作是大小写敏感的。当在string中查找子字符串时, 要注意大小写:


int main()
{
	string lowercase("annabelle");
	auto pos1 = lowercase.find("Anna");
	cout << pos1 << endl; //pos1 == npos
	system("pause");
	return 0;
}

string 类的 find_first_of 函数,该函数的功能是:在 name 中查找与给定字符串( numbers)中第一个字符匹配的位置。


int main()
{
	// 把 name 中的每一个字符依次 跟 numbers 中每一个字符比较,看是否相等,在 names 中 找到第一个与numbers 相等的字符,返回 name 中的下标
	// 说的不清楚,比如说 name 中的第一个字符是d, 把d 跟 numbers 中每一个字符比较,在numbers 中没有d,所以到 name 中第二个字符s
	// 过程一样,没找到; 然后是 name 中的d,在numbers 中没有d; 然后name 中的2, 在 numbers 找到2, 返回 在 name 中2 的下标,即3.
	string numbers("0r23456789"), name("dsd2");
	auto pos = name.find_first_of(numbers);
	cout << pos << endl;
	system("pause");
	return 0;
}

string 类的 find_first_not_of 函数,

int main()
{
	// 首先 dept 中的0 在 number 中找0 ,找到; 到 dept 中的3,找到;.....;到 dept 中 1, numbers 中没有1, 返回 dept中1 的下标,即3
	string dept("037143");
	string numbers("023456789");
	
	auto pos = dept.find_first_not_of(numbers);
	cout << pos << endl;
	system("pause");
	return 0;
}
int main()
{
	string::size_type pos = 0;
	string numbers("0123456789"), name("dsd2");
	while ((pos = name.find_first_of(numbers, pos))!= string::npos) 
	{
		cout << "found number at index: " << pos << " element is " << name[pos] << endl;
		++pos; // move to the next character
	}
	system("pause");
	return 0;
}

输出结果为:

found number at index: 3 element is 2

使用 string 类 的 rfind 操作从右往左搜索( 326P)



int main()
{
	string river("Mississippi");
	auto first_pos = river.find("is"); // returns 1
	cout << "输出 first_pos :" << first_pos << endl;
	auto last_pos = river.rfind("is"); // returns 4
	cout << "输出 last_pos :" << last_pos << endl;
	system("pause");
	return 0;
}

输出 first_pos :1
输出 last_pos :4

string 类 的 compare 操作( 327P)



使用 to_string 和 stod 操作 实现数值数据 和 string之间的转换( 327P)


int main()
{
	int i = 42;
	string s = to_string(i); 
	cout << "输出s:" << s << endl; // s ==42,将整数之转换为字符表示形式
	double d = stod(s);
	cout << "输出d:" << d << endl;  // d ==42 ,将字符串s转换为浮点数
	system("pause");
	return 0;
}
int main()
{
	string s2 = "pi = 3.14";
	double d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
	cout << "输出d:" << d << endl; // d == 3.14
	system("pause");
	return 0;
}

顺序容器的适配器 —— stack、queue、priority_queue( 329P)


stack 可以接受的顺序容器有:

  • deque、vector、list

每个适配器都定义两个构造函数:

  • 第一是默认构造函数,它创建一个空对象
  • 第二个构造函数的参数接受一个容器,然后使用该容器来初始化适配器
#include // 记得加头文件

int main()
{
	deque deq{1,4,5,6,3,7 };
	stack stk(deq); // stk 和 deq 的元素类型要一致
	system("pause");
	return 0;
}

在创建适配器时,我们可以在 “ < > ” 中通过将顺序容器命名为第二个类型参数来重载默认容器类型:

int main()
{
	vector svec;
	// 在 vector 上实现的空栈
	stack> str_stk;
	// str_stk2 是在 vector上实现的,初始化时保存 svec 的拷贝
	stack> str_stk2(svec);
	system("pause");
	return 0;
}

这三个适配器,可以使用哪些容器是有限制的。

  • 其中有一个要求就是所有适配器都要求能够添加和删除元素。因此,不能用 array 来构造适配器
  • 类似的, 我们也不能用 forward_list 来构造适配器, 因为所有适配器都要求容器具有添加、删除以及访问尾元素的能力。
  • stack 只要求 该容器具有 push_back、pop_back 和 back 操作, 因此可以使用除 array 和 forward list 之外的任何容器类型来构造stack,
  • queue 适配器要求该容器具有 back push_back front 和 push_front 操作, 因此我们可以用 list 或 deque 来构造适配器。quene 不能 在vector上 构造。
  • priority_queue 适配器要求该容器具有 除了 front push_back 和 pop_back 操作之外还要求随机访问能力,  因此可以拿 vector 或 deque 来构造,priority_queue 不能 在 list 上 构造。
int main()
{
	stack intStack; // empty stack

	for (size_t ix = 0; ix != 10; ++ix)
	{
		intStack.push(ix); 
	}
	while (!intStack.empty()) 
	{ 
		int value = intStack.top();
		
		intStack.pop(); 
	}
	system("pause");
	return 0;
}
  • 注意:每个容器适配器都根据底层容器类型提供的操作定义自己的操作。我们只能使用适配器自己的操作,而不能使用底层容器类型的操作。

  • quene 和 priority_queue 是在 queue  头文件中定义的。

  • 默认情况下,stack 和 queue 都是默认以 deque 方式实现的,stack 也可以在 list 或 vector 之上实现。而 priority_queue 是默认在 vector 上实现的。


本章总结完.....

 

你可能感兴趣的:(Primer,2,C++,Primer,中文版(第五版),C++,Primer,顺序容器)