斯坦福-CS106A-B-L-X-编程入门笔记-十二-
斯坦福 CS106A/B/L/X 编程入门笔记(十二)
【编程抽象方法 cs106x 2017】斯坦福—中英字幕 - P9:Lecture 09 - Recursion 3 - 加加zero - BV1By411h75g
快速快速公告,快速提醒,今天是星期五,现在是第三周,今天是你最后一天可以退课了,我知道你们都不想放弃这门课,因为你做得很好,你是如此的棒等等,但如果你有朋友认为,他们犯了一个可怕的错误。
报名参加了六个X中的一个,你应该警告他们,今天是他们清算的日子,如果你想知道这是否会变得更容易,稍后,我的回答是哈哈哈哈不,我是说,我们将处于这种循环中,大概一周一个任务。
伊什有一个期中考试即将在三个星期左右,所以你知道,就是这里了,这是个很难上的课,我把你淹没在家庭作业中,这基本上是残忍而不寻常的C加惩罚,所以你知道你得到了什么,如果你今天之后还在这里,你是我的。
我可以做任何事,我想让你在学术上,所以无论如何,嗯小心点,就是今天,所以我有几个人问从X切换到B,或者别的什么,嗯,我的意思是,B是很多相同的课程材料,但是作业少了,和一点不同的分级曲线。
一些类似的东西,所以你知道两者都有好处,但我希望你们中的一些人在今天之后会留下来,嗯好吧,所以我想和你们做更多的递归,今天我要讲分形,它们是递归图形,真的很酷,但总的来说,这是做更多递归的另一个借口。
这就是游戏的名字,事实上,如果你展望下周,我们正在做更多的递归,我们将使用一个名为回溯的递归特定应用程序,我们星期一再谈,但是没关系,让我们再快速复习一遍,嗯递归,这时函数调用自己。
如果你必须写一个递归函数,你应该做些什么,有些东西是什么,你应该考虑一下,方法是什么,是啊,是啊,你寻找一些寻找,2。这个问题跟它本身有什么相似之处,是啊,是啊,完全没问题。
这可能会给您一个关于递归调用需要是什么的提示,从你的电话到下一个电话,好啦,你还应该考虑什么,是的,是的,精细基础案例,谢谢你,我想在接下来的几周里,如果你不确定要回答什么问题,就说基本情况。
可能是对的,或者至少部分信贷,嗯,是啊,是啊,寻找基本情况,寻找不需要递归的情况,因为你得在某个时候停下来,对呀,所以是的,这些都是很好的建议,好啦,所以我的意思是,当你谈论自相似性时。
我觉得你应该想想,我的电话和下一个电话有什么相似之处,那个电话怎么像我,所以你看,让我们开始吧,让我们再做一些例子,如果这东西花了你一段时间,就像如果你看看,我想很多学生说他们,当他们看到解决方案。
就像他们看到我写解决方案,他们走了,是啊,是啊,好吧有道理,但如果他们必须用空白屏幕解决问题,他们冻结了,他们走得很好,我不知道,我不知道写它和读它有什么不同,所以我真的认为。
即使您遵循我们正在编写的代码,我觉得你应该通过解决问题来测试自己,在没有其他人帮助的地方,你还没有看到答案,试试看,因为大多数使用递归代码的学生,他们从,你知道的,从一个州到另一个州。
他们开始说我的代码不起作用,我不知道为什么,后来他们说我的代码有效,我不知道为什么,所以你知道,反正我也不知道,呃好吧,我们再练习一下,这里还有一些递归问题。
我想先教你们一种特定的递归,我相信这是我在幻灯片上首先看到的,哦不,我不介意,我只想做个练习,这个有点棘手,这叫评估,我不想写一个完整的表达式解析器,因为要花很多时间,会有太多的代码等等。
但我想看看包含数学运算的字符串,我想计算一下这些操作的结果,让这变得更容易一点,我要说的是所有东西都用括号括起来了,这意味着如果有任何运算符要执行,比如加号或乘法或其他什么,那个操作员和它的,你知道的。
操作数的相关部分将被圆括号包围,所以我可以用这些来寻找操作,好啦,我还将假设字符串是有效的,里面没有假字,也只是为了让它变得非常简单,我要让所有的数字都是个位数,所以我们不必喜欢,试着担心在。
或者类似的东西,原来如此,这里唯一的字符是数字,括号和运算符,我想让它变得更简单,我要说你只有加次数,虽然我认为你可以很容易地一旦我们得到它的工作,我们也可以添加其他运算符,这就是计划。
这就是我想和你们一起写的,你刚给了我一些好建议,你叫我找基本情况,你让我寻找自我相似的东西。
好吧好吧,让我们试着听从那个建议,让我们去可爱的创造者,如果你想跟着,这个是一步一步来的,也在这个可爱的创作者项目上,所以你说,思考,想想自我相似的事情,你能告诉我这有什么相似之处吗,有什么问题,好啦。
我把这些打印在括号里,我想那是其中的一部分,有一种措辞我正在寻找,虽然,比如如何评估,计算表达式如何类似于计算表达式,你明白我的意思吗,是啊,是啊,在黑暗中的后面,是啊,是啊,是啊,是啊。
所以括号内的任何东西都好像是整个,好啦,如此如此,如果这是我需要计算的表达式,这是一个我需要评估的表达式,你这么说好像,这也是一个我可以排除的表达,所以如果我知道它的价值。
我会更接近于知道这一切的价值对吧,因为那样我就一加八了,然后也许我可以看看我是否能理解,所以是的,感觉是对的,右边这个直角,我想你也是这么说的,只是用不同的话来说,这不是这里要做的全部。
但这是个好的开始,那么基本情况或简单的事情呢,嗯,什么是容易评估的表达式,是啊,是啊,你的表情就像第一个是七,只是一个数字,你有一个表达式,它只有一个运算符,都很容易评估,好啦,当然,当然。
如果你只是看到一个数字本身,只是,你知道的,将字符串转换为int,将7字符串转换为7 int,求值并返回,好啦,这些都是好的开始,我觉得最难的是,我想和你们一起做这个的原因。
我想即使我们看到了这些好东西,这些都是很好的起点,我想有时候还是很难喜欢,做所有的管道喜欢,做我们刚才说的那些事,好啦,我想我可以说,如果这是一个单一的字符串,它是一个整数,然后转换或其他什么。
但我认为很容易迷失在所有不同的角色中,因为如果我有这个屏幕,比如我怎么知道我需要把这部分拿出来,你知道我的意思,就像我是怎么想的,我会去找这个,把它变成一个节拍,然后现在就容易多了。
但我怎么知道角色就在那里,我应该变成一个八,我好像漏掉了一些步骤,有点,你知道的,好吧好吧,让我提出一个思考这个问题的方法,我认为这将是有帮助的,不要把它当成一根绳子,把它想象成一排代币。
每个代币都可能是不同种类的东西之一,它可以是一个整数,也可以是开头或结尾的括号,也可能是接线员,如果你想象一下,就假装这是一个字面上的队列,支架,关心什么的,我可以把字符从队列中拉出来。
然后当我看着他们的时候,我可以做任何我想做的事情,如果你这么想的话,我认为这可能有助于解决这个问题,有人举手,是啊,是啊,去吧,我在想,我们可以再次通过每个操作员本身,但这并不像一些人那样容易。
因为你得找到括号里的对手,这就是为什么,但你只要把操作数传来传去,嗯,我想我们可以做的是如果我们看到一个操作数,好吧,其实让我这么说吧,在你看到接线员之前,上环,对不起,你会看到一个插入语。
您可以假设字符串是有效的,对呀,所以你会看到一个插入语,紧随其后的是歌剧,后面跟着一个操作员,后面跟着一个操作员,所以我永远不会随机看到一个加号,我总是会看到我们的插入语,如果我看到一个插入语。
我可以启动这种五步过程,读括号,读一个数字,读取运算符,读一个数字,读一篇文章,那有点过于简单化了,虽然,因为这些可能不仅仅是数字,对呀,它们可能是表达,它们可能是括号中的整个表达式。
所以这似乎是一种俄罗斯娃娃的自我相似性,那里发生的事情,嗯好吧,嗯,在这里,这是我想在这里带来的最后一种见解,我想如果你想把这当成,这是一个字符队列,你可以想象我们在穿越角色,看着他们,现在呢。
我们不用循环,因为它们是递归的很酷,我们不需要循环,但我确实认为从一个电话到下一个电话,你应该有进步,所以我们要么把这根绳子削小一点,或者我们需要以某种方式移动自己,所以我们看到的是弦的不同部分。
我可以想象一个人说,你知道的,也许我会嗅出一些字符,你可以这么做,我想让你看看,我认为一个很好的技巧是如何做到这一点,传递您在字符串中的位置的索引,你现在看到的地方,这不是解决这个问题的唯一方法。
但我认为它最终会很优雅,所以看看我在想什么,我们可以写一个帮助函数,很多时候当你做递归的时候,我们在周三的讲座上看到了这一点,有时你写另一个函数,它有你需要的参数,然后我们有一个函数,我们应该写。
调用我们的函数,所以我们有点两全其美,如果我在评估eval helper中编写了类似于,它需要一根绳子,但它也需要对索引的引用,我在弦上的什么位置,好吧,那么当你第一次打电话的时候,评估实际评估功能。
让我折叠这个注释,以便当您调用实际的求值函数时,你会说像int指数等于,你从哪里开始零右,然后你说在我的表达式字符串上返回eval helper,从那个索引开始,所以现在我要做的是,我有一个全局int。
我可以前进,当我看着每一个角色,所以是的,问题是,为什么不使用静态变量,静态变量的问题,好吧,让我们看看,问题是一个静态变量,即使所有的电话都打完了,我返回的静态变量保留了它的值,所以我想要的是。
我想要一个贯穿我的调用和所有子递归调用的变量,但当我回来,我离开这里,我想让变量噗的一声消失,所以我不希望它停留在指数12或其他什么,在我做完之后,但这是个好问题,我是说静态变量没有错。
我想在这种情况下它们不起作用,所以我的想法是这样的,我觉得这个帮手,你知道你可以,您可能正在解析,你知道一加二,二乘三加四,我说对了吗,除了你什么都可以,你知道的,指数三,所以这意味着你。
你知道你是那种零一,二三,你在你在这里,所以你在绳子的某个位置,你会假设也许你已经解析了所有的东西,您处理了这个索引之前的所有内容,但不包括这个指数,所以让我们想想所有不同的情况。
在这个索引中可能有什么,嗯,它可能是我们所看到的给定索引中的内容,我们必须把我们需要处理的所有不同的情况分开,它可以是一个插入语,也可能是个数字,你可能会说很好,如果是接线员什么的呢。
但就像我在见接线员之前想的那样,您将看到一个括号,所以我想我们现在还不需要检查接线员,所以我想我们要做的是,我们想做一些像,哎呦,如果你想知道一个字符是否是一个数字,有一个叫做数字的方法。
你传递一个字符,它返回一个布尔值,所以不如,如果是个数字,什么位于x括号索引,哎呀哎呀,呃,所以如果那里有一个数字,那是什么样子,如果我们像这里,让我们说,那我们该怎么办,字符串到整数,好啦,是啊。
就像转换,有一个字符串到整数函数,但我想它需要一根绳子,而不是一个关心,我想我能做到,但我认为最简单的方法是说,从Care 0到9转换为int 0到9,所以我可以把它退回去,所以换句话说。
如果我就在这里,那我要做的就是,我只需返回一个1的int数,所以我不想在这里解析整个字符串,算一算,我只是在计算当前的子表达式,我指的是现在,这就是这个helper函数的目标,所以如果我坐在一个数字。
那么当前子表达式就是这个数字,另一件事是,当您的助手正在处理字符串时,我需要前进,所以每次我看角色的时候,我需要喜欢,越过那些字符,这样其他电话就不会再看那些相同的字符了,好吧我想我需要做的是。
你知道的,int结果等于,然后指数加加,然后返回结果,如果你想成为忍者,你可以说返回指数加,你知道的,我的意思是,就像这样先从索引中得到它,然后当它跳伞的时候,它会加加它,所以说,如果你像那样奇怪。
你就去吧,那是基本情况,对吧,我们不必在那里做任何递归,基本情况,好吧不然,我们可能遇到的其他类型的案件是什么,括号,好啦,让我们先处理括号,我听到接线员,2。我想最后到总机,我想我们会明白为什么。
如果x索引是括号呢,让我们记下来,因为我不想忘记任何事,让我们记住,我们也要考虑闭括号,我们想考虑运算符,因为你们就是这么说的,这些是唯一的其他角色,我能想到的权利,让我们记住我们不想忘记他们,好啦。
如果是插入语,那一般呢,我们现在要这么做吗,我想也许发生了什么,我们看到我们有,你知道的,二加三,同样的事情找到其他括号,是呀,那是个有趣的主意,你可以,你可以说很好,如果这是我的插入语,谁和我匹配。
我可以去找对了,所以我当然可以这么做,我想我想试着找到一个不同的解决方案,因为向前看匹配过程,感觉更像是,我需要一个循环,否则我就得打电话给find或字符串上的什么东西,我想我能做的是。
我可以把大块的字符,如果我做了正确的递归,我几乎不需要知道匹配的括号在哪里,我想你会明白的,如果我们做对了,所以我是说我们会看到的,我们以后再说,所以说,如果我在括号里,嗯,也许我应该做的第一件事。
我想超越它,因为我想,我要读一下,基本上我会处理,所以说,也许我会说索引加号,这意味着跳过括号字符,现在呢,什么,括号里是什么,这是一个,它通常有一个操作数,然后是一个操作员,然后一个操作数右。
然后它也应该有一个结束括号,好啦,嗯,让我们读操作数,所以操作员就像一个二,所以我应该做这种,你吃了多少,那种代码,那我想是的,我们再叫帮手,新的X是的,所以关于操作数的问题是,操作数可以是表达式。
子表达式,所以我不应该只读一个字符,就像这里的2,我应该说嘿,在这个括号之后,不管是什么,那个索引是我需要得到的值,它可能只是一个int,会是这样,也可能是一堆括号之类的东西,就像,如果我的光标是。
二乘三是我整个表达式的左操作数,就在这里就在这里,所以让我们使用递归读取操作数,所以说,相同的字符串和相同的索引,因为我把自己加到前面的括号里,所以现在这里有一个关键的概念要理解,这通电话打完之后。
让我们假设我们在看这根弦,我们刚刚做了一个递归调用,记住递归是如何工作的,你应该假装,我希望我自己的代码有效,我希望我的递归在这里做正确的事情,如果成功的话,接下来会发生的是,它会计算2乘以3。
然后返回6,索引的光标将在哪里,这家伙做完之后,如果他工作,应该是他会狼吞虎咽地吃他正在处理的所有东西,如果他的编码正确,所以他应该把我留在那里,因为我们共享对同一个索引变量的引用。
我们所有的调用都共享这个变量,所以在这通电话之后,如果代码有效,索引就在这里问题,但指数不会只是在,数字2,然后回到左边,好吧,对不起,我想我想我们在电话里有点迷路了,我想是的好的,让我让我再试一次。
我想我认为那是不正确的,但让我看看我能不能通过这个,所以让我们假装我的电话,我们追踪的电话,我们是对的,所以我不是一个数字,所以我读了一个括号,我把我的索引加加在线四一,所以索引现在就在那里。
现在我说做一个递归评估,所以第二个电话在这里,所以他进入了同样的逻辑,所以他读了两遍括号,三个括号胜过它们,对我来说是六倍的回报,现在我要回到这里,我想现在我觉得有点难,因为我们还没有写代码来喜欢。
做时间做结案流程,所以我有点假装这是要去工作时,我还没写呢,但这是一张像借条一样的借条,我希望这能评估整个表情,把那六个还给我,然后它会把光标放在圆括号的末尾,是啊,是啊,问题,你能和接线员谈谈吗。
操作员,哦,实际上做接线员,没有聪明的把戏,真的吗,我只想说如果还有别的什么,所以是的,让我们读一下那个接线员,操作员是我现在要扮演的角色,在递归调用al helper之后,如果你在这个例子中迷路了。
我知道这是个更难的问题,所以我选对了,所以做这个,走过这个,所以如果我是一个插入语,好吧好吧,我是索引加加现在跳过它,做Eval助手,它也会看到这个,把它转换成整数2然后把它还给我。
他们会把索引加起来给我,所以我知道这很管用,因为,我看得出来我要跳到这里来,在第二次通话时做到这一点,我可以追踪到所以现在我知道是第二个人把我搬到这里来的,所以我在这里,所以现在我将读取这个运算符。
所以说,什么,哎呦,词运算符,是呀,呃,我永远不会忘记,你知道很长很长,很久以前,呃,本科,你知道我在帮助学生,就像你们一样,我去实验室帮助学生解决他们的错误,你知道吗,我超级紧张。
因为我想有这么多不同种类的虫子,我也不知道,你知道我不知道,如果我能帮助他们,然后有一个叫吉格尼什的家伙,他就像一个更高级的部门领导,他就像,别担心,男人,你得到了这个,一切都会好的。
就像你可以做任何事,然后我带着我的第一个学生过去帮助他们,我就是想不通,就像,这是Java代码,我只是我看不到虫子,我看不到窃丨听丨器,我开始觉得,因为我觉得自己很无能,然后我示意吉尼什过来,你能不能。
我只是找不到这个窃丨听丨器,他走过去,他看了半秒钟,他说他们把变量命名为continue,那是个关键词,他走开了,他们就像哇哦,他真聪明,我就像,他怎么知道的,后来我就像伙计,你怎么知道的,他很好。
屏幕上是蓝色的,你知道关键字变成蓝色,哦,颜色,所以实际上,只是你让我想到了,因为这个操作员是黄色的,是呀,反正,所以好吧,那是关于我的一点,嗯,所以阅读运算符,所以我们要抓住那个接线员。
现在要么是加号,要么是倍数什么的,嗯,每当我们喜欢读一些我们要处理的东西,那是我们的,我们应该把输入光标移到右边,所以我想我们应该在这里做指数加,所以我们走在前面,所以现在我们的光标将在这里。
现在我们得到了操作员的右侧,右上兰特,所以我觉得我们应该再来一次,这里,让我试试这个,这个代码越来越高了,我想试着在屏幕上适应更多,所以大家可以看到,所以现在当正确的事情做了,他会读并吃掉这三个。
所以他会把我的光标放在那里,如果成功的话,所以现在这里应该有一个结束括号,我想我可以用if语句来测试,但如果我假设字符串是有效的,我应该知道我所扮演的角色,现在必须是一个结束括号。
所以我想跳过最后的括号,我会说索引加号,所以这只是意味着跳过那个字符,你也许会说,谁在乎,如果我跳过或不跳过,但这就像嗯,如果我是这种事情的一部分,你知道我需要跳过公主到什么是下一个或任何权利。
所以跳过括号,现在我做对了,我想我有这个左运算符或操作数,我有运算符,我有正确的操作数,所以基本上我只需要说如果运算符是正的,然后返回左加右,否则,如果这次行动,我想我甚至不必说如果还有,只是。
你知道的,我知道这次行动,因为我们的假设,向左返回,好啦,是啊,是啊,这是一个接一个的谜题,我能出界吗,如果字符串格式无效,是呀,如果我能假设字符串是有效的,那就不会发生了,所以说。
如果你在最后有喜欢呢,然后每当你使用索引时,一旦我们到达终点,哦,就像你说的,在溪流的尽头,你就在这一切的最后,索引将是一个通过,但没有人会试图去那里,没有人会试图进入那里,我想我们会没事的,所以好吧。
我们基本上完成了,我们基本上完成了,我要说的是,我要把这个删掉,这里的if语句,因为如果你假设这些字符串是有效的,这是你现在唯一能有的两个案子,你们提到了接线员,但我永远不会去见接线员。
除非它前面有圆括号和操作数,所以我不必检查它作为自己的外部情况,就像结尾一样,如果我必须检查这些东西,我在检查伪造的表情,我不想那样做,所以我要把其他的,我会评论如果,我编译和运行,然后呢。
我觉得它很酷,如果您在看到代码工作时遇到困难,我的建议是进行大量的递归,如果你不太明白,在递归调用的开始处放置一个see out语句,就说Eval助手,x等于x,比如打印参数,你知道的,所以我的意思是。
啊,哎呦,我做得好的是什么,让我们来看看,这样很难读懂,所以让我们把它当成一个,如果阿尔帮手,它从索引零开始,所以它就在那里,导致了一个带有索引1的递归调用,就在那儿,所以实际上。
然后索引1调用导致对2的调用。
就在那儿,实际上,为了让这一切真正发挥作用。
我想我要做的是,我再放一样东西进去,哪里,如果我要退货,我说出去,int结果等于看到返回结果,然后我会说返回结果,然后在这里我将做int结果,我只是想让这个更容易调试的结果等于,结果等于。
然后这里我会说返回结果,所以也许我会说递归大小写返回,在这里,我会说基本情况返回,所以让我们再试一次,那么我哪里做错了,我没有还,为什么它不给我一个错误,C加+烂透了,只是把垃圾还回去。
你有没有注意到它像200亿一样回来了,管他呢好吧,我再试一次,嘿嘿,你想看些很酷的东西吗,如果不是看到外面,所以有一个函数,我们图书馆里有个标题叫呃,递归,点H不,它不包括自己,聪明的屁股。
虽然它应该它真的应该,我应该进去换一下,我应该说包括递归,但不管怎样,那个h文件有几个函数,递归的妙处,所以我认为这里有一个函数叫做递归缩进,所以你能做的就是,你可以用它打印缩进的东西,就像这样。
我想我有一阵子没这么做了,但我想是的,你看现在都缩成这样了,哦耶,酷,所以现在我不必坐在这里手动做这个,所以好吧,那么它擅长什么呢,我们称它为指数零,所以它就在那里,然后打电话给索引1上的助手。
它就在那里,然后在索引2上调用,就在那里,这是一个基本情况,所以它返回int也是这个家伙的一部分,他对索引4做了另一个递归调用,因为他读了,一个是子呼叫,然后他读加号,然后他对那个调用进行递归。
那是基本情况,它返回三个,上面这个人回来,这是他应该归还的,因为他在处理字符串的那一部分,所以他回来,现在他回来之后,他把光标从这个人一直移到这里,所以这家伙会看到他在这里有一个时间,现在他说好。
我得读对操作数,所以他从索引七打了这个电话,哪个在那里,但你知道你怎么能,你可以追溯一下这是怎么回事,所以我想我不会从头到尾读一遍,但不管怎样,这就是它的工作原理,那个有点棘手,很难表达。
但你可以做很多很酷的事情,你知道的,自我,类似的递归函数类型调用关于表达式求值的任何其他问题,所以求值函数的唯一点就是初始化,启动递归,是啊,是啊,所以计算函数只是让我们进入函数,很多时候,如果你说。
啊,我想我能解决这个问题,但我必须传递一个额外的,一个额外的字符串和额外的向量,罚款,写那个,但如果你必须做我说要写的那个,那就让我的呼唤你的,只要用。
用我的作为适配器打电话给你的,再表演一次,你怎么做到的。
然后看起来会是,我是怎么做缩进的,所以你必须包括递归h,然后你就看到绳子,有一个名为递归缩进的函数,它返回一个空格字符串,基于深度递归调用的数量,你在同一个功能,所以更多的空间,更多电话。
所以我就打印了那条流,接下来是什么,我其实,但这只是一个澄清,但我们回来了,结果和增量索引,是呀,所以在表达式中间使用加号,它将使用旧值,因为它在看东西,但在它之后,它就会增加价值。
所以如果你不喜欢这个,呃风格恰到好处,只是我真的,当我为自己编码时,我不这样编码,我以为你们这些怪人会喜欢,所以我会写这个没有加号,因为那是我想要的,我想要当前的索引,然后在它自己的线上。
我想说指数正加,是啊,是啊,但这就是它的作用,你说的对,好再来一张,然后我想继续前进,然后弄清楚你的感受,Dent中的递归是如何工作的,那是黑魔法,你得上黑魔法防御课才能了解这一点,有点复杂。
以下是我的建议,如果你想知道我们的图书馆是如何实现任何东西的,双击右键,单击它并按照符号操作,很容易,很容易,你可以去看代码,等一下等一下,但它叫这口井,这怎么会,所以嘿,那种是自我。
查找某物的自我相似过程,反正我也得查点东西,我鼓励你去看看。
好啦,所以我想继续前进,我想讨论分形,我不想没时间了,这里还有一件我今天要看的东西,称为尾递归,我稍后再讲,你不需要它做家庭作业,但我要谈谈分形,分形是递归的艺术,分形很酷,他们真的很有趣。
你可能在重复模式之前见过这种分形图像,自相似模式,我喜欢分形,它是,你知道的,我想教你递归,但我希望你能用它做点什么,那很有趣,很酷,分形看起来很整洁,它们画起来很有趣,做动画也很有趣,所以嗯。
屏幕上有一些分形的例子,有很多很多这样的例子,好啦,所以这里有一些例子,这个叫做兹文斯基三角,又名三力,但是它们内部不断重复的一系列越来越小的嵌套三角形,这个叫曼德尔布鲁套装。
我要让你在作业中编写其中的一些代码。
我们今天要在这里编码其中的一两个,所以分形不仅仅是老师们喜欢的东西,它们实际上是自然界中发生的事情,不同的岩层、植物、云朵和雪花,各种各样的东西实际上都有这些自我相似的重复模式,他们很酷。
分形真的很有趣,数学和计算之间的美妙舞蹈。
好啦,那么如何对分形井进行编程,通常我们所做的是讨论分形的水平,我们说,啊,第四层是相邻的层之间通常有相似之处,你说得好,这是通过把扑克从它的三个侧面拉出来而变成的,或者像你这样的人。
你描述了一个从一个到下一个的突变过程,然后从这个到这个,你用同样的方法把这个的边缘都拉出来,然后你得到下一个,或者类似的东西,你知道你,但是这个术语就像一个分形的水平,分形有某种基本模式。
然后变成重复的版本,在下一个层次上的更小版本,所以画分形,我们将使用斯坦福图形库,这不是很好,因为它很慢,不过没关系,它会,会有用的,嗯,标准坐标系,就像你在家庭作业里看到的,一个零,零在左上角。
然后我们往下走,从那里我们使用这个叫做G窗口的对象,我们在上面绘制,我们不需要很多,它有其他方法你可以在现场查看,如果你想知道,但最主要的是我们只是想从一个点到另一个点画我们的线,原来如此。
我不打算在这张幻灯片上花很多时间,我们只是想划清界限,这很容易,就说画线嗯,那么你怎么把这个分形画好呢,你得把方块画对,但你得把它们按正确的顺序画出来,那么你画方块的顺序是什么,嗯。
您必须对每个当前调用进行四次递归调用,分形的每一级或每一阶,你必须,在某个时候,有人必须画一些矩形,那么你把填充矩形的调用放在哪里,有什么想法吗,A,B,C,D,E,所以这个x和y坐标是约化的。
所以这意味着它在左上角,右上角,左上角的人在我下面,所以这意味着他必须被画在我面前对吧这个是右下角,他也在我下面,意思是他在我面前,这个是右上角,他在我上面,所以他一定是被我吸引来的,这个是左下角。
他在我上面,所以他只是,我觉得是,呃,是C,我们得把自己的位置画对,所以不管我是什么意思,你可以把它放在不同的地方,看到一个稍微不同的数字出来,我只是想向你们展示画不同阶的分形的想法。
注意到我们所做的是当我们进入下一个层次时,我们缩小我们的规模,所以我们得到了一个更小的自己,最终得到0或负一,不管基地是什么,我们就会停止成长,所以实际上你会在这些分形故事中看到的一件事是,很多时候。
基本情况和递归情况可能不同,它可能更像是一个空的基本情况,你只是不画任何东西,所以好吧,让我们画一个非常简单的分形,它被称为康托集安德森只是一个模式,你有水平线,然后随着水平的下降。
你有越来越小的水平线看起来像这样,这个我觉得是七级一二三四五六七,模式是你画的下一层,以前水平的三分之二,所以这是第三个,以前水平的前三分之一和前三分之一,对呀,就是这样,所以说。
让我们写一个名为cantor的函数,你告诉我画在哪里,就像线的xy位置,线路的起始位置,你告诉我线的长度或大小,以及您想要的图形的级别数,我会把它画得,递归右基例,康托尔集的一个容易的水平是什么。
第一个只是一条线,对呀,好啦,我想我可以做这部分,你们这些家伙,我的意思是,可能会让你帮我写另一部分,但我想我能做到,让我看看,我只是在这里打开我的分形文件,我在这里设置了一个康托尔,好啦。
所以我们很平淡,你喜欢我的腹肌艺术吗,顺便说一句,那是艺术可以设置的,所以说,你刚才说,我觉得第一关就是一条线,马马虎虎,如果等级只有一个,那我就对着窗户说,我想画一条从x y到井的线,它是一条水平线。
从这里开始,一直延伸到这么长,所以x随着长度的增加而增加,Y一点都没变,所以是x加上长度,一样的Y,否则,我想我可以说,如果级别小于或等于1,只是为了不让我们得到任何奇怪的负数的东西,如果级别不止一个。
嗯,听起来很难,嗯,很明显是相似的,但我不知道如何表达,如果你想画一个七级,因为这看起来真的很难,有时一个有用的建议是想一个,这只比简单的基本情况提高了一个级别,你怎么画二级,康托尔集,后面,是啊。
是啊,所以你从上面的水平中提取,这是第一次,第三次,一个,第三次,又是第二个标记,好啦,所以画两条小一点的线,我说的是在水平之间放置20像素的垂直空间,所以基本上如果我是第二级。
我仍然需要为第一级画一条线。
但我需要画这两条较小的线,所以如果我是第二级,不如我们记下来吧,二级,我还是会画这条线,但在我下面我会画两条较小的线,所以你是说,左边的第三个,就像这里的这个人,和我一样是开始的x。
但只有三分之一的y是我,所以像同样的x y,但也许会向下移动到20,然后长度是我的长度超过三,他的Y和他开始的Y加20是一样的,那是左边的,然后你说对的那个就在旁边,再加上三分之二的长度。
这样你就不能通过长度了,你实际上在这里传递结束的x坐标,所以我认为这是x加上长度,或者类似的东西,就像,画两条小一点的线,然后画,我不想吹毛求疵,我认为这是一个很好的开始。
只是我们的递归函数缺少了一些东西,我不太清楚那是什么,有一个X加号的人,少了一个x加号,好啦,在三扇窗户上,好啦,聪明的家伙,罚款,有两件事,我的反应还是少了点什么,它没有任何递归,对吧,那很重要。
嗯,好啦,也许会变得更清楚。
如果我看第三级康托集,所以我想要这个,所以现在我还是要画我,我真的想要这两个,但我也想要这些,然后呢,你知道就像,我怎么做,有什么想法吗,是啊,是啊,是啊,是啊,也许不用下面这两条线。
你这样在最下面的两行代码,下面的两行是52和53,所以与其说生产线制革机,然后有对应的x和y,然后好吧,所以不仅仅是,我想在这里画一条线,我想画一个水平,两个慢跑在这里,就这么大,所以一个级别。
三个计数器集是一条线,紧随其后的是两个较小的,两套计数器,所以把这个改成,康托尔集,它需要一个窗口,所以我得通过窗户,x y长度水平,所以实际上长度是3的长度,嗯,我不用再通过考试了,有哪些级别。
我应该写在这里,我的水平,我什么水平,我少了一个,对所以是的,这里也一样,把这个变成慢跑套装,相同的长度超过三个,我想然后水平我做错了吗,你什么意思,长度要乘以3,因为你会好起来的,不,如果我是第三级。
这个长度的癌症集,这意味着我是第三级,我是,但我希望小一点的人是我的三分之一,但你要上去,你的水平在上升,也在下降,这意味着你要在数字上上升,这取决于你想怎么想,我觉得你让我画第三层,我这么大。
所以如果我要传递到较小的部分,他们必须比我小,所以我把我三分之一的长度传给他们,所以你的想法是自下而上,也许如果我想画七级,我应该先画两个然后再画一个大一点的然后再画一个大一点的。
我认为这种方式从大开始,然后把它分解成更小的部分,我认为坐标数学比试图计算,如果我是如果我一直在这里,我想去更大的地方,我想这可能更难,我不知道,肯定不止一种方法可以做到这一点,让我们哎呀,稍等一下。
没有改变我的主要,这是正确的,呃,我们再试一次,我还有虫子吗,你不知道,你不必嘿看看,我有一套慢跑装备,对不起,我不必做我想做的事,二级以下,你不必划清界限,因为你已经为第一级画了一条线,是啊,是啊。
我想我想我们通常在这里做的是,如果级别至少是一个,我们这样做,这是一种较短的版本,我想我想这很管用,所以如果我至少是一级,画一条线,然后画小一点的家伙,但如果那些小个子最终变成了零级。
他们不会输入if语句,也不会画任何东西,所以实际上,这是同一段代码的一个稍微小一点、更性感的版本,嗯,如果你真的想让它更容易看到,你可以做一个,就像两个两个像素厚的画两条线。
这样在投影仪上就更容易看到了,还有一个是如果你想看图纸的顺序,你可以说停顿10或者20,只是短暂的停顿,然后你看的时候,你可以看到它有点填充,每一行都是自上而下,它在向右之前先向左,但这是一种递归。
我真的认为它可以提供很多信息,如果你还在学习递归,把延迟调高一点,只是看着它,把每一个电话,你知道的,我可以一整天都这样,谁需要瑞克和莫蒂真是太好了,当你有一个缓慢的绘制,分形,我能做更高的水平吗。
当然呃就像,我觉得问题是,它们在下面变得很小,基本上就像一个小像素,就像,啊,嗯,做一个更大的窗口。
也许两三个,我在我的幻灯片上还有一个,我可能没时间给你看,如果你有兴趣,也就是,嗯,让这些小饼干在它们的两边弹出来,这个其实更难,如果你想挑战自己,你可以试着往右走,这个,我觉得困难之处在于,就像。
这个和这个的坐标在哪里,结果证明真正有帮助的是,因为你要编码,这个是用极坐标,因为在极坐标系中,你只需指定一个角度和距离,你想旅行,想弄清楚所有这些的x y真的很难,但也算是说得好,我想从这里到这里。
你可以说很好,那是一只雄鹰,这么远的20和1θ,你说,好啦,我要走那条路,但是,哎呦,我是递归的,所以几乎三分之一的方式,那我就转啊转啊转,你可以更容易地描述这个数字,所以如果你想看看这些幻灯片。
看到这种进展,这是我最后几分钟的建议,因为我不想,我无法在两分钟内完成编码,所以我不打算开始,但是幻灯片中的解决方案,如果你想看看你折磨人的家庭作业会是什么。
我给你看一下,我有一些演示,我可以跑,你们的部分作业是画一些分形,所以你要画像谢尔宾斯基三角形这样的东西,那是第一级,这是第二级,你知道第三级吧,你要,您将编写代码来绘制这个zpinsky三角形。
像这样对吧,所以你会做那个,您还将执行一个称为递归树的操作,就是你画一个小树枝,这也得益于极线、角度和坐标,那很有趣,你还要演一个叫洪水的,填充,世界上最慢的绘画算法,你点击一个正方形。
它就会用项圈填满它,然后呢,当然,您可以递归地这样做,通过寻找与您颜色相同的相邻像素,以此类推,不要点击白色背景部分,上色大概需要四到五分钟,哎呦,你想让我做,好啦,罚款,呃,走啊,看看你让我做了什么。
看看你让我做了什么那首歌烂透了,我不敢相信托特做了那首歌,嗯,还有,其实还有曼德尔布鲁套装,呃艾米,告诉我,她觉得它可能坏了,让我看看它是否和我的演示一起工作,在那里检查出来,其实那个看起来不太好。
但如果我给它指定更多的订单,会收紧的,否则会更好看,我再试一次,有曼德尔兄弟的布景,那个很酷,这就是你们三人作业的第一部分,您还将编写一个句子解析器,你走了一套语法规则来生成随机的愚蠢句子。
也许我会运行真正的快,我知道该走了,但是你要指定一个文件名,像句子文本,你说你想要判决,你想要十个,然后你会得到像大大学一样的荣誉,大潜意识大学,我想这是某种事件,无论如何,那是第二部分,这是递归的。
因为你知道短语可以包含子短语等等,所以你递归地生成这些句子,第三部分是,你要写一个20个问题的游戏,在问题和答案树中行走,试图猜测用户在想什么,它有一个非常有趣的数据文件,你在想什么动物。
那个人在躲着你,这就是你的任务。
祝你周末愉快,我们星期一再见。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P1:[2]CS 106L Winter 2020 - Lecture 1_ Intro - GPT中英字幕课程资源 - BV1Fz421q7oh
开始了。
哎呀,你能看到我吗?真糟糕。
欢迎大家。哦我的天。是的,随意坐在左边的一半。
实际上,我们首先会请你转向你左边或右边的人,做自我介。
绍。通常,106L 是一个对 C++ 或其他编程语言非常感兴趣的,人参加的课程。所以它通常是一个非常紧密的社区。希望这个季度我们能够继续保持这种氛围。哦,你们的名字是什么?Pam?Sonia?
Sonia。Sonia。好的。还有 Glenn。好的,非常好。这是我在外面教的课程。你们在做设计哲学。是的。我会很快讲完。是的,一切都完成了。一切都很好。
是这个,对吧?哦,你好。欢迎大家。欢迎大家。
好的,如果你们准备好了,我先打开 Piazza 页面。是的,所以我们在这个课堂上主要使用 Piazza 来提问。这是我们的 Piazza 页面。在最底部。这正是你们所期望的样子。是的。
这里会发布诸如作业修改或填写调查问卷的提醒,这,些都能为你们提供延期等。是的,当然,我们也希望你们在 Piazza 上提出任何 C++ ,相关的问题。是的,Avery 和我很乐意回答任何问题。
即使这些问题不一,定是我们在课堂上讨论的内容。是的,这门课真的给你们一个与我们一起探索语言的机会,所以如果你们在本季度的 106B 中看到过的内容,也可以,随时询问。再说一次。
请访问这个链接加入课堂的 Piazza。欢迎大家。是的,请向前移动。我们不小心弄了一个巨大的讲堂。所以请坐到前排或附近。请向前移动。我们不会咬人。只有 Avery 会。只有我。是的,但我现在真的很饿。
但是请向前移动,因为这个讲堂真的很大,我们想和你们互,动。我们的讲座大多是互动的。所以请向前移动。不要坐在后面。我们保证最终会记住你们所有的名字。是的。是的,也请向前移动。请再向前一些。135?133?
230。230?好的。好的,所以我们还有一分钟课程开始。所以你们可以趁这个机会看看你们左边或右边的人,做自,我介绍。比如说,你们的年级、学的专业。是的,相互了解一下。至少要知道彼此的名字。你好。
欢迎大家。欢迎大家。是的。坐在前面任何地方。是的,请坐在前面。我之前实际上上的是数学课。所以看起来有点像这样。是的。好,你们想开始了吗?我开始了,好的。好的,我们稍后会回到这一页。但让我们开始吧。是的。
我们有很多幻灯片。让我们看看。好的,一切准备好了。太棒了。好的。大家好。欢迎来到CS106L。好的,所以耶。是的,好吧,我们今天要做什么?今天,我们首先欢迎大家来到CS106L。
我们会谈论为什么选择C++。在所有存在的编程语言中,为什么C++如此重要?然后我们会问,好的,那为什么要选CS106L?CS106L在你们在斯坦福的长途旅程中是如何定位的?我们会谈论这些。
然后我们会讨论一些关于这门课的后勤事务,这门课的要,求是什么。然后我们会深入探讨C++的历史和哲学。安娜会讲解这些。最后,如果我们有时间的话,我们会介绍一些基本的C++。示例代码。是的。昨天。
如果你在基思的课上,他做了C++的简要介绍。我们会介绍另外一部分。对比一下,基思在课堂上讲的现代版本。好的,我们开始吧。首先是一个介绍。这些是我们的照片。是的,你好。很高兴见到你们。我的名字是安娜。
我是本季度106L的两位讲师之一。我现在是计算机科学专业的本科生,专注于系统,并且对网,络安全感兴趣。如果你对这些感兴趣,可以在课外和我聊聊。大家好。我叫艾弗里。那是我抱着一只玩具鳄鱼的照片。是的。
我现在是大三学生。我是数学专业的,虽然我可能会做一个计算机科学的联合,学位。所以可以随时问我相关的问题。是的,我们喜欢教学。过去,我们都曾是部分课程的负责人。是的,曾经是CS106项目的负责人。是的。
所以可以随时问我们有关CS106、CS106A、CS106B的,任何问题,如何申请成为课程助教。如何申请成为课程助教。是的,我们会稍后详细讲解这些。但这就是我们自己的简要介绍。
让我们谈谈为什么选择C++。那么,有多少人现在在CS106B呢?好的,所以你们中有很多人。然后,好的,有多少人以前上过CS106A的Java课程?好的,106A的Python课程呢?好的,我明白了。
所以,很多106B的学生上学期学了106A。上学期,你们学了Python,但你们可能会想,好的,我们已经,有了一个很好的编程语言。那为什么还要学另外一种语言呢?
所以,尽管很多人会告诉你,哦,C++是一个过时的语言。Python是世界上的新语言。这并不完全正确。C++仍然是一个非常流行的语言,尤其是在最近几年。这些年,它们对语言进行了重大改进,使其更容易使用。
因此,C++的受欢迎程度仍在增长。是的,所以C++在Python的影响下复兴了。我们发誓C++和Python之间没有战争。这只是一个内部的竞争。是的,尽管我昨天确实去了CS41的Python课程。
试图挖学生,这并没有奏效。但是,是的。
使用C++的课程。所以有很多课程使用C++。事实上,你会发现,C++的许多应用都是与性能相关的。你可以看到,有很多并行计算、计算机网络。许多这些课程使用C++是因为C++是一种性能强劲的语言。
使用C++的公司。我们总是问到的最大问题是,C++会帮助我找到工作吗?答案是肯定的,会的。几乎每家公司都在某处使用C++。事实上,如果你去Handshake上搜索C++,你会得到成千上万,的结果。所以。
这是一个有趣的事实。许多浏览器都是用C++编写的。你们都有用这些浏览器吗?好的,是的。所以你们可能听说过,浏览器编程非常困难,它们全都是用,C++编写的。因为性能很重要。你想要一个慢的浏览器吗?你不想。
这就是为什么许多浏览器用C++编写的原因。C++中的软件。我总觉得很有趣,Java实际上是用C++实现的。所以如果你正在使用任何语言……,我不确定Python。Python是用C++实现的吗?好的。
不是。啊,太糟糕了。但是,是的,Java是用C++编写的。你用过的许多软件工具,都是用C++编写的。游戏。许多游戏是用C++编写的。所以如果你……,从你对游戏的了解来看,当你玩这些游戏时,这些游戏相当。
密集。这些游戏使用了大量内存。如果你尝试在你的笔记本电脑上运行游戏,如果你的笔记,本电脑不够好,你的笔记本电脑会变得相当慢。为了应对这个问题,许多这些游戏需要真正高性能的语言。
你会注意到我一直提到高性能,因为这是C++的最大优势之,一。我们将在本讲座稍后深入探讨高性能的实际含义,特别是,当我们谈论历史时。好的。所以,有趣的事实。C++存在于不止一个星球上。好的?
所以有一个火星探测器。这个还在运行吗?我不认为它还在运行。但是现在里面仍然有C++代码。可能还有其他探测器里面也有更多的C++代码。许多这些火星探测器、许多这些飞行器,它们里面都有用,C++编写的代码。
好的?好的。每季度我都试图发音这个名字,但总是失败。你怎么发音?比约恩·斯特劳斯特鲁普?比约恩·斯特劳斯特鲁普。可能。好的。我们就叫他,他是C++的创始人。确实如此。是的。如果你上网。
看到他写的任何东西,人们都会非常尊敬。他不仅是开发C++的人,他还写了C++的核心哲学是什么。所以我们将阅读很多他写的东西。但这是他给出的一句名言。这太长了,我会让你稍后再读。但现在你可能想问,好的。
显然 C++ 很重要。那你为什么想学 106L 呢?难道 106B 不教 C++ 吗?所以我们将简单比较一下 106B 和 106L 的区别。106B 叫做编程抽象。你会注意到。
C++ 这个语言并没有写在名字里。C++ 不是 106B 最重要的方面。106B 的目标不是仅仅学,习 C++。106B 的目标是学习编程抽象。你很快会在 106B 中学到这意味着什么。尽管我们给你一。
个 C++ 的基本介绍,你也只学到足够的内容,以便实际编,写有意义的程序,使用像递归、抽象数据类型、链表和二,叉搜索树这样的数据结构。C++ 是一个工具,但 C++ 不是 CS106B 的重点。
另一方面,在 CS106L 中,我们的目标是教你标准 C++。你可以通过一些方式来看,稍后我会提到这一点。但确实,CS106B 因为 C++ 不是目标,所以目标是学习对所,有语言都重要的编程概念。
CS106B 是相当语言无关的。即使你从未使用过 Python 或其他语言,在学习完 106B ,后,目标是你可以将这些知识转移到其他语言中。
但我们在 CS106L 中学到的大多数东西都直接适用于 C++,我们特别关注 C++ 的独特之处。其中最大的部分是模板。在 CS106B 中,你学到的 C++ 只是足够学习编程抽象的更,大目标。
但在 CS106L 中,我们将教授足够的 C++ 以应对任何工作,、任何实习、任何你做的研究。我们不会教你整个 C++ 语言,但我们会给你一些基本的熟,悉度。
以便你可以轻松地在需要时查询一些 C++ 的内容。在 CS106B 中,使用的是 C++98 的版本。有猜测这是什么时候发布的吗?1998 年,好吧。那是在我出生之前。是的。
这是一个非常非常旧的 C++ 版本。想到 C++ 从那时起没有演变是令人遗憾的。好吧,实际上它有演变,但 106B 并没有真正采用这些变化,在 CS106L 中,我们将学习 C++17 的内容。
有猜测这是什么时候发布的吗?2017 年,是的。实际上,今年还有一个新标准即将发布,叫做 C++20,2020,C++20 在所带来的变化方面非常具有革命性。最后一次重大变化是 C++11。
发生在九年前,现在他们还在,进一步改进。关于 CS106B 的一件事,我不认为 Keith 还没有提到,但,在 CS106B 中,你将使用斯坦福库,因为 C++ 是一种非常,混乱的语言。下节课。
我们将讨论流,关于如何获取用户输入。用户输入真的非常复杂,你将在接下来的课上学习。斯坦福库所做的是抽象出所有混乱的细节,以便你可以直,接调用一个名为 getInteger 的函数。
Keith 还没有使用过 getInteger,对吧?好的。在斯坦福库中,有一个名为 getInteger 的函数,它做的正,是你想的那样。明白了吗?但要真正理解 C++,你需要深入了解这些混乱的细节。
了解, getInteger 是如何工作的,以便你可以编写自己的函数,是的。在CS106B中,如果你去找任何一位助教,问他们如何使用,cin,他们会告诉你,请不要使用cin。
只要使用getInteger,即可。好么?但在CS106L中,我们将实际学习如何驯服cin。是的。给106B的作业一个小贴士。不要使用cin。好么?如果你使用cin。
你可能会遇到一个需要花费不少时间来调,试的bug。是的。根据经验。是的,cin使用起来非常困难。我们会在星期四看到这一点。所以如果你去找助教,如果他们看到你使用cin,他们会告,诉你,嘿。
使用getInteger。Cin真的很难用。但现在你们将知道如何使用它,所以你们实际上会走在前,面。耶。但仍然,请不要在考试中使用它。好,CS106L的目标是什么?我们有三个主要目标。
第一个目标是了解C++中有哪些功能以及它们为何存在。因为Google现在如此可用,实际上没有必要记住所有内容,但知道这些功能是什么很重要,这样当你确实需要使用这,些功能时,你就知道它们是功能。
并且可以在Google上查找,好么?所以我们将探索C++中的酷功能。另一个我们要做的事情是讨论如何阅读C++文档?
因为这是一个如何在C++中声明向量的例子。好么?所以如果你不熟悉向量是什么,向量在Java中类似于数组,列表。在Python中,它就是一个普通列表。所以要创建一个向量,我认为有12种不同的构造函数可以。
使用。好么?你应该知道第一个。第一个只是创建一个空向量。但你会看到所有这些奇怪的向量。有填充向量、范围向量、移动向量。还有一个叫做初始化列表的东西。那到底是什么?然后你会看到这些奇怪的类型,这些与符号。
Keith将会讲解一个与符号的意思,但你可能会好奇,两个,与符号是什么意思?好么?我们会讲解所有这些。你将能够阅读这些C++文档。好么?到目前为止有任何问题吗?很好。是的,到10周结束时。
我们将了解这意味着什么。好。
最后一点是,我们希望熟悉现代C++的设计哲学是什么。现代C++中有一些常见的习惯用法和设计选择。我们将探索这些内容。关键点是不要记住C++的语法。因为有太多语法,很难记住。但你只需要知道这些语法的用途。
以及如何查找它们。好。这是我们将要经历的有趣图示。我们有四个主要单元。我们将首先讨论C++基础知识。然后我们将探索标准模板库,它给你一个关于集合、迭代,器的良好概述。接着我们将讨论面向对象编程。最后。
我们将探索一些现代C++的概念。一个旁注是,我们通常推介106L的方式之一是说,它对编程,面试非常有用。我们之所以这样说,实际上是在第二单元,即标准模板库中,可以体现出来。
在那里你会学到C++已经为你实现的各种内,置工具。这些工具包括C++已经以最快的方式实现的算法等。所以,是的。总结一下,我们希望你们从中获得两个主要目标。第一个目标当然是展示C++有多酷。
并让你们了解所有的功,能。另一个目标是确保你们觉得自己可以从零开始构建一个程,序,并且能够感觉自己可以自己编写一个真正的程序,这一,点我知道在106B中我并没有完全获得。是的。例如。
如果你正在寻找一个暑期实习,那边的橙色、黄色气,泡将涵盖你需要知道的所有编程面试内容。一些基本的物流信息。我们的讲座是的,我仍然不确定双周是每周两次还是每两,周一次,但他们将在这个房间每周二和周四的1:
30到2:20,进行。你会注意到这个时间段预定的是1:30到3点,这不是偶然的,我们可能会从2:20到3点在这里,所以这将作为我们的办公,时间。如果你对作业有任何问题,如果你对C++有任何问题,或者。
讲座结束后有任何问题,请在那个时间段随时问我们。好吗?我们还会有几个额外的办公时间,以防你不能参加那个时,间段。我们的课堂网站是CS106L,上面写着。基本上是CS106B,只是将B改为L。
我们还没有更新那个网站,但我们会在今晚更新,所以你将,能够看到新网站。寻求帮助。我们有办公时间,我们有Piazza。请不要使用Lair,因为这些课程助理不一定知道标准C++。他们中的大多数。
很多人甚至不是计算机科学专业的,所以,如果你问他们标准C++问题,他们可能会无从回答。避免使用Lair。可以通过电子邮件联系我们,或者在Piazza上提问。作业有三个,总共三个。你必须完成两个。
而且这两个必须包括最后一个。为了明确一点,好吗?有八种可能的方式来完成作业。不要做其中的任何一个。如果你什么都不做,你将不能通过这门课。如果你做了所有的,你肯定能通过这门课。如果你选择做两个。
你可以做第一个或第三个,或者第二个,和第三个。你必须做两个,而且必须包括最后一个。明白了吗?是的?是的。再重申一下,你不能做第一个和第二个。你必须做第三个。这样做的部分原因是。
作业的重点并不是要用一个棘手的,算法问题来挑战你。作业的重点实际上是让你练习C++语法。所以我们希望你完成最后一个作业的部分原因是,到那时,我们将涵盖几乎所有我们想要涵盖的主题。
这将让你有最多的机会练习你在课堂上学到的知识。是的。简单概述一下。第一个作业涵盖红色气泡。第二个作业涵盖黄色气泡。第三个作业涵盖最后两个气泡。明白了吗?因为第三个作业非常重要,它涵盖了两个气泡。
所以你必须,完成第三个作业。明白了吗?当然,我们建议你完成所有的作业,显而易见的。好的。延迟天数。所以默认情况下,我们不提供延迟天数,但你可以通过完成,调查获得最多三天的延迟。明白了吗?
所以我们会发出调查问卷。如果你填写了它。调查问卷上有名字字段吗?有的。好的。上个学期,我们忘记加了名字字段。所以有人问,怎么知道谁获得了延迟天数?我们只是知道。我们阅读你的评论。是的。所以,不用担心。
通常,第一个调查问卷用于给我们提供你背景的概述,然后,后续的调查用于反馈你希望在课堂上看到的内容。明白了吗?你的名字不一定与调查回应直接相关,所以不用担心。是的,我们还想强调。
我们知道这是一个一学分的课程,再,次强调,这并不是要给你增加更多的工作。这只是为了给你一些动手实践的机会。所以,如果你有其他情况,随时跟我们说,我们可以解决一,些问题。是的。
所以不要因为担心CS106L而影响你的心理健康。在开发方面,我们将使用的平台是Qt Creator。我知道你们中的一些人会问,是否可以使用其他平台?好的?基本上,对于我们的作业。
第一个作业使用了一个图形库,这个库是特定于Qt Creator的。第二个作业使用了一个网络库,这个库也是特定于Qt ,Creator的。所以,对于前两个作业,你不能使用其他平台,必须使用C++。
和Qt Creator。因为,不幸的是,C++ 目前没有内置的图形库。C++20将会有一个图形库。第三个作业,我们还在微调,但我的计划是允许你选择使用,其他开发软件。有时在例子中。
我们可能会使用其他环境,以便你可以看到,C++在Qt之外的其他环境中的工作方式。很酷。让我们看看。荣誉准则。有任何问题吗?是的,随时向我们提问。我们喜欢回答问题。好的,荣誉准则。
与CS106B相同的规则。不要作弊。明白了吗?更具体的规则可以在线查看。基本上,作业是个人完成的。我们预见这些作业不会占用太多时间,所以我们希望你个,人完成它们。它们主要是为了你自己的练习。
不要在网上搜索CS106L作业一,试图找到解决方案。不要作弊。为了你自己的学习去做。明白了吗?还有一件重要的事,如果你之前没有来过,这里是 Piazza ,的链接。你必须去那里手动加入。另外。
我们会在这里发布作业更新、办公时间变更和我们,做出的任何公告。所以如果你想上这门课,加入 Piazza 是很重要的。课堂后的所有幻灯片都会在课程网站上发布。所以如果你现在错过了,你可以随时回到网站上。
还要注意一点,讲座持续到第九周。明白了吗?我们知道你们第十周可能有很多期末项目。我们第十周也有期末项目。所以我们的课程将在第九周结束。明白了吗?所以我们会给你们最后一周的时间去做其他项目。
我们称之为真正的死周。可能要完成 CS106B 的作业。
好。QT Creator 设置。我们使用与 CS106B 相同的设置。在 CS106B 页面上,你会看到有一个故障排除的会话,时间,是 1 月 9 日,星期四,晚上 8 点到 10 点,在 Lair。
有人知道 Lair 是什么吗?好。谁不知道 Lair 是什么?好。对。Lair 是所有正常 CS106 课程的办公时间的地方。这是我们告诉你不要用于 106L 故障排除帮助的地方,唯。
一的例外是设置 QT Creator。Lair 的位置在 Truster Student Union,大致在 ,Decadence 附近。在那里你应该能找到一台允许你注册帮助的电脑。如果你找不到助教。
他们会在周围走动。对。如果你有任何问题,也可以随时在 Piazza 上给我们发消,息。如果我去年设置过 QT Creator,有什么变化吗?有的。我认为大约在圣诞节期间,他们发布了一个主要更新,修复。
了 QT Creator 中一个非常烦人的 bug。所以我建议你可能不需要卸载整个 QT Creator。我认为可能有一个更新应用程序可以尝试。所以一定要尝试一下。
如果你的 QT Creator 实在太旧了,比如一年以上,可能需,要卸载所有内容并重新安装。明白了吗?不会花太多时间。我们会发送如何访问 QT 升级面板的说明,以防这有帮助,对。坦率地说。
如果你问我们关于 QT Creator 的 bug,该怎么,做?我们可能会说你试过卸载然后重新安装吗?明白了吗?这就像是比关闭和打开机器多一步。明白了吗?对。QT Creator 确实很奇怪。
通常卸载和重新安装会解决一些问题。对。为了澄清,如果你还没有设置 QT Creator,这是 106B 网,站,106B。stanford。edu。右边有一个链接,写着设置 QT Creator。
那里实际上有非常详细的设置说明。如果你按照这些步骤操作,你应该不会遇到任何问题。你不应该遇到任何 bug。这个课程仅适用于在按照要求操作时出现问题的情况。
好的。只是后勤比较。CS106B 每周有三节讲座。如果你在 106B,你仍然应该去上那些课程。CS106L 不会替代 CS106B 的任何内容。明白了吗?有些学生把 106L 当作一个小组。
他们不去参加他们的小,组课程。确保你也去参加那个小组课程。是的。CS106L 是一个一学分的课程,采用通过-不通过的评分方,式。欢迎旁听。CS106L 有三个作业。我们选择两个,主要是为了最后一个。
我们没有考试,也没有小组课程。106B 有八个作业。哇。好的。太棒了。是的。所以这基本上涵盖了课程的所有后勤安排,以及我们为什,么希望你参加这门课程的理由。所以到目前为止,有谁有问题吗?好的。完美。
是的。所以在这种情况下,我们想做的一件事,再次,Avery 指出,的一个目标之一,是不仅要了解 C++ 的语法,还要了解 ,C++ 设计的背后哲学。C++ 的一个好处是,它仍然是一个非常活跃的语言。
正如我,们可以看到的那样,我们有这么多年的 C++。因此,它也有一些设计决策,这些决策是由这种历史造成的,所以这将只是一个简要概述。你们可能会在你们以后上的一些其他课程中看到这些内容,所以,是的。
欢迎来到 CS106L。那么,欢迎来到你们第一次接触标准 C++ 的课程。昨天,我相信 Keith 给你们讲解了一个使用斯坦福 C++ ,的简要介绍程序。这基本上是使用标准 C++ 的等效版本。
你会注意到有一些你可能不认识的奇怪的语法,比如这个 ,std colon colon。我们实际上会很快深入了解所有这些内容,所以你会很快,熟悉的。C++ 的一个重要特点是,它继承自其他语言。
因此必须向后,兼容。因此,当你需要保持向后兼容时,C++ 就会有一些很麻烦的,特性。例如,这就是我们在 C++ 中编写 hello world 的标准方,式。事实证明。
这也是一种在 C++ 中编写 hello world 的标,准方式。虽然我有点撒谎,它不一定是标准的,因为如果任何 C++ ,程序员看到这个,他们会问你在做什么?事实证明。
这是一种在名为 C 的语言中编写 hello world, 的标准方式,我们会很快介绍。不幸的是,这实际上也是一种在 C++ 中编写 hello world, 的方式,也就是说,C++ 因为向后兼容。
实际上有很多,我,想说的是,它非常灵活。如果你想用名为 C 的语言或名为汇编的语言来编写你的,代码,你可以这样做,但这也意味着有时你在浏览互联网时,可能会看到像这样的糟糕的东西。所以为了给你一个大概念。
以防你以前没见过。所以再次说明,最后一个例子是一个叫做汇编的示例。汇编语言在某种程度上,是您的计算机所理解的语言。您会注意到它要复杂得多,而且完全不具备人类可读性,这,与我们习惯的东西。
特别是像 Python 这样的语言不同,您,之前可能已经见过。那么汇编语言从何而来?嗯,汇编语言是人们与计算机交流的最初方式。所以它有您在前一张幻灯片上看到的令人难以置信的简单,指令。它速度极快。
因为您只是在指挥,您只是在直接与计算机交,流。注意写得好的时候的括号,这不一定容易做到或者总是这,样。最后,汇编语言确实让您完全控制您的程序。作为程序员,您实际上是在指挥在什么时间把哪块内存放,在哪里。
那么鉴于所有这些好处,为什么我们不在所有的编码项目,中使用汇编语言?有人猜猜吗?说吧,斯图帕。很难,像复杂的想法用汇编语言编码非常困难。绝对,没错。所以是的,这不是一个棘手的问题。
我们不使用汇编语言的简短答案是因为它看起来像这样。所以,我猜简单来说,是的,它有简单的指令,但这意味着正,如穆斯塔法所说,做简单的任务需要大量的代码,而且很难,理解别人的代码。此外。
人们不常想到的另一个原因是,此页面上的这种汇编,语言实际上是特定于您正在使用的任何特定计算机或操作,系统的。所以您用汇编语言编写的代码根本不可移植。这就成了一个问题,因为当人们想要分享他们的想法并构。
建更复杂的想法时,这就促使了 C 语言的发明。所以再次,我们的情况是编写汇编语言太难了,但计算机,这些机器,实际理解的唯一语言是汇编语言。那么我们该怎么办?
为什么我们不把这两个功能分成程序的两个不同部分?一方面,我们可以编写对我们这些编写它的程序员来说有,点像英语的代码。另一方面,我们可以使用另一个程序将我们正在编写的单,词翻译成计算机能理解的这种语言。
这实际上是您如果继续上更多计算机科学课程将会在未来,的课程中了解到的东西,称为编译器。所以是的,我们将专注于第一个,这正是 C 语言。所以 C 语言是由,这都是为了历史知识,所以您可以在下。
次聚会上把它当作一个事实说出来。C 语言是由肯·汤普森和丹尼斯·里奇于 1972 年发明的。是的,再一次,C 语言的好处是它使编写快速、仍然简单,但这次是跨平台和人类可读的代码变得容易。实际上。
如果您继续学习计算机科学,那么在 B 之后您要,上的下一门课是 CS 107,在那里您将非常熟悉 C 语言。好的,好的,再一次。所以在历史的这个节点,我们有了 C 语言。那么我们还需要什么?再次。
C 语言有它自己的弱点。回到想要构建更复杂想法的这个想法,C 语言没有任何对,象或类。为了强调为什么这真的很重要,您可以想象,比如说您想构,建自己的程序,叫做,比如,Bookface。
我认为这是人们常用,的一个例子。然后想象一下,如果你想编写代码并跟踪所有这些你拥有,的数据。你知道,例如,平台上有一个人,具有生日、朋友等信息。在 C 语言中,没有单独对象的概念。
如果你想跟踪所有这些信息,你必须每次手动维护,例如,使用 Python 的数组或列表来记录所有这些不同的细节。所以这就是 C 语言的一个非常大的困难点,就是没有办法,抽象出对象或类的概念。
编写通用的代码很困难,并且在编写大型程序时总体上是,繁琐的。是的,我看到一个问题。结构体不是 C 语言的一部分,还是后来添加的?不,这是一个很好的问题。是的,在 C 语言中。
实际上有一个称为结构体(struct)的,概念,但结构体有它自己的缺陷。所以结构体是确实存在的。我们实际上会在讨论标准 C++ 的类时涵盖一些缺陷。但从某种意义上来说,你可以将结构体视为一个仍然是开。
放的公共列表,没有任何复杂的功能。是的,这是一个很好的观点。是的,还有其他问题吗?这是对所有历史的一个简要概述。好的,很棒。所以欢迎来到 C++。所以这是目前这一编程领域的前沿技术状态。是的。
回到 C++ 的创始人,Bjarn Stroustrup 于 1983 年,发明了 C++。而且,我们仍然希望有一个快速、简单、跨平台的语言,但,我们也希望有一个具有高级特性的语言。
所以 C 语言做到了前三点,然后 Bjarn 创建了 C++ 以添,加最后一个的功能。因此,最初很多人认为 C++ 只是 C 语言加上了类的功能,它只是添加了可以将你的代码以对象或类的形式进行查看。
的新功能。但随着时间的推移,C++ 发展成了一个非常灵活且复杂的,语言,拥有比你能使用或想使用的更多特性。但幸运的是,我们在这门课上想做的就是突出 C++ 中最有,用和最常用的行业特性。而且,是的。
黄色标记的部分是 C++ 多年来的重要更新。斯坦福大学在其入门级课程中使用的很多是 C++98,如 ,Avery 所提到的。然而,在行业中,对于任何真正使用 C++ 的公司,你几乎只。
会找到 C++11、14 或 17 中的现代 C++ 特性。所以这就是我们将教给你们的内容。以及即将到来的 C++20。是的,我们现在是在 2020 年。好的,我们快速回顾一下 C++ 的设计哲学。
我们还有 17 分钟。好的,我们可以完成。好了,这里是设计哲学。第一个哲学是允许程序员完全控制、负责和选择,如果他,们想要的话。所以如果你不想要,那么你不一定需要承担所有这些完全,的控制和责任。
但是作为程序员,你可以做很多不同的事情。例如,你将来会学到的一件事是内存分配。在 C++ 中,你必须自己进行内存分配。所以如果你正在创建一个向量,如果你正在编写向量类,你。
必须实际分配内存来放置你的变量,放置你的不同成员。你必须自己做这件事,并且要记得以后释放内存。这与 Java 有很大的不同。在 Java 中,一切都被隐藏在你之外。你不一定需要自己处理内存。
在 Python 中,你绝对不需要处理内存。所以在 C++ 中,如果你想要完全控制,比如控制内存的工,作方式,控制指针的工作方式,C++ 允许你这样做。
这实际上是 CS106B 使用 C++ 的原因之一,因为 CS106B ,想要深入了解如何实现低级特性,而 C++ 让你可以做到这,一点。第二个想法是我们想要在代码中直接表达思想和意图。
尽管 C++ 给了你完全的责任,但代码的编写方式是为了让,你可以完全在代码中表达所有的思想和意图。很多时候,C++ 的习惯用法,你应该以这种方式编写 C++ ,代码,会强调哦。
你这样写是为了强调某种意图。我们稍后会给出一个例子,就是 const。凯斯可能还没讲过 const,对吧?是的,凯斯将在下周讲解 const。const 的关键思想是表达意图。我们来看一下,这是什么?
哦,是的。关于表达意图的另一个原因是这里有三种不同的方法可以,找到一个向量的和。如果你查看第一个例子,你会看到,你在对一个向量进行 ,for each 循环。你在累加和。第一个方法是有意义的。遍历向量。
累加所有元素。在第二个例子中,你会注意到有稍微不同的地方,即你会注,意到 const 这个词和符号 &。这两个程序基本上是相同的,但第二个程序更好地表达了,意图。第二个程序特别说明了,在我们遍历向量时。
我们并没有实,际更改值。我们在本课程中推广的第三种版本是第三种。这使用了一个 STL 函数叫做 accumulate,它最好地表达,了意图。我们从零开始累积整个向量。到第三周。
我相信你应该能理解这一行的每一部分。这是表达意图意味着什么的一个完整例子。第三个,尽可能在编译时强制执行安全性。这意味着当你按下编译按钮时,编译器应该非常擅长为你,检查错误。当你进入 CS107 时。
C 编译器并不擅长检查错误。通常情况下,程序可以编译,运行时却不工作,而你不知道,原因。C++ 的目标是能够在编译时尽可能地强制执行安全性。例如,如果你尝试意外地将一个 double 转换为 int。
C++ ,会尝试在类型不匹配时警告你。
第四个,这非常重要,不要浪费时间或空间。有一个我喜欢的非常酷的东西,C++ 现代设计。如果你在 Google 上搜索设计原则,如果你去这里,你将能,够看到,在哪里?这里,核心指南。
这里是使用 C++ 的指导原则的主文档。这里面有太多东西了。我最喜欢的一些是,哲学方面的,让我找一下。这里,这里。P9,不要浪费时间或空间。为什么?这是C++。
对C++的基本理解是我们想做一切可能的事情,即使它看起,来有点疯狂,以便使一切尽可能快速和高效。这可能意味着做更复杂的事情,自行管理,但是一切都是为。
了不浪费时间或空间的目的。这个主题会一再出现。C++11中的很多东西是为了节省时间和空间。这就是我们所说的高性能的含义。C++在节省时间和空间方面非常出色。最后一部分是将混乱的构造进行模块化。
正如Anna所说,C语言的主要缺点是没有类。在C++中,你的目标是将所有混乱的特性进行模块化,以便,使用你编写的内容的用户不必自己处理这些问题。例如,斯坦福的库,它们将混乱的构造进行模块化,以便你。
不必担心它们。你要能够编写像斯坦福库一样的库。明白了吗?太棒了。是的。我们还有11分钟。
很好。另一种方式。另一种方式。好的。我们只是想给你们一个简要的预览,展示一下标准C++代码,是什么样的。你可能会从我们刚刚讲解的幻灯片中认出这三个主题。再一次。
这只是为了强调C++确实支持这三种不同的编码方,式。这与Avery提到的设计原则有关,即允许C++尽可能灵活,以,满足程序员的需求。C++不想成为你无法做某事的原因。从某些方面来说,这是个祝福。
从某些方面来说,这是个诅咒。所以,为了证明你们确实可以在C++中完成上述所有事情。
我们现在使用的是C++ 17版本,我相信。所以,也请原谅我,因为我通常不使用Mac,所以可能会。
哦,天哪。好的。让我们看看。
哦,是的。其实,我可以把这些注释掉。是的。好的,完美。所以,再次说明,这里发生了什么,你可能对主函数比较熟,悉。如果不熟悉,它是程序开始的地方。现在,我们将执行这个顶层函数,正如我们所说,这是C++中。
打印内容的标准方式。所以我们可以用Ctrl-R运行它。
看看会发生什么。
完美。它打印出hello world。好的。所以,再次证明其他两种方法确实有效,我们将这些注释掉,尝试再次运行。
完美。我们看到它三次都打印出hello world。
这实际上有点棘手,因为这个汇编代码只有在Avery的电脑,上才能运行。所以如果我们试图在我的电脑上运行,可能不会成功。这也是使用汇编语言的一个缺点,它是所有现代编程语言,的源头。太棒了。
所以今天我们想要讲的最后一件事。我们实际上提前完成了,这很好,因为这意味着我们可以回,答一些问题,其中一个问题已经发生了。是的,请说吧。是的,很好的问题。是什么使得这个是 C++ 的。
而其他的不是那么 C++ 呢?这实际上是一个很好的问题,因为这是我们想要教授的关,键内容之一,即使用我们所谓的 C++ 语法特性。是的,正如你提到的,这个非常 C++ 的特点是它使用了特。
定的 C++ 构造。这儿有几个例子。实际上,我们现在可以深入探讨一下。如何增加缩放?Windows 和 Mac 用户。是的,你们能告诉我这个顶级 C++ 方法中哪些看起来不熟,悉吗?
你们注意到了什么现在还不太理解?随时举手告诉我。什么看起来奇怪?还是你们都能认出所有东西?在这种情况下,我们可以实际。我们可能就取消这节课。似乎是。是的,太棒了。是的,那正是一个点。一个是插入符号。
你们还注意到什么?哦,就在这儿。哦,是的。好的,这点很重要。这点很好。是的,好点子。是的,继续,Mark。返回零?好的,这点很重要。是的,还有什么是你们没有注意到的?是的。冒号,冒号。
这是一个非常棘手的点。这是一个值得指出的好点子。你们还注意到什么?是的,继续说。STD?是的,这看起来有点奇怪。还有其他的吗?实际上就是这些了。干得好。是的,不错。是的,所以实际上。
所以你们可能会注意到。例如,如果你们来自 106a 课程学习 Python,尝试打印 ,hello world 的时候,只需要写一个类似 print 的语句,可能带上括号。
然后是 hello world。而在 C++ 中打印 hello world 似乎需要所有这些额外的,设置。这实际上是一个你们会发现的主题,因为 C++ 提供了很多,灵活性。
这也意味着有时你需要写更多的语法。但现代 C++ 的一个目标是随着时间的推移使一些语法变,得更容易。所以你会发现 C 中的语法甚至看起来更丑。当然,在汇编语言中,它看起来最丑。
这就是 C++ 试图维持的平衡。是的,既然我们有时间,我们甚至可以简要介绍一下你们刚,刚提到的所有部分。第一个是你们提到的。你叫什么名字来着?Julie。第一个 Julie 提到的是插入符号。
你们见过 pound include 吗?谁没见过?好的,是的,pound include 可以被认为是 Python 中的 ,import。它是你可以引入你已经定义的另一个库的方式。在这种情况下。
C++ 的语法是 pound include。然后有两种不同的方法来包含一个库。一个你可能以前见过的东西看起来像是 include ,iostream。h,类似这样的。谁见过 。h 这个?好的,是的。
完美。所以是的,这实际上是一个很好的问题,我也曾经有过,就,是这两个选项之间有什么区别?事实证明,主要的区别是尖括号用于任何已标准定义的库,在这种情况下。
iostream 是一个已经在 C++ 语言中定义,的库。作为我们将要介绍的标准模板库的一部分。所以这就是为什么我们在这里使用尖括号。你使用引号的地方是当我们在学期晚些时候定义我们自己,的类和头文件时。
h 文件,那么我们可以使用 #include,引号引用我们的头文件名来包含我们写的文件。是的,所以这是一个很好的点。实际上,这是一件常常被忽略的事情。是的,好的。那么你们指出的其他部分。
一个是 return zero。实际上,指出这一点是好的。事实上,我们不一定需要在这里有它。这有点像是模拟 main 函数通常返回零的概念。这实际上只是一个遗留的东西。你不必担心它。所以,是的。
我们实际上完全可以删除这个 return zero ,并将这个函数改为返回 void。好的,然后还有其他你们指出的几个问题。第一个是 std。有人提出了标准库的想法,这也是为什么这段代码是 C++ 。
的原因,而第二种方式不是。这确实是原因之一。所以 cout 和 nl 是我们将会非常熟悉的东西。事实上,Avery 在我们的下一节课中,将会讲解流。所以你将会学习到很多关于 cout,两个尖括号符号。
通常,称为流操作符,以及 nl 的内容。所以,是的,我们下次会讲这些。std:这可能是正在学习 106B 的人们认为最困难的部分,就是记得使用 std:原因是,在 106B 中,你可能见过类似的东西。
使用 ,namespace std。这也是一个预告。我们将在学期后期讲解为什么我们实际上不一定要使用它,具体来说,这个 std 是用来做什么的?作为一个简要预览,std 是所谓的作用域命名空间解析器。
所以它有点像是在 Python 中,当你说类似 import numpy, as np,然后在程序的其他地方,你必须写 np。 来使用 ,numpy 中的函数,在 C++ 中也是一样。
如果你想使用来自这个库 IO stream 的函数,实际上,因,为它在标准库中,所以你可以说,啊,是的,我想使用标准,然后两个冒号的等效符号,然后是来自那个库的函数 cl。是的。
所以这就是基本的 Hello World。所以你们有什么问题吗?或者关于你们在这里看到的功能有什么问题?是的,Julie,来吧。这是一个很好的问题。所有标准库头文件都是 std 命名空间的一部分吗?
是的。是的,是的,绝对是,从 std 调用。是的,好的问题。是的,还有其他问题吗?是的,实际上,我们在教授 1。6。0 时最喜欢的部分之一就,是我们总是能收到学生们非常棘手的问题。是的。
所以如果我们不知道答案,我们会告诉你我们不知道,然后去查找答案,之后会把答案发布到 Piazza 或其他地,方。但我们确实喜欢尝试被挑战或难住。是的,问吧。好问题。是的。
iostream 基本上是标准库吗,还是只是它的一个小,部分?是的,它实际上只是标准库的一个小部分。我们稍后会展示给你如何查看。实际上,我们有。好的,我们只有一分钟的时间,所以我就不做了。
我们会在稍后的演示中告诉你如何查看标准库中的所有库,这样你就会知道标准库中有什么,不包含什么。
是的,这是一个很好的问题。我直接结束吗?好的,所以在最后的部分,请确保填写调查问卷。可能会有点麻烦抄写整个内容。我有点想去。因为通常你做这个时,你需要检查哪些是大写,哪些不是大,写?
但这也在 Piazza 上。如果你注册了 Piazza,链接就在那儿。是的。所以在下周二之前填写,您将获得额外的一天延迟。所以快速概述一下我们接下来的内容。我们将从流开始。好的。
这是对未来发生的事情的快速总结。下一次讲座,我们将讲解前四个部分。可能只有三个,但我们会尝试讲解前四个部分。在 CS106B 中,我认为是在周五。周五,Keith 会讲解文件流和标准库。
这将把我们从 iOS ,流过渡到文件流。下周二,我们将讲解类型和更深入的流,包括如何实现 ,Stanford 库中的 getInteger。好的?是的,还有最后一点。
你会注意到我们一直在与 CS106B 并行进行,所以如果你,现在在上 CS106B,你绝对可以参加这个课程。我们会非常紧密地跟随它,这样你不会学到任何内容。我们不会假设你知道某些内容。
直到它在 CS106B 中讲解,是的,再次重申一下我们为什么希望你填写简介调查问卷,这给了我们一个了解你经验的机会,这样我们就知道是加,快还是放慢速度,或者我们可以基于什么来调整教学。是的。
即使你觉得不需要延迟天数,也请填写简介调查问卷,调查问卷的一部分还询问你想要学习什么,因为我们学期,的后半部分,我们有上一季度的一些幻灯片,但我们也愿意,教授你想学习的内容,好吗?所以如果你有任何想法。
直接写在表单上,我们会查看的。
太棒了,谢谢大家。冬季学期第一周愉快。所以如果你有任何问题,我们会在这里待半小时,可能更少,因为我想吃东西。你好。你是网络安全方面的负责人吗?是的,我是。
是的,当然。实际上,我有几个问题。你的名字是以诺吗?是反向的冒号加一个 K。好的。好的,明白了。是的,不管怎样。第一个问题是,我猜,这不太可能发生,但是如果我们只是,旁听和真正上课之间有什么区别呢?
当然,可以的。所以在课程中的唯一区别是,如果你正式成为课程的一部,分,那么要获得学分,你必须提交三项作业中的两项,包括,最后一项。然后,当你提交作业时,我们将能够评分你的作业。如果你只是旁听课程。
你将无法提交作业。尽管如果你想把作业发给我,我总是喜欢和任何人讨论计,算机科学。所以你可以把作业发给我,然后我可以看看,如果你愿意的,话。是的,特别是如果你觉得,哦,我可能没有时间做额外的两,项作业。
虽然不太可能,但如果是这样,你也可以旁听课程。出勤,我们强烈建议参加,但我们不检查出勤情况。你有你的电子邮件地址吗?有的。
是的,如果你去那个,嗯,它们在幻灯片上。实际上,我不确定它们是否在幻灯片上,但我们会确保添加,它。它也在网站上,所以只需访问CS106L,将CS106B网站上的B,改为L,你就能访问。好的,没问题。
没问题。谢谢,Adam。你好,你能提醒我你的名字吗?Flynn。Flynn,好。那么当编译器编译你的代码时,是逐行编译吗?还是一次性编译?好问题。这绝对不是顺序的,因为有时你会有不同的函数调用在不。
同的方向上。是的。是的,所以这绝对不是顺序的,尽管它比Java复杂一些,因,为在Java中,一切都是直接完成的,但在C++中,我认为在,Keith的课程中会讲到,你必须写出原型。也许。
因为你将在周三讲到它,但在C++中,你必须在顶部声,明一个原型,以便C++知道这个函数存在。是的,所以,嗯,由于这个原因,它不一定是逐行完成的,但,C++有很多奇怪的要求。
Qt中的调试检查器是如何工作的?当你可以逐行运行它时?哦,针对Qt的?调试器是如何工作的?在Qt中?只是一般的使用方法?嗯,不是,比如说如果你编译了你的程序,它是否不是逐行,的?就像是任何。
像汇编语言一样。哦,等等。刚才的问题是什么?对不起,我直接跳入了。Qt的编译是如何工作的?哦,是不是逐行执行?是的,所以实际上,当你逐行执行时,那是在你的代码运行,时发生的。
所以编译时和运行时是两个不同的阶段。所以在编译时,你实际上已经生成了所有的代码,然后在运,行时,有一套不同的事情发生。所以像变量写入内存中的栈或堆等事情。而这就是你在调试器中访问的内容。但是编译时。
它会记住每一行的内容吗?是的,所以我认为Qt Creator,它会将你的代码插入不同的,东西,以便在你阅读时暂停。所以这就像一个附加组件。是的,它会添加更多的指令。它基本上将其转换。
使您能够读取内存的不同部分。是的,如果你参加 CS110 课程,你实际上会实现一个叫做 ,ptrace 的东西,它基本上做相同的事情,这很酷。也许明年吧。顺便问一下,你叫什么名字?Flynn。
Flynn,好的。很高兴认识你。嗨。Sebastian。是的,我对你提到的系统和网络安全感兴趣,所以想聊聊。我最近对网络安全产生了兴趣。哦,真的吗?是的,我上个学期注册了黑客实验室。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P10:[16]CS 106L Fall 2019 - Lecture 9_ STL Summary (Screencast) - GPT中英字幕课程资源 - BV1Fz421q7oh
我意思是我勉强能做一个。我做过两个,有点艰难。我认识的人做过两个以上,他们都没问题。所以我做不了这个。所以我差不多达到64。而其他人,他们可以轻松达到80、90。先生?我的一个朋友说。
我一年内完成了这个专业两次。等一下,这可能吗?我想,我想,不是整个专业,而是核心要求。等一下,除了这个专业,因为他是一个非常难的数学专业。所以他有60个学分?我认为他可能是从他的本地社区学院获得了学分。
他修了15个学分?15个学分的数学?我想他上了四五门这样的课。哇。那之前的106L讲师,他也是数学专业吗?他创造了六个数学专业的记录。他告诉我他最后得到了两个不及格,但你知道,他上了六门,数学课。
他说这是最糟糕的之一。我很高兴这是永久性的。像每周六次PSAT。你可以每周做六次PSAT。我勉强能做一次。是的。所以,是的。所以你不必,我学到的是你不必做到最好。你不可能在所有事情上都做到最好。
这没关系。我显然是一个高年级学生。好了。欢迎大家。感谢你们在期中考试周来这里。所以你们,我想,获得了特殊的体验,因为你们可以在这里,参与,因为我们今天基本上要做一个大示例,使用我们学到。
的关于STL的所有内容,这对第二个作业也会有帮助。好了。好吧。在我们做之前,先简单总结一下STL。为了让你们了解你们已经学到了多远,如果你们还记得,这,就是我们在五节课前第一次看到的东西。
现在你们知道了,这每一部分的意义。例如,容器,我们看过向量、双端队列、列表、集合、映射,我们还看过容器适配器。谁能说出容器适配器的例子?容器适配器的例子是什么?是的。大声点说。栈。栈和队列。容器适配器。
结果是它们实际上不是容器本身。我们看过迭代器和迭代器的四个函数,比如 dot begin、,dot end、解引用和递增迭代器,还有五种类型,输入、输,出、前向、双向、随机访问。说实话。
当谈到迭代器类型时,我们很快就讲完了。所以如果你对它们的区别仍然感到困惑,我建议你回去看,看幻灯片。但真正要记住的是,每种迭代器承诺具有不同级别的能力。
所以如果你在查看一个数据结构时看到它说类似前向迭代,器的东西,现在你知道,啊,好吧,我需要回去看看前向迭代,器承诺什么,以便我知道如何使用这个数据结构。你们看过Avery介绍的迭代器适配器。
谁记得一个迭代器适配器的例子?举手。是的,这个有点棘手。是的,绝对是的。是的,Avery 涉及了一个概念,你可以使用类似于后插入器,的东西。所以说你想要复制元素。
你不能保证你尝试复制的目标中已经有足够的空间。所以你使用一个后插入器,它是 STL 为你已经知道的迭代,器包装的一个层,以便在你做事情时可以增加内存。所以,正是这样。比如后插入器。如果你记得。
我们能够通过使用 copy 直接写入 C out,而,不实际使用 C out,alligator,alligator,然后是值。所以直接复制到输出流迭代器。这又是一个迭代器适配器的例子。所以,再次。
将所有这些概念结合起来。你们现在已经见过仿函数和 Lambda 表达式。实际上,还有一些像仿函数或函数适配器这样的东西,我们,没有实际讨论过。部分原因是它们实际上在 C++20 中已不再使用。
所以函数适配器现在已被 Lambda 表达式取代了。所以你们知道 Lambda 表达式。这就是你们需要知道的全部。最后,当然,还有我们最喜欢的算法。我们已经查看了算法可以做的各种不同事情,包括排序、。
累积、搜索、打印等等。你会注意到,算法接受迭代器和仿函数。所以再次重申,你们现在可以解释 STL 中的每一部分。最后一点是 STL 还有更多内容。实际上,并不多。你们已经学到了几乎所有重要的概念。
还有一些库可能对你们有趣。例如,正则表达式库。如果你在研究解析流,并且听说过正则表达式,这是一个有,趣的库。另一个有趣的库是多线程相关的。那么如何让你的代码在同时运行多个处理器时工作呢?
STL 实际上有一整套与此相关的东西。所以有锁、条件变量等。事实上,这是我们从课程中移除的讲座。你们会记得,我们在 STL 中增加了一次额外的讲座。但如果你们想要了解这方面的内容,我们会在最后一讲中。
作为选项提供。如果你感兴趣,可以看看这个。最后,尤其是对于那些因为有特定应用需要使用 C++ 的同,学,有另一个库集合叫做 Boost 库,它不是 C++ 标准库的,一部分。
但它提供了很多非常有用的、不是那么常见到需,要成为 STL 一部分的东西,但足够有用,以至于人们到处,使用它。这包括机器学习的线性代数库等等。所以如果你想要了解 STL 之上的下一个层次,可以看看 。
Boost。它是由与 STL 不同的组织编写的,但它们也被广泛使用。所以 Boost,就是它们的名字。很好。好的。最后一个概念来总结 STL。所以你们已经学习了算法、函数对象、容器、迭代器。
从 STL 中要带走的一个普遍概念就是这种抽象的思想。在 STL 中,我们真正试图做的是表达一般性问题,而不是,特定问题。为了给大家一些感受,我们在计算机科学中首先接触的是,基本类型。你们知道字符。
有整数、双精度数、字符串等等。因此,在最底层,我们有基本类型。但在设计 STL 时,我们问自己的是,我们能否跟踪一个基,本类型的集合,无论这个类型是什么?所以从类型中抽象出来?绝对可以。
你们已经看到了这一点。这就是 STL 中的容器。所以我们可以表示一个整数向量或字符串向量,实际上是,任何类型的向量,无论那种类型是什么。因此,我们构建了一个叫做容器的类型抽象。下一个问题是。
我们可以对容器执行操作,而不管容器是什,么吗?有人知道我在引导什么吗?是什么让我们能够对容器执行操作?算法。对。算法是一个好的答案。算法是与什么一起工作的?迭代器。迭代器。没错。所以迭代器确实是,就像。
嗯,这是一个不完美的类比,但就,像线粒体是细胞的动力源,迭代器是 STL 的动力源。所以对,它们允许我们从容器中抽象出来。最后,我们能否对迭代器进行操作,无论它是什么类型的迭,代器?
这正如我们已经说的那样。我们有算法允许我们对任何类型的容器使用任何类型的函,数进行操作。这让我们不仅可以计算,比如说,所有小于5的元素是什么,还可以基于我们传递的任何谓词,找到所有元素,眨眼眨眼,眨眼。
所以,对,这只是为了强调在 STL 中,作为程序员,我们真,正带走的两个组件是首先它提供的抽象量。我甚至没有提到模板,但模板当然是这里最著名的抽象之,一。其次是速度。
我们强调 STL 已经针对速度进行了优化。所以,对,抽象和速度。恭喜你们。这基本上结束了我们的 STL 单元。再回顾一下,这是我们开始时引用的那句话。我们将这些抽象概念,如数据结构和算法。
提升到它们最一,般的形式。所以恭喜你们。很酷。所以在我们跳入综合示例之前,我想做几个公告。第一个是办公室时间。所以再次祝贺大家提交了第一份作业。我们更新了办公室时间。由于我不能再在星期二的课后待着了。
我在星期三增加了,额外的办公室时间。你可以在 Piazza 上找到所有这些信息。所以,即使你现在没有照片,你也可以在 Piazza 上找到它,Piazza 也是我们更新地点的地方。所以。
继续关注 Piazza。我还想简要提一下,这些可能在一周内发生变化。因此,确保在前往办公时间之前检查Piazza。是的。因为,尤其是当不同的作业截止日期临近时,我们可能会做,一些调整。
以更好地适应作业截止日期。所以,确保在计划去办公时间之前检查Piazza。是的。这是一个很好的点。是的。当然,这门课几乎足够个人化,如果你们不能参加这些时间,请联系我。感谢所有之前已经联系过我的人。
是的。我们很高兴能提供帮助。酷。在我继续之前,有没有问题?你们觉得。好的。实际上,我有点好奇。现在你们完成了STL单元,你们对STL的感觉怎么样?比如说,你们喜欢它吗?你们是否觉得,哎。
我怎么花了六节课在STL上?是的。比如说。好吧。好吧。是的。好的。酷。太棒了。如果没有其他问题。是的。布莱恩。好的。我想我有些问题应该在开始时问的。STL通常是以STD命名的。是的。是的。完全正确。
确切地说。而STD是为了STEM。是的。好的。不是STL。不是STD。不是STL。这是一个有趣的点。为什么不是STD而是STL?是的。但对。这是一个很好的杂项问题。是的。所以。
当你们在使用命名空间STD时,现在你们知道了。是的。好问题。还有其他问题吗。是的。最后一个问题。酷。好的。既然你们在这里,你们还将获得作业2的提前预览。所以不用担心作业2。我们。我们将其结构分为两部分。
第一部分。目的是将一个作业分成两个更容易处理的部分。所以这不是两倍的工作。这是相同的工作量,只是分开了。所以我们要发布的第一部分,我相信,明天就会发布。它将在之后的周五到期。它应该不会太难。
它主要是检查你是否有正常的互联网连接。因为这一次实际上需要你有正常的互联网访问。然后将会有。你们还需要处理一些算法。但为了给你们一个作业2的预览。所以我们这门课的一个目标是。不仅让你们练习标准C++库。
还要让你们即使只有四周的,C++课程,也能构建一些非常酷的应用程序。所以对于这个作业,我们将要做的是。你们中有多少人听说过叫做维基竞赛的东西?是的?好的。是的。你们中的一个人能解释一下它是什么吗?是吗?
所以这是当你尝试从一个网站访问另一个网站时的情况。完全正确。是的。所以……,是的。不。就是这样。所以如果你没听到,就是……,Wikiracing 是你尝试访问……,你会被给定两个网站。
你要尝试从第一个网站到达第二个,网站,仅使用维基百科的链接。所以你从一个维基百科页面开始,必须点击链接直到到达,下一个页面。而赢家是完成点击最少的人。有人知道维基百科上哪个页面被认为是所有其他页面都能。
访问的吗?这可能并不完全正确。没有?哲学?是的。大声说出来。哲学。所以结果是维基百科上的哲学页面几乎可以通过任何其他,页面访问到。但确实如此。可以在自己时间里试试。我认为……。
如果你只点击第一个链接……,所以如果你在任何维基百科页面上点击第一个链接,比如,这里,你点击链接。如果你只继续点击第一个链接,显然,大多数页面最终会到。
达哲学页面。是的。所以可以在自己时间里尝试一下。我们……,这很诱人,但我们不会在整个讲座中进行这个实验。是的。好的。所以这就是你们要做的事情。你们将要建立一种自动化方法来找出,我想,两个链接之间。
的比赛完成情况。这将会非常有趣,你们会发现,使用现在所知的内容,你们,可以用很少的代码行完成这个任务。所以我们明天会发布规格说明,你们可以查看一下。我想快速回顾一下规格说明中提到的一个点,即它会提到。
关于返回无序集合而不是集合的内容。有人记得这是什么吗?我想提到这一点,因为我们基本上在大约30秒内讲过它。是的。是的。完全正确。是的。完全正确。所以如果你们在106B或X课程中了解过哈希映射或哈希集。
合,无序映射和集合是C++ STL中这些的等效物。所以,它不会保证输出所有键的排序顺序,而是保证你可以,非常快速地访问它们。所以,是的,规格说明会提到,你们编写的一个函数需要返,回无序集合。
原因是因为在这个示例中,我们只是访问页面,上的单个链接,所以这些链接是否排序并不重要。是的。所以这是一个有趣的知识点,如果你们感兴趣,但,确实,如,果看到无序集合而不是集合时不要困惑。很酷。
关于这个有问题吗?是的。这是树集合吗?是的,这是一个很好的问题。树集合略有不同。树集合,我相信,它与常规集合的不同实现有关,并且做出,不同的承诺,但,是的,它确实不同。
树集合实际上比无序集合更接近常规集合。是的。是的,好的问题。对于那些没有经常关注 Piazza 的同学来说,我们实际上,在 Piazza 上发布了一个链接,指向斯坦福 C++ 库,因此,如果你想查看。
比如说,树集是如何实现的,你可以查看他,们使用标准 C++ 函数实现树集的方式。是的,还有数据结构。太棒了。是的。还有其他问题吗?酷。好的。所以,接下来我们要做的是看一个有趣的示例问题,然后看。
看我们如何用我们所学到的所有知识来解决它。所以,让我先介绍一下问题,然后这节课将比之前的课更互,动,这也很不错,因为今天我们的人数较少。是的,这会让你感觉更像是你们的 106 BRX 小组课,如果。
你们参加过的话。你们有多少人听说过《联邦党人文集》?是的。好的。好的。太棒了。所以,如果你不熟悉《联邦党人文集》,它是一系列在美国,创立宪法初期由三位不同作者发表的论文。当时,美国试图建立国家。
必须争论宪法是否应该被批准,因此这些论文由三位不同的作者发表。
来吧。汉密尔顿、麦迪逊和约翰·杰伊为宪法辩护。因此,它们被视为一段非常重要的历史,因为从某种意义上,说,它们也概述了美国的许多哲学根基。但关于这些论文有一个有趣的争论,就是哪篇论文是由谁,写的。因此。
《联邦党人文集》共写了 85 篇论文,但作者从未明,确说明哪篇论文是由哪位作者写的,因此这成为了一个有,趣的难题,试图确定究竟哪位作者写了哪篇论文。今天我们要做的就是编写一个程序,让我们知道哪位作者。
最有可能写了哪篇论文。所以,是的,我们要解决的问题是,我们能否从写作中发现。
作者的身份?这其实很有趣。这实际上是一个叫做风格学的领域,就是这个。根据这个描述,有没有人能大致猜测一下,我们如何从一篇,文章中检测出是哪三个人中的哪一个写的呢?有想法吗?是的,是的,是的。
这是一个很好的猜测。这就像是当人们自己坐下来思考,哦,怎样确定哪个人写了,哪篇论文?是的,他们可能想到的一个主意是,哦,好吧,我可以比较我,知道他们写的文章和我不确定的文章之间的词频。实际上。
这正是我们今天要做的事情。所以这很完美。是的,布莱恩,有什么问题?机器学习。那是新的电力。课程结束了。谢谢大家。不,我只是开玩笑的。是的,那将是一个有趣的扩展。
但是的,机器学习,人们当然用它来做这个。太棒了。是的,回到正题。所以这个想法是作者有自己的写作风格,并且倾向于保持,一致。所以我们今天的目标是首先能够找到一种方法来指纹识别,他们。
然后把它们全部编码出来。所以我们要使用的不可变项是。所以我想问题是,我们要计算哪些词的频率?因为例如,有些词可能与哪个作者写了哪篇论文实际上并,无关。所以像这些常见的词会给出一种作者写作风格的无意识指。
纹。所以作为我们正在做的工作的一般概念,假设我们正在处,理三个功能词。I, the, 和 their。所以我们的代码识别作者的方式是这样的。有点直观地查看每个词的数量。在这种情况下。
文本中没有“there's”。其他的也是如此。“There is” 是这样。谢谢。哇。好的。好的。这会搞乱我们的。不。好眼力。好眼力。我们会回去修复它。完美。好。所以现在我们已经建立了词频统计。
那么接下来我们的问,题是,如何使用这些统计来判断作者之间是否相似?这里的想法是我们实际上将从数学中借用。
你们有多少人听说过点积?很好。完美。所以我们正在进行跨领域学习,我们将计算两个词频向量,的点积,以了解这两个词频之间的相似程度。
我们最喜欢的,我们最喜欢的,我们最喜欢的方程式。如果你不理解这个,不要担心。代码。是的。如果你感兴趣,可以之后来找我们谈谈。但代码将仅仅依赖于我们知道方程式。我的意思是,不一定要理解为什么。太棒了。
所以在我们开始编码之前,你们对问题的设置或任何历史,问题有疑问吗?我可能能回答,也可能不能回答。酷。好。所以开始比赛吧。
好的。所以我们经常听到的另一个目标是如何从头开始编写程序,所以我们将从“你好,世界”开始编写。今天的部分不仅是如何使用 SDL,还包括你如何拆解一个,程序?你如何编写一个程序?是的。
我们会一起完成这些工作。所以再次强调,从“你好,世界”开始。我想提到的第一件事是,你会注意到在 BRX 中基本上所有,的程序中,你都只需要写“using namespace STD;”。
然后你就不必担心像“STD C out:C out”这样的东西。我们实际上希望弱化这种用法,部分原因是你可以把它想,象成将整个消防水管都扔到你的程序中。所以我们刚刚看到 STL 的规模。这就是说。
把它的所有内容都引入进来。所以我们想要使用的其实是非常细化的,比如说,好的,我,知道我想要使用。抱歉,我知道我想要使用的是 C out。但是每次都要写 STD:C out 真的很烦人。
所以我们可以做的是我相信 Avery 在之前的一节课中简,要提到过的一个别名。在这种情况下,它是一个使用声明,我们说,啊,每当我说 C, out 时,我实际上是指 STD C out,end 也是如此。
所以这是常见的风格。而且。所以在像这样的单文件程序中,使用命名空间 STD 并不重,要。但是,当你开始编写大型类和在单个程序中协作的不同类,时,这可能会给你带来一些麻烦,这实际上是我们在面向对。
象编程中要涵盖的内容。在这种情况下,使用这可能会让你陷入困境,因为假设你在,这里有一些函数在其他命名空间中重新定义,这开始变得,令人困惑。所以这是一个很好的练习,以便习惯在这里使用非常特定,的声明。
是的。问题。你能再说一遍吗?是的,这是一个很好的问题。那么这两行 include 和 using 的区别是什么?include 的作用是引入一个函数库。它引入的是其他人编写的代码。
或者我可以说它引入了类,本身。而 using 的作用是告诉程序你想使用这些类中的哪个函,数?所以在这种情况下,它可能看起来有点困惑,因为我们实际,上只使用一个 C out。
但是假设还有一个叫做 my bad library 的库,也定义了,一个 C out 函数。那么这个程序将不知道你指的是哪个 C out。所以你需要指定。是的。所以在语法上,这表示,即使没有重复。
我仍然需要指定我,使用的是哪个 C out。是的。很好的问题。很好的问题。太棒了。好的。所以我有点撒谎了,我们将从头开始。这是主函数,我们可以通过尝试运行来确认它是否正常运,行。Hello。
world。完美。但为了节省时间,我实际上给我们提前准备了一些东西。哦,不是那个。所以我实际上已经为我们编写了主函数,所有这些只是给,我们提供了我们想要做的蓝图。所以让我逐步解释一下。
那么就我们需要做什么来解决这个问题而言,我们首先需,要做的是读取我们正在处理的文件。所以我们的做法是:先取一个 Madison 的文件,一个 John, Jay 的文件和一个 Hamilton 的文件。
然后我们的目标是找出哪个人写了这个我们不确定是谁写,的未知文本。所以我们需要做的第一件事是处理文件,以便我们可以实,际读取它并计算单词数。最后,我们要做的是输出每个作者与未知作者之间的相似,度。
以便我们知道每个人写这篇文本的可能性有多大。所以你差不多能明白我们是怎么知道的,比如说。人们有的一个问题是,比如,你怎么知道在顶部要包含哪些,东西?你可以看到,好的,我知道我得用一个 IF 流。所以。
让我在这里包含一个 F 流。
然后别忘了一旦我包含了它。
抱歉,我不习惯 Mac 的键盘快捷键。开始吧。所以使用 STD,我流然后完美。现在它不再抱怨了。没错。然后流。所以我要包含字符串。然后,当然。太棒了。然后我们当然可以看到,文件流并得到一些字母相似性或。
者抱怨,因为我们还没写它们。我要从我们这里导入的最后一件事,我们不会一起写,实际,上是我们要比较的功能词的列表。我本可以输入这个,但我们不会。然后你会从这里注意到。没错。我也没有一个向量。所以开始吧。
是时候包含向量了。并且一如既往,使用 STD。完美。现在我们看到它识别了所有东西。而我在这里做的,如果你们之前没见过,就是我把这个声明,为常量。再一次,我们可以多谈谈像这样声明全局变量,比如这个常。
量与不是常量。但再一次,因为我们正在写一个单文件程序,在这种情况下,不是那么重要。但是是的。到目前为止关于这个有任何问题吗?太棒了。好的。所以在那种情况下,我们要做的,让我们试着实现这个。
所以我们要写的第一个函数。所以我猜一个问题可能是,为什么我们需要处理这个文件,呢?为什么我们不能直接为什么我们不能直接马上使用它?你们能想到原因吗?所以实际上,让我给你们展示。让我给你们展示这个文件。
所以假设我们的文件是这样的。并且记住,假设我们的功能词是像 it 或者 is 或者 the ,这样的东西。花 30 秒和你的邻座讨论。为什么?为什么我们不能直接通过这个文件并计算词频?
可能会出现什么问题?随意头脑风暴。所以,是的。好的。好的。有人愿意主动回答吗?如果你们要在这个文件中计算功能词的数量,你们可能会,担心的一个潜在问题是什么?是的。在后面。是的。是的。不。
这是个很好的观点。那么如果像 removed 这样的词是我们的功能词并且后面,跟着一个句号会怎么样?这似乎有点难处理。先透露一下,我们的程序实际上仍然处理不了这种情况。
但如果你们在这节课之后想要修改我们的代码来处理标点,这是一个很好的扩展。但这绝对是一个合理的担忧。是的。所以这是个很棒的回答。还有其他人想到别的吗?读取这个文件可能会有麻烦。是的,没错。是的。不。
很好的观点。所以在这种情况下,考虑一个像 is 这样的词。请记住,即使对我们来说这只是小写或相同的东西,当你从,文件流中读取它时,大写和小写并不是同一回事。所以,确实,大写是我们必须担心的问题。
所以这是我们现在要解决的一个问题。再次,我鼓励你们回去解决标点符号的问题。但是,让我们看看我们的 main。dot CPP。好的。这就是为什么我们需要处理文本,而不能直接使用文件流,的原因。再一次。
我们可以用其他方式来设计这个问题。但这是我们可以做到的一个方法。所以我们来看看。我知道我想创建一个函数,将文件流转换为一个包含所有,单词的字符串。这些单词可以是小写或大写。
所以我们要做的是将文件流传递给流。我有流,然后我们将通过引用传递它。实际上,有一个有趣的关系是,如果你不通过引用传递它并,尝试构建,它不会报错。啊,是的。就在这里。所以你会注意到它说错误:
调用隐式删除的拷贝构造函数,这只是对 Avery 讲解拷贝构造函数的一个小预览。但基本上,这阻止了你通过值传递它。所以我们必须通过引用传递它。太棒了。好的。所以我们再次的目标是从文件中读取文件的每一行。
并创,建一个包含所有单词并用空格分开的单一字符串。所以例如,与其像这样,我们想要的是像这样将其转换为小,写,例如 lowercase it,lowercase is 等等,没有换行符。
所以我会为你们开始这个函数。所以字符串。所以我们想要以某种方式返回一个字符串。然后在某个时候,我们知道我们将需要使用 get line。在 get line 中,我们使用它的方式,正如你们现在知道的。
那样,是将字符串作为第一个参数,将要读取到的字符串作,为第二个参数。好的。所以知道我们将使用 get line,为什么不再花一分钟左右,和你的伙伴讨论一下这个程序的总体结构会是什么样的?例如。
我会使用循环吗?我会如何将每个单词转换为它的小写版本?花时间讨论一下。可以随意查看 CPP 算法页面,看看是否有任何有趣的算法,可以帮助实现这个目标。但还是花时间探索一下吧。现在不需要找出答案。
但可以让思维活跃起来。是的,继续吧。好的。太棒了。好的。让我们再聚在一起。你们有什么想法?可以举手或者直接说出来。你们对如何编写有何想法?任何组件?我听到了一些很好的想法,所以我知道它们存在。
是的。完美。所以我们知道,哦,天哪,对不起,你们。
所以完美。在我们的 get line 周围加一个 while 循环。然后我们想在里面做些什么呢?不一定是语法,而是我们的目标是什么?我们在里面要做什么呢?很好。一个想法是检查每个字符。如果它是大写字母。
则将其转换为小写字母。或者反之,我们可以将所有字符转换为大写字母。所以这个思路的共同点是,你认为 STL 中是否有类似的功,能可以为我们做这件事?或者你还记得算法讲座中是否有类似的功能吗?遍历每个字符。
如果它还不是小写字母,就将其转换为小写,字母。我看到有人举手了。好的。是的,完全正确。所以有一个叫做 to_lower 的函数,它看起来是这样的,实,际上它来自于 C++ 标准库。所以在你上这门课之前。
我们可能会这样做:比如对每个字,符进行循环,比如 for (char C : line)
,然后 C = ,to_lower(C)
之类的。然后在那之后。
我们会做类似 RET += C
的操作。实际上,现在我们知道有一个函数可以为我们完成这个操,作,那么这让你想到了什么?那么 STL 中有什么功能可以接受函数并将其应用于行中,的每个字符呢?例如。
有人记得一个可能有用的算法吗?是的。对的。是的。所以 lambda 完全可以用。事实上,在这种情况下,实际上我们需要的函数是。所以 lambda 是我们想要应用的函数。那么在这种情况下。
我们想要应用的函数是什么呢?是的。好的。所以把这两个答案加在一起,实际上是所有三个答案都加,在一起。我听到了关键字 transform
。
我听到了关键字 to_lower
和关键字 lambda
。结果就是这样。所以 transform
是 STL 中的一个算法。你会注意到它没有将 transform
识别为有效的东西。
这是因为我们没有包含 algorithm
头文件。所以我们正在慢慢建立我们的库。然后现在如果我们输入 transform
,你会注意到 QT 做,得很好。对的。抱歉。我们还需要使用另一个。是的。
好的。你会注意到 QT 也有一个很好的功能,它告诉你函数的参,数是什么。所以我知道,transform
需要第一个和最后一个迭代器,作为输入。第三个参数是你想将变换结果写入的位置。
最后一个是你想要使用的函数。那么在这种情况下,我们想要变换的对象的第一个迭代器,是什么?可以直接喊出来。很好。非常好。正是如此。所以我们想要变换的对象是整行文本。所以它的开始迭代器是 `line。
begin。我们的第二个参数是什么?
line。end。对的。然后我们想要写入的位置在哪里?在这种情况下,我们实际上有几个选择。结果是我们可以再次写入
line。begin`,因为我们一次处。
理一个字符,然后用有效的字符替换它。所以,你会注意到在 C++ 中流是可变的,顺便说一下,如果,你没有注意到这一点。是的。哦,问得好。所以问题是,我们上次看到既有 begin line 也有 。
line。begin。结果是,如果你查看 begin line 的实现,它实际上是对 ,line。begin 的调用。所以你可以选择其中任何一个。它们有非常小的差异,但在大多数情况下,你不会遇到这些。
差异。其中一个处理了另一个没有处理的边界情况。是的。所以 line。begin 和 begin line 是等效的。太棒了。然后在这种情况下,我们要应用于行中每个字符的函数是,什么?两个小写。有把握。
两个小写。完全正确。太棒了。所以这实际上做的正是这个。所以我们最喜欢 STL 的地方就是摆脱了 for 循环。最后,我们要添加到返回值的内容是行加一个额外的空格。
这是因为 get line 返回的是带有换行符的行。所以如果我们想将所有内容作为一个流返回,我们就使用,空格来分隔所有单词。然后,当然,我们必须做的最后一件事。关于这个有什么问题吗?是的。是的。问得好。
那么为什么我们可以直接传递呢?为什么我们不需要包含 CC type?实际上我们完全可以。我们可以在上面写 include,它会完全编译通过。结果是 CC type 已经被另一个这些库包含了。
所以它已经知道它的存在。不过,是的,我们可以把它留在那里面以便清晰。是的。是的。问得好。to lower 对于标点符号的处理是不变的?这是个好问题。是的。还有其他问题吗?是的。啊,这是个有趣的问题。
所以问题是在这个第三个参数中,我们指定了要将变换写,入的位置。难道我们不能直接把它放入这个变量中吗?是的,我们绝对可以尝试这样做。我们可以做类似 STD,STD transform,line dot 。
begin,line dot end 的操作。然后使用我们学过的迭代器适配器,我们可以做类似 back, inserter a character to 的操作。让我们看看。后插入器。
它是 RET 然后是分隔它的东西。所以在这种情况下,由于是字符,我们不希望有任何东西来,分隔它。这实际上也可以完美地工作。对吧。所以如果我们想使用后插入器,那实际上还有另一个类我,们必须使用。
叫做插入器。我记得我很确定它确实如此,但也许不是。好的。不,它没有。我在想的是输出流操作符。是的,就是它。谢谢。是的。另外。对。好的。所以除了后插入器,我们还可以这样做。是的。所以我想不是流迭代器。
因为我想看看那个。是的。所以是的。所以这完全有效。实际上很有趣。如果你尝试一下,你实际上可以猜到这些哪个会运行得更,慢。我现在不打算这样做,因为还有其他我们想做的事情。但事实证明。
这种方法实际上比第一种方法更慢,因为在这,种情况下,你必须一次插入一个字符来返回。而在这里,你已经在处理行,因此你不必在每次插入一个字,符时扩展返回。所以,嗯,小差异。但确实,这完全是有效的。
所以我会把它注释掉。太棒了。是的。还有其他问题吗?在我们继续之前?很好。是的。好的。所以这告诉我们接下来要做什么。所以现在我们已经正确处理了文件,以便准备计算单词的,出现次数。
所以现在我们来尝试计算相似度。是的。所以现在我们进入了我们实际算法的大部分,即获取相似,度。所以我们要做的第一件事是,我将写。我们想输出的内容是表示两个特征向量点积的某个小数值。
你们还记得这是我们的最终目标。所以我们将输出一个双精度浮点数,计算字符串一和字符,串二之间的相似度。好的。所以我也会为我们编写这个伪代码,因为这回到了我们如,何计算相似度的算法。
所以你们还记得从幻灯片上,我们需要对这些向量做的第,一件事是什么吗?我们需要做的第一件事是什么?对不起,对每个文件字符串需要做的第一件事是什么?是的。计算频率。所以我们先做这个。
所以我们需要计算一个整数向量。比如说计算频率一,然后做同样的。接下来要做什么步骤?你们还记得从幻灯片上吗?如果不记得,我也可以分享。一旦我们有了两个特征向量,我们要做的下一件事是什么?是的。大声说出来。
点积。好。所以我们再说一次,我们没有这些函数,但我们只是写出我,们想要实现的伪代码。所以这是一种编码作业的风格,你首先。这是一种自上而下的方法。你首先弄清楚你想做的所有一般事项。然后你再回去实现函数。
完全正确。所以我们会计算这些的点积。然后最后一件事,如果你记不起来的话,我们要做一些事情。
。如果你再次查看方程式,余弦θ是我们想要返回的内容。所以我们已经得到了点积,即分子。
然后我们的分母是我们要除以某种第一个向量的幅度乘以,第二个向量的幅度。完美。关于这一点有任何问题吗?如果没有,我们也可以深入探讨。因此,创建频率向量将是这个程序的最大部分。而且,这也将适用于你的作业。
你基本上可以直接从这个类示例中提取代码。所以要注意这一点。所以我们实际上先实现这个。然后如果有时间,我们可以稍后做幅度和点积运算。如果时间不够,我会把计算幅度和点积作为一个挑战留给,你们。
因为它们实际上各自只需一行代码,使用一个 STL ,算法,这有点酷。但我们先做这个,这样你们可以看到你们在作业中会用到,什么。所以创建频率向量。我们将使用一个整数向量文本。
你会注意到它仍然在抱怨它不存在。原因是。你可能在作业中见过,因为我们在第一次使用它时已经声,明了它。所以解决这个问题的一种方法是将整个函数移动到我们第,一次调用它之前,或者使用函数原型。
我相信你们在作业中,见过。所以再说一遍,这样做就是在实现函数之前先声明它,以便,编译器知道这个函数的存在。所以这是一个小问题。如果你感兴趣的话,我们可以之后讨论。好的。所以我要为你们写这个。
因为这个不会包含大部分算法。所以,我们的目标是创建一个向量,表示这个向量中每个单,词在文件中出现的频率。有几种不同的方法可以做到这一点,这可能看起来不是最,快的方法。但这实际上对你们的作业会更有用。
所以我们就用这种方法。事实证明,这种方法的速度并没有比其他方法慢多少。所以基本上,我们要做的是,对于频率向量中的每个单词。我们将遍历整个文档,查看该单词出现了多少次。所以对于我们的特征向量中的每个单词。
我们将做结果回,溯。然后让我们做一些我们最喜欢的计数出现次数的方法,它,在我们文本中的单词例子中出现得很频繁。然后最后,所以再说一遍,我们只是逐步构建算法的工作,每次都在推送。很棒。然后最后一件事。
所以再说一遍,这是说,对于频率向量中的每个单词,计算,它出现的次数,然后将其添加到向量中。所以现在我们要做最大的。太棒了。所以我要做一件事,所以我会给你们做一些准备,然后我希。
望你们再花一分钟时间讨论一下。我们实际上再花两分钟,然后也许可以在下一节课完成这,个。再花两分钟讨论一下你们可能如何计算一个字符串在这个,文本中出现的次数。在你们这么做之前,我会给你们一个准备。
所以我们之前提到标点符号是一个问题。实际上还有一个其他的问题。让我们看看你们是否能识别出来。所以假设我们有一个单词像这样。在尝试查看一个字符串中单词出现的次数时,有什么问题?这有点棘手。哦,对了。
“the” 和 “there”。那么 “the” 会被识别为出现了两次多吗?因为它也出现在 “there” 中。为了处理这个问题,即处理这是一个子集或是一个出现在,其他单词中的词。
我们实际上要做一个小小的技巧,直接说,我们实际想找到的词只是这个完整的词本身。所以只是这个。注意,这就是我所说的,我们不处理标点符号,因为如果它,以句号结束呢?所以这实际上是一个很大很大的部分。
我建议你们在这次,讲座之后去实现它。很好。所以我希望你们利用课上的最后几分钟与伙伴讨论一下你,们可能如何实现这个计数出现次数。我给你们一个提示。你们可能最初会想到的是STD算法中的count。
因为看起来,哦,我们要计数。我记得有一个叫count的算法。查看一下count是如何实现的。我告诉你,这不是count。你们正在寻找的是STL中的另一个算法,它会帮助到你们。
所以你们的作业是下一节课带来那个算法是什么。然后我们将在下一节课的前五分钟完成这个任务。但现在,利用最后一分钟和你们旁边的伙伴讨论一下。看看你们是否能集思广益,然后我们将结束这节课。太棒了。
然后在所有事情上都使用CN。完全正确。所以,做一个while循环遍历CN并逐字读取文件。这确实会有效。一个问题是你只能读取任何IF流。实际上,不,这不是真的。我想你不能回绕。所以这是一个问题。
所以在这种情况下这是一个很好的点。所以我会说你实际上可以将其作为一个有效的解决方案来,实现。STL做的是提供一种更快速的方式来实现。所以这有点像是一个简单的解决方案。还有更好的解决方案。太棒了。是的。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P11:[17]CS 106L Fall 2019 - Lecture 10_ Classes and Const Correctness - GPT中英字幕课程资源 - BV1Fz421q7oh
是的。好的。太棒了。很酷。Ben, Victoria, Shunran, Peter, Peter, Yuzu, Byron。
。好的。太棒了。很酷。好的。那么我们开始吧。哦,对了。另外,如果之前不太清楚的话,所有这些糟糕的笑话都是你,们在填写的介绍调查中提供的。我觉得这个笑话挺有趣的,因为写它的人在最后加上了哈,哈哈哈。
这对我来说非常有趣。不过。很酷。今天,我们将完成上次的STL总结,简要概述一下类,因为我,们要开始新的单元——现代C++中的面向对象编程。最后,还有一个令人兴奋的话题。
我们将讨论你们关于关键字const的任何问题。const是C++中的一个词,它在语言刚开始时就存在,但随着,语言的发展,它增加了更多的功能。所以它可能有点混乱。
所以我们会详细讲解const的所有奇怪细节。讲完这个讲座后,你应该会对它非常熟悉。完美。我们将用前五分钟来总结一下上次的讲座。回顾一下,我们尝试解决的问题是,有一系列名为联邦论文,的文章,共约85篇。
每篇由这三位作者中的一位撰写。我们试图解决的问题是,如果我们从这些作者那里获得一,篇文本,并且还有一篇我们不知道作者的神秘文本,我们是,否可以使用某种算法来确定作者是谁?我觉得有人告诉我。
他们实际上在其他课程中已经在解决,这个问题,使用了稍微复杂一点的解决技巧。但我们这里使用的是这样一个思路:我们可以列出一些非,常基本的词,如果我们可以计算这些词在文本中出现的次。
数,我们可以把它当作该作者写作的指纹。所以我们的想法是,对于每篇文本,我们都能生成一个包含,这些常见词的特征向量。然后通过比较该向量与未知论文的向量,我们可以通过它,们之间的距离来判断作者是谁的可能性。
所以再次强调,角度越接近,文本越相似。
我们要用来做这个的数学方法是点积。不重要,只是如果你们感兴趣的话。
那么我们快速总结一下编码部分。再一次,我们上次做的是从这个主文件开始。我们读取了文件,将其转换为字符串。现在我们的目标是计算这些特征向量中每个词在我们从文,件中创建的那个长字符串中出现的次数。
我们到了这个名为count occurrences的函数,函数的目标,是计算在这里传入的特征向量中的一个词在这里传入的文,本中出现的次数。我让你们查看了STD算法库,看看你们认为应该使用哪个算,法?
因为这实际上不是立刻显而易见的。我们可能会认为,从名为count occurrences的函数中,我,们会使用之前见过的count算法。但是,有人能从你们周末看到的内容,或者凭直觉,给我解。
释一下为什么我们在这里实际上不能使用计数算法来找出,一个单词在字符串中出现的次数吗?花 30 秒和你的邻座讨论一下。为什么我们不能用计数?然后如果你知道了,我们应该想用什么算法来代替?
而且我们不期望你们凭直觉就知道。
你们可以查阅 CPP 算法库,看看那里有哪些算法。
但是,再次花 30 秒互相解释一下为什么在这种情况下我,们不能用计数。好的,所以我听到了一些质疑的想法,一些不错的想法在被,讨论。我很高兴你们在尝试,即使你们不一定确定这会走向何方。
有人有想法可能是什么吗?随意大声说出任何猜测。为什么我们不能用计数?黄色衬衫。你叫什么名字?埃利奥特。埃利奥特。哦,埃利奥特。当然我认识你。好的。是的。你的想法是什么?它分开了。
所以这里有这种找出被消除的元素的空间的问题。是的,所以这实际上完全在正确的轨道上。实际上,从那引出,这里更普遍的问题是我们接收一个代表,文本的单个字符串。
而我们想做的是我们想计算一个字符序列在那个文本中出,现的次数。但是注意,如果你们仔细阅读计数的文档,它有点回到每个,字符都相同的这个想法。我们没有单词和空白字符的概念。所以在这种情况下,明确地说。
我们不能用计数的原因是因,为我们怎么能用它?我们要做的是我们会传入一个字符串的迭代器。但是字符串的迭代器指向什么?是的,火。完全正确。你明白了。它指向一个字符,不是一个字符串。所以使用计数的问题在于。
如果我们尝试使用这个函数,我,们每次只能搜索一个字符,这不是我们想要的。所以这是这个解决方案的一个棘手部分。这个问题的解决方案是实际上有另一个叫做搜索的函数,它允许我们在传入的迭代器中搜索一个元素序列。
所以这不是真的通用的。这只是一个很好的点,提醒我们回去仔细检查我们的算法,在做什么,也让你们知道有一个叫做搜索的东西存在。所以,好的,干得好。让我们看看。好的。在那种情况下,让我们完成这个函数的编写。
所以我们知道,实际上,我们现在想用 std:search 。好的。所以为了节省时间,我不会让我们一起写这个的逻辑,但我,会把它给你们。如果之后不明白,随意查看。但我在做的时候会说出我的想法。
所以我们想要在字符串中找出这个单词出现次数的一般算,法本质上是使用一个 while 循环。所以当字符串未结束时,我们想要不断地在文本中找到该,字符串的下一次出现。那么让我们从我们想要开始搜索的位置开始。
然后是我们,想要结束搜索的位置。然后让我们有出现次数的计数。然后我们会说当 cur 不等于 end 时。所以当我们还没有到达文本的末尾时,我们想要使用我们,新的搜索算法。再一次。
在 Qt Creator 中,您会注意到它会告诉您我们正,在使用的算法的参数,这是一个方便的提示。所以在这种情况下,我看到我们想要第一个迭代器,最后一,个迭代器,然后是第一个和最后一个。让我再检查一下。
是的。好的。搜索从 s 第一个到 s 最后一个的首次出现,所以是前两,个迭代器中的第二个。所以我们想要传入的是我们在两者之间搜索的东西,然后,我们想要传入针,即我们正在搜索的东西。到目前为止这有意义吗?
有任何问题吗?好的。太棒了。然后我们可以说,所以,对。所以我们要说 std_search 所做的是它返回指向找到位置,开头的迭代器,如果未找到则返回结束指针。所以我们要做的是,如果未找到。
所以如果 found 等于 ,end,跳出循环。否则,我们在我们的文本中找到了字符串的一次出现,所以,我们增加计数。然后我们想要做的是我们想要将我们搜索的起始位置立即,设置为刚刚找到的位置之后。
所以我们要做 curr 等于 found 加 1。请注意我不必做 curr 等于 found 加上字符串的长度。我们完全可以,但请注意,即使移动一个,我们已经将其从,类似于“the”变为“he”。
所以这不是我们程序中的错误。有了这个,我们在 count_occurrences 函数中要做的最后,一件事就是返回计数。太棒了。到目前为止有人有任何问题吗?好的。太棒了。
所以这个计算一个字符串在另一个字符串中出现次数的函,数,提示,提示,可能对您在本课程中即将面临的某些作业,有用。是的。所以这是一个很好的函数,我们将在这之后将此代码发布,到网上。
但您也可以随意重新创建此函数。实际上,我们在这里使用的关键算法是搜索算法。好的。然后很快,我们完成程序所需的最后两件事是在获取相似。
度时,我们记得我们确定两个文本或两个特征向量的相似。
度的方式是通过取点积。所以我们要打印出这个相似度,余弦θ,最后要做的是能够。
计算点积和幅度。好的。所以乍一看,我们首先需要做的是计算两个特征向量之间,的点积。这似乎有点棘手。看起来我们将不得不做某种 for 循环,在其中我们将相同,元素的两个东西相乘。
我们应该从 Python 中压缩某些东,西等等。结果我们最喜欢的算法库实际上为我们提供了一些东西。有人马上知道那个算法叫什么吗?我会说 5,4。所以这是一个很有趣的算法,实际上它不在算法课程中。
它实际上在另一个叫做 numeric 的课程中。在那里你也会找到像 accumulate 这样的算法。我们今天使用的是 inner product。太棒了。是的。所以我不会在这上面花费太多时间。
因为现在主要是了解,有哪些算法。这只是为了展示,即使是像点积这样理论上复杂的东西,STL 已经为我们提供了现成的工具。所以我想说的关键点是,当你实现自己类型的代码时,如果。
有某种功能你认为应该足够常见以至于有人已经为它写了,代码,它很可能已经在 STL 或 Boost 库中存在。所以我总是建议在尝试自己重新创建之前,先查查已经有,什么工具。
除非你必须为了作业或类似的原因自己重新创建。但是如果你查找 inner product,它的作用是接收一个向,量的起始和结束位置,然后是第二个向量的起始位置。然后是你想要开始点积的初始值。
所以假设你想用五作为初始值,你可以输入五而不是零。但这实际上会为我们计算点积。所以我们要做的就是返回那个点积。然后对于 magnitude,我们将实际使用的是一个数学属性。
。
有人知道一个向量的大小等于什么吗?再说一遍。确切地说。是的。所以实际上,另一种写法是,一个向量的大小等于它与自身,点积的平方根。这只是另一个数学概念。再次强调,对这个课程来说并不是特别重要。
但只是为了完,善我们的实现。所以有了这些,我们在这个函数中,计算两个文本之间的相,似度。我们通过计算文本中出现次数来创建了两个频率向量。我们计算了这两个频率向量之间的点积。
最后,我们返回这个方程式,即点积除以各自的大小的乘积。
。好了。在我运行代码之前,有人对这个最后的例子有任何问题吗?很酷。那么我们准备好查看它的运行情况了吗?祈祷吧。通常来说,作为一种编码策略,这不一定是最好的方法。对。平方根在 C math 中。对了。
我不是一开始就知道这一点。如果你不确定一个函数的来源,可以去浏览器中输入比如,平方根 C++。然后在文档的顶部,会说明它来自哪个头文件。但确实,它来自 C math。是的。作为一般编码建议。
我会推荐尽早测试,而不是在你写完所。
有代码后才测试。但以此为例。让我们看看。好了。为了节省时间,我会稍后进行调试。
我也会发布原始代码,如果你们想自己试运行的话。这就是所有的算法了。至此,我们完成了算法和容器的内容。我们尚未涉及的是模板的内容。但你可以想象回到这里,将其泛化为任何东西的向量,例如,而不是字符串的向量。
类似这样的东西。假设你想在词语之外创建一个特征向量。是的。54。55。是的。这很有趣。所以,这基本上是在说,要计算点积,我需要知道的只是第,一个向量的开始、第一个向量的结束和第二个向量的开始。
我们实际上不需要知道第二个向量的结束,因为这个函数,假设你传入的两个向量长度是相同的。或者说,你只想计算第一个向量长度内的点积。所以原因是。所以这个第四个参数不必是向量到末尾。
这个第四个参数实际上是一个初始值。所以假设你想计算点积,但在前面加上一些常量。比如说我们想计算两个向量的点积加上五之类的。它允许我们提供一个起始值。我不完全确定他们为什么这样实现。
我认为是因为还有其他函数,比如累加函数,你也可以提供,一个起始数来开始累加。我相信这样做是为了与这种格式保持一致。是的。好问题。是的。彼得。是的。那么,为什么不阻止在这里识别为一个实例呢?
我们用来粗略防止这种情况的方法是在这个计数出现次数,中。我们在词语前后加了一个空格。所以这并不是万无一失的,因为它不处理标点符号的情况,所以一个很好的扩展是回去真正处理标点符号的正确方法。
处理标点符号的方法实际上是使用 SDL 的正则表达式库,如果你们对其他课程中的正则表达式有所了解,C++ 也有,相应的功能。而且它实际上是标准库的一部分。好问题。是的。再说一遍。这解释了为什么是零。
非常感谢。是的,就是这样。所以与其说是词语,不如说是查找。我实际上不知道这是否是唯一的错误,但这绝对是一个大。
问题。好的。是的。所以这不是唯一的错误,但这是一个错误。
谢谢。谢谢。很好地发现了问题。好吧。还有其他问题吗?目前的问题都很好。好的。在这种情况下,我会稍后发布这段代码,并且我还会发布一,个关于它在讲座中为什么不起作用的解释。
这样你们可以看到错误在哪里。但现在,有了这个,我想再次说明,这对作业也有用,我们已。
经结束了 SDL 单元。所以,大家做得很棒。SDL 说实话是 C++ 中最重要的部分之一。但即使它如此核心,很多人仍然不知道它的存在或使用。所以,很酷。好的。所以在我们进入下一个单元之前,有几个通知。
首先,A 部分的作业截止日期是本周六。如果您已经看过,您就会知道它超级简单。它实际上包括测试您的网络连接,然后基本上重新实现我,们在课堂上刚刚做的事情。所以我鼓励您在周六之前完成。是的。
如果花费超过两个小时,请来找我们。然后应在线请求,我们将在本次讲座之后的今天或明天发,布 B 部分。截止日期仍将是原来的 11 月 14 日星期四。但这样,如果有人想早点开始,他们可以。太棒了。是的。
哦,是的。所以作业三将在我想是 11 月 10 日左右发布。是的。因为我们想早点发布,只是为了让您觉得我们不是试图强,迫您在感恩节期间做某事。您在感恩节期间不必做任何事情。我们只是想给您一些额外的时间。
以便在那一天和感恩节,之间您有一些喘息的空间。好的。我猜大多数学生不会做作业三,这有点令人难过。但是,如果您正在做作业三,只是提醒一下,我们会早点发,布。是的,没错。是的。所以,是的。提醒一下。
在这门课中,您只需要完成三个作业中的两个。是的。梅森。是的。作业一应会返回。它会在作业二截止日期之前一点返回。主要是因为我们交给评分者,我们想给他们足够的时间评,分。是的。是的。
因为评分者他们本周也在评期中考试。然后他们也会感到困惑。所以我们想给他们多一点时间。他们都有自己要负责的部分。所以给他们一两周的时间。是的。另一件事是,关于这门课作业的反馈,因为它们主要是关于,功能。
而不是那么多关于风格。所以更多的评论也会不如您可能习惯的 B 或 X 那么详细,所以如果你们收到反馈并且或者想要更详细的反馈,比如,请在办公时间来找我们,我们也可以坐下来查看您的代码,并更仔细地看一看。
因为作业评分者可能只会提供一些一,般性的。是的。要么是错误,要么是反馈建议。
酷。太棒了。好的。那么在这个房间里,你们中有多少人熟悉面向对象编程。我能看到举手吗。好的。太棒了。好的。好。所以我看到对于一些人来说这仍然是有用的。对于其他人,我猜我也仍然会讲得快一点。
所以正如我们之前提到的,C++ 不仅仅是一种面向对象编,程语言。这是我们使用 C++ 的方式之一,但您也可以将 C++ 用作,函数式语言等等。也就是说,面向对象编程是人们使用它的最常见方式之一。
所以这将是我们剩余课程(除了最后几节)的重点。所以作为提醒,大概讲讲我们这门课要专注的面向对象编,程的方向。嗯,谈到多态性,接下来的几节课我们将讨论运算符重载和,虚函数。
模板类涉及到抽象和面向对象编程的概念,然后继,承就是继承,这也是我们要讨论的。然后类、对象和封装,这三个术语实际上你们在 CS16B 中,应该都熟悉。有人吗。我实际上不确定。X 讲过这个吗。
或者有没有人不熟悉这三个概念。好的。好的。是的。如果你们是但感觉不舒服,请课后跟我交流。
所以先简要回顾一下类,确保我们都在同一页面上。首先,如果你们熟悉这些,或者如果你们想复习,辛西娅大,概上周讲过。所以是的,请查看那些幻灯片。你们在 106B 中涵盖的内容,如果你们在 106B 中。
应该已,经看到了。h 与。cpp 文件的使用。你们应该熟悉基本的构造函数和析构函数、基本的常量、,基本的运算符重载。所以这就是我假设我们进入这门课所具备的,关于这些有,几点说明。那么,首先有人能告诉我。
h 和。cpp 文件的区别吗。举手。没错。是的,没错。所以正如梅森所说,h 文件是你可以定义类和类中的函数,的地方,而。cpp 是你实现这些的地方。更普遍的思考方式是,h 是你为你的类指定接口的地方。
所以这有点像是面向公众的,比如你希望其他类能够从你,的类中使用的 API。而。cpp 是你隐藏所有复杂细节的地方。所以你们可能之前都听说过这些。
但是,是的,这又是来自辛西娅的 106B 讲座。关于头文件的小细节。我们不一定会关注这些,但如果你们不熟悉,我绝对鼓励你,们回去再看一看。你们可能会注意到的一件事是,在顶部我写了头文件。h 啊。
与源文件。cpp 啊等等。你们可能会想知道所有这些扩展名之间的区别。
这是一个完全合理的问题。事实上,结果发现对于。h 和。cc 甚至还有更多的扩展名。有。hpp、。csx、。cxx、。c++、。C。相当令人困惑。所以这只是有趣的琐事,在某些小的边缘情况下稍微重要。
这就是我看到时的反应。结果发现你使用哪个扩展名取决于编译器。所以如果你们看底部的这个链接,实际上有一个列表,列出,了哪些编译器接受哪些扩展名。例如,G++或者抱歉。
GCC 我相信接受五个 CPP 扩展名中的,四个等等。并且就你们所知,C 实际上是最初的 C++扩展名。但是当文件系统开始使文件名不区分大小写时,就产生了,混乱。换句话说,C 和。
c 开始表示相同的东西。所以这就是他们开始提出。cc、。cpp 的原因。现在一般的标准是,任何 Unix 系统、Unix Linux 系统都,使用。cc。其他系统,例如 Windows 会使用。cpp。
实际上,这种情况可能只在您混合 C 代码和 C++代码时才,重要。所以,如果这种情况发生,请再次查看扩展名。但是,这主要只是有趣的事情,因为我知道人们一直对此感。
到困惑。所以,构造函数,这又是来自 106b 讲座的内容。这应该看起来很熟悉。在这个课程中,我们实际上将深入研究更复杂的构造函数,所以,复制构造函数、移动构造函数之类的,艾弗里将进行,讲解。下周。
实际上下周,是的。析构函数。老实说,关于析构函数,我们不会深入太多。是的,基本的想法是当一个类的对象或类的实例超出作用,域时,它会调用析构函数。那通常是您释放内存之类的地方。在课程快结束时。
我们将讨论这个称为 RAII 的重要概念,是的,这是真的。有没有更简单的发音方式?RAII。RAII。好吧,有一种最重要的 C++惯用法叫做 RAII,它与析构函,数有关。所以我们将讨论这个。是的。
这就像是使 C++与其他所有语言不同的东西之一。
太棒了。好的。是的。实际上让我讲完这些。
所以您可能也从 106B 讲座中看到过。辛西娅应该提到过 const 的这两种用法。一种您应该从本课程和 106B 或 X 中看到的是将其用作,参数。所以通过引用传递的常量参数非常常见。
您现在看到的另一种是将 const 用作函数。就是说一个函数是 const。有人能为我总结一下 const 函数的作用吗?它在旁边有说明。但是,是的。完全正确。完全正确。
所以 const 函数不能修改任何类变量或传递给它的任何,变量。完全正确。太棒了。然后今天我们要做的实际上是复习 const 的其他所有内,容。所以这只是略作探讨。但这实际上是我最喜欢的讲座之一。
我想我会使用之前讲座的幻灯片,我觉得超级有趣。但在我们开始之前,最后一件事是运算符重载,艾弗里将在。
下一讲中讲解。辛西娅真的讲过运算符重载吗?只是非常简略地讲了。只是非常简略地讲了。好的,很酷。没有像朋友那样的。我在下一讲也有足够的内容可讲。绝对的。好的。所以在我开始讲 const 之前。
有人对面向对象编程类有任,何问题吗,一般性回顾?好的。谁对 const 感到兴奋?好的。我看到那只手。我看到那只手。太棒了。是的。所以 const 实际上是我刚开始学习 C++时让我感到困惑。
的关键字之一。有点像我看到了它的常见用例,但我也在一些奇怪的用例,中看到了它,不确定它到底是什么意思。好的。所以我们有 22 分钟。
我相信我们可能会完成整个幻灯片,但是我们可以在下次,讲座中覆盖最后两个杂项主题。好的。是的。感谢Mike使用他的幻灯片。好的。所以我会给你们30秒来阅读这些,因为我认为这是非常常,见的。
这是一个关于为什么我们使用const的普遍问题。对不起字体太小了。好了。那就是我们从一个很酷的家伙那里得到的建议。是的。所以为什么使用const的整个想法。对不起,如果那段时间不够让你阅读。
这些幻灯片也会在网上发布。是的。所以在我们这样做之前,其实有一个更一般的问题。为什么我们不使用全局变量?你可能以前听过这个问题。全局变量可以被程序的任何部分修改或读取,这使得很难,推理它们的使用。
它们也可以被程序的任何部分获取或设置,并且通常被视,为不安全的。const也是一样的情况。所以实际上,这不仅仅是一个风格问题,我们会看到。使用const实际上对确保我们程序的安全性非常有帮助。是的。
一般来说,它涉及安全性、风格,以及确保我们承诺的接口,实际上是我们提供的接口。很棒。好的。那么为什么使用const的另一个动机是什么。有没有人发现这段代码中的错误?花30秒时间和你旁边的同学讨论一下。
看看你是否能找出,导致这段代码不按预期执行的原因。有点难以看清。好吧。实际上,这有点像是一个技巧性问题。基本上是不可能看到的。你会发现这里,其实这是我在学习CS106B时花了三个小时,调试的一个错误。
所以这对我来说非常重要。但你可以明显看到这段代码是一个人为设计的例子。但是当你构建越来越复杂的数据库时,你所做出的承诺变,得非常关键,以确保你的代码按预期工作。所以如果我们使用常量X和常量Y。
既然我们只是在这个函,数中进行cout操作,我们会收到这个错误。这会让我们知道。话虽如此,const的作用就仅仅是给我们提供编译器错误吗,它仅仅对我们提供编译器错误有用吗?不。那么我们以地球为例。好的。
假设我们是一些地质学家,我们想做一些无害的事情,比如,计算地球上的人口。所以我们调用这个函数来计算地球上的人口,我们期望它,返回某种人口数量给我们。然而,我们函数中的一个恶意圣诞老人实际上实现了这个。
计算人口的函数,添加了一个小帽子到地球上。所以,当我们只想计算人口时,结果是我们实际上还给地球,添加了一个小帽子,把它变成了火星,并用尘土星射死了它,这一切,当我们只想做的只是计算地球的人口。
那么这怎么发生的?正如我们所说的,当我们调用 count people 函数时,它调,用了三个实际上修改了我们传递进去的 Earth 对象的函,数,这并不是我们所期望的。是的,这里有一些政治评论。那么。
如果我们把这个方法改成 const 方法会发生什么呢,再说一遍,如果我们把这个方法改成 const planet 引用 ,P,会发生的事情是编译器会提前通知我们传递了错误的参,数。这点很重要。
因为当我们为其他人设计类时,我们首先要尽,可能地将它们设置为 const。其次,要让他们知道我们已经将其设置为 const,以便他们,可以按照他们的意图使用它。是的。
它们允许我们推断我们传递给这个函数的变量是否,会被修改。这对我们的客户很有用,也对我们有用。这样,Earth 也不会被破坏。再一次确认,当我们传递 const 参数时,const 方法保证。
不会改变我们传递的值。因此,我们可以确定顶部的 x 的值与底部的 x 的值是相,同的。好的,在我继续之前,有没有问题?是的,Elliot。
这里的假设是 count people 函数内的三个函数都通过引,用传递 x,而不是 const 本身吗?是的,这是个好问题。所以我是否正确理解你的问题,如果我们不通过引用传递 ,x。
那么它们也不会被修改呢?是的,完全正确。所以这里一个更好的例子是使用一个 planet 对象,而不,是 int,因为我们希望通过引用传递以节省空间。因此 const 关键字真的很重要。或者,例如。
对于任何非原始类型,我们希望通过引用传递,以节省空间,但仍然需要某种保证它不会被修改。是的,很好的问题。是的,Peter。它会返回运行时错误还是编译时错误?是的,好的问题。关于 const。
它会返回运行时错误还是编译时错误?关于 const 的好处在于它返回编译时错误。是的,因此编译器可以为我们检查是否意外修改了不应该,修改的内容。是的,很好的问题。还有其他问题吗?是的,Shiran。
如果我们将 x 参数改为 const 引用 x,我们是否也需要,将这个 x 参数改为 const int?是的,这是个好问题。答案是肯定的。const 所做的事情之一是,它不仅确保这个一级函数是 。
const 正确的,还确保你从那里调用的任何函数也满足 ,const 属性。如果你不这样做,你会看到编译器错误,提示尝试将 const, 对象传递给非 const 函数,类似这样的信息。是的。
Byron。是的。再说一遍。是的,很好的问题。是的,这是个好问题。那么,如果我们尝试使用一个不遵循这些良好 const 属性,的库,会发生什么呢?Avery,随时可以插话,但我首先的回应是。
那么在这种情况,下,真的完全取决于我们作为客户,确保我们在自己的端上,强制修改参数。在像计数人员的例子中,假设我们不知道计数人员是如何,实现的,这就是进入模糊领域的地方。是的,我一时还想不到,哦,好吧。
实际上,一种防止这种情况发生的办法是,如果你知道在初,始化后不需要再修改它,那么你可以将 Earth 声明为常量,变量本身。这样即使它被传递到一个尝试修改它的函数中,也会抛出,编译器错误。
但是如果你确实需要修改 Earth 并且还需要传递它,我相,信你可以将它传递为其常量版本。我很确定。我只是补充一下 Anna 的回答。那么你的问题是库本身不是常量,对吗?所以它没有做任何常量的事情。
但你声明了某个常量变量,而你不能传递它。这就是问题吗?是的。哦。哦,好吧。哦,是的,我明白了。我明白了。谢谢。是的,所以这肯定不是理想的,但有一种解决方法。有一种叫做常量转换的东西。
它基本上可以去掉常量。现在,这不好,因为基本上是别人的代码有问题,所以我们,将这个坏问题传播下去。所以通常这不是一个好主意,但如果你真的必须调用他们,的函数,那么你可以使用常量转换来去掉常量。
这是一个好点子。是的,因此如果你有一个常量对象,要么你将无法调用该函,数并且会抛出编译器错误,要么你将不得不使用常量转换,不过在这种情况下,我会质疑为什么你一开始要将变量设,为常量。
如果你知道在某个时候会改变它。但是,是的,好问题。这表明如果你在实现一些库时是多么重要。你必须确保你的代码是常量转换的,否则使用你的库的人,将会遇到很大的困难。还有一个旁注。
CS110 在某些作业中使用常量转换,因为他,们的起始代码写得不好。是的,实际上我们会看到,我不知道我们是否有时间去这节,课,或者我忘记是否实际上包含在这些幻灯片中。
实际上使用常量转换就是我们如何在类中实现重载函数,其中有常量函数和非常量函数。事实证明,最佳的实现方式实际上是使用常量转换。所以有其用途,但,是的,一般来说,当我们编写自己的代码,时。
我们希望避免丢弃常量。是的,好问题。到目前为止还有其他问题吗?好的,很棒。好的,那么我们看到的第二件事是,啊,是的,我们不仅可以,使参数成为常量,我们还可以使函数本身成为常量,也就是。
所谓的常量成员函数。为了了解这一点,常量如何与类交互?假设我们有一个定义对象类的 cloud,其中有一些我们不,需要担心的私有成员变量。之前,我们对类的理解是,我们有一个对象类,我们有操作,该类的函数。
现在我们意识到,实际上,那些函数可以分为两种类型。一种是常量成员函数,或者它可以是非常量成员函数。实际上,非常量成员函数能够处理常量和非常量变量,而常,量成员函数只能提供常量接口。因此。
以我们的例子为例,我们看到我们将这个计数人口函,数定义为常量函数,而死亡之星函数定义为非常量函数。那么假设我们有一个接受常量参数的函数。如果我们然后尝试在常量参数上调用常量函数,我们能够,这样做。然而。
如果我们尝试在常量参数上调用非常量函数,我们无,法这样做。所以有点直观。但是,是的,有人对此有任何问题吗?每个人都明白为什么这最后一行无法编译吗?有点回到我们之前所说的关于常量如何确保其下面的所有。
内容也是常量。好的。好的。我认为,关于常量,最棘手的事情之一是当涉及到常量指针,时。所以我们将经历这个。我们将慢慢地逐步处理这个。所以这个第一个例子,指向常量的整型指针是指向非常量,整数的常量指针。
换句话说,你能做的是你可以解引用指针并更改该值并将,其设置为其他值,但你不能更改指针本身。所以另一种思考方式是你有指针手指。你被允许更改它所指向的任何内容,但你不能移动这个手,指。这有意义吗?
所以再次读取的方式,从右到左。你有一个指向非常量整数的常量指针。另一方面,你也可以有一个常量整型指针 p,这是一个指向,常量整数的非常量指针。并且,再次,从右向左读取。和这个一样。
对于相同概念的两种不同语法。然后最后,我们也可以有一个指向常量整数的常量指针。这有意义吗?有人有问题吗?我们对这个感觉有多舒服?好的,现在我看到那些了。所以再一次,这三者之间的区别,再次。
这只在你使用指针,时发生。所以这会发生在,比如说,你在 C 中工作并且在 C++中使,用指针,例如当你在堆上声明某些东西时。所以这再次是,常量如何与指针相互作用,就是有两件事你,在处理。
你可以有一个常量或非常量指针,意味着箭头本身,手指本,身,是否被允许移动。所以在说常量指针和常量指针的情况下,你知道指针不被,允许移动。并且你也可以控制指针所指向的东西的常量性。所以在这种情况下。
指针是常量整数类型,意味着你不能更,改它所指向的整数。而在这种情况下,指针是常量整数类型,意味着你不能更改,指针所指向的东西。所以再一次,你可以从右向左读取。
第一个有一个常量指针和它所指向的非常量的东西。第二个有一个变量指针。你可以移动指针本身,但无论你指向什么,你都不能改变它,然后最后一个,你不能改变任何东西。好的。好的,我们也会在最后看一个快速的例子。
以防它没有意义,但还是,重新看一下。是的,记住你可以把它分成这两个部分。再次强调,当你处理对象而不是原始数据类型时,一切都适,用。例如,如果你在堆上声明了一个小部件之类的东西。啊,是的。
所以使用带有 const 的指针确实有点棘手。好的。当涉及到迭代器时,迭代器如何与 const 交互,实际上,迭,代器比指针更像是指针的超集。但我们现在不会深入讨论这个问题。
这里的关键棘手点是 const 的迭代器行为类似于常量迭,代器。换句话说,当我们说 const 然后是一个迭代器类型时,我,们是在说指针本身不能改变,这与常量迭代器相同。
那种情况同样表明迭代器本身是不能改变的。之所以棘手是因为,如果我们希望指向的对象不能改变,我,们必须定义一个新的类型叫做常量迭代器。所以,如果你查看在线的 C++ 规范,你可能会看到每个类。
都有迭代器和常量迭代器,这就是原因。所以再举个例子,假设我们有一个包含 1 和 2312 的向量,一个常量向量迭代器意味着迭代器本身不能被改变。你的指针不能被改变,这意味着你不能增加迭代器,但你可。
以修改迭代器所指向的内容。所以在这个例子中,它会把 1 替换为 15。到现在为止有明白吗?有问题吗?好的,好的。再次强调两者之间的区别,第一个可以改变迭代器指向的,内容,但不能改变迭代器本身。
第二个则不能改变迭代器指向的内容,但可以改变迭代器,本身。好的,是的,如果你还没理解,随时查看幻灯片。另外,常量迭代器在 C++ 中出现的频率比常量指针要高一,点。所以我建议查看幻灯片。
确保你理解了发生了什么。但记住,我认为最重要的是,你在处理指针和迭代器时,总,是要处理两种类型的常量。一种是指针或迭代器本身是否可以移动,另一种是它所指,向的内容是否可以改变。太棒了。作为总结,哦。
是的,Brian。好问题。是的,我们可以将 const 与 auto 一起使用吗?绝对可以。所以 const 和引用符号 (&) 是 auto 无法处理的两个方,面。所以是的。
你完全可以将 const 与 auto 一起使用。是的,好问题。还有其他问题吗?好的,那么作为简要总结,你们刚刚在比大多数人见过的地,方看到更多的 const。
我们看到了它在所有类型的参数、局部变量、返回值以及,函数中的应用。所以函数可以是常量的。作为对你们的挑战,我认为如果你们愿意,可以在Piazza上,看到我们发布的额外挑战,理解这个函数在做什么。
虽然看起来很吓人,但我保证,如果你逐一拆解每个const,你可以准确地翻译它的意思。所以下次讲座开始时,我会发布一张幻灯片,解释这些,const的作用。但这确实是一个不错的挑战。如果你能理解这些。
我必须说你已经完成了常量卓越证书,好的,所以再看一遍这张幻灯片。然后回顾的第二部分,总体而言,从哲学角度来看,当你编,写自己的类时,你要标记所有不会被修改的内容为const。
这不仅仅是为了阅读者的风格问题。它实际上对你代码的有效运行和其他人使用你的类是重要,的。通过const引用传递,const对象和值比按值传递更好,除非,是bool、int、double等基本类型。这点。
我们将在稍后讨论的第三个要点中提到,它涉及到,const_cast来定义类中的const和非const函数。然后,最重要的是,我们热爱地球,不想去火星。太棒了,所以花点时间阅读这个。这只是总结幻灯片。
解释对象和函数const之间的区别。但之后,我今天就讲这些了。下次讲座中,我们将讨论类中的运算符重载。是的,这些幻灯片都会发布,以便你们在未来使用const时,有一个参考。然后,是的,Byron。
再说一遍。是的。等一下,我没有听清最后那部分。是的。是的,你完全可以。是的,你可能会想,当你想初始化成员变量时,构造函数该,怎么办。我们下次会更多讨论这个问题。你将不得不使用一种叫做初始化列表的东西。
因为如果你声明一个const对象,那么在构造函数中,你甚,至不能初始化私有成员变量。解决这个问题的方法是使用初始化列表,我们会在讨论构,造函数时讲到这个。太棒了。太棒了。大家做得很好。祝好运。
或者如果你已经完成了期中考试,恭喜你。我们将在外面回答问题。感谢你们今天的到来。另外,有人打算今晚来我的办公时间吗?很好。因为我可能需要调整它们。是的,我可能还会取消本周的一个办公时间,所以请留意。
Fiato的更新。好的,谢谢大家的到来。
好的。我会确保保存所有内容。好了,停止录制。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P12:[18]CS 106L Fall 2019 - Lecture 11_ Operators (Screencast) - GPT中英字幕课程资源 - BV1Fz421q7oh
我可以尝试安装我们 SSL 库的东西,但我想在此期间我会,做这个,我不知道,因为我还是想开始做作业的第二部分,因为我想这不算非常新,但其他人可能会遇到这个问题。比如说。
我是按照 CS106D 和 106L 的指示安装的,所以至,少还有其他人有相同的安装和实现。我看到这个错误了吗?好的,谢谢。好吧,我原本打算把这些扔掉,但我想我会在开始时分发这,些。这些是凤梨酥。
对于我做的每一部分,我总是会有一部分。通常是在期中考试之后,人们就开始不来上课了,然后我会,带上凤梨酥。我来自台湾,这些在台湾是超级重要的,所以如果你有兴趣,可以尝试一下凤梨酥。它们很好吃。
关于凤梨酥我还要说什么呢?凤梨。在美国,它们被标记为异国水果,但相信我,它们真的很好,吃。有人想要凤梨酥吗?好的,太好了。口味方面,有蔓越莓味的,然后其他的都是原味的。这些稍微小一点。
这个感觉稍微大一点,这个由于某种原因,真的很长。所以我会绕一圈,如果你有偏好,告诉我。偏好?这没有帮助。这是蔓越莓味的,好吧,好吧。那这个呢?随便说个 1、2、3 或者蔓越莓味的。开始吧。3,好。
3 是这个。那就拿这个吧。你已经把原来的分掉了吗?我有很多,所以不用担心。这应该可以维持到这周和下周。我会拿这个。原味的,哪种?那我就把这个扔给你。好的,其实我可能不应该扔它们,因为它们也有点碎,可能。
要小心一点。我会再回来。你要一个吗?是的,你要一个吗?凤梨酥。好的,如果你想要一个,大家都会有一个,但我想尽快完成,这个,所以是或不是。为了更快一点,我会把整个东西带过来,问你们是否想要一,个。
多少蔓越莓味的?好的,给你。我总是在期中考试之后带这些,所以大多数人不会来。你喜欢哪个?嗯,我已经拿这些很久了,所以。我认为它们是莳萝味的,然后是原味的。谢谢。蔓越莓味?对。你说对了。对。
还剩下所有的吗?没有。好的。我在想,是你听错了还是我听错了?你要一个吗?是的,你会得到一个。你也会得到一个?你要一个吗?这个?你很贴心。我原本打算把这些留到下周,还打算买万圣节糖果,但我想。
着外面太冷了。是的,没问题。你已经拿一个了吗?你已经拿一个了吗?你拿到了吗?好的,你要一个吗?什么口味?原味的。长的?不是长的那个。那不长,好吗。我不会扔掉它。还有其他人吗?谁还没有拿到一个想要一个?
好,什么口味?很好。还有其他人吗?菠萝蛋糕。什么口味?原味还是蔓越莓味。还有其他人想要一个吗?很好。好吧,我们开始吧。好,如果你刚刚进来,结束时你可以再拿一个。明白了吗?酷。哦不,人越来越多了。好的。
下周我会带更多的点心。好,我们来说说运算符。今天我们要讨论运算符。好,今天我们要讨论运算符。今天的课不会涉及大量代码,因为运算符重载有其语法。我认为甚至在106b的幻灯片上写过,所以它的语法相当简,单。
但问题更多在于类设计。你什么时候需要重载一个运算符?如果你重载一个运算符,需要遵循哪些典型原则?因为C++允许你重载运算符,让它们做奇怪的事情。你可以重载一个运算符,让它做一些非常奇怪的事情,C++。
也会编译。所以我们要讨论更多设计选择,你怎么决定如何重载一个,运算符,运算符应该返回什么等等。明白了吗?很好。在我继续之前,106b的同学们,期中考试怎么样?竖起大拇指,真的。好的,酷。
今天我们要讨论运算符重载。然后我们会通过很多例子。我把这些称为规范形式。这些本质上是你会看到的最常见的运算符重载类型。然后我们要讨论一种叫做POLA的东西。它代表一个非常有趣的原则,我们稍后会讨论这个。
首先,让我们谈谈运算符重载。作为一个快速挑战,你们有30秒。和伙伴一起,尽量列出你能想到的尽可能多的运算符。好的,我听到很多好的运算符。我不会要求你列出很多运算符。我只是想问,最奇特的运算符是什么?
奇特的意思是如果你说哦,这个是运算符,人们会感到惊讶,哦,那个实际上是运算符?好的,就像鳄鱼那些运算符,对吧?是的,好的。所以像我们用来做cout和cin的鳄鱼运算符,实际上是运算,符,所以很好。
还有其他更奇特的运算符吗?好的,最奇特的之一是逗号。逗号实际上是一个运算符。是的,逗号运算符的使用非常罕见。所以大约有60多个运算符,其中大约40个可以重载。我会解释重载的意思。
但今天我们会通过这份列表。在紫色框中的一个非常重要的运算符,即等号,这被称为赋,值运算符,这个运算符非常复杂,并且将是下节课的主题。所以我们现在会忽略紫色的那个,但我们会讲解现在标记,的那些。
这些是非常常见的重载运算符,所以这些是我想讨论的内,容。其他的,比如逗号,不是很常用。你知道吗,new 实际上是一个运算符,你可以重载 new 吗?你可以重新定义 new 的含义。现在。
我不是说你应该重新定义 new 的含义。从技术上讲,你可以重新定义双重与运算符,使其不再表示,逻辑与,它表示其他东西。再次强调,这不是一个好主意,但你从技术上讲可以这么做,是的,问题?好问题。
这是这一张幻灯片,我会谈到这个。但你提出了一个好点子,就是这些运算符,对于所有 C++ ,基本类型,它们都有特定的含义。对于 C++ 基本类型,算术运算符,你们都知道它们的作用,位运算符。
如果你学过 CS107,你会理解位运算符的工作原,理。关系运算符,你们已经用过很多,它们存在于几乎所有编程,语言中。我们一直在使用流运算符,虽然我们还没有在基本类型上,使用这些流运算符。
如果你在基本类型上使用它们,那些是位移运算符,也是 ,Windows 7 话题,我们不会覆盖。递增运算符,这些是作用于整数的运算符,你在每个你写的,循环中都见过它们。最后,这些内存运算符。
你在处理指针时见过它们。对于基本类型,这些运算符的工作方式完全如你最初想象,的那样。像这样。问题是,假设我们在处理一个向量,然后我们使用这个括号,运算符。C++ 如何知道当你在向量上使用括号运算符时。
你实际上,是想索引到向量中?因为向量不是 C++ 内置的基本类型,有人必须最初定义了,好吧,每当我使用括号运算符时,我的意思是我想索引到向,量中。类似的,对于这个,看看,对于加等于,当你做加等于时,当。
你访问一个元素时这是什么意思?这里是一个字符串。加等于是什么意思?将一个字符串加等于另一个字符串?追加。编写字符串类的人说,好吧,加等于在这里意味着追加。但你也可以想象,你可以将加等于定义为其他东西。
我可以将 ASCII 码加在一起,这没有太大意义,但有人必,须决定,好吧,字符串的加等于意味着追加。当我们编写自己的类时,你也可以决定这些运算符在你的,类中意味着什么。当我们实际使用这些运算符时。
C++ 实际上会调用这些函,数。每个运算符都有一个关联的函数,它正在做相同的事情。例如,看看这一行。你看到的这个括号运算符,在这里我们做了 V 括号零。这实际上可以被重新解释为 V 点。
然后我们调用这个成员,函数运算符括号,参数是零。简写形式是你通常写的,但 C++ 编译器将其转换为,每次,你使用运算符时,我将调用这个函数。如你所见,当你重载一个运算符时,你将编写一个具有此签。
名的函数。明白了吗?有问题吗?酷。其他需要注意的事情,cout 是一个流,所以流也有一个运,算符鳄鱼函数,你也可以链式调用它们。V 点运算符括号一,这将返回某些内容,然后这些内容可以。
调用运算符加等于来获取其他东西。这个东西返回一个字符串。在这里,这个东西返回一个字符串,然后我们可以取这个字,符串并调用运算符加等于。好的。这是另一种形式,适用于许多这些二元运算符,例如在这里。
我们在某个其他值上调用 cout。实际上,你可以用第二种形式来编写它们,如下所示。这些现在是非成员函数。这些就像是自由函数,不属于任何类。你可以说运算符,它接受两个参数,cout 是一个流,这个东。
西是一个字符串。你是在说,好的,当左侧是一个流而右侧是一个字符串时调,用这个运算符。这两种方式都有效,我们将讨论使用哪些以及为什么。到目前为止,你有任何问题吗?是的,有问题。这是一个小问题。
当你做括号操作时,怎么知道零放在括号内,而加等于则放,在右侧?这只是内置的吗?是的。这只是内置的。好问题。问题是,你怎么知道这里括号中的零,像是放入的参数,而,对于加等于,有左侧和右侧。
怎么知道右侧在这里?这只是括号运算符的内置语法的一部分。你可以把它想象成,如果他们愿意,他们本可以完全选择将,其表示为 V 括号括号零,仅仅表示索引零。这只是一个设计。我这里也犯了一个小错误。
让我们看看。我犯的错误在这里。注意我特别没有将其写为二元运算符,这也应该是 V 点运,算符括号一。有些运算符无法写成非成员函数,我也会讲解一下。实际上,为了让这段代码编译,STL 中的某个人必须编写了。
类似这样的函数。例如,某人必须编写一个自由函数运算符 alligator,它接,受一个输出流、一个常量值和一个常量字符串。某人必须编写这个函数,以便当你尝试将字符串打印到流,中时。
流知道你希望如何打印字符串。同样,在这里,编写向量字符串类的人必须说,好,当你说括,号时,你实际上是在取一个索引并索引到向量中。好的?我们到目前为止进展如何?好?好的,那么我们来看看一些示例。
我们实际上会编写一些这,些代码。所以在 Piazza 上,我发布了今天的讲座代码。今天不是一个代码密集的日子,但这些代码只是字符串向,量和分数类的代码。好的?在你自己的时间里。
我鼓励你更多地了解字符串向量类。安娜将在几周后更多地讨论字符串向量类,但我们现在做,的只是尝试向这些字符串类添加一些运算符。好的,所以我们从字符串类开始。
让我在这里编写代码。好的?这是我们的字符串类,但我基本上要编写一个使用我们字,符串类的程序。哦。好吧,所以你可以看到我在这里创建了一个向量字符串。我没有使用 STL 库。
因为我实际上是写了这个字符串向量,然后我只是做你预期你可以做的事情。所以我将添加 hello,添加 world。好吧?然后,如果我做一个 for each 循环,const string s in 。
vec,我只是将其输出,输出。好吧,你期望打印出什么?尴尬。好吧,你期望在这里打印出什么?Hello 在一行,world 在另一行。对吗?好吧,这正是你认为向量类的工作方式。
好吧,如果你做,哦不。Vim fraction。好吧,给我一秒钟。让我修复一下这里的东西。我们稍后会详细讨论这一部分。
我忘记编辑掉了。明确。Vim fraction。cpp。这是转换操作符。
好吧。好吧。好了。操作符。好了。Hello, world。好吧?我们想要做的是能够重载加等于操作符,使其基本上像添。
加一样工作。所以你知道在斯坦福库中,如果你不喜欢 add 方法,你可,以随时做 vec plus equals veto 和 vec plus equals ,16。9 ounces。
你可以在斯坦福库中做类似的事情。对吧?有趣的是,STL 库我认为没有重载加等于操作符,所以你可,能想考虑一下为什么。但我们要做的是在我们的字符串向量类中重载操作符,使,其支持这些操作。
所以是的。所以确切地说,我们将做类似这样的事情。我们还可以重载一个加等于的版本,其中不是接受字符串,而是接受一个完整的向量。好吧?如果你使用斯坦福库,这样做将会把第二个向量追加到第,一个向量的末尾。
所以你基本上将两个向量合并为一个大向量。好吧?酷。所以我们现在就来编写这些操作符。好吧?现在,所以在。我们需要编写这些函数,以基本上告诉 C++ 当我们对这个,成员向量字符串执行加等于时,这意味着什么?
所以在这里,我还没有告诉你返回值是什么,我也没有告诉,你参数类型是什么,尽管它是某种元素。我们正在获取元素,并将其推送到我们的向量中。明白吗?好吧。现在,还有很多其他东西,我们会讨论放置什么。类似地。
在这里,我们只是说,在我们的向量类中。我们说,好的,其他可能是某种向量。我们将遍历整个向量,并将所有值添加到我们当前的向量,中。好吧。这个代码有意义吗?好吧。关键部分只是忽略所有其他的花哨的。
这些括号的东西。现在忽略它。这行和这一行的核心思想明白了吗?好吧。这就是操作符重载的精髓部分。我们正在获取另一个向量,遍历它,并将其添加到我们当前,的向量中。好吧。那么现在,有趣的是,好吧。
但这个运算符需要某种返回值,而且还需要一些,比如我们给元素赋予什么类型?好的。让我们从简单的开始。您认为这个元素是什么类型?好的。它是一个字符串,但是我们如何传递这个字符串?我听到常量字符串。
还有其他想法吗?好的。非常量引用。还有其他想法吗?好的。我听到的想法是,您可以使用常量引用,可以使用不带引用,的常量,可以使用不带常量的引用,而且我认为最先提到的,是。
它只是一个没有任何常量或引用的字符串,对吗?所以有四个。哪一个是最好的?有两个可行,并且有一个是最好的。好的。是的。好的,常量引用,您为什么这么说?好的,是的。所以我完全同意引用部分。
您甚至不想复制字符串,它可能是一个巨大的字符串。不过常量部分很有趣,因为如果我不指定常量,让我们看看。
。让我们尝试编写代码。
Vim 字符串向量向量点 CPP。
好的。所以让我们在这里实现 += 运算符。好的。所以有一些返回值,我还不知道是什么,然后是运算符 += ,这是在字符串向量类中,所以别忘了写上,字符串向量: ,然后在这里我们想要。
您说我们想要做常量字符串引用。我把参数叫什么?元素。好的,让我快速复制那里的代码。您将元素压入。好的。这个常量非常重要,这不仅仅是,哦,必要时使用常量是好,的风格,而是这对于正确性实际上很重要。
有人能明白为什么吗?您看到这里的这个常量很重要。是的,嗯嗯。是的,完全正确。好的,完全正确。所以我们必须在这里将其标记为常量,因为如果调用此函,数的任何人本身有一个常量字符串,那么他们将无法将其。
常量字符串传递到这个非常量函数中。
好的。所以,例如,如果在 main 中我有这样的代码,假设我调用。
假设我想要有一个字符串 常量字符串 s 等于 eto n 。除非我也将参数标记为常量,否则此代码将无法编译,因为,我试图将一个常量字符串用作参数。您不能将一个常量字符串放入接受非常量引用的参数中。
这有意义吗?问题,是的。我从未见过,那是做什么的,以及它与不使用常量引用传递,有何不同?所以只是常量而没有引用,这意味着我们正在通过值进行,复制,但然后您仍然将复制的值标记为常量变量。
所以如果您试图在文本中修改它,那么它会继续存在吗?是的。是的,完全正确。我真的,我真的想不出有什么情况下这很重要,因为关于常,量最重要的事情是确保调用函数的人和函数本身之间的交。
互对于是否允许更改值本身是一致的、达成一致的。并且这是唯一重要的事情吗?是的。所以,如果这只是一个非 const 且不是通过引用的情况,哎呀,不是那个,如果这个是非 const 且不是通过引用的。
那么我们在这里写的代码会有效,因为发生的情况是函数,本身会复制 S,会创建一个 S 的副本,所以在这种情况下。
其实并不重要。明白了吗?
当然,有问题,嗯。让我来处理一下。我可以在讲座后讨论这个问题吗?
因为这是个好问题,但与我们正在讨论的内容没有直接关,系。是的,让我讨论一下,嗯,嗯,因为这与更多的 107 相关,所,以我会在接下来的部分讨论这个问题。
很好。那么,现在困难的问题是,返回值是什么?好的,我听到的是布尔值。所以,考虑一下当你重载这些运算符时,思考一下如果你在,普通的整数上使用这些运算符会发生什么。假设我有一个变量 i,是一个整数。
我做 i plus equals 2,当你执行 i plus equals 2 时,实际上会从那个语句中返,回一些东西。有人知道是什么吗?是的,嗯哼。这是 i 的新值,但我们并没有创建一个新值。
这是 i 本身的值。是的,所以我们加了 2 到 i 上,但返回值是 i 本身的值,好的,是的,所以它是一个整数,但不仅仅是一个整数,它是,对刚刚添加到的 i 的引用。好的,为了澄清这一点。
让我写一些代码,这样即使它看起。
来很奇怪,这也应该是有效的代码。
哎呀,不对,main。cpp。好的,所以我应该能做 vec plus equals s,然后我拿这个,返回值,再做 plus equals 16。9。哦,我们做绿茶。这段代码应该是有效的。好的。
如果 vec 是一个整数,而这些是其他整数,这段代码,会有效。基本上发生的情况是我们向 vec 添加了一些东西,然后这,个表达式将返回对原始向量的引用。然后我们再向那个向量添加另一个东西。明白了吗?
因为对于基本类型,plus equals 将返回对该值本身的引,用。如果你重载了 plus equals,你也应该返回对对象本身的,引用。明白了吗?
好的,为了澄清我的意思。哎呀。是的,这里的返回值应该是一个对字符串向量的引用。明白了吗?哦,好的,对不起,我在这里打错了。这应该是一个字符串和一个字符串向量。最初我的例子是整数。
但后来我把代码改成了字符串,所以,这些应该是字符串。好的,有问题吗?是的,嗯哼。这是,嗯,这个是一个指针。这个在 106B 中有讲解,对吧?好的。这个在 106B 中讲解了吗?还没有?真的吗?好的。
好的,所以这基本上指的是,这是指向当前对象本身的指针。
。好的,例如,在这里的代码中,如果我在类内部使用这个,这,指的是那个向量本身。
好的,然后我们返回对那个向量的引用,所以我们不需要一,个指针,我们必须取消引用那个指针。这明白了吗?好的,这一部分初看时不容易理解,这在运算符重载中是非,常重要的,你必须不断思考,好的。
返回值应该是通过引用,、通过值还是通过 const?参数应该是通过引用传递还是通过值传递?它们应该是常,量吗?好,所以现在非常重要的是要理解为什么这些必须是引用,到目前为止你有任何问题吗?好。
纯粹是理论上的。如果我移除了这些引用,如果我尝试打印出这些字符串,代。
码的结果会是什么?所以如果是通过引用,那么这应该打印出eto n,然后是那,一行,green t。但如果不是通过引用,会发生什么?是的,嗯?正是如此。正是如此。你能解释一下为什么吗?嗯哼。嗯哼,是的。
正是如此。所以如果我们不通过引用返回它,那么vec plus equals s,所做的是将s添加到向量中,但它返回的是该向量的副本。当你执行plus equals green t时。
你是在将green t添加,到那个新副本中。好,然后那个副本在语句完成后就消失了,所以当你实际尝,试打印出vec时,vec中只有s,即eto n。这有意义吗?好,有很多非常奇怪、意外的C++用法。
但你会惊讶于有些,人写这种代码的频率,这真的很让人困惑。所以当你编写自己的类时,你必须假设编写类的人可以做,这种奇怪的事情,你必须返回正确的语义。
好,所以在这里我们必须返回引用。好,概念检查。我们为什么要返回一个引用?好,我们刚才已经展示了这一点。我们为什么要返回这个?好,我们已经解释过了。好,所以问题是plus equals。
plus equals技术上接受两个操作数,对吧?它接受向量本身和你要添加的字符串,对吧?但在我们的函数中,我们只看到右侧。左侧去了哪里?当我们在这里编写这段代码时,右侧,即字符串本身,显示,在这里。
这应该是字符串。但左侧,即实际的向量,让我们简单一点,回到这里。
vec在哪里显示?是的,嗯哼。嗯哼,是的,正是如此。所以记住,由于这些是成员函数,这些函数是在某个向量上,调用的。你调用的那个向量,就是这个向量,对吧?
这一行等同于vector。operator plus equals green team。
所以左侧是隐式调用的函数。在C++中,你可以使用this来检索那个对象。这是指向那个对象的指针。好,目前有任何问题吗?嗯哼,是的。是的,正是如此。所以pushback是vector。
string中的一个成员函数。我们继续。好,我跳过那个。问题我已经问过了。我现在有一个新符号,avocados。好,我们写了。好,我们来做一个例子,其中。好,改为实现plus。
而不是做plus equals。好?所以在斯坦福图书馆中,我认为你可以取一个向量和另一,个向量,并将它们加在一起。当你将一个向量加到另一个向量时会发生什么?那个东西应该返回两个向量拼接在一起的结果。好。
但快速问题,它会改变你最初添加的两个向量吗?不会,对吧?
所以,如果我这样做,string vector vec1 eto n,string ,vec2 is green t,以及16。9盎司。如果我说 c。好吧,我不想做 c。
String vector vec3 等于 vec 加上 ,vec2。对吧?这会返回一个包含所有元素的向量,但实际上不会改变我,添加的两个向量 vec 和 vec2 本身。明白了吗?所以。
如果我们在 vector。cpp 中实现这个功能,那么这应,该是一个。好的,所以如果我们要实现加号运算符,即 string vector, operator plus,参数应该是什么?所以。
肯定是两个向量,对吧?因为我们试图说明当你说 vector 加上 vector 时是什么,意思。但向量是如何被 const 限定或引用限定的?你是通过值传递两个向量,还是通过引用传递,还是通过 。
const 引用传递?我们这里用 const 引用,因为这些向量即使在加法操作后,也不应该改变。v1 const。实际上,这是一个成员函数,所以我们实际上只是做 other,好的,所以第一个向量是向量。
即隐式的 this,另一个向量,是 other。加号运算符的返回值是什么?是一个通过引用的 string vector。不是通过引用,对吧?因为当你执行加法时,你正在创建一个包含两个向量合并。
在一起的新向量。这不是对我们给出的原始向量的引用。好的,你怎么实现这个?是的,嗯哼。所以我们这样做,然后我们对。这些是字符串,const in other。我们只是遍历 other。
然后执行 result。push_back。所以这里我们复制了这个,因为我们实际上不想改变它本,身。我们在这里做一个副本,然后将另一个向量的字符串添加,到 s 中。好的,明白了吗?
我们稍后会进一步讨论这个操作。这叫做拷贝构造函数,这就是重要的等号出现的地方。有问题吗?好点。我们也可以这样做。很棒的点。是的?是的。是的。所以这段代码在技术上是无法工作的,因为等号有问题。
这是下一节课的内容。基本上,计划是今天结束时,我会指出这实际上不起作用,原因就是等号。所以让我留到后面讲。但确实,这段代码在技术上不起作用。如果我们修正等号,它是可以工作的,但等号不起作用,我。
们将讨论为什么等号默认情况下不起作用。好的,这里还缺少一件事,那就是最初,我想将其写成一个,非成员函数,包含第一个向量和第二个向量两个参数。在这里,我们只将第一个向量作为隐式的 this。但从技术上讲。
在这个向量内部,我们可以从技术上改变它,我们如何防止在这个运算符函数内部更改 this?我给你一个提示。它肯定是某处有一个 const,但我们把 const 放在哪里?是的,我们把它放在这里。
表示这个方法是 const 的。好吗?
好的,让我们回到代码中。好的,所以是的,这基本上是可以为此编写的代码。好的,但问题是。
在这里,我们将其声明为成员函数。实际上,将其声明为非成员函数更好。好的,那么作为非成员函数,这会是什么样的呢?它会是这样的。第二种。好的,所以这不再是类的一部分了。字符串向量结果等于第一个。
然后对 com 结果加上 s。所以我们有两种可能的写法,第一种是作为成员函数,第二,种是作为非成员函数。是的,可能是第二种。是的,第二种。谢谢。好的,你认为哪种方式更好?哦,难题。哪一种更好?好的。
我实际上不指望你立刻知道答案,但我会解释为什么。
第二种作为非成员函数更好,这个理由,我在下一张幻灯片,上有吗?好的,我不会先讲理由,而是先讲一般的原则。你是将运算符实现为成员函数还是非成员函数?好的,规则在这里列出。我会讲解其中的重要规则。
即某些运算符,比如括号运算符,必须作为成员函数实现。其他运算符,比如流运算符,必须作为非成员函数实现。快速问题,那么为什么我们要这样实现,我在下一张幻灯片,上有吗?
没有,好吧。让我们看一个更详细的例子,我们将尝试为流重载大于号。
运算符。
所以我们去 fraction。cpp。好的,你可以阅读这个。这基本上就是一个可爱的分数类。让我们为流实现大于号运算符。好的,这将允许我们写出这样的代码。好的,所以分数类的作用是。
它基本上是一个可爱的分数类,比如在这里,我声明了两个分数,我和你,然后这些是我在,期中考试中解决的问题,这些是你在期中考试中解决的问,题,这是我们总共解决的问题,因为这完全是考试的工作方,式。
然后你可以写出这样的代码。好的,酷。那么,我刚才要说什么呢?我暂时跳过这个,然后等我实现流后再回到这个。好的,再做一个例子,我们想要重载大于号,这样我们可以,打印分数。好的?好的,那么,运算符。现在。
我们是要将其实现为成员函数还是非成员函数?非成员函数,为什么是非成员函数?是的,好吧,所以如果你考虑参数,如果这是一个非成员函,数,它将是一个输出流,以及分数本身。想象一下如果我们将其实现为成员函数。
我们需要做什么,如果这是一个成员函数,我们要在什么类中编写这个函数?输出流,对吧?我们实际上能为输出流编写成员函数吗?不能,因为这是 STL 库。我们不能编写,我们不能覆盖 STL 库。好的。
所以在像这样的某些情况下,你不能在左侧编写函数,你不能将其实现为成员函数。所以在这里,我们必须将其实现为非成员函数。好的,但这确实带来了一些问题。顺便问一下,大于号运算符的返回值是什么?这是一个输出流。
这是对一个输出流的引用。谁能猜到原因呢?是的,嗯哼,嗯哼。是的,是的,因为在某些代码中,我可以写 Z out,一些分数,但有时我也想像这样链式调用它们。这个链式调用之所以有效。
是因为它做的是 Z out 点运算,符括号分数一。好吧,所以它调用了这个东西。这个东西返回对一个流的引用,对 Z out 的引用,这允许,我们再次调用它。所以现在这是 Z out,我们调用运算符括号。
括号。这就是为什么我们返回输出流的引用。总是考虑这些的语义,因为这让你确定,如果有人实际调用,这个运算符,他们需要一个引用还是一个值?好吧,你会怎么实现这个?我直接告诉你答案。O S F 点分子,好吗?
这只是打印分子斜杠分母。好吧,现在这带来了一个问题。谁能看出这为什么是个问题?是的,嗯哼。嗯哼,好问题。所以 F 点 num 本身是一个整数。所以写了 C out 或者流类的人知道如何打印一个整数,是。
的。但是他们不知道如何打印一个分数,所以我们必须告诉它,如何打印一个分数。好吧,作为提示,num 和 denum,在类中它们是什么?那些是私有变量,对吧?所以你只能访问私有变量。
如果你的函数是一个成员函数,但这是一个非成员函数。所以我们技术上不允许访问 num 和 denum。好吧,是的,我听到友元。你想说什么?好吧,所以我听到两件事。我们可以获得它的副本。所以要获得副本。
你可以做类似于,你可以调用 get num ,和 get denum,这给你 num 和 denum 的副本,然后你可以,在这里修改它。是的,我们可以这么做。一些类没有那个,没有用于私有的访问器。
所以有一个更通,用的方法,你可以使用,就是友元。你有问题吗?嗯哼。好吧,友元。友元做什么?所以本质上,友元做的是,作为友元的函数是一个在类外部,但仍然可以访问你的私有成员变量的函数。
好吧?所以要做到这一点,我们在类内部声明一个友元,称为运算。
符,括号,括号,它接受 ostream、os 和一个,还有什么?分数。分数。常量分数。然后当我们这样做时,现在它声明那个运算符为友元。所以当我们尝试编译这个代码时,这个函数可以访问 f 的。
私有成员变量。好吧,从这点中获得的关键是,关键的收获。首先,你应该总是考虑参数的常量性。所以这里,os 不是常量,因为我们实际上在向输出流添加,内容,但分数是常量,因为我们没有修改 f。
总是考虑我们是否按引用返回。所以这里,它是按引用的。然后我们得到的另一个收获是友元。好吧,我有一张非常可爱的幻灯片。你为什么需要朋友?因为他们可以帮助你度过第六周。但实际上,真正的答案是。
你需要他们来访问私有成员变量,我们还剩多少时间?五分钟?好的,我们可以再讲一点。让我们讨论一些通用规则。所以我们已经看到了一些例子,对吧?我们见过加法、等于、加法和流操作符,我们也看过应该。
用哪些操作符。让我快速讲解一下成员函数和非成员函数的经验法则。所以经验法则是,首先,一些运算符必须实现为成员函数。没有其他选择。所以括号运算符、等于运算符,必须实现为成员函数。
有些运算符必须实现为非成员函数,比如我们刚才看到的,鳄鱼运算符,因为你不能写入到 ostream 类中。所以你必须实现为非成员函数。对于一元运算符,我们没有时间实现递增运算符,但你可以,实现递增运算符。
你必须将这些实现为成员函数。这是另一种情况。所以如果一个二元运算符,比如加法和小于运算符,如果它,们对两个操作数的处理是一样的,那么你应该实现为非成,员函数。所以考虑一下加法运算符。
加法运算符对第一个操作数和第二个操作数的处理是一样,的。所以这应该实现为非成员函数。但如果它没有平等地处理两个操作数,主要是因为它实际,上修改了一个操作数而没有修改另一个,比如加等于运算,符。
在这种情况下,你应该实现为成员函数。是的。嗯哼。对的。获取两个输入。是的。嗯哼。是的。尤其是那些你确实希望实现为成员函数的情况,比如在向,量运算符中,一边是向量,但另一边是字符串。你没有平等地处理它们。
你在将字符串添加到向量中。所以你应该将其实现为成员函数。明白了吗?这样做有其原因,但主要原因是如果你将其实现为成员函,数,访问其中一个需要修改的私有变量会更容易。所以加等于运算符。
我们实际上需要修改左侧的操作数,而,不是右侧的操作数。我有点跳跃。我会重新排序这些幻灯片。好的,总结一下要点。好的。POLA 代表最少惊讶原则。有人想猜猜这是什么意思吗?随便猜一下。是的。嗯哼。对的。
基本上,无论你实现了什么功能,让其他人使用时不会感到,惊讶。这是1984年的一句话。如果它具有高惊讶因子,那么你应该重新设计它。一些例子。一些关键规则。一个是设计运算符时要尽量模仿常规用法。
返回引用或将参数标记为 const 的部分,大多是为了模拟,常规用法。常规用法通常意味着如何实现基本类型。例如,加等于运算符,我们假设加等于运算符返回左侧的引,用。例如,看看这个函数。
它使用了我们多次使用的 time 结构,体。假设我重载了小于号。很明显,开始小于结束是什么意思。它表示哪个时间先到。但是考虑一下开始加等于10。加等于10究竟做了什么?
我们是在给分钟加10还是给小时加10?不确定。如果有任何歧义,就不要重载那个运算符。你应该实现一个叫做更改时间的函数,或者说更改秒数,改,变分钟或改变小时。例如,结束减减。再说一次,它改变了什么。
小时还是分钟?最后,真的很荒谬的事情。不要重载逗号运算符做这种事情。你真的不知道那在做什么。我本来打算写一个例子,如果你这样做,它会用ASCII艺术,打印分数,但我昨天没有时间了。但基本上,是的。
你真的不知道这做了什么。所以不要实现,不要重载那些运算符。对于像加法这样的对称运算符,两边是相等的,你应该使用,非成员函数。我有更深入的解释,涉及对称性。如果你将加法实现为成员函数。
你可以将一个分数加到一,个整数上,这会有效,但这就不行了,因为这将A视为我们调,用函数的对象,但这将1视为对象,这样就不行了。你总是希望将对称运算符实现为非成员函数,以避免这些,不对称的惊喜。最后。
始终提供所有相关运算符的实现。我所说的相关集是什么意思?这些都是相关集。如果你实现了等于等于和小于号,你应该实现这六个运算,符。不应该有人写这样的代码,A小于B,然后说,好吧,这有效。
然后B大于A也应该有效。不要只实现一个而不实现另一个。否则,人们会感到惊讶。同样,不要实现加法,却不实现加等于,除非有特定原因。否则,如果你实现了加法,你也应该实现加等于。酷。我们跳过那个。在最后。
我们时间不够了,但我基本上会给你预览其他运算,符。如果你想离开,你也可以离开。一些我们不会覆盖的其他有趣的运算符,可能会在后面覆,盖。首先,你会注意到这些,箭头运算符和箭头星运算符,这些。
实际上是你可以重载的运算符,这实际上是我们将在智能,指针讲座中做的,安娜将讲授。函数对象,我们跳过这个,但这基本上是一个lambda。你可以定义自己的内存分配器。如果你们中的任何人上过CS107。
你已经写了一个堆分配器,你可以把那个堆分配器放到C++中作为实际的分配器。没有必要这样做,但一些公司确实希望他们自己的自定义,分配器以满足性能问题。你实际上可以这样做。还有更多的多线程内容我们不会覆盖。
还有太空船运算符,我想覆盖,但我们没有时间。这是C++20中的一个特性。它被称为太空船运算符。它看起来是这样的。它基本上支持不同等价类之间的三种比较。如果你上过 CS103,你会知道这些是什么意思。
现在有 CS103 的同学吗?好的。这些术语有点熟悉吗?也许吧。没有?好的。嗯,Keith 可能稍微调整了一下课程内容。如果你上过一些数学课程,你会明白这些是什么意思。本质上。
这个想法是有些东西是不可比较的。你需要一种更强大的方法来排序不同的元素。这就是 spaceship 操作符所做的事情。好的。我们下一次要做的关键内容是,假设我们写了这样的代码,我们写了这样的代码。
等号实际上不起作用,因为当我们在这个向量上使用等号,时,这个向量,大小为四。这些元素指向堆上的东西。当你使用等号时,会发生什么?当我尝试将这个向量复制到另一个向量时,会发生这种情,况。默认情况下。
等号会复制每个成员。这里,因为这是一个指针,它只是将指针复制到相同的位置,这就是问题所在,因为假设我们尝试向其中一个向量添加,一个新元素。那么发生的情况是两个向量都指向相同的向量。如果你改变了一个。
你实际上也在改变另一个。好的。这被称为浅拷贝与深拷贝。好的。我讨厌 Python 的原因是 Python 对此非常模糊。是的。在 Python 中,你可以向列表中添加东西。你改变了原始对象。
列表中的东西也会被修改。好的。这就是我讨厌 Python 的原因。但在 C++ 中,你可以完全控制拷贝操作的工作方式。好的。这一切的罪魁祸首就是等号,这也是接下来两节课的主题,全是关于拷贝的内容。
我们将讨论所有的拷贝构造函数。然后在第 13 讲中,我们将讨论移动语义,这是我从 C++ ,中学到的最酷的东西之一。移动语义。好的。确保下周你们都来。我会说接下来的两周是概念上最复杂的讲座,需要大量的。
概念理解。确保你们做好准备来听讲。好的。很好。好了。如果你有任何问题,请告诉我。我会在外面。是的。没问题。下次我会带更多的。现在有办公时间吗?是的。我会在外面,你们可以问问题。很好。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P13:[19]CS 106L Fall 2019 - Lecture 12_ Special Member Functions - GPT中英字幕课程资源 - BV1Fz421q7oh
不,这是一回事。这些是剩下的所有东西。我在考虑这些应该如何分配,是按问题分还是随机分配?偏袒?一,二,三,四,五,六,七,八,九。有九个。超过九个。好,有人真的想要一个菠萝蛋糕吗?好,那绝对少于九个。
一,二,三,我数到三个,还有其他人吗?四,五,六,好,七,好。好,七个少于九个,所以每个真的想要的都可以拿到一个。你们想要直接过来拿一个吗?因为上次有点混乱,所以直接过来拿一个吧。另外。
只有举手的才下来。有什么不同?这是蔓越莓味的,其他的都是普通的。谢谢。只是不同的品牌。你认为哪个更好?两个都很好。谢谢。好,最后两个,有人真的想要一个吗?如果你真的想要一个,就来拿一个吧。没问题。
还有其他人想要一个吗?最后一个。我们应该把这些拍卖掉。最后一个,是蔓越莓味的。好,太棒了。给你。菠萝蛋糕。好,大家好。今天是第一天,是第七周的星期二,所以记住这门课只有九,周,因为这个季度非常特殊。
感恩节发生在第九周之后,这,意味着我们将在感恩节之前完全结束。好吗?是的,感恩节快到了。关于作业的几项通知。如果你在作业二的A部分,即网络测试中遇到任何问题,安,娜应该已经给你发了邮件。
如果还有进一步的问题,请告知,我们。另外,我们对A部分的截止日期并不是特别严格。我们真正想检查的是确保你的网络正常工作,所以如果你,还没有完成,但想要继续做作业二,请尽快告诉我们,好吗?
作业二的截止日期大约在一周半之后?一周半。下周四,不是这周四。好,所以,是的,开始做作业吧。我查看了一些提交的作业。实际编写的代码并不特别长,记住这次作业的关键点是使,用模板。
使用STL库提供的函数和算法。好吗?所以,如果你发现自己写了很多代码来做一个STL算法可以,完成的事情,你应该重新考虑你的方法。好吗?实际的代码非常简短。我们还会查看像算法的使用、随机数的使用等。
这应该从作业说明中自然体现出来,所以如果你对如何着,手感到困惑,务必回来找我们或发邮件给我们。好。上周日,是周日吗?我们在周日举行了作业一的评分派对,问题是并不是所有,的评分员都来了。
因为他们还在评分期中考试。所以有些已经评分,有些还没有评分。我们会尽快回复你,好吗?你们肯定会在下周初拿回它们的。好吗?哦,对了,成绩方面其实很简单。要么是成功了,要么是失败了。你想要修正它吗?好吗?
是的。不用担心。我们并不主动想让任何人失败。是的。好的。是的。所以今天我们要讲解特殊成员函数。本周和下周,我们会详细讨论这些重要的函数。这些就是特殊成员函数。为什么它们是特殊的?它们之所以特殊。
是因为如果你不在类中声明这些函数,编,译器会为你创建它们。好吗?但问题是,有时候编译器为你创建的函数可能不是你实际,需要的。所以你确实需要知道如何声明它们,以备不时之需。所以我们快速讲一下。
今天我们将讨论特殊成员函数。我们主要关注复制。然后在周四,我们会讨论除了复制之外,是否可以移动东西,我会说这个周四的讲座可能是更具概念性的挑战之一。所以我认为你们都在从期中考试中恢复。周四请全力以赴。
好吧?酷。那么对上周的快速回顾。上周我们快速讲了运算符重载,并讨论了一些可以重载的,运算符。好的?有一些非常常见的,比如加号、减号、等号。也有一些比较晦涩的,比如逗号,甚至括号也可以被重载。好的?
我们看了很多例子。我不认为我讲完了所有计划中的例子,所以有些例子在上,次的讲座幻灯片中。我正在整理上次和这次的代码,确保本周内上传。好的?是的。那么上次的关键点是,如果我们声明了自己的类,C++ 如何。
知道这些运算符对我们的类是什么意思?答案是你可以通过编写这些重载运算符函数来定义你自己,的运算符。好的?所以你可以编写一个运算符加等于函数,来重载加等于运,算符。上次的关键点是,无论何时重载这些函数。
都要遵守该运算,符的通常语义。例如,当你重载加等于时,你应该返回对原始对象的引用,即 this 自身。这样做的原因是我们希望我们的运算符与基本类型的运算,符一致。好吗?以便使用它的人不会感到意外。
其他我们提到的要点包括,如果你重载了加号,确保如果合,适也重载减号。这样当有人使用这些运算符时,他们不会感到惊讶。哦,加号有效,但减号也有效,尽管直观上它们都应该存在,另一件事是。
如果你在实现这些二元对称运算符时,确保将,它们实现为非成员函数而不是成员函数。这背后的原因是,如果这两个向量在这个例子中被平等对,待,你不希望它们一个是自身,而另一个是一个外部的。
你希望它们被实现为对称的。这样做的原因是,如果你使用加号,你可以对称地使用它们,明白了吗?当你实现这些函数时,你也应该考虑 const 和非 const ,的情况。所以上周我略过了这个例子。
但如果你重载括号运算符,最好实现一个 const 版本和一,个非 const 版本。明白了吗?对于向量字符串,括号运算符返回一个引用,这样你可以访,问向量中的不同元素或编辑向量中的元素。
你认为实现 const 版本和非 const 版本是个好主意的原,因是什么?有什么想法吗?是的,请说。对,完全正确。因为有时候我们可能需要这个函数是 const 的。
一个好的例子是如果我们尝试运行这个代码。明白了吗?所以你可以看到我声明了两个向量。一个向量是非 const 向量。一个向量是 const 向量。明白了吗?
const 向量就像 const 整数或 const 字符串一样。它们本质上是你不能改变的对象。而对于你实现的类来说,不能改变的定义是你只能调用 ,const 成员函数。明白了吗?
这个想法是 const 成员函数不会改变它们的内部状态,而,非 const 成员函数会改变。所以如果你有一个像这样的 const 对象,那么你只能调用, const 成员。明白了吗?
为了考虑我们可能有一个 const 向量和一个非 const 向,量的情况,如果我们运行这一行,这将调用这里的非 const, 版本。这可以工作,因为这会返回。对了,这些不是字符串。这些是整数。
但你可以想象我们用其他字符串替换它。然后因为我们返回的是字符串的引用,我们可以将那个字,符串,比如 v 括号 1,设置为其他字符串。明白了吗?但如果我们尝试。如果我们只有第一个而没有第二个。
那么我们的 const 成,员函数将无法调用这个。它将无法调用这个括号运算符,因为这是一个 const 对象,const 对象只能调用 const 函数。这有意义吗?
所以为了让我们的 const 对象能够像这样调用括号表示,法,我们应该声明一个 const 版本。在使用 const 版本时,返回的字符串引用是 const 的。所以有人不能尝试这样做。
因为那样你会将一个 const 字,符串设置为某个值。我这里打错了。这些不应该是零。这些应该是其他字符串。到目前为止有任何问题吗?太好了,是的。所以现在你可以看到 const 正确性在处理类时是非常重。
要的。你应该想象所有可能的使用情况,以便其他人使用你的类,好,最后我们通过了最小惊讶原则,这意味着当你实现这些,运算符时,让它们按你期望的方式工作。例如,一个关键的点是,除非你有充分的理由,否则不要重。
载逗号操作符,因为没有人真正知道逗号操作符的作用是,什么。如果你重载了加号,你可能还应该实现加等于,因为直观上,如果你能将某物加到一个对象中,你也应该能将两个对象,加在一起。好了。
让我们快速复习一下构造函数和析构函数。我知道106B和X已经非常详细地讲解了这些内容,所以我会,跳过,基本上快速过一下复习部分,然后进入关于构造函数,和析构函数的最重要的部分。
这是我们将为字符串向量实现的构造函数和析构函数。
快速回顾一下,字符串向量基本上就是一个字符串的向量。
它的私有成员包括一个元素数组、一个逻辑大小和一个分,配大小。如果你开始使用优先队列,你应该知道逻辑大小与分配大,小的区别。逻辑大小是向量中实际包含的元素数量。
分配大小是我们可以在当前数组中放置的元素数量。如果我们需要超出分配大小,那么你必须以某种方式扩展,数组。是的,优先队列会让你实现这一点。我们不会过多讨论这个,但关键部分是我们有这个元素数,组。
在构造函数内部,正如你们所知道的,当你实现构造函数时,你必须初始化所有成员。例如,在106B中,你可能会写出这样的代码。你可能会写,好的,让我们把逻辑大小设置为零。让我们将分配大小设置为某个初始大小。
这是一个常量,我认为。然后你将elims设置为其他值。现在,这通常是有效的,对于106B的目的,你应该实现这样,的代码。但这会遇到一个问题,那就是常量正确性。如果我尝试声明一个常量向量。
你会遇到什么问题?有什么想法?对,正是如此。如果你声明一个常量向量,这意味着在创建该向量后,你不,能更改任何成员,即使在你的类内部也不允许这样做。如果我们这样做,那么我们基本上是声明了变量,但随后将。
变量赋值为某个值,而我们不被允许这样做。那么我们在这里要介绍的关键概念是初始化列表,初始化,列表的意思是,当声明这些私有成员时,它们也被直接初始,化。这与声明变量如const int i。
然后尝试做i = 3之间的关,键区别。你不被允许这样做。但你可以做const int i = 3。在声明变量的同时,你也同时初始化它。对于常量变量,你必须同时进行,因为声明后不允许编辑它。
这在这里是一样的道理。如果你有一个常量向量,那么这个变量是常量,所以你在声,明后不能编辑它。将其放入初始化列表中的想法是,在这些变量被声明时,你,也同时用这些值初始化它们。我们不在这里进行赋值。
而是直接使用初始化列表。有任何问题吗?好的。这还有一个有用的地方,这不仅仅对常量正确性有用。你能再想一个例子吗?在初始化某个东西后,你不能重新赋,值那个变量?在 C++ 中还有其他类似的情况吗?
在初始化之后,你不能,重新赋值那个变量?静态的?这是对的。我对此不太确定。静态的情况稍微不同。是的,静态的情况稍微不同。静态涉及到哪些变量、哪些常量。我们可能在未来的讲座中会讨论这个,但你猜得很好。
猜得很好。好的,答案是引用。记住,我们需要清除一个引用。引用有点像是另一个变量的别名。一旦你将引用设置为某个东西,你不能重新赋值那个引用,如果你尝试重新赋值,它只会改变它引用的其他变量。
如果你有类似的东西,我本来想举一个例子,在那个例子中,我们将创建一个新的向量,称为 loggedStringVector。它的作用是每次执行某些操作时,会写入一个文件,说明它,现在正在做什么。
我本来打算进入那个例子,但实现起来花了太多时间。而不是这样告诉你,但你会如何设计这样的东西呢?作为你的向量的成员,你可能会保持某种 OStream。你会保持一个 OFStream。
然后每次你向向量写入内容时,你会写入那个 OFStream。这有点像是日志。每次你对向量执行某些操作时,它会将日志记录到某个文,件中。那么你会如何跟踪这样的东西呢?你可能会希望作为成员保持一个引用。
在这种情况下,你不能仅仅复制文件等于文件是什么。在这种情况下,你必须使用初始化列表。这个想法是,只要可能,你应该尝试使用初始化列表来复制,成员。大家对初始化列表的语法清楚吗?是的。语法有点奇怪。
关键部分是你放置一个冒号,然后基本上有一个用逗号分,隔的列表,你可以说,好吧,我想声明 logical size 为零,我想声明 allocated size 为那个。这非常有趣,因为通常当你。
我想我在分数中有这个例子。记得我们上次写了分数吗?上次,如果我们想声明一个这样的分数,你必须使用这个,因为如果你只是做 num 等于 num,它不知道你指的是哪个,成员还是参数。但这不是很好。
我们想使用初始化列表,在这种情况下,如果你使用初始化,列表,你实际上可以像这样使用两个名称。这表示初始化 num 成员变量为这个 num,然后你会看到初,始化 denum 为参数。
这是一个变量作用域不同并不重要的例子。这将编译通过。你会经常看到这种情况。你会看到 i,括号 i,j,括号 j,作为一些懒惰的程序员不,想给他们的变量命名。有任何问题吗?我有事要做。
如果你声明了一个 const fraction 并且没有初始化列表,它不会起作用吗?是的,因为如果你有一个 const fraction,那么。安娜,如果我错了请纠正我,但如果你有一个常量分数,你。
不能像这样给它赋值。是的,为了更清楚,你是说常量分数还是常量分子?比如一个常量分数,比如说3除以4,我们初始化它像常量分,数3,常量4。所以我相信你仍然可以在没有初始化列表的情况下使用它。
因为你会创建分数,然后将那个分数设置为常量。艾弗里谈到的关于常量正确性的问题是,如果类的任何私,有成员变量被声明为常量,那么没有初始化列表你不能初,始化它们。哦。
所以这只是当私有成员被声明为常量时的唯一情况。是的。所以你仍然可以将它们声明为常量变量。这是个好问题。是的,好问题。另一个可能比较棘手的初始化列表问题,或者至少以前让,我困惑的地方是。
如果你使用初始化列表,你必须按类中私,有成员变量的声明顺序进行初始化。所以这只是一个小的实现提示,有时可能会让人感到困惑。
太棒了。然后对于析构函数,我不会详细讲解,但析构函数,你要确。
保你实现了一个析构函数。
很酷。让我们谈谈拷贝操作。通常由编译器生成的特殊成员函数通常意味着有很多例外,但通常你会得到这四个函数。你会得到一个默认构造函数、一个拷贝构造函数、一个拷,贝赋值运算符,然后是一个析构函数。
所以对于一些类,比如分数,我们没有实现析构函数,但编,译器仍然会为你创建一个析构函数。我不认为它做了什么,但它仍然会在你的分数超出范围时,调用析构函数。它调用析构函数。
我们要讨论的最重要部分是这些拷贝构造函数和拷贝赋值,关键的想法是它们在你使用等号时有时非常重要。你可能经常会想,好吧,如果我创建一个向量,然后说另一,个向量等于那个向量会发生什么?
你刚刚赋值的向量会发生什么?然后这个想法是,当你尝试做这样的事情时,它会调用拷贝,构造函数或拷贝赋值运算符。这两者之间的关键区别是拷贝构造函数在你使用现有对象,创建新对象时调用。
这个想法是我们只是创建一个现有对象的副本。而拷贝赋值运算符则是你有两个对象,一个你正在赋值给,另一个,然后发生的事情是你赋值的那个对象,你必须首先,清除那里的一切,并用另一个向量的内容替换它。
一个是构造函数。一个是我们直接创建一个新对象。另一个是对象已经存在。我们只是清除它并把其他东西的副本放进去。这样理解吗?关键的想法是这两者之间的区别。作为一个快速测试,让我们看看这些。
假设我们实现了拷贝构造函数和拷贝赋值。每行调用了哪些特殊成员函数?第一个被调用的是什么?我给你一个提示。一些拷贝构造函数在 Vec0 中被调用。创建了哪个拷贝构造函数?
在 Vec0 中创建了哪个特殊成员函数?是的,正是这样。拷贝构造函数,因为我们正在创建一个新的构造函数,并且,由于我们按值传递,所以我们传递的任何向量都会在这里,创建一个名为 V0 的拷贝。
什么是 Vec1?哪个被调用了?默认构造函数?是的,因为我们没有给它任何参数。第二个呢?第二个有点棘手,因为这不是真正的特殊成员函数。如果你不声明任何构造函数,它将无法编译。这只是一个普通构造函数。
这只是你创建的一个普通构造函数。这实际上是初始化列表构造函数,我们稍后会讲到这个。第三个呢?这实际上是一个陷阱问题。第三个实际上不是构造函数。这个第三个实际上声明了一个函数。非常奇怪。
这被称为 C++ 最令人困惑的解析。除了不要这样做之外,没有太多别的要点。如果你问我为什么这是一个函数而不是构造函数,我也不,知道。安娜,你有没有好的理由解释为什么这是一个函数?理论上。
你可以想象你可以从你任何一个作业中拿到一个,函数,然后将返回值、流向量、函数名称改成 Vec3,然后,删除任何参数。这在理论上是一个有效的函数原型,如果你曾经用过那种。
在顶部声明然后在代码的后面实现的函数原型。这只是函数原型存在的一个不幸结果。如果你对函数原型不熟悉,不用担心或者问第二节课。哎呀,我跳到下一个了。这里调用了哪个?拷贝构造函数。
因为其中一个参数是另一个向量,Vec4 成,为 Vec2 的拷贝。第五个呢?棘手,棘手。是的,第五个是默认构造函数。C++11 引入花括号的原因是为了让你不再需要这样做。你可以直接使用这个。
这将调用默认构造函数。好吧,我要继续这些。这个调用了拷贝构造函数。我们创建了 Vec3 加 Vec4,那是一个向量,然后我们用它,来创建 Vec6 的拷贝。好吧,快速问题。下一行,我不想展示答案。
下一行调用了哪个?Vec7。是的,确实是拷贝,肯定是拷贝什么。构造函数还是赋值?好吧,谁认为是构造函数?谁认为是赋值?好吧,要考虑答案,你应该考虑,我是创建一个新的对象作,为 Vec4 的拷贝。
还是将一个现有对象替换为 Vec4 的内,容?这是什么?是的,答案是我们创建了一个全新的向量。这个 Vec7 直到我们现在声明它之前不存在,所以这实际,上调用了拷贝构造函数。
我们不是将 Vec7 赋值给某个新的东西,因为 Vec7 是我,们直接创建的东西。这就是为什么有些人不喜欢这种赋值方式。大多数人,嗯,有些C++程序员更喜欢这种构造函数,因为这。
样可以清楚地看到这是一个拷贝构造函数,而这,感觉有点,混淆,不是赋值也不是构造函数。这是一个构造函数,好吗?Vec7等于Vec2。好吧,Vec7已经存在了,所以我们要替换Vec7。这是一个拷贝赋值。
好的,快速提问,在返回值中,记住当你返回某物时,你实际,上创建了一个新的向量,因为,我的意思是,Vec7当前是一,个在这个作用域中的向量,所以你必须创建一个新的向量,才能将其传递到另一个函数中。
所以当你按值返回某物时,拷贝构造函数会再被调用一次,好的,到目前为止有任何问题吗?我们只是想确保我们知道哪个构造函数是哪个,以便在实,现时,可以考虑正在实现的内容。酷。好的,所以上次的问题。
我们简要讨论了上次的问题,当我,们实现加法运算符时,这段代码没有起作用,原因是因为当,你做等号操作时,我们从未实现拷贝构造函数。所以当你没有实现拷贝构造函数时,C++做的事情是编译器。
为你创建一个拷贝构造函数。这个拷贝构造函数是相当直观的。它基本上只是复制每个成员。所以,例如,当你创建拷贝时,它复制for,因为这是一个原,始数据类型,它是一个int,但它还复制了指针。现在。
当你复制一个指针时,你并没有实际创建数组的完整,深拷贝。你只是创建了另一个指针,它指向相同的数组。因此,默认的拷贝构造函数并没有完全做我们想要的。好的,到目前为止有明白吗?此时有任何问题吗?
因为这真的很重要。所以当你向拷贝中添加元素时,它实际上是在添加到两个,共享的数组中。然后当你调用返回值时,它会创建另一个拷贝。所以现在你有三个指针指向同一个。然后当vec和copy超出作用域时。
会发生什么?所以vec的析构函数试图释放这里的内存。所以那个数组被释放了一次。然后当copy的析构函数超出作用域时,会发生什么?所以copy调用它自己的析构函数,它试图释放那个数组两,次。
你不想释放东西两次。最糟糕的是,后来,这东西还会被再次释放。所以你会释放这个数组三次,这很糟糕。好的,那么我们怎么防止这种情况?好吧,问题在于这个拷贝操作。所以我们必须自己实现这个拷贝构造函数。好的。
细节。所以这是一长串基本上拷贝操作必须做的事情。但这简单来说就是拷贝构造函数需要复制所有成员。然后特别是当它遇到某个成员时,你不能仅仅做简单的赋,值,你必须找出一种方法来深拷贝它。
所以让我们实现我们的字符串拷贝构造函数。哎呀,我已经在这里有我的答案了。好的,这只是一个构造函数。我们将其设置为构造函数。但它接受的参数是对另一个向量的const引用。这有意义,对吧?
我们正在创建一个已有向量的拷贝。然后我们遍历所有可以轻松复制的成员。我们可以直接使用等号来复制这些内容。我们在初始化列表中使用它们。因此,这里的逻辑大小应该与其他逻辑大小相同。
然后分配的大小也应该与其他分配的大小相同。有趣的是,这些是私有成员。所以当你实现这个拷贝构造函数时,你实际上可以访问其,他类的私有成员并从中复制内容。这只是一个有趣的点。但是有一件事我们不能简单地复制。
那就是指针本身。那么我们如何对那个数组进行深拷贝呢?当你做 PQ 时,你也需要这样做。所以希望你已经考虑过了。人们开始做 PQ 吗?好的,很好。好吧,我想是的。嗯哼。好的,很棒。
所以基本上我们需要创建一个新的字符串向量,即新的 ,std string。大小是多少?就是分配的大小。好的,然后 elims 已经是指针了,所以我们也将指针设置,为指向一个新的数组。
我们需要进行逐元素的复制。我们怎么做呢?我希望你不是在想 for 循环。是的,对吧?有一个叫做 copy 的算法。好的,那么我们传递给 copy 的是什么?我们想复制的范围是什么?是 other。
begin,很好,到 other。end。然后我们复制到哪里呢?对,所以基本上复制到 elims,对吧?或者因为这个类我们也支持 begin,所以我们可以直接调,用 begin。
Begin 会给你一个指向数组起始位置的迭代器。就这样,非常简单明了。有问题吗,嗯?好的,好问题。所以我在尝试编译这个代码,显然如果你想在一个非常现,代的 C++ 编译器中编译这个,你必须指定这个词。
这基本,上意味着构造函数不能抛出异常。你可以想象如果构造函数抛出异常会发生奇怪的事情。这有点像,好的,你实际上做了一个拷贝还是没做拷贝?我不太确定。我还需要再实验一下,因为我不知道你必须写这个,直到大。
约 20 分钟前我尝试运行它的时候。所以是的,我还需要再检查一下,但显然你必须写这个 ,noexcept 才能编译,明白了吗?如果你使用旧版编译器,我认为你不需要 noexcept,它可,以为我检查。
明白了吗?因为我很惊讶发现你还需要写这个。是的,这在 C++ 11 中。11,好的,是的。但这很有意义,因为如果你不知道是否会抛出异常,因为你,不太确定是否会抛出异常。我到底分配了内存还是没分配内存?
不确定。所以你必须指定 noexcept 来说,好的,我保证我不会抛出,异常。所以你可以假设当这个函数完成时,你已经分配了内存,记,得释放它。
好的,就这样。让我们谈谈拷贝赋值。拷贝赋值有点不同,因为拷贝赋值不是构造函数。
拷贝赋值只是你在实现等号。所以记住我们如何实现,我们如何重载等号?好,你会使用这种语法,字符串运算符等于,对吧?它有一些返回值,并且还有一些参数。好,思考一下,和你的伙伴讨论一下返回值和参数是什么。
当你使用等号时,你期望参数是什么,等号的返回值是什么,和你的伙伴讨论,30秒。我在后面听不到很多讨论声。我们害羞还是累了?好,那么你对参数有什么看法?参数应该是什么?好。
考虑一下我们如何调用拷贝赋值运算符,对吧?拷贝赋值发生在你有一个字符串向量 V1 和 V2,然后你想,将 V1 设置为等于 V2,对吧?如果我们将其改写成运算符形式,那就是 V1 点运算符等,于。
那么你会传入什么参数,对吧?记住,在这些成员函数中,你将右侧作为参数之一传入,好,吗?所以这就是,你也会传入一个向量。常量还是非常量,对吧?等号不应该改变右侧,对吧?那将是一个非常奇怪的等号运算符。
所以确保你将其传入为常量。好,等号的返回值是什么?同样的,对,你说得对。你说同样的是什么意思?对,嗯,类型相同。字符串向量,好,我们传入的是副本还是引用?引用还是副本?对,是引用,好吗?
通常你不想以这种方式编写代码,但从技术上讲,你可以这,样做。你可以做 V1 等于,然后 V2 等于 V3。所以这会将 V2 赋值给 V3,然后将 V1 赋值给 V2,因为这,会计算为对 V2 的引用。
好吗?你会在很糟糕的 C 代码中看到这种情况。如果你查看很糟糕的 C 代码,你会看到,像是赋值链在一,起。所以为了避免人们在尝试编写这样的代码时感到惊讶,确,保你返回对这个的引用。
所以我们已经知道最后你返回对这个的引用。好,我们如何复制所有内容?有什么想法?从高层来看,你需要做哪些步骤来将内容复制到现有的一,个中?对,继续。你只需要复制这些东西,比如对于数组,基类中的数组可能。
大小不同,所以你需要确保它与数组的大小相同。好,所以复制逻辑大小,复制分配的大小,然后确保你复制,比如,创建数组的副本。好。确保。对。我们要做的一件事是,你基本上只是完全擦除原来的内容,好吗?好。
所以将 elims 改为大小为分配大小的新数组会更简单,对吧?所以最后,我们将 elims 更改为一个新的分配大小的数组,然后我们只是将 elims 从其他复制到这个。但在我们这样做之前。
我们需要做一些事情,对吧?因为我们正在将 elims 更改为指向一个新数组。旧数组发生了什么?对,嗯哼。对,记得释放它。当你进行拷贝赋值时,你基本上需要销毁所有。你需要释放旧的所有资源。
然后才能设置新的资源。所以在这里,确保你释放旧的资源。我们来快速实现一下这个。如何释放旧的资源?你可以直接说,delete elims,然后我们创建一个新的数组,elims 等于 new。是的。
记住数组是不可以调整大小的,所以你基本上需要删,除旧的数组并创建一个新的。新的 std string,分配大小。记得还要复制所有内容,所以 allocated size 等于 ,other。
allocatedsize,logical size 等于 ,other。logical。好的,快速问一下。为什么我们不能在这里使用初始化列表?因为这不是一个构造函数,对吧?记住。
你只能在初始化完全新的内容时使用初始化列表。这是一个运算符,只是覆盖东西,所以不能使用初始化列表,这意味着代码看起来比这个丑陋一点,但,随便啦。好,我们完成了吗?还有一步。记得复制所有内容。
所以复制 other。begin,end 和 begin,我漏掉了什么吗?我觉得我准备好了。现在,有一个奇怪的边界情况需要注意,那就是当你做这样,的事情时,技术上这是被允许的。
如果你做 v1 等于 v1 呢?这是自我赋值。你正在将 v1 赋值给 v1。这段代码现在会发生什么?好的,所以它删除了自己,然后尝试从删除的自己复制所有,内容到新的单元,这样是不允许的。在这种情况下。
你基本上只需要检查,我是否在尝试赋值给,自己?如果是的话,就什么都不做。好的,所以你通常会看到的代码是,确保这不是其他的。确保这个,所以这是一个指针,那么我们如何将这个与其他,进行比较呢?好的,等于。
和百分号其他,是的,完全正确。记住其他就是向量本身。这是一个指针,所以你要检查指针是否相等。好的,再问一个问题。为什么不能这样做?我在想为什么不能这样做。有什么想法吗?是的,嗯。嗯哼。哦,不,继续。
继续,是的,嗯。对,嗯哼。说得对,所以是的,嗯哼。是的,这两个都是好的点。所以是的,这要求一个字符串向量实现等号,我们没有这个,嗯,不一定。我们不一定有这个。所以一般来说,正确的检查方式是使用这个。
是的,嗯哼。问题?说得很好。所以我们实际上是在检查指针是否指向相同的东西。好,如果是这样的话,实际上大多数情况下,通常像这样实,现,如果不相等,那么做所有这些,但无论如何,仍然返回向,量本身。问题。
是的,嗯哼。继续。other。begin 与 begin。还有 other。哦,这个?你是说这个,对吧?是的,这就是在说如果左边的指针与右边的地址相同。不一定,因为向量,所以因为向量本身。
你不能确切知道它,是如何存储元素的。它在别的地方存储元素,对吧?但实际上,对象本身有一个地址。
我没有图示。来看看,这里有一个图示。这个和&其他的,这个和&其他的指的是这个地址,这个东西,的地址和这个东西的地址。但你并不一定比较数组本身的位置,因为数组本身在这里,是的。
所以我们只是检查这个地址是否等于这个地址。
如果它们相等,那么它们是相同的向量。还有其他问题吗?是的,继续。可以是const,嗯,好点子。所以是的,这些可以是常量成员变量,原因是,因为,如果你,要复制某些东西,那么要在其中使某些东西变为常量是非。
常困难的,因为你试图将东西复制到一个已经有常量成员,的常量中。所以是的,那些不能是常量,特别是在这种情况下,那些也,不能是常量,因为你在实现一个向量。是的,好问题。如果你想做一些你有常量成员变量的事情。
那么你可以在,赋值运算符内不赋值,但记住在拷贝构造函数中,你仍然可,以赋值。所以在这种情况下,如果你想说逻辑大小是常量,那么我们,可以将其设置为常量,在拷贝构造函数中赋值,但在拷贝赋,值操作符中,抱歉。
拷贝赋值时,我们只需要不再设置它,因,为我们知道它已经存在并且具有正确的值。
好吧,最后几分钟,简单提一下你可以做的事情,但你可能。
会看到这个,有时。所以在一些情况下,你不希望能够复制你的对象,例如,对,于ifstream,人们说ifstream。对于ifstream,你不能复制ifstream,因为它有一个文件正,在读取或写入。
你不能复制这个。为了防止这种情况,你可以声明拷贝构造函数为删除,这会,告诉编译器,嘿,甚至不要为我声明一个拷贝构造函数。好的,如果有人试图使用我的拷贝构造函数,就会抛出编译,器错误。好吧。
快速提一下三法则。那么什么时候需要你自己特定的成员函数?当默认的一个不起作用时。默认的一个什么时候不起作用?当你的类使用了它拥有的某些资源时,默认的一个不起作,用。例如,如果你有一个指针。
你假设你的类本身拥有指针所指,向的内容。其他类不应该拥有那个东西。如果你尝试使用默认的拷贝来做一个简单的拷贝,它将拷,贝指针,但实际上不会拷贝资源。这在不同种类的资源中都是如此。例如,文件流。
你不能复制它们的原因是,当你打开一个文,件时,你拥有对那个文件的所有权,在大多数情况下。所以是的,还有其他一些常见的原因你可能会遇到拥有权,问题。所以当这种情况发生时,三法则,即一个非常常见的习语是。
如果你定义或删除了复制构造函数、复制赋值操作符或,析构函数中的任何一个,你就应该定义或删除这三者。如果其中之一存在,那么这三者都必须存在。有没有人能想到为什么会这样呢?作为提示。
考虑一下何时需要声明析构函数。是的,请继续。嗯哼。嗯哼。对,是的,完全正确。当你特别需要处理指针的所有权问题时,你会实现一个析,构函数。所以如果你必须在析构函数中处理一个问题,那么在复制,构造函数中。
当你想要复制指针时,这个问题也会出现,在,复制赋值操作符中,当你想要替换另一个指针并进行复制,时,这个问题也会出现。所以如果你声明了一个,你应该声明所有三个。现在,问题是你不会再听到三法则了。
现在称为五法则,因为接下来在星期四,我们将讨论另外两,个特殊成员函数。还有一个叫做零法则的概念,这意味着如果默认的函数有,效,那么就不需要声明它们。例如,在我们编写的分数类中,没有特别的资源需要处理。
所以不需要声明这三种特殊的成员函数。这样会更简洁,也不会出错,如果它有效的话。好,复制的小问题,我将快速讲解一下,但假设你有这段代,码,它本质上创建了一个单词向量。它将一堆单词读入向量中。它返回向量。
然后将这个局部变量赋值给新的向量。明白了吗?这段代码有意义吗?这可能是你们在需要读取字典时为 n-gram 编写的代码。现在问题是,在整个过程中创建了多少个向量?所以我将快速过一遍。在 main 中。
你在这里创建了一个。在 main 中,我们创建了一个单词向量,所以那里有一个。然后当你调用 find all words 时,你创建了一个局部变,量 words,它本身有一个数组。我们用某些东西填充它。
当你调用 return 时,会进行一次复制。会进行一次复制,以便你能够返回值,然后当你在这里进行,赋值时,当你在这里进行赋值时,我们调用了复制赋值操作,符,再次复制了那个数组。总共创建了多少次向量?
三个不同的向量,三个不同的数组。好,如果你考虑一下,这有点没意义,对吧?为什么要复制这么多次?它确实是同一个数组。现在在实践中,有一个概念叫做复制省略和返回值优化。它本质上是,如果编译器足够智能。
知道当你返回这个向量,时,你最终会返回这个向量,所以当你在局部作用域中创建,这个向量时,它实际上在 main 的栈中创建它。所以你可以看到,它实际上跳过了这个局部向量,以便只调,用复制赋值操作符。
注意到使用复制省略时,我们跳过了一个向量。好,但这里的问题是我们能做得更好吗?一个可能更好的想法是,好吧,我们可以这样做,我们在这,里创建一个向量,在这里创建那个向量,但不需要复制所有,内容。
我们可以这样做吗?我们把旧的数组去掉,用第一个向量的单词。它可以窃取它在这里的这个数组吗?然后它可以驱逐另一个数组,从而基本上是窃取了它的资,源。好的,是的。所以下周,或者说下周四。
我们会讨论一个叫做移动语义的,新概念,本质上是问你是否可以在不复制的情况下移动东,西?因为移动会更高效。我们会讨论, 有些人说移动语义就像是在谈论窃取和驱逐,别人的房子,这种说法有点道理。
我们是在从另一个变量中窃取资源。有问题吗?移动和复制是非常深奥的事情,你确实需要在做之前认真,考虑,这就是我们下周四要做的事。好的,如果你有问题,请在外面问。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P14:[20]CS 106L Fall 2019 - Lecture 13_ Move Semantics (Screencast) - GPT中英字幕课程资源 - BV1Fz421q7oh
不,不是那个。CS106L。好吧,我的文件组织几乎为零,所以一片混乱。移动,move。pdf,好了。
然后,代码,就这样。这些我们不需要。我应该从 mplaceback 开始吗?好吧,我从 mplaceback 开始。好,所以,大家好。欢迎回来。今天我们要讨论移动语义,这可能是我讲过的所有讲座中。
最酷的一门。这是一个非常现代的话题。这个特性是九年前才出现的。八九年前。所以,这是一个非常现代,非常新的话题。我喜欢它的原因是它 encapsulates C++ 最大的哲学之一,不要牺牲效率。
尽可能快和高效。好吧?所以,移动语义会涵盖这些内容。但首先,我想快速插入一个旁注。所以,今天我们要讨论一个叫做 L 值和 R 值的东西。顺便说一下,上次我说这是一个很难理解的概念。大家准备好了吗?
大家都带上了最佳状态了吗?今天大家都还好吗?好。我昨晚睡了 10 个小时,所以我准备好了。当然。当然。好了,我们都准备好了吗?我们都没缺觉吗?好。是的,那么我们将讨论移动构造函数和赋值,它们是拷贝构。
造函数和赋值的亲戚。然后我们会讨论一个叫做交换函数的东西。所以,我们将实现一个交换函数,这个交换函数是一个非常,好的交换函数。不是你通常写的那种普通的交换函数,而是一个非常非常,好的交换函数。
不会产生额外的拷贝。然后,如果有时间,我们会讨论完美转发。虽然在制作幻灯片时,我做了两张幻灯片,然后我觉得,我,们永远不会到这一步,所以我停下来了。但如果有时间,我会讲的。这非常非常非常酷。
但我没有幻灯片。所以,我就直接打代码。酷。在我们谈论这个之前,我想讲一下 mplaceback。
所以,如果你去 CPP reference,你会看到在 vector 中,vector 中有两个非常相似的方法。一个叫做 pushback,另一个叫做 mplaceback。
有没有人用过 mplaceback?这是一个很奇怪的函数。所以,pushback 就是你想的那样。所以,pushback,我直接打开 pushback。Pushback,基本上,它接受一些值。
这是一个模板类,Anna 下周会讲到。但本质上,你只需输入你的值,它就会被添加到 vector 中,这就是你在整个 1。6b 中一直在做的事情。但是 mplaceback 有一个非常奇怪的参数,arg。
两个 &,然,后后面跟着三个点。我们不会讨论这三个点。这叫做变参模板。我们本来打算讲的,但三周前时间不够所以停了。这叫做变参模板。基本上,这个函数接受一个可变数量的参数。它不仅仅接受两个参数或三个参数。
你可以输入任意数量的参数。现在,这里特别的是两个 & 符号,今天我会讲解这个。但我只是想简单地谈谈 mplaceback 是做什么的。mplaceback 解决了一个奇怪的问题:如果你想将某个东西。
添加到向量中,通常你可以直接将那个值放进去,这样就完,成了。例如,如果你有一个患者的向量,你如何将一个患者添加到,向量中?你会创建一个患者对象,然后调用 vector。push_back 来。
添加这个患者。但问题是,想想你要创建多少个患者对象。你必须首先创建一个患者对象。然后你必须把它放入向量中,这样就会创建另一个副本。这有点低效。为什么在将患者放入向量之前必须先创建这个虚拟的患者。
mplaceback 的作用是,你不需要创建患者对象本身,而是,可以传入创建患者所需的参数,然后向量会直接在向量内,原地创建患者对象。好吧,这有点长。让我再重复一遍。通常。
当你有一个患者并且想将它添加到向量时,你必须首,先创建一个患者对象,然后在那个患者上调用 push_back,这样就会在向量中创建另一个患者对象。现在你有两个患者副本。你有原始患者。
然后你还有一个在向量中的副本。这些是分开的。明白吗?但问题是,如果你只是将患者添加到向量中,并且你不真的,需要那个最初的患者,那么为什么不让向量直接在向量内,创建患者,而不是从最初的向量开始?
你如何初始化一个患者?你给它一个优先级和名字。与其创建一个虚拟的患者对象并将其拷贝到向量中,不如,直接将名字和患者传递给向量,然后向量会在其中直接创,建患者对象。关键点是这里没有额外的拷贝。
这就是 emplace_back 的作用,它接受可变数量的参数,因,为你传入了调用构造函数所需的所有参数。对于患者的向量,你需要传入名字和优先级。很酷,对吧?这基于一个整体的哲学。如果不需要额外的拷贝。
就不要创建额外的拷贝,这就是 ,emplace_back 的作用。如果你看这里的例子,我们有一个总统的向量。我正在找 emplace_back 的位置。在这里,你可以看到。为了创建。
我们有一个总统的向量,总统有名字、国家和年,份。一种将总统推入向量的方法是你可以调用构造函数,这会,创建一个对象,然后将该对象传递给向量,这样向量会再创,建一个副本来放入向量中。相反。
你可以直接传入创建总统所需的参数,将其传递给向,量,向量会在向量内直接创建总统对象。我只是想简要提一下。如果我们在最后谈到完美转发时,我们可以解释如何实现,这一点,但我们不会涉及到这一点。
所以我只是提一下。emplace_back,就是这样工作的。有问题吗?就可读性而言,当你在扩展项目时,如果我是一个新开发者,我读到这些内容,我很难立刻理解你正在创建的这些对象。
以及这些是总统对象所需的所有参数。这在行业中被认为是最佳实践吗?当然,是的。这在行业中确实被认为是一个很好的实践,因为你可以看,看 elections 是什么,然后你可以弄清楚它的类型,这会。
告诉你需要传入哪些参数。这么做的原因并不是为了可读性。而是为了提高效率。想象一下,不是一个总统对象(只是一个小的结构体),而是,一个巨大的向量。替代的方法是创建这个巨大的向量,复制它,并将其放入向。
量中。而我们要做的是直接通过传递给该向量的参数来创建这个,巨大的向量。我不想在这上面花太多时间,但这只是一个 FYI。这就是为什么这种方法存在。直到最近我才理解这种方法,但这是一个非常酷的函数。
通常对于一个 int 的向量,因为 int 不需要构造函数,你,只需传递 int 就可以了。对于 int 的向量,pushback 和 emplaceback 完全一样。但对于较大的对象。
emplaceback 的作用是获取参数并将,其转发给构造函数,构造函数会在向量中创建对象。这个思想被称为完美转发。它是为你将参数转发给构造函数。我们不会详细讨论完美转发,因为我们没有时间。
但 FYI ,它存在,而且这就是使这个函数更高效的原因。有问题吗?当你说 emplaceback 对于 int 时,你的意思是 pushback, 和 emplaceback 之间没有区别?是的。
因为对于 int 来说,效率上没有太大区别。只是你复制 int,然后将它放入。你必须复制,对吧?是的,没错。你说的这个 forward byte 复制或其他东西并不重要,对,吗?确实不太重要。
因为对于 int 来说,int 其实。int 的构造函数,你传入一个 int,然后得到同样的 int。int 的构造函数实际上没有做太多事情。它只是接收一个 int 并返回一个 int。
对于 int 来说,这并不重要,但对于构造函数需要不同参,数的较大对象来说,这就是 emplaceback 发挥作用的地方。
。功能上,emplaceback 和 pushback 是不同的东西吗?
完全一样。这只是解决了一个非常烦人的问题,有时你可能。让我们考虑 n-gram。对于 n-gram,你必须创建一个初始向量,然后将其放入 ,map 中。相反,想象一下,如果你可以跳过初始复制。
直接在 map 中,创建它。
我不想在这上面花太多时间,因为这并不是特别重要,但我,只是想说这种方法存在。转到语义部分。上周,我们简要讨论了特殊成员函数。默认构造函数、复制构造函数、复制赋值函数和析构函数,上次。
我们为 string 向量实现了构造函数和析构函数。你们应该都知道如何做这一点,从 1。06b 中学到的。我们简要讨论了拷贝构造函数和拷贝赋值运算符的作用,并且我们实现了它们。关键思想是。
如果你有一个指针,它自己拥有对数组的唯一,所有权,你不能仅仅复制指针,因为那样两个指针都会指向,同一个数组。相反,你必须对那个数组进行深拷贝,这就是这里所做的。我们正在创建一个完全独立的数组副本。
并将所有元素复,制到那个新数组中,以便如果你修改其中一个数组,另一个,向量的数组不会被改变。对于拷贝赋值运算符,一切都是一样的,只有两个例外。一个是你必须记住释放旧对象的资源,因为你要覆盖它,所。
以你必须确保释放那个对象中的资源。基本上,这就是析构函数的作用。然后你还要确保不要自我赋值。如果我将 A 赋值给 A,那应该是有效的。A 应该什么都不做。我们简要讨论了三法则。
然后我们快速谈谈复制的问题。我上次简要讲过这个,但这是一个挑战。看看这段代码,并与伙伴讨论一下。告诉我每个特殊成员函数被调用了多少次。一共有四个特殊成员函数。在这段小代码中。
字符串向量的这些特殊成员函数被调用,了多少次?然后我们实际运行代码,看看你是否正确。我给你一分钟时间。和伙伴讨论一下。考虑每个函数被调用的次数。哦,有或者没有。假设没有拷贝消除。如果你不知道那是什么。
完全没关系。只是假装它不存在。哦,对不起。这不是查找所有单词。是查找名称。是的,对不起。完全忘记了这个。是查找名称。读取名称。好,好。
你知道吗?这太难了。我就这样做吧。
Main。cpp。好了,这就是函数。用这个代替。
一,我的错。二,读取。是什么?好了,做这个。
然后将名称两个等于读取名称。就这样。特殊成员函数被调用了多少次?好了,有人有答案吗?
不确定?好的。这很棘手,但让我们检查一下答案。只是为了给你展示现在发生了什么,我进入了 string ,vector。cpp。
注意在所有这些构造函数中,我写了这行代码。来自默认构造函数的问候。来自填充构造函数的问候。来自拷贝构造函数的问候。以及来自拷贝赋值运算符的问候。实际上,我得先注释掉这个。这是我们今天要写的。
但我得先注释掉它。
好的,然后在 string vector。cpp。h 中,我们有特殊成员。
函数,忽略这两个。
好的,所以我要运行这个命令。哎呀,我得先运行 make。好的,然后我要运行这个命令。不要担心它做了什么,但它基本上只是计时一切,同时还会,计算每个函数被调用的次数。好的,我还缺少一件事。好的,稍等一下。
这本来不应该发生的。
好,然后我告诉你忽略拷贝消除,所以我要写,好吧,不要做。
拷贝消除。我要把这个改为 C++11。好,酷。再试一次,我们就叫做 move。好,所以有一个填充构造函数。有一个拷贝构造函数。有一个析构函数,拷贝构造函数,默认构造函数,填充构造,函数,拷贝构造函数。
析构函数,拷贝赋值,析构函数,析构,函数,析构函数。我们完成了吗?好,我们来了。这就是所有被调用的函数,我们来看看。我不想再运行命令了。好,但是看看每个函数被调用了多少次。析构函数被调用了六次。哦。
是的,所以填充构造函数不是一个特殊成员函数。它只是这个构造函数,vector。cpp。
就是这个构造函数。它不是默认的构造函数,但它只接受元素数量和默认值。它是这里正在使用的构造函数。这里正在使用的构造函数。好,所以这就算了,好吗?所以我们调用 read names,这里调用了。
这里调用了什么构造函数?填充构造函数,好。这里发生了什么?我们返回 names。是的,我们必须调用拷贝构造函数,因为当你返回一个值时,你在创建一个该局部变量的拷贝。names 只存在于局部变量中。
但你必须创建一个拷贝以便,在主函数中出现。所以我们调用了拷贝构造函数。这里发生了什么?我们将 name one 设置为这个向量的值。好,这是一个拷贝构造函数,记住,因为这本身是在构造一,个新对象。
所以即使你看到一个等号,这实际上是一个拷贝,构造函数。好,有问题吗?是的。是的。是的,好点子。好,所以在拷贝构造函数发生后,names 被销毁,这就是为。
什么你最初看到的时候,看到填充构造函数、拷贝构造函,数,然后这个析构函数说,好吧,names 的局部变量已经消。
失了。好,我们继续。read names,所以它读取了某些数量的名字。这是一个向量。这是一个拷贝构造函数,因为我们正在构造 V1。这里调用了什么?默认构造函数,好。好。
然后我们再次调用 read names。同样的事情在这里发生。我们调用了填充构造函数。我们调用了拷贝构造函数。然后我们对 names 调用了一个析构函数。这现在是一个向量。这里发生了什么?拷贝赋值。
因为 name two 存在,所以我们用这个新的向量,替换 name two 中的内容。好,最后,在最后发生了什么?所以 name one 消失了,name two 消失了。好,总共六个。哦,对了。
此外,这在技术上也是一个向量,所以一旦这一行,结束,这个对象就消失了,所以你也必须对这两个临时对象,调用析构函数。好,这有意义吗?所以总结一下,所有六个析构函数在哪里?第一个析构函数在哪里调用?是的。
在 return names 后,这个已经消失了。第二个析构函数在哪里调用?是的,所以当这个临时对象消失时,我们对那个临时对象调,用了一个析构函数,所以有两个。第三个析构函数在哪里?是的。
在返回 names 时,我们必须调用 names 的析构函数,所以有三个。第四个在哪里?好吧,renames,这是一个临时向量,所以这行代码执行完后,那个向量也会消失,所以有四个,第五个和第六个在哪里?
是的,name one 本身就是一个向量,name two 是一个向量,你必须析构全部六个。明白了吗?
酷,对吧?好吧,我们做了多少次拷贝?这很多拷贝。一、二、三、四。我们做了四次拷贝。好吧,这有点让人失望。我们能做得更好吗?上次,我简要介绍了一个叫做拷贝省略的东西,基本上就是,当你返回一个值时。
有时编译器能够跳过那个拷贝构造函,数。它知道你会返回一个向量,因此当你在内部声明那个向量,时,当你最初声明这个 names 向量时,它知道你会返回它。
因此当它创建向量时,会直接在这里创建它。明白了吗?好吧,如果我启用拷贝省略,我们就忽略这个,然后把它改,为 C++17。顺便说一下,在 C++17 中,拷贝省略是有保证的,所以你的。
编译器会基本上为你加速代码。你甚至不需要要求它这样做。我们这样做,然后,实际上,让我们这样做。好吧,然后这做了相同的事情,只是它计时,并且基本上汇,总了一切,所以它计算每个出现的次数。好吧,完成了。
所以这花了九秒钟,它调用了拷贝赋值一次。它调用了默认构造函数一次。它调用了三个析构函数,并且调用了两个填充构造函数。你在这次运行中注意到了什么?好吧,半数的析构函数消失了,这意味着一半的构造函数也。
消失了。哪一半构造函数消失了?是的,拷贝构造函数消失了,具体来说,是返回 names 的拷。
贝构造函数消失了。返回 names 本身必须做一个拷贝以便将本地的拷贝到这,里,但由于拷贝省略,编译器足够聪明,知道当你创建这个,向量时,它只是直接在这里创建这个向量,所以你不需要做,拷贝。好吧。
这样会有显著的加速效果。
我认为之前的时间大约是 16 秒。现在是 9 秒。好吧,今天,我们要做得更好,那么这里的哪一部分可以做,得更好?我们可以做得更好的部分是拷贝赋值。好的,因为注意这里,你会发现应该仍然有一个拷贝构造函。
数。好吧,所以我想它也,哦,它跳过了一个拷贝构造函数。
好吧,我想它跳过了这个拷贝构造函数,因为它知道你会赋,值。好的,关键部分是我们如何改进这一行?好的,因为这本身就是一个向量。读 names,这本身就是一个向量。这是相同的向量,但你只是移动。
实际上是将这个向量拷贝,到 name two。所以你现在有两个向量的拷贝。这有点让人失望,因为这个向量将在下一秒钟就会消失。这是一个临时向量,它将被返回,但它会在这行代码执行完,后就消失。对。
析构函数将被调用。那么为什么我们还要拷贝所有东西呢,如果这个向量实际,上会消失呢?我们能否将这里的内容直接放入新的名称二,而不进行任,何复制?好吧,这就是今天的目标。大家到目前为止都跟上了吗?
这非常重要,明白了吗?你可以看到,为什么这是一个临时对象,它会在下一秒就消,失。所以,我们为何不直接将这里的所有内容移动到名称二呢?反正它都会消失。
好吧,但是问题是,简单问一下,假设我稍微改一下代码,让。
它变成这样。所以我本质上是把它捕获到名称三中,然后让名称二等于,名称三。所以这完全是一样的代码。不是把这行放在这里,而是放在一个单独的变量中。好吧,这里。我们还能尝试将名称二中的内容转移到名称三中吗?
我们可以转移它,并将它放入名称三中吗?为什么不呢?嗯,是的。为什么不呢?嗯,是的,没错。因为这里的名称三,我们可能还想用它做其他事情,对吧?这不是一个临时值。这有点不同,因为在这行代码完成后。
名称三仍然存在。它仍然是一个有效的向量。所以我们不能只是偷走它的向量,然后将它放入名称二中,对吧?这个例子,与另一个例子不同,在那个例子中,这个向量反,正会消失。所以我们可以安全地从中偷取。
它会在下一秒就消失。好吧,所以现在的问题是,如何区分这两者?一个是临时向量,所以我们可以安全地从这个向量中偷取,另一种情况是,另一个向量不是临时向量。它是一个仍然可以使用的实际向量。我们不能从中偷取。
我们如何区分这两者?
好吧,我们将讨论一个叫做 L 值和 R 值的东西。好吧,这是一种对超级复杂主题的粗略简化。如果你和任何 C++ 专家交谈,他们会非常生气,如果你试,图向他们解释这些内容。好吧,这是一种非常粗略的简化。
但问题是,这足够让你编写良好的代码。你不必了解 GL 值、PR 值、X 值等语义。我们不会讨论所有这些。我们将讨论两个非常简单的类别。所以,简单定义一下。L 值是一个有名称和身份的表达式。
因为它有名称和身份,你可以使用取地址运算符找到这个,值的地址。而 R 值是一个没有名称或身份的表达式。这些是临时值。好的,因为它们是临时值,你不能使用取地址运算符找到它,们的地址。明白了吗?所以。
直观上,这不完全准确,如果你想要精确的解释,但直,观上,你可以理解为 L 值是可以出现在等号左右两边的,而 R 值只能出现在等号的右边。明白为什么 R 值不能出现在左边吗?好吧,如果你有一个临时值。
你不能将那个临时值设置为某,个值。好的,例如,如果你写了整数三,那么这个三没有被分配给,任何变量,所以它就像一个临时值。你不能把三分配给某个东西。这没有任何意义。好的,快速举几个例子。好的。
这些会越来越复杂。明白了吗?所以,我想让你们看一下等号两边的内容。它们是左值还是右值?好的,第一个。二,是右值还是左值?右值,对吧?这是一个临时值。它没有地址。二没有地址。所以,这是一个右值。
那val呢?左值,对吧?因为val有一个名字val,它也有一个地址,对吧?你可以用取地址符ampersand val来获取它的地址。大家到现在为止都明白了吗?好的,下一个。这个奇怪的地址。
是右值还是左值?右值,对吧?所以,这也是一个临时对象。在这一行之后,我不能重用这里的内容。而且,即使这是一个地址,你也找不到那个地址。那个表达式。它和这边的是一样的。你不能找到二的地址。
因为二不是实际存在于内存中的东,西。所以,你也找不到这个地址。什么是指针?左值还是右值?左值,对吧?因为,你能找到指针的地址吗?是的,你可以,对吧?只需用取地址符ampersand pointer即可。
你将得到指针存在的地址。好的,一、二、三。这些是右值。什么是V1?左值,对吧?因为它有一个名字。它有一个标识。现在,变得非常棘手。什么是V1?V1是左值还是右值?V1是左值。让我为录音重复一遍。
V1是左值,因为它有一个名字,有一个标识。你可以找到V1所在位置的地址。V2也是一样。什么是V1加V2?左值还是右值?右值,对吧?V1加V2返回一个向量,但这个向量是一个临时值。它只是浮动的。
它只是一个由函数返回的值。它实际上并不存在于内存地址中。V1加V2是一个右值。这是一个临时值,在下一行就会消失。想一想。除非V4是它的副本,否则你无法访问这一行中的V1加V2。什么是V4?左值。
快速提示一下,基本上这里左边的所有内容都是左值。V1加等于V4?左值。那V4呢?左值。好的。Size underscore TE。Size等于V点size。什么是size?左值,对吧?它在左边。
它有一个名字,有一个标识。什么是V点size?V点size是一个右值,因为V点size是一个临时值。它是一个由函数返回的整数,但这是一个临时值。它不存在于任何地方。你找不到它的地址。好吧,非常非常棘手。
大小。什么是大小?L 值。大小有一个名字,有一个身份。什么是大小,但你将它强制转换为 int 呢?这是一个 R 值,对吧?因为这是大小,但你将它强制转换为另一种类型。
但那个用新类型的表达式没有自己的内存地址。所以这是一个临时值。这是一个 R 值。明白了吗?好。Val 是一个 R 值,当然。四倍 I 是什么?R 值,对吧?I 本身是一个 L 值,但如果你将它乘以四。
那么没有内存,地址存储值四倍 I,所以这是一个 R 值。这是一个 R 值。基本上,如果你不能给它一个名字,比如你不能给它一个变,量名,那么它就是一个 R 值。V1 括号 1 是什么?L 值还是 R 值?
所以你知道这是一个 L 值。为什么这是一个 L 值?这是一个常见的误解。如果这是一个函数返回的值,比如这里的 V。size,V。size ,是一个 R 值,但 V1 括号 1 是一个 R 值。
但它们都是由,函数返回的东西。为什么?是的,V1 括号 1 是一个 L 值。为什么 V1 括号 1,作为一个函数返回的东西,为什么是一,个 L 值,而 V。size 为什么是一个 R 值?是的。
所以括号操作符,如果你记得的话,它返回的是一个,引用的元素。不是字符。它返回的是通过引用的元素。所以因为你是通过引用返回的,每次你使用 V1 括号 1,你,都在引用一个实际的变量。
你在引用向量中的一个实际字符串。所以这是一个 L 值。但 V。size,它返回的是 size_t,而不是 size_t &,所以它,返回的是一个临时值 size_t。最后几个。Val 的地址。
R 值还是 L 值?R 值,对吧?这是一个地址,但地址本身没有地址。你不能说 &val 等于某个东西。这只是设置一个地址。你不能这样做。你不能说 &val 等于某个东西。指针是一个 L 值。
如果你取消引用某个东西,好吧,哦,这,很棘手。什么是 &pointer?L 值还是 R 值?所以我听到的是一个 L 值。有投票支持 R 值吗?快速问题。你可以做 *pointer = 某个东西吗?
这有效吗?是的,对吧?你只是取消引用并将当前值设置为另一个值。所以指针的地址,那是一个 L 值,是的。我们应该只用左值和右值吗?好吧,L 值。好吧,这为什么重要?哦,这里是答案,以防你想检查。好吧。
我们将跳过为什么重要,直到下一张幻灯片。好吧,现在有一种叫做 L 值引用和 R 值引用的东西。所以之前,我们谈到过一种叫做 L 值引用的东西。L 值引用是对 L 值的引用。之前,当我们做这个事情时。
pointer2 基本上是 pointer ,的另一个名字。你可以改变 pointer2,它对 pointer 做相同的事情。这有意义吗?我们已经看过很多次了。当你通过引用传递时,这就是它的工作方式。
你在创建对原,始变量的引用。你对 pointer2 所做的任何更改都在改变 pointer。到目前为止明白了吗?我们暂停一下。此时有任何问题吗?因为如果我们现在不解决任何概念问题,会变得越来越混,乱。
所以现在有任何问题吗?你想让我重新解释什么吗?有人在打瞌睡吗?好的。现在的问题是,你不能将左值引用绑定到右值。左值引用仅绑定到左值。但你可以将右值绑定到右值引用。这叫做右值引用。
它的作用基本上是延长这个的生命周期。这个通常应该在下一行就消失了,但通过将右值引用附加,到这个,你基本上延长了它的生命周期。对 V4 进行的任何更改都会改变你对象的临时值。明白了吗?是的。
所以双与号表示这是右值引用,而不是左值引用。我在这里使用 auto,但你可以使用类型本身。所以你可以这样做,这将是一个 int 星号与号。这将是一个向量 int 与号与号。好的?酷。这不行。
因为这是一个右值。你不能将左值引用绑定到右值。类似地,你不能将右值引用绑定到左值。不过,你可以做一个奇怪的例外,你可以将一个常量左值引,用绑定到右值。好的?如果你这样做,你会经常看到这个错误。
你可能之前见过这种情况,如果你尝试将一个临时右值传,递到这里,如果你将其作为左值传递给左值引用,这不会编,译。这第四行不会编译。但如果你将其作为常量引用传递,那就会编译。有没有人见过这个错误?好的。
这在凯斯的下一季度更常见。是的,因为凯斯真的很喜欢临时值。所以凯斯在下一季度你会看到很多这些错误,但在本季度,不会。好的,但回到之前,嗯,你可以将常量左值引用绑定到临时,值。这为什么有意义?
为什么非 const 左值不起作用,但常量左值可以绑定到右,值?有什么猜测吗?是的,说说看。如果是非 const,那么当你修改左值时,你会为所有地方修,改它。你不是说,你应该为所有地方修改它。而常量。
无所谓它技术上是一样的,因为你反正不会修改它,对,完全正确。所以在这里,如果我们可以修改 V 是不合适的,因为如果,我们在这里修改 V,那么我们就修改了这个临时表达式。好的?除非你明确说,是的。
我想要临时表达式,并且我确实想要,修改它,那么它就允许你。好的?但如果你只是说,哦,我只是想要一个常规左值,那么这将,不起作用。但如果它是常量,那么你可以修改它,因为即使它是临时值,但你反正不会改变它。
所以这没问题。好的?好吧,我们为什么要在乎?让我暂停一秒钟。到目前为止有任何问题吗?这非常重要。是的,有问题。我想,我的问题是,所有这些分析是否都是为了提高效率?因为到目前为止在我的 C++ 程序中。
我从未考虑过这些,但也许我通过忽视一些特定情况而没有使我的代码尽可能,高效,例如何时某个东西是 L 值还是 R 值。但是否还有其他的考虑?是的,正是如此。这正是我们接下来要讨论的内容,即移动操作。
这个概念是,R 值是临时值,所以它们很快就会消失。你可以窃取它的资源。听起来真的很黑暗。听起来真的很黑暗。是的,临时值不再存在了,所以你可以随意获取它的资源。但如果它是 L 值。
那意味着那个地址有一个名称,有一个,身份,这意味着其他人可能会在之后使用它,这意味着你不,能从 L 值中窃取资源。你可以从 R 值中窃取资源,但不能从 L 值中窃取资源。因为 R 值会很快消失。
所以你可以从中窃取资源,没问题,是的,这与 C++ 的一个核心目标有关,即允许程序员拥有,尽可能大的控制权,并允许他们编写高效的程序。你可以想象效率可能很重要。
人们通常将 C++ 用于像嵌入式设备这样的东西,在这些情,况下,效率确实是一个重要因素。但正如你所说,你可能已经注意到,在你编程 C++ 的整个,过程中,你从未真正担心过移动语义或类似的东西。是的。
这是一个非常高级的 C++ 话题。大多数 C++,斯坦福的大多数人都不知道什么是移动语义,这个概念是在八年前刚刚发展的。好的,很好。另一种说法是,L 值是不可丢弃的。它有一个名称,有一个身份。
它不是一个可丢弃的值。你不能从它那里窃取资源。而 R 值是可丢弃的。R 值是一个临时对象。它很快就会消失。所以你可以获取它的资源。你可以从中窃取东西。另一种说法是,你可以从 L 值中复制。
但不能从 L 值中移,动。而对于 R 值,你可以选择复制或移动,最好是移动。获取它的资源比查看它拥有的资源然后自行复制要便宜。这有意义吗?好的。今天我们将介绍两个新的特殊成员函数,移动构造函数和。
移动赋值函数。注意关键区别。复制构造函数从现有的 L 值创建一个新对象,而移动构造,函数从现有的 R 值创建一个新对象。因为这是 R 值,移动构造函数可以将 R 值中的东西移动,到自身。
而不必担心 R 值。明白了吗?如果明白了,请点头示意。到目前为止有任何问题吗?如果没有,我们将开始编程。好的。所以函数签名看起来是这样的。我只是添加了这些,然后添加了异常处理来确保,我们不会,抛出异常。
但我做了一些研究。显然你不需要它们。我不是很确定。我不认为你需要它们。但它们本来不应该抛出异常,所以我只是把它放在那儿。很好。所以注意移动构造函数、拷贝构造函数和赋值运算符,它。
们接受的是 const L 值引用,但移动的接受的是普通的 R, 值。很好。所以移动构造函数的关键步骤。它是一样的,只是我们不是复制所有元素,而是将所有内容。
移动过去。好。例如,想一下 vector 内部发生了什么。实际上,让我打开 string vector。h。让我取消注释这些。
。这两个函数是我们要写的。
vim string vector。cpp。
好。不管怎样,我们要写这个函数。实际上,我已经写好了,所以我们来看一下。好。特别之处在于,让我们看看,移动构造函数。好,所以注意到,哦,抱歉,我跳过了一个步骤。对不起,大家。这个不应该在这里。好。
所以这是你如何实现这样的东西。你会说,好,elements 是一个指针。我们只需复制那个指针。逻辑大小,复制逻辑大小。已分配,复制已分配。基本上,复制所有内容。为什么在这种情况下是移动?
因为我们不是像之前那样创建新的数组,而是把其他拥有,的指针复制到自己身上,然后我们说,好,其他,这个数组现,在不属于你了。把它设置为空指针。好,明白了吗?关键区别在于。
我们不是创建一个新指针并复制所有元素,而是从其他那里偷走指针,这个数组现在是我们的了。好。我有一个动画,但它是为了赋值运算符的,所以让我先讲讲,赋值运算符的。这些在哪里?烦人的事情是。
我不得不在稍后重新输入这些,因为接下来,就是这些,所以我必须删除这些。好,所以移动赋值运算符也做同样的事情,不过你必须进行,额外检查,这意味着你必须首先删除,必须删除被擦除的 。
string vector 的数组。我们覆盖了 elums。我们复制一切,复制指针,然后 RHS 就没有指针了。
让我做个模拟。所以,在之前我们有这个,这个,哦,这不是未命名返回值,这是其他。很抱歉,我复制了幻灯片,忘记更改它,不过这里,好,所以,我们做的是,首先删除旧向量拥有的东西,对吧,这一行做。
的就是这个。
我们复制逻辑大小和分配大小到 words,然后我们复制指,针,这个指针现在指向这里,然后我们说,好,其他,这个数,组不属于你了,把那个指针设置为没有指针,现在你的这个,向量对这个向量拥有唯一的所有权。
我们没有复制任何东西,我们只是把所有东西移动过去。所以,这是那个代码。是的,这是那个代码。好,这还不是完美的,还有一个问题。实际上,有人能看到这个问题是什么吗?这真的把所有东西都移动了吗?
还有一些副本被创建了,不是向量本身,不是数组本身,而,是其他东西被复制了。你能看到那是什么吗?是的,嗯哼。对不起,我听到有人说话了。他说了什么?是的,嗯哼。这些都是拷贝,对吗?好吧,你可能会觉得,好的。
这是一个 int,所以拷贝一个 ,int 没什么大不了的。拷贝一个指针,好吧,这也没什么大不了的,但这些都是拷,贝。如果我们有一个更极端的例子呢?所以,让我们看看另一个类。
另一个名为 Access 的类。它有一个学生的向量,然后如果我们想进行移动操作,我们,会这样做。Students 等于 rhs。students。这是一个拷贝。这个等号是一个拷贝。为什么这是一个拷贝?
我觉得我有点跳过了,但问题是,这是一个拷贝,因为 ,rhs。students 是左值还是右值?这是右值吗?这是左值。它有一个名字,有一个身份,有一个内存地址。非常混乱的是,尽管我们说好的,这是一个右值。
我们说 ,rhs 绑定到一个右值,但 rhs 本身是左值。这有意义吗?Rhs 绑定到一个右值,所以无论 access 传递进去的是什,么,那是一个右值,但在这个函数中,rhs 有一个名字,有一,个身份。
它是左值。当你做这样的事情时,这会生成一个拷贝,这是不对的。在这里的例子中,rhs 在我们的向量中,这个是左值,所以,所有这些都是拷贝。在这里更严重,因为我们实际上是在拷贝所有学生,那么我,们该怎么办?
如果这只是一个右值,那么这将是一个移动操作。有任何想法吗?你怎么把它变成右值?你可以将其强制转换为右值,这就是 move 函数的作用。Rhs 是一个左值,但我们希望它成为一个右值,这样当你做,等号时。
学生将被拷贝过来。在这里我们希望这样做,我们这样做 std move。move 做的是它接受你传递的任何参数并返回右值。它无条件地将变量强制转换为右值。这现在是一个移动操作,因为无论这个类看什么。
它都会说,哦,是的,无论你正在做的拷贝赋值,那拷贝,即你传递的 ,rhs 是一个右值。这有意义吗?最令人困惑的部分是为什么这是一个左值?这不是右值引用吗?右值引用绑定到一个右值,但右值引用本身是左值。
因为它,有一个名字,它有一个身份。move 做的就是它基本上覆盖了这一点,它说把它当作临时,值对待。这意味着如果你对任何东西调用 move,你应该不要再使用,你调用了 move 的那个东西。
你把它当作临时值对待。我们假设在这段代码之后,我们不再使用 rhs 了。那就是那段代码。
让我快速修改一下代码。唯一的区别是你必须在所有这些地方添加 std move,以确,保你移动了它们所有。从技术上讲,这些是 int 值,所以这没有太大区别,但每当,你编写需要拷贝其他成员的代码时。
你必须确保对它们所,有调用 move。在这里你必须对它们所有调用 move。有任何问题吗?
有趣。让我们看看它快了多少。请不要有编译错误。是的。它会非常快。什么?好的。够好了,够好了。看,相比于九秒,现在是五秒。大约是原来的一半。有人看出为什么大约是原来的一半吗?
最初我们使用填充构造函数创建了向量。我们做了它的一个副本。实际上我们创建了两个不同的向量。现在我们只有一个,因为我们创建了向量然后将它移动了,时间大约减半了。明白了吗?
现在,在接下来的两分钟里,你们的任务是编写一个名为 ,swap 的函数。哦,我将在最后做后续的工作。交换。我希望你们编写一个名为 swap 的函数,它接受两个参数,并交换它们。例如,这里我们有 v1。
我们有 v2。这些是巨大的向量。交换后,v2 应该包含大量的 ends,而 v1 应该包含大量的, etos。我不认为我在讲座中曾经大声说过这两个词,但确实是这,样。你会怎么写这个?与伙伴讨论一下。
考虑一下你会怎么写。代码有三行长。如果你卡住了,就假装你不知道任何关于右值的知识。如果你只是要天真地交换它们,你会怎么做?从那里开始,然后看看你是否能做得更好。好的。
好吧。我看到你们中的一些人真的很累,特别是后面的那些人。我们快速写出代码。如果我们没有任何关于移动语义的知识,这会是什么样的?有没有志愿者?好的。在我讲到这一点之前,你们说得完全对,但在我讲到这一点。
之前,让我先处理参数。参数应该是什么样的?两个参数还是向量?好的,但我们希望它用于多种类型,对吧?所以我们使用模板。类型名 t。好的,我们有两个,称之为 a 和 b。它们应该怎么传递?按引用?好的。
只是普通的引用吗?最好是常量引用,对吧?好的。哦,不,实际上,不可以是常量引用,因为我们在交换它们。好吧。好。好吧,你们提到的,所以设置一个临时变量。好的,那么你们怎么声明它?temp 等于 a。好的。
但 temp 的类型是什么?对,是 t。你也可以使用 auto。好的,然后呢?还有其他人吗?好的,所以首先不要做移动,因为我们可以稍后添加移动,但 a 等于 b,然后 b 等于 temp。
我们只是交换它们。这就是你现在如何交换,对吧?如果你做的是向量,你会这样写,对吧?你们都已经开始了向量。好的,那么我们怎么做得更好?因为所有三行都在复制。这是一个副本,这是一个副本,这是一个副本。
a、b、temp,它们都是左值或右值。左值,对吧?它们有名字,有身份。所以这些都是左值,这意味着所有三个操作都是复制。我们怎么才能把它们变成移动操作呢?对,嗯?移动 temp?是的,t。
temp 等于 std:move(a)。所以 a 是一个左值,但这将它转换为右值。好的,所以 std:move。好的,所以这会将所有东西转换为一个右值,然后当你进行,复制时,因为它看到这是一个右值。
它会对这一项执行移动,构造函数,对这两项执行移动赋值。明白了吗?如果我看这里的代码,是的。这是一个快速的模拟,明白了吗?所以我们首先声明一个新变量 temp,或者我们叫它 c,然。
后我将 a 转换为移动的,这意味着当我偷取它时,我将偷,取这个。然后当我声明这个时,我将从那个偷取,然后当我偷取时,我将得到那个。好的,这真的很快,我们没时间了,但你可以再次查看这个,模拟。
它正在执行交换,明白了吗?没有额外的数组副本被创建。酷,这里是通用的交换函数。哦,五大规则,之前是三大规则,但现在你有了两个额外的,函数。相同的规则适用,如果你声明其中任何一个,你应该声明全,部。好的。
然后,是的,我们不可能完成这个,所以我们结束了。好的,谢谢大家。