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

本文详细介绍了C++ Primer第五版中关于泛型算法的使用,涵盖find、accumulate、equal、fill、fill_n、back_inserter、copy、replace、replace_copy、sort、unique、stable_sort、partition、lambda表达式等多个算法及其应用场景。重点讨论了如何通过lambda表达式自定义比较操作,以及在不同容器中的应用,强调了迭代器在泛型算法中的重要性,并给出了多个示例和练习题目解析。
摘要由CSDN通过智能技术生成

目录

目录

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<int> 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<string> 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<int> 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<int> 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<string> 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<int> roster1{ 0,1,2,3,4,5 };
	vector<int> roster2{ 0,1,2,3,4,5 };*/

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

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

	vector<int> roster1{ 0,1,2,3,4,5 };
	vector<int> 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<int> roster1{ 0,1 };
	list<float> roster2{ 1.f,2.f,3.f };*/

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

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

		// 下面这样发生运行错误
	vector<int> roster1{ 1,2,3,4 };
	list<float> 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<double> 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<int> 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<int> 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<int> vec;
	fill_n(vec.begin(), 10, 0); //灾难:修改vec中的10个(不存在)元素,因为vec是空的
	for (auto tt : vec)
	{
		cout << tt << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

看运行结果为:

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

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


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


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

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

int main()
{
	vector<int> 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 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值