C++Primer 第五版 —— 《第十章 》泛型算法

目录

目录

Preface

概述( find 算法)

find 算法是如何工作的

只读算法(find、count、accumulate 338P)

使用 accumulate 算法将 vector中的 string 元素连接起来

用equal 操作两个序列(339P)

使用 fill 和 fill_n 算法向容器写入元素(340P)

介绍  back_inserter 插入迭代器向容器中插入元素 ( 341P)

使用 copy 和 replace 、replace_copy 拷贝算法 (341P)

使用 sort 、unique 算法来重排容器元素(342P)

         使用 sort 算法的重载版本传递谓词(334P)

使用 stable_sort 排序算法(345P)

partition 算法(345P)

介绍 lambda 表达式、find_if 算法(346P)

向 lambda 传递参数(347P)

使用 lambda 的捕获列表 和 find_if 算法( 347P)

for_each 算法( 348P)

lambda 捕获 和 返回(349P)

在 lambda 捕获列表中使用 “ 值捕获” (350P)

 

在 lambda 捕获列表中使用 “ 引用捕获” (350P)

lambda 捕获列表采用“ 隐式捕获 ” (351P)

lambda 捕获列表同时采用“ 隐式捕获 ” 和 “ 显式捕获 ”(351P)

lambda 捕获列表的简介(352P)

使用 mutable 关键字来使lambda 函数体中可以修改被捕获变量的值(352P)

为 lambda 指定返回类型 和 使用 transform 算法 (353P)

迭代器的种类(357P)

插入迭代器(358P)

反向迭代器( 363P)

反向迭代器 的 base 成员函数 (363P)

泛型算法中迭代器的类型(365P)

泛型算法 5 种迭代器支持的操作类型 ( 365P)

算法传递参数的规范(367P)

算法参数的命名规范(368P)

reverse 、 reverse_copy、remove_if、remove_if_copy 算法 (369P)

list 和 forward_list 提供特有的算法操作(369P)

list 和 forward_list 提供了 Splice 算法 (370P)

 


Preface


 泛型算法它实现了一些经典的算法公共接口,比如说: 排序、搜索值、替换和删除一个特定值。那么泛型算法可以用于不同的元素类型和容器类型(其中包括标准库类型,如vector; 内置的数组类型; 和其它类型的序列)。


概述( find 算法)


  使用泛型算法的时候需要包含头文件 algorithm , 头文件 numeric 中定义了一组数值泛型算法。

 那么一般来说, 这些泛型算法是不会直接操作容器中的元素,相反,它们通过遍历由两个迭代器限定的元素范围来进行操作 ( 泛型算法操作迭代器范围)。通常,当算法遍历范围内的元素时,它会对范围内的每个元素执行某些操作。


int main()
{
	int val = 42; // 我们将要查找的值
	vector vec{ 10,445,30,42,70,90};

	// 如果在vec中找到想要的元素,则返回结果指向它,否则返回结果为vec.cend ()
	auto result = find(vec.cbegin(), vec.cend(), val);

	cout << "值 " << val << (result == vec.cend() ? " 没有出现! " : " 出现了!") << endl;

	// 迭代器范围是 vec[1] 至 vec[4]( 但不包括 vec[4]) 的元素
	auto res = find(vec.cbegin() + 1, vec.cend() - 2, val); //还可以在序列的子范围中查找 ,传递首尾迭代器就可以了
	if (res != (vec.cend() - 2))
	{
		cout << "找到值 42" << endl;
	}
	else
		cout << "没找到值 42" << endl;
	system("pause");
	return 0;
}

输出结果为:

值 42 出现了!
找到值 42

如果范围中无匹配元素, 则 find 返回尾后迭代器来表示搜索失败。因此,我们可以通过比较返回值和第二个参数( 尾后迭代器 )来判断搜索是否成功。


int main()
{
	list vec = { "huang","chengt","tao","a value" };
	string val = "a value"; // 要查找的元素值
	auto result = find(vec.cbegin(), vec.cend(), val);
	cout << "值 " << val << (result == vec.cend() ? " 没有出现! " : " 出现了!") << endl;
	system("pause");
	return 0;
}

类似的,  由于指针就像内置数组上的迭代器一样, 我们可以用 find在数组中查找给定值:

using std::find;
using std::begin;
using std::end;

int main()
{
	int ia[] = { 27, 210, 12, 47, 109, 83 };
	int val = 83;
	int* result = find(begin(ia), end(ia), val);
	cout << "值 " << val << (result == end(ia) ? " 没有出现! " : " 出现了!") << endl;

	//在从ia[1]开始,直至 (但不包含)ia[4]的范围内查找元素
	auto res = find(ia + 1, ia + 4, val);//还可以在序列的子范围中查找 ,传递首尾迭代器就可以了
	if (res != (ia + 4))
	{
		cout << "找到值 83" << endl;
	}
	else
		cout << "没找到值 83" << endl;
	system("pause");
	return 0;
}

输出结果为:

值 83 出现了!
没找到值 83
  • 规则:要想 find的函数的返回值判断有没有效,必须跟 find 算法的第二个参数( 即尾后迭代器)进行比较 , 如果等于第二个参数( 即尾后迭代器),那么就是没有找到,  如果不等于第二个参数( 即尾后迭代器)进行,那么就是找到了。这是唯一规则.

find 算法是如何工作的


  •   像find 这样的类似操作完全不依赖于容器所保存的元素类型,只要有一个迭代器可访问元素, find 就完全不依赖于容器(甚至无须理会保存的元素是不是容器, 比如说可以是内置数组)。
  • find 用的是元素 类型的 == 运算符来完成每个元素与给定值的比较。 如果该容器保存的是 未定义 == 运算符的元素类型 ( 比如类类型),那么就不可以直接使用find来查找元素。 但是可以重载 find 的默认行为, 使 find 可用于类类型,具体方法看本书的344P。
  •   迭代器令算法不依赖于容器, 但算法依赖于元素类型的操作(比如说: 比较操作)。
  •   泛型算法是执行迭代器的操作而不是执行容器的操作。算法不会改变底层容器的大小。算法可能会改变容器中保存元素的值, 也可能在容器中移动元素,但是永远不会直接删除添加元素(可以删除添加的—— 使用插入器)。

练习10.1:

int main()
{
	vector vec;
	int val = 0; //要查找的值
	cout << "请输入要查找的值:";
	cin >> val;
	int pushElem = 0;
	while (cin >> pushElem)
	{
		vec.push_back(pushElem);

	}
	auto cnt = count(vec.begin(), vec.end(), val);
	cout << "值" << val << "出现了" << cnt << "次!" << endl;
	system("pause");
	return 0;
}
  • count 函数返回给定值在序列中的次数。

只读算法(find、count、accumulate 338P)


  •  大部分标准库算法都对一个范围内的元素进行操作。 我们将此元素的范围称为 “ 输入范围”。 接受输入范围的算法第一个参数接收想要处理范围中第一个元素的迭代器, 第二个参数接收处理范围内的元素的尾元素之后的迭代器。
  •   find 、count 这两种算法都是只读算法,它们只会访问一定范围内的元素,不会改变元素的值。  另一个只读算法是 accumulate , 该算法定义在 numeric 头文件中。前两个参数指定要求和的元素范围。第三个是总和的初始值。假设vec是一个整数序列,如下所示:

int main()
{
	vector vec{ 1,2,3,4 };
	int sum = std::accumulate(vec.cbegin(), vec.cend(), 0); // 第三个参数作为求和的起点值
	cout << "vec中元素的总和为:" << sum << endl;
	system("pause");
	return 0;
}

vec中元素的总和为:10
  • 注意 : accumulate 的第三个参数的类型决定了使用哪个类型的加法运算符, 以及accumulate返回值的类型。
  • 注意: 序列中元素的类型必须与第三个参数类型匹配,或者能够转换为第三个参数的类型( 看练习题10.4)。这样才能将元素类型加到起点值的类型上。
  • 364P 中还有对 find 算法的使用。

使用 accumulate 算法将 vector中的 string 元素连接起来


int main()
{
	//使用 accumulate来将vector中所有string元素连接起来, 因为string 定义了+ 运算符,
	vector vec{ "huang","cheng","tao" };
//必须显式创建string空串,否则该类型是const char*,就会错误,因为没有定义 +运算符
	string sum = accumulate(vec.cbegin(), vec.cend(), string("")); 
	cout << "vec中元素的总和为:" << sum << endl;
	system("pause");
	return 0;
}

vec中元素的总和为:huangchengtao

用equal 操作两个序列(339P)


  equal 也是一个只读算法 , 用于确定两个序列是否保存相同的值。它将第一个序列中的每个元素与第二个序列中的对应元素进行比较。如果所有对应元素都相等, 则返回 true, 否则返回false。此算法接受三个迭代器: 前两个(与以往一样)表示第一个序列中的元素范围, 第三个表示第二个序列的首元素:

int main()
{
	/* 这样两个序列相同
	vector roster1{ 0,1,2,3,4,5 };
	vector roster2{ 0,1,2,3,4,5 };*/

	/* 这样两个序列保存的值也相同
	vector roster1{ 0,1,2,3};
	vector roster2{ 0,1,2,3,4,5 };*/

	/* 这样就会发生错误,不会出现编译错误,而是运行错误
	equa1基于一个非常重要的假设:它假定第二个序列至少与第一个序列一样长。
	此算法要处理第一个序列中的每个元素,它假定每个元素在第二个序列中都有一个与之对应的元素。
	vector roster1{ 0,1,2,3,4,5 };
	vector roster2{ 0,1,2,3,4 };*/

	vector roster1{ 0,1,2,3,4,5 };
	vector roster2{ 0,1,2,3,4 ,5 };
	if (equal(roster1.cbegin(), roster1.cend(), roster2.cbegin()))
	{
		cout << "两个序列相同!" << endl;
	}
	else
		cout << "两个序列不同!" << endl;
	system("pause");
	return 0;
}

输出结果为:
两个序列相同!
int main()
{

	/*可以通过调用equal来比较两个不同类型的容器中的元素。而且,元素类型也不必一样,
	只要我们能用 == 来比较两个元素类型即可(意思是说两个序列中的元素类型需要某种转换机制,如果两个元素类型都没有什么关联
	那么就不可以。) 

	如果使用 equal 来比较两个不同类型的容器中的元素, 两个容器中的元素个数要一样,否则错误。

	下面的序列不同
	vector roster1{ 0,1 };
	list roster2{ 1.f,2.f,3.f };*/

	/* 序列相同
	vector roster1{ 1,2,3};
	list roster2{ 1.f,2.f,3.f };*/

	/*序列不相同
		vector roster1{ 0,2,3,4};
		list roster2{ 1.f,2.f,3.f };*/

		// 下面这样发生运行错误
	vector roster1{ 1,2,3,4 };
	list roster2{ 1.f,2.f,3.f};
	if (equal(roster1.cbegin(), roster1.cend(), roster2.cbegin()))
	{
		cout << "两个序列相同!" << endl;
	}
	else
		cout << "两个序列不同!" << endl;
	system("pause");
	return 0;
}

 equal 值得注意的是:

  • 它总是假定第二个序列至少与第一个序列一样长。因为此算法要处理第一个序列中的每个元素, 因此假定每个元素在第二个序列中都有一个与之对应的元素。这一规则只适用在那些只接受一个单一迭代器来表示第二个序列的算法。( 在340P 中有相应的概念)
  • 如果使用 equal 来比较两个不同容器类型中的元素时, 两个容器中的元素个数要一样,否则错误。
  • 使用 equal 来比较两个两个容器时,不管它们的元素和容器类型是否相同,两个容器中的元素类型必须 支持 == 运算符,否则错误, 除非可以实现自定义 == 运算符。
  • 算法 equal 会将其第一个序列中的每个元素与第二个序列中的对应元素进行比较。 如果第二个序列是第一个序列的一个子集, 则程序会产生一个严重错误 —— equal会试图访问第二个序列中末尾之后(不存在)的元素。

练习10.4:

accumulate 的第三个参数是和的初值,  它还决定了accumulate 函数的返回类型, 以及函数中使用哪个类型的加法运算符。因此, 本题中的调用是错误的, 第三个参数0告知 accumulate 的和是整型的, 使用的是整型加法运算符。


int main()
{
	vector vec{ 1.6,2.3,3.6,4.5 };
	int sum = std::accumulate(vec.cbegin(), vec.cend(), 0); // 第三个参数作为求和的起点值
	cout << "vec中元素的总和为:" << sum << endl;
	system("pause");
	return 0;
}

vec中元素的总和为:10

从输出结果为可以看出 accumulate  函数返回的是一个整数10,但是 vector 中的数加起来可不是10. 正确的调用方法是将 0.0 作为 accumulate 函数的第三个参数传递给 accumulate.


使用 fill 和 fill_n 算法向容器写入元素(340P)


当使用某些泛型算法将一个新值赋值给序列中的元素时,我们必须确保序列的原大小是至少不小于我们要求算法写入的元素数目。

int main()
{
	vector vec{ 1,2,3,4,5,6 };
	//fill首先接受一对迭代器表示一个范围,第三个参数表示将给定的这个值赋值给“输入范围”中的每个元素
	// 注意的是,我们传递“输入范围”确保要有效,这样写入操作就是安全的
	std::fill(vec.begin(), vec.end(), 0); // 将每个元素重置为0
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;
	//将容器的子序列设置为10
	std::fill(vec.begin(), vec.begin() + vec.size() / 2, 10);
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

0 0 0 0 0 0
10 10 10 0 0 0

  • fill_n 算法第一个参数接受一个迭代器来表示从该位置开始赋予元素,
  • 第二个参数需要赋予多少个值,第三个参数表示都赋予什么值。

假设 fill_n 的调用形式如下:

fill_n(dest, n, val)

fill_n 假设 dest 指向一个元素,并且从 dest 开始的序列中至少有n个元素。

int main()
{
	vector vec{ 1,2,3,4,5 };
	
	//fill_n(vec.begin(), vec.size(), 0); 正确
	//fill_n(vec.begin() + 2, 3, 5); // 正确

	// 错误,从 vec[2]( 包括vec[2]) 开始后面只有2个元素, 要被赋值现在只能有三个元素,而这里指定了4个元素,所以错误
	fill_n(vec.begin() + 2, 4, 5); 
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

 注意:不要在空容器上调用 fill_n (或类似的写元素的算法):

int main()
{
	vector vec;
	fill_n(vec.begin(), 10, 0); //灾难:修改vec中的10个(不存在)元素,因为vec是空的
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

看运行结果为:

C++Primer 第五版 —— 《第十章 》泛型算法_第1张图片

注意: 从目的位置的迭代器开始写入数据的算法假定目的位置足够大,  可以容纳正在写入的元素的数量 。

如果要想使用 fill_n 或者 其它泛型算法 添加元素可以参考使用下面的 back_inserter 插入迭代器算法,该算法确保有足够的元素空间来容纳要插入的元素数量。


介绍  back_inserter 插入迭代器向容器中插入元素 ( 341P)


 back_inserter  该函数定义在 头文件 iterator 中。

要想使用 back_inserter 首先接受一个指向容器的引用,  然后返回一个与该容器绑定的插入迭代器。 此时当我们通过返回的插入迭代器赋值时, 赋值运算符会调用 push_back 将一个具有给定值的元素添加到容器的末尾 —— 只要该容器支持 push_back 操作,才能使用 back_inserter  来插入元素,否则不可以。比如 forward_list 、array 就不可以使用。:

int main()
{
	vector vec = { 1,2,3,4,5 };
	auto it = back_inserter(vec); // 通过它赋值会将元素添加到vec中的末尾
	*it = 42; // vec 中有一个值是42
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

1 2 3 4 5 42

 我们常常使用back_inserter 来创建一个插入迭代器, 作为 fill_n 泛型算法的目的位置迭代器来使用。例如:

int main()
{
	vector vec = {1,2,3,4,5,6}; 

	fill_n(back_inserter(vec), 6, 0); 
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

1 2 3 4 5 6 0 0 0 0 0 0

下面的算法也有对 back_inserter 插入迭代器的使用。 还有就是本书的359P 有关于插入迭代器的介绍。


使用 copy 和 replace 、replace_copy 拷贝算法 (341P)


拷贝(copy)算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。此算法接受三个迭代器, 前两个表示一个输入范围,第三个表示目的序列的起始位置。此算法将输入范围中的元素拷贝到目的序列中。传递给copy的目的序列至少要包含与输入范围一样多的元素( 意思就是说 如果A 拷贝给B,那么B 中的元素个数至少与A 中的元素个数一样多。)

下面的代码使用copy 算法实现内置数组的拷贝:

int main()
{
	int a1[] = { 0,1,2,3,4,5,6,7,8,9 };
	int a2[sizeof(a1) / sizeof(*a1)]; // a2 has the same size as a1
	//ret指向拷贝到a2的尾元素之后的位置
	auto ret = std::copy(std::begin(a1), std::end(a1), a2); // copy a1 into a2
	for (auto tt : a2)
	{
		cout << tt << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

0 1 2 3 4 5 6 7 8 9


 copy返回的是其目的位置迭代器(递增后)的值。意思就是说 ret 永远指向拷贝到a2 元素之后的值。

下面的代码使用 copy  算法 实现容器之间的拷贝:

int main()
{
	list lst = { 1,2,3,4 };
	listlst2;
	cout << "使用back_inserter 插入元素!" << endl;
	copy(lst.cbegin(), lst.cend(), back_inserter(lst2));
	for_each(lst2.cbegin(), lst2.cend(), [](int tt) {cout << tt << " "; });
	cout << endl;
	system("pause");
	return 0;
}

使用back_inserter 插入元素!
1 2 3 4

 replace 算法读入一个序列, 并 将其中所有等于给定值的元素都改为另一个值。此算法接受4个参数:  前两个是迭代器, 表示输入序列,  后两个一个是要搜索的值, 另一个是替换搜索值的新值。它将所有等于第一个值的元素替换为第二个值:

int main()
{
	list ilst{ 55,0,0,23,0,8,23,0 };

	std::replace(ilst.begin(), ilst.end(), 0, 42); //将所有值为0的元素改为42
	for (auto tt : ilst)
	{
		cout << tt << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

55 42 42 23 42 8 23 42

如果我们希望保留原序列不变, 可以调用 replace_copy。此算法接受额外第三个迭代器参数, 指出调整后序列的保存位置:

int main()
{
	list ilst{ 55,0,0,23,0,8,23,0 };
	vector ivec;

   //使用back inserter按需要增长目标序列
	replace_copy(ilst.cbegin(), ilst.cend(),back_inserter(ivec), 0, 42);
	cout << "输出list容器中的元素:";
	for (auto tt : ilst)
	{
		cout << tt << " ";
	}
	cout << endl;

	cout << "输出vector容器中的元素:";
	for (auto tt : ivec)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

输出list容器中的元素:55 0 0 23 0 8 23 0
输出vector容器中的元素:55 42 42 23 42 8 23 42

此调用后, ilst并未改变,  ivec包含ilst的一份拷贝, 不过原来在ilst中值为0的元素在ivec中都变为42 .


练习10.7:

(a)是错误的。因为泛型算法的一个基本特点是: 算法总是通过迭代器的输入范围操作容器, 因此不能直接向容器添加、删除元素, 无法直接改变容器大小。因此, 对于copy算法, 要求传递给copy的目的序列至少要包含与输入范围一样多的元素。而此程序中, vec 初始化为空, copy无法进行。如需改变容器大小, 需要使用插入器迭代器。我们可以将第三个参数改为back_inserter (vec) ,通过它, copy算法即可将 lst 中元素的拷贝插入到vec的末尾。

int main()
{
	vectorvec;
	list lst;
	int i;
	while (cin >> i)
	{
		lst.push_back(i);
	}

	std::copy(lst.begin(), lst.end(), back_inserter(vec));
	if (equal(lst.cbegin(), lst.cend(), vec.cbegin()))
	{
		cout << "两个序列是相等的!" << endl;
	}
	else
		cout << "两个序列不相等!" << endl;

	cout << "输出ilst容器中的元素:";
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

输出结果为:

请为 vec 输入元素:
55
66
3
^Z
两个序列是相等的!
输出ilst容器中的元素:55 66 3

(b)也是错误的,因为 reserve 操作并不会改变容器中的元素数量,它仅影响 vector 预先分配多大的内存空间。 所以  vec.reserve(10) 该调用只是为 vector  分配了10个元素内存空间而已,并不是分配了10个元素。 当该调用结束后, vector 还是一个空容器。 fill_n 有一个准则,不可以在空容器上调用 fill_n。

int main()
{
	vector vec;
	//vec.reserve(10); 
	fill_n(back_inserter(vec), 10, 5);
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

5 5 5 5 5 5 5 5 5 5

其实本题要不要 reserve 操作都无所谓, 空间大小并不是问题,容器都能根据需要自动扩容。

练习10.8:

  • 严格来说, 标准库算法根本不知道有 “容器” 这个东西。它们只接受迭代器参数, 运行于这些迭代器之上, 通过操作这些迭代器来访问元素。
  • 因此, 当你传递给算法普通迭代器时, 这些迭代器只能顺序或随机访问容器中的元素, 造成的效果就是算法只能读取元素、改变元素值、移动元素, 但无法添加或删除元素。
  • 但当我们传递给算法插入器, 例如back_inserter时, 由于这类迭代器能调用下层容器的操作来向容器插入元素, 造成的算法执行的效果就是向容器中添加了元素。
  • 因此, 关键要理解: 标准库算法从来不能直接执行容器操作, 它们只操作迭代器范围, 从而间接访问容器。能不能插入和删除元素, 不在于算法, 而在于传递给它们的迭代器是否具有这样的功能。

使用 sort 、unique 算法来重排容器元素(342P)

  • sort 算法使容器中的元素使之有序,它是利用元素类型的 “ < ” 运算符来实现排序的。  需要注意的是: 如果该容器的元素类型并没有实现 “ < ” ,那么就不可以使用sort 算法。
  • sort 算法是一个不稳定的排序算法。
  • unique 算法 使用元素类型的 “ == ” 运算符来检查容器中的重复元素,
  • sort 算法前两个参数接受一对要排序的元素的输入范围。unique 算法也是一样。
  • unique 算法也会重排vector,它会将不重复的元素放在vector的开始部分。它返回一个指向不重复区域之后一个位置的迭代器。如果 该容器中没有重复的元素, 将返回一个 尾后迭代器。
  • 通过向 sort 传递一个反向迭代器,可将容器中的元素降序输出( 5、4、3、2、1)—— 363P
void elimDups(vector &words)
{
	// 按大小顺序排序排列,以便我们可以找到重复的数字
	sort(words.begin(), words.end());

	// unique重新排序输入范围, 使得每个数字只出现一次
	// 使得不重复的数字出现在vector序列的开始部分,返回指向不重复区域之后一个位置的迭代器

	auto end_unique = unique(words.begin(), words.end());
	cout << "end_unique 的值为:" << *end_unique << endl;
	for (auto tt : words)
	{
		cout << tt << " ";
	}
	cout << endl;
	// 使用向量操作erase删除重复数字
	words.erase(end_unique, words.cend());
	for (auto tt : words)
	{
		cout << tt << " ";
	}
	cout << endl;
}
int main()
{
	vectorwords{ 0,2,3,4,3,6,2,0,7 };
	elimDups(words);
	for (auto tt : words)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
C++Primer 第五版 —— 《第十章 》泛型算法_第2张图片 unique 返回的迭代器指向箭头所指之处
C++Primer 第五版 —— 《第十章 》泛型算法_第3张图片 箭头所指之处是erase 要删除的迭代器范围

 

 sort 、unique  不会改变容器中元素个数, 只是元素的顺序被改变了。unique  并不会真正删除重复的元素, 只是覆盖相邻的重复元素,使得不重复元素出现在序列开始部分。unique返回的迭代器指 向最后一个不重复元素之后的位置。此位置之后的元素仍然存在, 但我们不知道它们的值是什么。所以为了真正删除 end_unique 指向的 到 word.end() 之间无用的元素,我们必须容器 erase 容器操作来删除元素。


使用 sort 算法的重载版本传递谓词(334P)


  • find ( 使用元素类型的 == 进行比较)、sort ( 使用元素类型的 < )、equal ( 使用元素类型的 == )或者其它泛型算法会比较输入范围中的元素,它们都使用元素类型的 < 或 == 运算符来进行比较。 这些类型的算法还有重载的版本,这些重载的版本允许我们提供自定义的相应操作来代替元素的默认运算符。
  • 比如说 sort 算法 默认使用元素类型的 < 运算符, 但是我们希望排序的顺序是降序 ( 5、4、3、2、1),或是我们的序列可能保存的是未定义 < 运算符的元素类型, 在这两种情况下, 都需要重载sort的默认行为。要想达到此目的可使用lambda 表达式 或者 二元谓词。

什么是谓词?

  • 谓词是一个可调用对象,其返回的值是一个可以用作条件的值。
  • 谓词有两类: 一元谓词( 只接受一个参数),二元谓词(接受两个参数)
  • 谓词可以是 函数、函数指针、lambda表达式、重载了函数调用运算符的类、等等
  • 如果某些泛型算法接受谓词参数,那么会对输入范围中的元素调用谓词。因此,必须能够将元素类型转换为谓词的参数类型。

bool isShorter(const string &s1, const string &s2)
{// 比较函数,用来按长度排序单词
	return s1.size() < s2.size();
}

int main()
{
	vectorwords{ "huang","chen","tao","taotao" };

	// 接受一个二元谓词参数的sort版本用这个谓词代替<来比较元素
	sort(words.begin(), words.end(), isShorter); // 按长度由断至长来排序
	for (auto tt : words)
	{
		cout << tt << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

tao chen huang taotao

使用 stable_sort 排序算法(345P)


  • 需要 包含 头文件 algorithms. 
  • stable_sort  既可以按元素的大小排序,还可以让相同长度的元素按字典序排序。这种算法是稳定的排序算法,它可以维持相等大小元素的原有顺序。
void elimDups(vector &words)
{
	// 按大小顺序排序排列,以便我们可以找到重复的单词
	sort(words.begin(), words.end());
	for (auto tt : words)
	{
		cout << tt << " ";
	}
	cout << endl;

	// unique重新排序输入范围, 使得每个单词只出现一次
	// 使得不重复的单词出现在vector序列的开始部分,返回指向不重复区域之后一个位置的迭代器

	auto end_unique = unique(words.begin(), words.end());
	for (auto tt : words)
	{
		cout << tt << " ";
	}
	cout << endl;
	// 使用向量操作erase删除重复单词
	words.erase(end_unique, words.cend());

	for (auto tt : words)
	{
		cout << tt << " ";
	}
	cout << endl;
}

bool isShorter(const string &s1, const string &s2)
{// 比较函数,用来按长度排序单词
	return s1.size() < s2.size();
}
int main()
{
	vectorwords{ "huang","chen","taoo","taotao","tao","huang","chen" ,"cen"};
	elimDups(words); //将words按字典序重排,并消除重复单词

// 按长度重新排序,长度相同的单词维持字典顺序
	stable_sort(words.begin(), words.end(), isShorter);
	for (const auto &s : words) // 无须拷贝字符串
		cout << s << " "; // 打印每个元素,以空格分开
	cout << endl;

	system("pause");
	return 0;
}

cen chen chen huang huang tao taoo taotao
cen chen huang tao taoo taotao
cen chen huang tao taoo taotao
cen tao chen taoo huang taotao

partition 算法(345P)


练习题10.13:

void output(vector::iterator beg, vector::iterator end)
{
	for (auto iter = beg; iter != end; ++iter)
	{
		cout << *iter << " ";
	}
	cout << endl;
}

bool isShorter(const string &s1)
{
	return s1.size() >= 5;
}
int main()
{
	vectorwords{ "huang","chen","taoo","taotao","tao","huang","chen" ,"cen" };
	

	auto iter = partition(words.begin(), words.end(), isShorter);
	output(words.begin(), iter);

	system("pause");
	return 0;
}

huang huang taotao

介绍 lambda 表达式、find_if 算法(346P)


我们传递给算法的谓词必须接受一个或两个参数,这取决于算法是接受一元谓词还是二元谓词。

  •  find_if 该算法可以查找某个序列中第一个具有特定大小的元素。前二个参数是一对迭代器 ,表示一个输入范围中的元素。 第三个参数是一个一元谓词。
  • find_if 算法对输入范围中的每个元素调用给定的这个谓词。  它返回第一个使谓词返回非0值的元素,  如果不存在这样的元素,  则返回尾迭代器。
  • find_if 算法只接受一元谓词。因此,传递给find_if 算法的任何函数必须严格接受一个参数。

 我们可以将任何类型的可调用对象传递给算法。

什么是可调用对象?

  • 如果一个对象或者一个表达式可以使用调用运算符, 则称它为可调用对象。
  • 可调用对象有:函数和函数指针、 重载了函数调用运算符的类、Lambda 表达式。

lambda表达式是什么?

一个lambda 表达式 就是一个可调用的代码块,  可以理解为未命名的内联函数。与其它函数类似, 但与函数不同的是,lambda 表达式既可以定义在函数内部,也可以定义在函数外部。 语法为:

[ capture list ] ( parameter list ) -> return type  { function body }
  •  capture list (捕获列表)是一个lambda 所在函数中( 指的是 该lambda 定义在某个函数内部)定义的局部变量的列表(通常为空,如果为空的话,表明此 lambda不使用它所在函数中的任何局部变量),其它的跟普通函数一样。 但是lambda 表达式必须使用尾置返回类型。
  • 注意:  我们还可以忽略参数列表和返回类型 但是不能忽略捕获列表 和 函数体。
auto f = [] {return 42; }; //  定义了一个可调用对象f,它不接受参数,返回42
int main()
{
	cout << f() << endl;  // lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符

	system("pause");
	return 0;
}
  • 如果 忽略 “  ( parameter list ) ” 等价于指定一个空参数列表。 在上述代码中, 当调用 f时, 参数列表是空的。 
  • 如果忽略 “ return type ” ,   lambda 根据函数体中的代码推断出返回类型。
  • 如果 “ function body ” 只有一个return 语句, 则 返回类型可以从返回的表达式的类型推断而来。  否则, 返回类型为 void
  • 注意:如果 lambda 的函数体包含任何单一return语句之外的内容, 并且未指定返回类型, 则返回void.

向 lambda 传递参数(347P)


向 lambda 传递参数 时需要注意的有:

  • 调用一个 lambda 时,给定的实参初始化对应的lambda形参。
  •  lambda 不能有默认形参。所以在调用 lambda 的时候 提供的实参个数必须与形参个数一致。一旦形参初始化完毕后,就可以执行函数体了。 
void elimDups(vector &words)
{
	// 按大小顺序排序排列,以便我们可以找到重复的单词
	sort(words.begin(), words.end());

	// unique重新排序输入范围, 使得每个单词只出现一次
	// 使得不重复的单词出现在vector序列的开始部分,返回指向不重复区域之后一个位置的迭代器

	auto end_unique = unique(words.begin(), words.end());

	// 使用向量操作erase删除重复单词
	words.erase(end_unique, words.cend());
}



int main()
{
	vectorwords{ "huang","chen","taoo","taotao" };
	elimDups(words); //将words按字典序重排,并消除重复单词

// 按长度重新排序,长度相同的单词维持字典顺序
	stable_sort(words.begin(), words.end(),
		[](const string &a, const string &b)
	{ return a.size() < b.size(); });   //这里使用lambda 来比较两个单词的长度

	for (const auto &s : words) // 无须拷贝字符串
		cout << s << " "; // 打印每个元素,以空格分开
	cout << endl;

	system("pause");
	return 0;
}

chen taoo huang taotao

使用 lambda 的捕获列表 和 find_if 算法( 347P)


捕获列表( capture list)如何使用局部变量的:

  • 虽然 lambda可能出现在某个函数中,但只有在指定要使用哪些局部变量时(在 捕获列表( capture list)中指定该函数中已经明确定义的局部变量(包括该函数的形参,因为函数的形参也是局部变量)),lambda 才可以在函数体中使用该函数的局部变量。
  • 注意:所以说一个 lambda 只有在其捕获列表中捕获一个它所在函数中的局部变量, 才能在函数体中使用该变量。
  • 注意:  捕获列表只能用于局部非 static 变量,  lambda 函数体可以直接使用局部static变量 和 在它所在函数之外声明的名字。(348P for_each 算法那)
  • 注意: lambda 只可以捕获它定义之前的名称,之后的名称不可以捕获。

C++Primer 第五版 —— 《第十章 》泛型算法_第4张图片

 

本书中对 find_if 的完整程序:

void elimDups(vector &words)
{
	// 按大小顺序排序排列,以便我们可以找到重复的单词
	sort(words.begin(), words.end());

	// unique重新排序输入范围, 使得每个单词只出现一次
	// 使得不重复的单词出现在vector序列的开始部分,返回指向不重复区域之后一个位置的迭代器

	auto end_unique = unique(words.begin(), words.end());

	// 使用向量操作erase删除重复单词
	words.erase(end_unique, words.cend());
}


string make_plural(size_t ctr, const string &word, const string &ending)
{
	return (ctr > 1) ? word + ending : word;
}

 //本来用该函数来比较两个string的大小的,现在移到biggies函数中lambda中了
/*bool isShorter(const string &s1, const string &s2) 
{// 比较函数,用来按长度排序单词
	return s1.size() < s2.size();
}*/


void biggies(vector &words, vector::size_type sz)
{
	elimDups(words); // 将words按字典序排序,删除重复单词

	// 按长度排序,长度相同的单词维持字典序
	stable_sort(words.begin(), words.end(), [](const string &a, const string &b){ return a.size() < b.size(); });

	//获取一个迭代器,指向第一个满足size ()>= sz的元素
	auto wc = find_if(words.begin(), words.end(),[sz](const string &a){ return a.size() >= sz; });

	// 计算满足size >= sz的元素的数目
	auto count = words.end() - wc;
	cout << count << " " << make_plural(count, "word", "s")<< " of length " << sz << " or longer" << endl;

	// 打印长度大于等于给定值的单词, 每个单词后面接一个空格
	for_each(wc, words.end(),[](const string &s) {cout << s << " "; });
	cout << endl;
}


int main()
{
	vectorwords{ "huang","chen","taoo","taotao" ,"he","sh"};

	biggies(words, 3);

	system("pause");
	return 0;
}

4 words of length 3 or longer
chen taoo huang taotao

biggies 函数中的该代码:

//获取一个迭代器,指向第一个满足size ()>= sz的元素
	auto wc = find_if(words.begin(), words.end(),[sz](const string &a){ return a.size() >= sz; });

[ ] 这里面提供的是以逗号分隔的局部变量的列表, 这些局部变量都是在该函数中定义过的。

find_if 算法参数规则:

  •  find_if 该算法可以查找某个序列中第一个具有特定大小的元素。 前二个参数是一对迭代器 ,表示一个输入范围中的元素。 第三个参数是一个一元谓词。 find_if 算法对输入范围中的每个元素调用给定的这个谓词。  它返回第一个使谓词返回非0值的元素,  如果不存在这样的元素, 则返回尾迭代器。

for_each 算法( 348P)


该算法在本书中没有更多的介绍:

  • for_each 算法有点类似于 范围for语句。 
  • 该算法 前二个参数是一对迭代器 ,表示一个输入范围中的元素
  • 第三个参数是一个可调用对象或者可调用的表达式。 
  • for_each 算法对输入范围中的每个元素调用给定的这个可调用对象或者可调用的表达式。

练习题10.14:

int main()
{
	auto f = [](int s1, int s2) {return s1 + s2; };
	cout << "输入两个数求和:\n";
	int num1, num2;
	cin >> num1 >> num2;
	cout << "两个数的总和为:" << f(num1, num2) << endl;
	system("pause");
	return 0;
}

练习题10.15:

void  result(int num1)
{
	auto f= [num1](int num2) { return num1 + num2; };
	cout << "输出值为:" << f(100) << endl;
}

int main()
{
	result(122);
	system("pause");
	return 0;
}

 


lambda 捕获 和 返回(349P)


  •  当我们定义一个 lambda 时, 编译器会生成一个与该 lambda 对应的新(未命名)类类型。可以理解为: 当我们将lambda传递给一个函数时,同时定义了一个新类型和该类型的对象:传递的参数是这个编译器生成的类类型的一个未命名对象。
  • 类似的, 当使用auto 定义一个用 lambda 初始化的变量时, 定义了一个从 lambda 生成的类型的对象。
  • 默认情况下,从lambda生成的类都包含与lambda捕获的变量相对应的数据成员。类似任何普通类的数据成员, lambda的数据成员也在lambda对象创建时被初始化。

在 lambda 捕获列表中使用 “ 值捕获” (350P)


  [ ]  里的局部变量的捕获方式也类似于参数传递, 我们可以通过值或引用的方式来捕获变量。 下面的示例代码采用的是值捕获的方式, 采用值捕获的前提是该变量是可以拷贝的。那么与参数不同的是, 被捕获变量的值是在lambda 创建时拷贝, 而不是调用时拷贝。因此随后对其值修改不会影响到lambda内对应的值。 


void fcn1()
{
	size_t v1 = 42;
	//将v1拷贝到名为f的可调用对象
	auto f = [v1] {return v1; };
	v1 = 0;
	auto j = f();  // j为42; f保存了我们创建它时v1的拷贝
	cout << "输出v1 的值:" << v1 << ", 输出j 的值:" << j << endl;
}

int main()
{
	fcn1();
	system("pause");
	return 0;
}

输出v1 的值:0, 输出j 的值:42

 


在 lambda 捕获列表中使用 “ 引用捕获” (350P)


  •  如果采用的是引用捕获的方式,当我们在lambda函数体内使用此变量时, 实际上使用的是引用所绑定的对象。 
  • 在本例中, 当lambda返回v1时, 它返回的是v1指向的对象的值。如果我们采用引用方式捕获一个变量, 就必须确保被引用的对象在lambda执行的时候是存在的。因为 lambda 捕获的都是局部变量,  假设这些变量在函数结束后就不复存在了。lambda可能在函数结束后执行, 捕获的引用指向的局部变量已经消失。

下面的的代码采用引用捕获的方式:

void fcn1()
{
	size_t v1 = 42;
	//对象f2 包含 v1 的引用
	auto f = [&v1] {return v1; };
	v1 = 0;
	auto j = f();  // j为0; f2保存v1的引用,而非拷贝
	cout << "输出v1 的值:" << v1 << ", 输出j 的值:" << j << endl;
}

int main()
{
	
	fcn1();
	system("pause");
	return 0;
}

输出v1 的值:0, 输出j 的值:0

作者告诉我们尽量保持 lambda捕获变量时简单化

  • 如果捕获一个普通变量, 如 int, string 或 其他非指针类型,  通常可以采用简单的值捕获方式。在此情况下, 只需关注变量在捕获时是否有我们所需的值就可以了。
  • 如果我们捕获的是一个 指针或迭代器, 或采用引用捕获方式, 就必须确保在 lambda执行时, 绑定到迭代器、指针或引用的对象仍然存在。而且, 还需要确保对象具有预期的值。在lambda从创建到它执行的这段时间内, 可能有代码修改绑定的对象的值。也就是说, 在指针(或引用)被捕获的时刻, 绑定的对象的值是我们所期望的, 但在lambda执行时, 该对象的值可能已经被修改。
  • 建议:一般来说, 我们应该尽量减少捕获的数据量, 来避免潜在的捕获导致的问题。而且, 如果可能的话, 应该避免捕获指针或引用。

lambda 捕获列表采用“ 隐式捕获 ” (351P)


  •  为了指示编译器推断捕获列表,应该在捕获列表中使用&或=。
  • &告诉编译器通过引用捕获
  • 而=表示值是通过值捕获的。 

下述程序使用隐式值捕获的方式,

void elimDups(vector &words)
{
	// 按大小顺序排序排列,以便我们可以找到重复的单词
	sort(words.begin(), words.end());

	// unique重新排序输入范围, 使得每个单词只出现一次
	// 使得不重复的单词出现在vector序列的开始部分,返回指向不重复区域之后一个位置的迭代器

	auto end_unique = unique(words.begin(), words.end());

	// 使用向量操作erase删除重复单词
	words.erase(end_unique, words.cend());
}


string make_plural(size_t ctr, const string &word, const string &ending)
{
	return (ctr > 1) ? word + ending : word;
}

//本来用该函数来比较两个string的大小的,现在移到biggies函数中lambda中了
/*bool isShorter(const string &s1, const string &s2)
{// 比较函数,用来按长度排序单词
	return s1.size() < s2.size();
}*/


void biggies(vector &words, vector::size_type sz)
{
	elimDups(words); // 将words按字典序排序,删除重复单词

	// 按长度排序,长度相同的单词维持字典序
	stable_sort(words.begin(), words.end(), [](const string &a, const string &b) { return a.size() < b.size(); });

	//获取一个迭代器,指向第一个满足size ()>= sz的元素
	 sz 为隐式捕获,值捕获方式
	auto wc = find_if(words.begin(), words.end(),[=](const string &s){ return s.size() >= sz; });

	// 计算满足size >= sz的元素的数目
	auto count = words.end() - wc;
	cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer" << endl;

	// 打印长度大于等于给定值的单词, 每个单词后面接一个空格
	for_each(wc, words.end(), [](const string &s) {cout << s << " "; });
	cout << endl;
}


int main()
{
	vectorwords{ "huang","chen","taoo","taotao" ,"he","sh" };

	biggies(words, 3);

	system("pause");
	return 0;
}

lambda 捕获列表同时采用“ 隐式捕获 ” 和 “ 显式捕获 ”(351P)


下列程序对部分变量采用值捕获, 对其他变量采用引用捕获, 可以混合使用隐式捕获和显式捕获:

void elimDups(vector &words)
{
	// 按大小顺序排序排列,以便我们可以找到重复的单词
	sort(words.begin(), words.end());

	// unique重新排序输入范围, 使得每个单词只出现一次
	// 使得不重复的单词出现在vector序列的开始部分,返回指向不重复区域之后一个位置的迭代器

	auto end_unique = unique(words.begin(), words.end());

	// 使用向量操作erase删除重复单词
	words.erase(end_unique, words.cend());
}


string make_plural(size_t ctr, const string &word, const string &ending)
{
	return (ctr > 1) ? word + ending : word;
}

//本来用该函数来比较两个string的大小的,现在移到biggies函数中lambda中了
/*bool isShorter(const string &s1, const string &s2)
{// 比较函数,用来按长度排序单词
	return s1.size() < s2.size();
}*/


void biggies(vector &words,vector::size_type sz,std::ostream &os = cout, char c = ' ')
{
	elimDups(words); // 将words按字典序排序,删除重复单词

	// 按长度排序,长度相同的单词维持字典序
	stable_sort(words.begin(), words.end(), [](const string &a, const string &b) { return a.size() < b.size(); });

	//获取一个迭代器,指向第一个满足size ()>= sz的元素
	// sz 为隐式捕获,值捕获方式
	auto wc = find_if(words.begin(), words.end(),[=](const string &s){ return s.size() >= sz; });

	// 计算满足size >= sz的元素的数目
	auto count = words.end() - wc;
	cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer" << endl;

	os隐式捕获,引用捕获方式;c显式捕获,值捕获方式
	for_each(words.begin(), words.end(), [&, c](const string &s) { os << s << c; });

	 os显式捕获,引用捕获方式; c隐式捕获,值捕获方式
	for_each(words.begin(), words.end(),[=, &os](const string &s) { os << s << c; });
	cout << endl;
}


int main()
{
	vectorwords{ "huang","chen","taoo","taotao" ,"he","sh" };

	biggies(words, 3);

	system("pause");
	return 0;
}
  • 当我们混合使用隐式捕获和显式捕获时,  捕获列表中的第一个元素必须是一个 &或 =。此符号指定了默认捕获方式是引用还是值。
  • 当混合使用 隐式捕获 和 显式捕获 时, 显式捕获的变量 和 隐式捕获的变量必须采用不同的捕获方式。即, 如果隐式捕获某个变量,采用的是引用方式(使用了&),  则显式捕获某个变量必须采用值方式,  因此不能在其名字前使用&。
  • 类似的, 如果隐式捕获某个变量采用的是值方式(使用了 = ), 则显式捕获某个变量必须采用引用方式,。 即,在名字前使用&。

lambda 捕获列表的简介(352P)



使用 mutable 关键字来使lambda 函数体中可以修改被捕获变量的值(352P)


 下列程序改变它所捕获的变量的值:

void fun3()
{
	size_t v1 = 42; // local variable
	// f可以改变它所捕获的变量的值
	auto f = [v1] () mutable {return ++v1; };
	v1 = 0;
	auto j = f(); // j is 43
	cout << "输出v1 的值:" << v1 << ", 输出j 的值:" << j << endl;
}

int main()
{
	fun3();
	system("pause");
	return 0;
}

输出v1 的值:0, 输出j 的值:43

 一个引用捕获的变量是否(如往常一样)可以修改依赖于此引用指向的是一个const类型还是一个非const类型的对象:

void fun3()
{
	size_t v1 = 42; // local variable
	// v1是一个非 const 变量的引用
	// 可以通过 f 中的引用来改变它
	auto f = [&v1] () mutable {return ++v1; };
	v1 = 0;
	auto j = f(); // j is 1
	cout << "输出v1 的值:" << v1 << ", 输出j 的值:" << j << endl;
}

int main()
{
	
	fun3();
	system("pause");
	return 0;
}

输出v1 的值:1, 输出j 的值:1

为 lambda 指定返回类型 和 使用 transform 算法 (353P)


  • 默认情况下, 如果一个lambda体包含除了 return之外的任何语句, 则编译器假定此 lambda 返回void 。如果被推断返回 void的lambda不能返回值。 此时我们无须指定 返回类型,因为可以根据函数体中的代码推断出返回类型。
  • 当我们需要一个lambda 返回一个值,那么该lambda 必须使用 尾置返回类型。
int main()
{
	vectorvi{ 1,11,-11,88,-9,-8,-7 };

	// 没有使用尾置返回类型来返回lambda 的值
	transform(vi.begin(), vi.end(), vi.begin(),[](int i)  { if (i < 0) return -i; else return i; });
	// 有使用尾置返回类型来返回lambda 的值
	transform(vi.begin(), vi.end(), vi.begin(), [](int i) ->int { if (i < 0) return -i; else return i; });

	for_each(vi.cbegin(), vi.cend(), [](const int &s) {cout << s << " "; });

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

不过奇怪的是 它们在编译器上都编译、运行正确,并且获得预期的效果。

int main()
{
	vectorvi{ 1,11,-11,88,-9,-8,-7 };
	transform(vi.begin(), vi.end(), vi.begin(),[](int i) { return i < 0 ? -i : i; });
	for_each(vi.cbegin(), vi.cend(), [](const int &s) {cout << s << " "; });
	cout << endl;
	system("pause");
	return 0;
}

1 11 11 88 9 8 7

 transform 算法接受三个迭代器和一个可调用对象

  • 前两个迭代器表示需要被处理的输入序列
  • 第三个迭代器表示目的位置

算法对输入序列中每个元素都会调用可调用对象, 并将结果写到目的位置

如本例所示, 目的位置迭代器与表示输入序列开始位置的迭代器可以是相同的。当输入迭代器和目的迭代器相同时, transform将输入序列中每个元素替换为可调用对象 操作该元素得到的结果。


迭代器的种类(357P)



插入迭代器(358P)


  • 插入器是一种迭代器适配器,它接受一个容器,并产生一个迭代器,该迭代器能将元素添加到指定的容器中。
  • 当我们向一个 插入 迭代器赋值时,迭代器会调用容器操作来向给定容器的指定位置添加元素。 —— 如果该容器类型并没有相应的容器操作,就不可以使用 插入 迭代器 来添加元素。
  • 每一种插入迭代器类型都接受一个容器类型 和 一个 指向给定容器的迭代器,当调用它们的时候,将得到一个迭代器, 接下来使用的时候, 会将相应的元素插入相应的位置。

插入迭代器 inserter 的示例代码:

int main()
{
	vector vec{ 5,6,7 };
	cout << "下面使用插入迭代器 inserter 插入元素!" << endl;
	// 元素会插入到该迭代器指定的元素之前, 
	auto it = inserter(vec, vec.begin() + 1);  //使用插入迭代器向容器中插入元素, 但是要小心提供第二个迭代器,以免越界

	int val = 200;
	*it = val;
	for_each(vec.cbegin(), vec.cend(), [](int s) {cout << s << " "; });
	cout << endl;

	cout << "下面使用 insert 插入元素1" << endl;
	auto tt = vec.insert(vec.begin() + 1, val);  // 等价的调用
	for_each(vec.cbegin(), vec.cend(), [](int s) {cout << s << " "; });
	cout << endl;
	system("pause");
	return 0;
}

下面使用插入迭代器 inserter 插入元素!
5 200 6 7

下面使用 insert 插入元素1
5 200 200 6 7

front_inserter  的代码示例:

int main()
{
	list lst = { 1,2,3,4 };
	listlst2, lst3,lst4;

	cout << "使用front_inserter 插入元素!" << endl;
	copy(lst.cbegin(), lst.cend(), front_inserter(lst2));
	for_each(lst2.cbegin(), lst2.cend(), [](int tt) {cout << tt << " "; });
	cout << endl;

	cout << "使用inserter 插入元素!" << endl;
	copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));
	for_each(lst3.cbegin(), lst3.cend(), [](int tt) {cout << tt << " "; });
	cout << endl;

	cout << "使用back_inserter 插入元素!" << endl;
	copy(lst.cbegin(), lst.cend(), back_inserter(lst4));
	for_each(lst4.cbegin(), lst4.cend(), [](int tt) {cout << tt << " "; });
	cout << endl;
	system("pause");
	return 0;
}
使用front_inserter 插入元素!
4 3 2 1
使用inserter 插入元素!
1 2 3 4
使用back_inserter 插入元素!
1 2 3 4
  • 当调用 front inserter (c) 时,  我们得到一个插入迭代器,  接下来会调用 push_front
  • 当每个元素被插入到容器c中时, 它变为 c 的新的首元素。因此, front_inserter生成的迭代器会将插入的元素序列的顺序颠倒过来,  而 inserter back_inserter 则不会。

反向迭代器( 363P)


  • 反向迭代器指的是从该容器的尾元素向首元素方向移动的迭代器。
  •  对于反向迭代器递增(以及递减)操作的含义会颠倒过来。递增一个反向迭代器(++it)会移动到前一个元素; 递减一个迭代器(--it)会移动到下一个元素。
  •   除了 forward_list 之外, 其他容器都支持反向迭代器。

我们如何获得反向迭代器?

  • 我们可以通过调用 rbegin、rend、 crbegin 和 crend 成员函数来获得反向迭代器。这些成员函数返回指向容器尾元素和首元素之前一个位置的迭代器。与普通迭代器一样, 反向迭代器也有 const 和非 const 版本。
C++Primer 第五版 —— 《第十章 》泛型算法_第5张图片 比较 begin / cend 和 rbegin / crend 迭代器

利用反向迭代器逆序输出容器中的元素:

int main()
{
	vector vec = { 0,1,2,3,4,5,6,7,8,9 }; 
	for_each(vec.crbegin(), vec.crend(), [](int tt) {cout << tt << " "; }); // 利用for_each 算法和 lambda 输出序列
	cout << endl;

	for (auto r_iter = vec.crbegin(); r_iter != vec.crend(); ++r_iter) //等价的调用
	{
		cout << *r_iter << " ";
	}
	
	system("pause");
	return 0;
}

9 8 7 6 5 4 3 2 1 0
9 8 7 6 5 4 3 2 1 0 

通过向 sort 传递一个反向迭代器,可将容器中的元素降序输出:

int main()
{
	vector vec = { 55,7,63,99,78,100 }; 

	sort(vec.begin(), vec.end());  // 升序排序
	for_each(vec.crbegin(), vec.crend(), [](int tt) {cout << tt << " "; });
	cout << endl;

	sort(vec.rbegin(), vec.rend());  // 降序 排序
	for_each(vec.crbegin(), vec.crend(), [](int tt) {cout << tt << " "; });
	cout << endl;

	system("pause");
	return 0;
}

100 99 78 63 55 7
7 55 63 78 99 100
  • 某个容器只有在支持 “ ++ ” 和 “ -- ” 的操作时才能定义反向迭代器。
  • 那么除了 forward_list 之外, 标准库上的其他迭代器都即支持 “ ++ ”又支持 “ -- ” 。
  • 但是流迭代器不支持“ -- ” , 因为不可能在一个流中反向移动。因此, 不可能从一个 forward_list 或一个流迭代器创建反向迭代器。

反向迭代器 的 base 成员函数 (363P)


  base 成员函数 会返回其对应 的普通迭代器:

int main()
{
	string line = {"FIRST,MIDDLE,LAST" }; 
	auto comma = find(line.cbegin(), line.cend(), ','); // 查找第一个元素
	cout << string(line.cbegin(), comma) << endl;
    
    
	auto rcomma = find(line.crbegin(), line.crend(), ','); // 查找最后一个元素
     // 正确:得到一个正向迭代器,从逗号开始读取字符直到line末尾
	cout << string(rcomma.base(), line.cend()) << endl;
	system("pause");
	return 0;
}

FIRST
LAST
C++Primer 第五版 —— 《第十章 》泛型算法_第6张图片 反向迭代器和普通迭代器之间的关系
  • 反向迭代器的用于表示元素范围, 而这些范围是不对称的。 这导致一个重要 的结果: 当我们从一个普通迭代器初始化一个反向迭代器, 或是向一个反向迭代器赋值时,  结果迭代器与原迭代器指向的并不是相同的元素。

泛型算法中迭代器的类型(365P)


  •  任何算法的最基本特性是它要求其迭代器提供哪些操作(就是说该算法提供的迭代器有哪些操作元素的能力( 比如说:读写、访问元素、递增迭代器、等等。)).  每个算法都必须指定为每个迭代器参数提供哪种迭代器
  •  迭代器按它们提供的操作进行分类,这些类别形成了一种层次结构。除了输出迭代器之外,高级类别的迭代器提供低级类别的迭代器的所有操作。

泛型算法 5 种迭代器支持的操作类型 ( 365P)



算法传递参数的规范(367P)


alg(beg, end, other args);
alg(beg, end, dest, other args);
alg(beg, end, beg2, other args);
alg(beg, end, beg2, end2, other args);

算法参数的命名规范(368P)



reverse 、 reverse_copy、remove_if、remove_if_copy 算法 (369P)


算法遵循一套命名规范和重载规范。 这些规范是用来处理诸如:

  • 如何提供一个操作来代替默认的 <  或 == 运算符以及算法是将输出数据写入输入序列还是写入指定的目标位置。
  • 如果有些算法接受谓词参数来代替 < 或 == 运算符,并且不接受额外参数的算法( 例如: sort、unique、),通常都是重载过的函数。
  • 如果有些算法接受一个额外的元素值(例如:find),那么该算法通常有另一个不同名的(不是重载的)版本, 该版本接受一个谓词来代替元素值。接受谓词参数的算法都有附加的 if 前缀(例如: find_if).
  • 默认情况下,重排元素(例如:unique)的算法会将重新排列的元素写回给定的输入范围中。这些算法还提供了另一个版本,将某个元素添加到一个指定的目标位置。这类算法都在名字后面附加一个 _copy( 例如:replace_copy 、reverse_copy )。
  • 一些算法同时提供 copy 和 if 版本 ( 例如: remove 算法中的 remove_if、remove_if_copy 算法 )。这些版本接受一个目的位置迭代器和一个谓词

 


list 和 forward_list 提供特有的算法操作(369P)


  • sort 的通用版本需要随机访问迭代器。因此,sort 不能 与 list 和 forward_list 一起使用,因为这两种类型分别提供了双向迭代器 和  前向迭代器
  • 列表类型定义的其他算法的通用版本可以与列表一起使用,但要以性能为代价。因为这些算法交换输入序列中的元素。列表可以通过更改元素之间的链接来快速交换元素,而不是 “ 交换” 这些元素的值。因此,列表提供的特定版本算法比相应的通用版本获得更好的性能
  • 所以说 对于 list 和 forward_list, 应该优先使用成员函数版本的算法而不是通用算法。

list 和 forward_list 提供了 Splice 算法 (370P)


  • 列表类型还定义了一个splice算法,该算法特别适用于列表。因此,该算法没有通用版本。
  • 大多数列表的特定版本的算法与它们的通用版本算法相似,但并不完全相同。然而,列表特定版本和通用版本之间的一个至关重要的区别是:列表版本会改变了底层容器。 例如,remove 的列表版本删除指定的元素。unique 的列表版本删除了第二个和后续的重复元素。

本章完......

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