《C++ Primer》第10章 算法(一)

news2024/10/7 2:29:12

参考资料:

  • 《C++ Primer》第5版
  • 《C++ Primer 习题集》第5版

10.1 概述(P336)

大多数算法定义在头文件 algorithm 中,头文件 numeric 中也定义了一组数值泛型算法。

一般情况下,算法不直接操作容器,而是通过迭代器来进行操作。比如标准库算法 find ,前两个参数是表示元素范围的迭代器,第三个参数是一个值。find 将范围内的每个元素与给定值相比较,返回第一个与给定值相等的元素的迭代器,如果范围中无匹配元素,则返回第二个参数

int val;
auto result = find(vec.cbegin(), vec.cend(), val);

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

int *result = find(begin(arr), end(arr), val);

算法如何工作

只要有一个迭代器用来访问元素,find 就完全不依赖容器类型。

迭代器令算法不依赖于容器

利用迭代器解引用实现元素访问;使用迭代器递增运算符可以移动到下一个元素;使用迭代器比较可以判断是否到达序列末尾。

但算法依赖于元素类型的操作

find 使用元素类型的 == 运算符,其他算法可能要求 < 运算符。

关键概念:算法永远不会执行容器操作!算法只会运行于迭代器之上。算法可能改变容器中元素的值,也可能在容器中移动元素,但永远不会直接添加或删除元素

尽管一类特殊的迭代器——插入器(inserter)可以在底层容器上执行插入操作,但算法自身不会执行这样的操作。

简单来说,算法只会执行迭代器操作和元素操作。至于真正操作的是什么类型的迭代器,算法并不关心。

10.2 初识泛型算法(P338)

标准库算法大多对一个范围内的元素进行操作。接受输入范围的算法总是用前两个参数表示这个范围,分别指第一个元素和尾元素之后的位置。

虽然大多数算法遍历输入范围的方式类似,但它们使用范围中元素的方式不同。理解算法最基本的方法就是了解它们是否读取元素改变元素或是重排元素

10.2.1 只读算法(P338)

一些算法只会读取范围内的元素,如 findcount 。另一个只读算法是 numeric 中的 accumulate ,它接受三个参数,前两个指出需要求和的范围,第三个参数是和的初值

// vec是一个整数序列
int sum = accumulate(vec.cbegin(), vec.cend(), 0);

accumulate第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型

算法和元素类型

accumulate 将第三个参数作为求和起点,这要求将元素类型加到和上是可行的:

string sum = accumulate(v.cbegin(), v.cend(), string(""));
string sum = accumulate(v.cbegin(), v.cend(), "");    // 错误,const char*没有+运算符

操作两个序列的算法

另一个只读算法是 equal ,用于确定两个序列是否保存相同的值。此算法接受三个参数:前两个表示第一个序列中的元素范围,第三个表示第二个序列的首元素:

equal(roster1.cbegin(), roster1.cend(), roster.cbegin());

equal 只会一一比较元素,所以假定两个序列一样长。两个序列的元素类型不必相同,只要能用 == 比较即可。

10.2.2 写容器元素的算法(P339)

fill 算法接受一对迭代器表示一个范围,还接受一个值并将这个值赋予输入序列中的每个元素

fill(vec.begin(), vec.end(), 0);    // 将每个元素置0

算法不检查写操作

fill_n用一个迭代器和一个长度表示范围,算法假定容器足够容纳要写入的元素。

介绍back_inserter

当我们通过插入迭代器(insert iterator)赋值时,一个与赋值号右侧相等的元素被添加到容器中。

back_inserter 定义在头文件 iterator 中,接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器:

vector<int> vec;    // 空向量
auto it = back_inserter(vec);
fill_n(it, 10, 0);    // 通过插入迭代器向vec中添加元素

拷贝算法

copy 算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置,将输入范围中的元素拷贝到目的序列中。目的序列至少要与输入序列包含一样多的元素:

int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
int arr2[20] = {};
copy(begin(arr1), end(arr1), begin(arr2));

copy 返回迭代器,指向拷贝的最后一个元素之后的位置。

多个算法提供拷贝版本:

vector<int> vec1 = { 0,1,0,1,0,1,0,1,0,1 };
vector<int> vec2;
// vec1内容不变
replace_copy(vec1.begin(), vec1.end(), back_inserter(vec2), 0, 1);
replace(vec1.begin(), vec1.end(), 0, 1);

10.2.3 重排容器元素的算法(P342)

sort 利用元素类型的 < 运算符来实现排序。

消除重复单词

假设我们有如下输入,并希望消除重复单词:

the quick red fox jumps over the slow red turtle

为了消除重复单词,首先将 vector 排序,然后使用 unique 算法重排 vector 使得不重复的元素出现在 vector开始部分。如果我们想要删除重复元素,还需要使用 vectorerase 成员:

void elimDups(vector<string> &words) {
	sort(words.begin(), words.end());
    // unique返回不重复区域之后的位置
	auto end_unique = unique(words.begin(), words.end());
	words.erase(end_unique, words.end());
}

10.3 定制操作(P344)

10.3.1 向算法传递函数

如果我们希望将单词先按单词长度排序,再按字典顺序排序,则可以使用另一个版本的 sort ,它接受第三个参数——一个谓词(predicate)

谓词

谓词是一个可调用的表达式,其返回结果是一个能作为条件的值

标准库算法使用的谓词分两类:一元谓词(unary predicate)二元谓词(binary predicate)。接受谓词参数的算法对输入序列中的元素调用谓词。

接受一个二元谓词参数的 sort 版本用这个谓词代替 < 来比较元素:

bool isShorter(const string &s1, const string &s2) {
	return s1.size() < s2.size();
}

sort(words.begin(), words.end(), isShorter);

排序算法

为了保持相同长度的单词按照字典序排列,我们可以使用 stable_sort 算法。

10.3.2 lambda表达式(P345)

假设我们要知道大于等于一个给定长度的单词有多少,并且只打印这些符合条件的单词。我们可以使用 find_if 算法,它接受一对迭代器表示范围,接受一个一元谓词,对每个元素调用这个谓词,返回第一个使谓词非 0 的元素的迭代器。

介绍lambda

一个对象或一个表达式,如果可以对其使用调用运算符,则称其为可调用的。可调用对象有四种:函数、函数指针、重载了调用运算符的类、lambda表达式。

lambda 表达式是一个可调用的代码单元,可以将其理解为一个未命名的内联函数。lambda 表达式具有返回类型、参数列表、函数体。与函数不同,lambda 可以定义在函数内部:

[capture list] (parameter list) -> return type { function body }

其中,capture list(捕获列表)是 lambda 所在函数中定义的局部变量列表(通常为空)。lambda 表达式必须使用尾置返回来指定返回类型。

我们可以忽略参数列表返回类型,但必须永远包含捕获列表函数体

auto f = [] {return 42; };

cout << f() << endl;

lambda 忽略括号和参数列表等价于指定一个空参数列表,如果忽略返回类型,lambda 根据函数体的代码推断返回类型:如果函数体包含 return 语句,则返回类型从返回的表达式类型推断而来,否则返回类型 void

lambda传递参数

lambda 不能有默认参数:

[](const string &a, const string &b)
	{return a.size(), b.size(); };

使用捕获列表

lambda 如果想要使用所在函数中的局部变量,就需要在捕获列表中指明:

// sz是指定的单词长度
[sz](const string &a)
	{return a.size() > sz; };

调用find_if

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

for_each算法

for_each接受一个可调用对象,并对输入序列中每个元素调用此对象:

for_each(wc, words.end(),
	[](const string &s) {cout << s << ' '; });

捕获列表只用于局部非 static 变量lambda 可以直接使用局部 static 变量和它所在函数之外声明的名字。

10.3.3 lambda捕获和返回(P349)

当定义一个 lambda 时,编译器生成一个与之对应的、新的、未命名的类类型。当我们把 lambda 传递给函数时,目前可以理解为同时定义了一个新类型和该类型的一个对象,传递给函数的参数就是这个对象。

默认情况下,从 lambda 生成的类都包含对应捕获变量的数据成员,并在对象创建时被初始化。

值捕获

值捕获的前提是变量可以拷贝,需要注意的是,被捕获变量的值在 lambda 创建时被拷贝,而非调用时拷贝:

int x = 0;
auto f = [x] {return x; };
x = 1;
cout << f() << endl;    // 输出结果为0

引用捕获

int x = 0;
auto f = [&x] {return x; };
x = 1;
cout << f() << endl;    // 输出结果为1

如果采用引用方式捕获一个变量,就必须保证被引用对象在 lambda 执行时是存在的。

隐式捕获

我们可以在捕获列表中写一个 =& ,让编译器根据 lambda 体中的代码来推断我们需要哪些变量:

auto wc = find_if(words.begin(), words.end(), 
	[=](const string &a)
	{return a.size() > sz; });

隐式捕获和显式捕获可以混用,但必须保证捕获列表中的第一个元素是 &= ,且显式捕获的变量必须使用与隐式捕获不同的方式

此处有个图片

可变lambda

默认情况下,对于一个值捕获的变量lambda 不会改变其值。如果我们希望改变被捕获变量的值,就必须在参数列表后加上 mutable 关键字。因此,可变 lambda 不能忽略参数列表:

int x = 0;
auto f = [x] ()mutable {return ++x; };
x = 10;
cout << f() << endl;    // 输出为1

指定lambda返回类型

标准库 transform 算法接受三个迭代器和一个可调用对象,前两个迭代器表示一个输入范围,第三个迭代器表示目的位置。算法对输入范围内的每个对象调用可调用对象,并将结果写到目的位置:

vector<int> v = { -1, 2, -3, 4 };
transform(v.begin(), v.end(), v.begin(),
	[](int i) {return i < 0 ? -i : i; });    // 正确
transform(v.begin(), v.end(), v.begin(),
	[](int i) {if (i < 0) i = -i; return i; });    // 正确
transform(v.begin(), v.end(), v.begin(),
	[](int i)->int {if (i < 0) i = -i; return i; });    // 正确

10.3.4 参数绑定(P354)

如果我们需要在多个地方使用相同的操作,或是一个操作需要很多语句才能完成,我们最好定义一个函数,而非使用 lambda 表达式。

如果 lambda 的捕获列表为空,通常可以用函数替代它;如果捕获列表非空,用函数替代就不太容易(比如前面提到的 find_if 函数只接受一元谓词,而用函数替代 lambda 则至少需要两个参数)。

标准库bind函数

bind 函数定义在头文件 functional 函数,接受一个可调用对象,生成一个新的可调用对象:

auto newCallable = bind(callable, arg_list);

arg_list 是逗号分隔的参数列表,对应给定 callable 的参数。当我们调用 newCallable 时,newCallable 会调用 callable ,并传递给它 arg_list 中的参数。

arg_list 中可能包含形如 _n 的名字,这些参数是“占位符”,表示 newCallable 的参数,例如:_1newCallable 的第一个参数。

绑定check_sizesz参数

void print(const int &x, const int &y) {
	cout << x << ' ' << y;
}

auto print1 = bind(print, _1, 6);
print1(1);    // 输出1 6
auto print2 = bind(print, 6, _3);
print2(0, 1, 2);    // 输出6 2

使用placeholder 名字

名字 _n 都定义在命名空间 placeholders 中,而 placeholders 又定义在命名空间 std 中:

using namespace std::placeholders;
// 单纯的using namespace std不可行

bind参数

我们还可以用 bind 给可调用对象中的参数调整顺序:

sort(words.begin(), words.end(), isShorter);    // 升序排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1));    // 降序排序

绑定引用参数

假设我们有下面的 lambda 表达式:

// os是一个局部变量,引用一个输出流
// c是一个局部变量,类型为char
[&os, c] (const string &s) {os << s << c; };

我们试着用 bind 来实现类似的功能:

void print(ostream &os, const string &s, char c){
   os << s << c;
}

bind(print, os, _1, ',');    // 错误,os不能拷贝
bind(print, ref(os), _1, ',');    // 正确

如果我们不希望 bind 拷贝参数,必须使用 ref 函数或 cref 函数。

直白地说,就是先将参数传递给 newCallable ,这一步是值传递还是引用传递由 bind 函数决定;然后,newCallable 将参数传递给 callable ,这一步是值传递还是引用传递由 callable 决定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1247196.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SOLIDWORKS 2024新功能之CAM篇

SOLIDWORKS 2024 新功能 CAM篇目录概述 • 附加探测周期参数 • 反转切割的固定循环螺纹加工 • 包含装配体的零件的正确进给/速度数据 • Heidenhain 探测类型 • 2.5 轴特征向导中岛屿的终止条件 • 链接轮廓铣削操作的切入引导和切出引导参数 • 螺纹铣削操作的最小孔…

SpringCloud 微服务全栈体系(十七)

第十一章 分布式搜索引擎 elasticsearch 七、搜索结果处理 搜索的结果可以按照用户指定的方式去处理或展示。 1. 排序 elasticsearch 默认是根据相关度算分&#xff08;_score&#xff09;来排序&#xff0c;但是也支持自定义方式对搜索结果排序。可以排序字段类型有&#…

标题导航点击导航滑动到指定位置滑动到指定位置选中对应导航vue3

菜单导航栏点击导航滑动到指定位置&滑动到指定位置选中对应导航 效果 实现 话不多说直接上代码&#xff0c;有用素质三连(点赞、评论、加关注) import { defineComponent, onBeforeUnmount, onMounted, reactive, ref } from "vue"; import { map } from &quo…

家用小型洗衣机哪款性价比高?口碑最好迷你洗衣机排行榜

由于我们的内衣、内裤和袜子等等贴身小件衣物的清洁频率比一般的衣物要高。而且&#xff0c;如果我们人工手洗的话&#xff0c;不仅会大大浪费了我们的时间&#xff0c;而且还不能进行对这些贴身的以为进行深层消毒和除菌。这种情况下&#xff0c;就得需要一台专门用于清洗内衣…

怎么去掉视频水印?分享三种视频去水印技巧,轻松搞定!

怎么去掉视频水印&#xff1f;在分享视频时&#xff0c;我们常常会遇到因为水印而影响观感的问题&#xff0c;因此&#xff0c;掌握有效的去水印方法显得尤为重要&#xff0c;随着技术的进步&#xff0c;现在有多种方法可以帮助我们去除视频中的水印。 接下来分享三种简单且有效…

【算法挨揍日记】day33——1027. 最长等差数列、446. 等差数列划分 II - 子序列

1027. 最长等差数列 1027. 最长等差数列 题目描述&#xff1a; 给你一个整数数组 nums&#xff0c;返回 nums 中最长等差子序列的长度。 回想一下&#xff0c;nums 的子序列是一个列表 nums[i1], nums[i2], ..., nums[ik] &#xff0c;且 0 < i1 < i2 < ... < …

两个链表的第一个公共节点(相交链表),剑指offer,力扣

目录 题目地址&#xff1a; 题目&#xff1a; 我们直接看题解吧&#xff1a; 解题方法&#xff1a; 审题目事例提示&#xff1a; 解题思路&#xff08;双指针&#xff09;&#xff1a; 具体思路流程&#xff1a; 代码实现&#xff1a; 算法思路补充证明&#xff1a; 力扣题目地…

Doris中的物化视图-查询(十九)

物化视图创建完成后&#xff0c;用户的查询会根据规则自动匹配到最优的物化视图。 比如我们有一张销售记录明细表&#xff0c;并且在这个明细表上创建了三张物化视图。一个存储了不同时间不同销售员的售卖量&#xff0c;一个存储了不同时间不同门店的销售量&#xff0c;以及每…

图数据库HugeGraph:HugeGraph-Hubble基于Web的可视化图管理初体验

原创/朱季谦 一、HugeGraph-Hubble简介 关于HugeGraph&#xff0c;官方资料是这样介绍的&#xff0c;它是一款易用、高效、通用的开源图数据库系统&#xff08;Graph Database&#xff09;&#xff0c; 实现了 Apache TinkerPop3 框架及完全兼容 Gremlin 查询语言&#xff0c…

2023.11.23 云服务器实现 Spring Boot 项目文件上传并访问

环境介绍 云服务器&#xff1a;京东云云服务器系统&#xff1a; CentOS 7.9JDK 版本&#xff1a;1.8Spring Boot 版本&#xff1a;2.7.17 具体步骤 步骤一 首先我们得先创建一个 Spring Boot 项目 创建如下目录结构 关于如何创建一个 Spring Boot 项目 请点击下方链接详细了解 …

vs2015如何远程启动程序来进行调试

vs远程调试的方式有两种&#xff0c;远程启动方式和附加进程方式。   一般来说&#xff0c;咱们使用vs调试代码时&#xff0c;直接附加进程即可&#xff0c;但某些时候附加进程方式无法命中断点。比如我们想调试的C代码&#xff0c;但是调试的入口程序是C#程序&#xff0c;如…

JavaScript实现动态背景颜色

JavaScript实现动态背景颜色 前言实现过程HTML实现过程CSS实现过程JS实现过程全部源码 前言 本文主要讲解JavaScript如何实现动态背景颜色&#xff0c;可以根据颜色选择器选择的颜色而实时更新到背景中&#xff0c;如下图所示。 当我们在颜色选择器中改变颜色时&#xff0c;会…

2023年10月纸巾市场分析(京东天猫淘宝平台纸巾品类数据采集)

双十一大促期间&#xff0c;刚需品的纸巾是必囤商品之一。今年双十一&#xff0c;京东数据显示&#xff0c;10月23日至29日&#xff0c;清洁纸品成交额同比增长40%&#xff0c;由此也拉动了10月纸巾市场的销售。 鲸参谋数据显示&#xff0c;今年10月&#xff0c;京东平台纸巾市…

Django JSONField/HStoreField SQL注入漏洞(CVE-2019-14234)

漏洞描述 Django 于2019年8月1日 日发布了安全更新&#xff0c;修复了 JSONField 和 HStoreField 两个模型字段的 SQL 注入漏洞。 参考链接&#xff1a; Django security releases issued: 2.2.4, 2.1.11 and 1.11.23 | Weblog | DjangoDjango JSONField SQL注入漏洞&#x…

java设计模式学习之【工厂模式】

文章目录 引言工厂方法模式简介定义与用途&#xff1a;实现方式&#xff1a; 使用场景优势与劣势工厂模式在spring中的应用电费计算示例&#xff08;简单工厂模式&#xff09;改善为方法工厂模式代码地址 引言 在软件开发的世界中&#xff0c;对象的创建可能是一个复杂且重复的…

NX二次开发UF_CSYS_edit_matrix_of_object 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CSYS_edit_matrix_of_object Defined in: uf_csys.h int UF_CSYS_edit_matrix_of_object(tag_t object_id, tag_t matrix_id ) overview 概述 Updates the specified coordinat…

杰发科技AC7801——ADC简单理解

前言 7801资料读起来不是很好理解&#xff0c;大概率是之前MTK的大佬写的。在此以简单的方式进行描述。我们做一个简单的规则组软件触发Demo。因为规则组通道只有一个数据寄存器&#xff0c;因此还需要用上DMA方式搬运数据到内存。 AC7801的ADC简介 7801的ADC是一种 12 位 逐…

7-22 龟兔赛跑

import java.util.Scanner; class Main {public static void main(String[] args) {Scanner scnew Scanner(System.in);int timesc.nextInt();sc.close();int wugui 0;//乌龟里程int tuzi 0;//兔子里程int tuzi_run0;int tuzi_rest0;int is_rest0;//是否需要休息&#xff1a;…

Python大语言模型实战-记录一次用ChatDev框架实现爬虫任务的完整过程

1、模型选择&#xff1a;GPT4 2、需求&#xff1a;在win10操作系统环境下&#xff0c;基于python3.10解释器&#xff0c;爬取豆瓣电影Top250的相关信息&#xff0c;包括电影详情链接&#xff0c;图片链接&#xff0c;影片中文名&#xff0c;影片外国名&#xff0c;评分&#x…

游戏是第一生产力!Python实现生命游戏的示例代码(tkinter版)

文章目录 生命游戏(Game of Life)游戏概述生存定律图形结构 代码实现运行界面使用简介后续改进关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五…