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

请尽可能地详细说明一个VLA项目的立项训练到落地的全流程 一个机器人VLA项目的全流程是一个复杂的系统工程,它深度融合了机器人学 (Robotics)计算机视觉 (CV)和自然语言处理 (NLP)。其核心挑战在于高质量数据的获取和从仿真到现实的有效迁移。与纯粹的软件AI项目相比,它增加了硬件、物理交互和安全性的维度,但其最终能让AI走出虚拟世界、在物理世界中产生实际价值,这也是其巨大的魅力所在。 阅读详情

目录

目录

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 5 42

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

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

	fill_n(back_inserter(vec), 6, 0); 
	for (auto tt : vec)
	{
		cout << tt <
Qwen-Image(阿里通义千问)技术浅析(一) Qwen-Image(阿里通义千问多模态模型)是阿里巴巴推出的视觉-语言多模态大模型,能够理解图像内容并完成复杂的跨模态任务。 阅读详情

相关推荐

基于FreeSwitch与SIP UA的蓝牙电话坐席系统实战部署

本文详细介绍了如何基于FreeSwitch开源软交换平台,结合SIP UA软电话与蓝牙电话App,在局域网内搭建一套低成本、自主可控的电话坐席系统。文章提供了从环境准备、FreeSwitch安装配置、用户与拨号计划创建,到SIP软电话测试和蓝牙设备接入的完整实战部署流程,并给出了关键优化与排坑指南,适合小微团队或个人开发者构建内部通讯工具。

zeta9的博客 545

超分辨率重建:ESPCN与SRGAN技术对比

超分辨率重建:ESPCN与SRGAN技术对比​ 人工智能,计算机视觉,大模型,AI,超分辨率重建是一种通过技术手段,将低分辨率图像转换为高分辨率图像的过程。它的目的是恢复低分辨率图像中丢失的细节信息,使图像具有更高的清晰度和视觉质量。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 7万+

php环境如何实现国密SM2相关功能

🏆本文收录于《优快云问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

**My Coding Family** 1616

数字图像处理:直方图匹配或规定化Histogram Matching (Specification)原理及感悟

本文介绍了连续图像和数字图像直方图匹配(直方图规定化)的原理、处理过程,并提供了案例进行了讲解。可以看到,直方图均衡处理是直方图匹配的一个重要桥梁。 最后,对于直方图规定化这个翻译个人觉得很low,个人感觉叫指定直方图更好理解,但Histogram Specification怎么翻译确实不太好把握,因此还是叫Histogram Matching直方图匹配最好。

老猿Python 2880

开关电源-LLC基本原理

本文详细介绍了LLC谐振变换器的兴起原因、基本工作原理、变换器特点及拓扑结构。重点讨论了半桥、全桥LLC及其软开关技术,包括零电压开关(ZVS)和零电流开关(ZCS)。通过理解简单的谐振电路(RL、RC、LC)来阐述LLC如何改变电压增益,以及如何通过调节频率实现输出电压的稳定性。

让你爱上电路设计 2万+

华为OD面试八股真题 - 操作系统 - 1

华为OD技术面,操作系统相关八股真题提供详细解答,这篇博客主要涉及于进程和线程相关知识点

qq_45776114的博客 1644

第 5 篇:责任链模式 (Chain of Responsibility) —— 协议栈的流水线

本文介绍了责任链模式在嵌入式通信协议解析中的应用。针对传统"面条式"代码维护困难的问题,提出将各协议解析器解耦为独立处理单元,通过链表形式串联。文章详细展示了模式实现:定义抽象处理者接口、实现具体协议处理器、构建链表管理器。该方案具有内存占用低(O(1)栈空间)、可动态扩展、支持优先级调整等优势,并探讨了性能优化策略及动态插件系统等扩展应用,为嵌入式通信协议处理提供了清晰的架构方案。

一路往蓝-Anbo 的博客 179

一文掌握 MongoDB 详细安装与配置(Windows / Linux / macOS 全平台)

本文详细介绍了MongoDB社区版7.0+在Windows、Linux和macOS三大操作系统上的完整安装、配置与安全加固流程。主要内容包括:各平台推荐安装方式(Windows使用MSI安装包、Linux通过官方仓库、macOS通过Homebrew)、基本配置参数说明、服务启动方法以及关键安全措施(如创建管理员用户和启用身份验证)。文章还提供了系统最低要求、下载地址、数据目录设置建议以及生产环境使用注意事项,适合开发、测试及生产环境部署参考。

数据知道的博客 6715

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

上一篇: 《译 SFML Essentials 英文版》—— 《第五章》 “ Exploring a World of Sound and Text ”
下一篇: 《随心记六》SFML中的 “ Rendering Special Effects with Shaders ”
To-String
博客等级 码龄11年 218粉丝 209原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值