斯坦福-CS106A-B-L-X-编程入门笔记-十四-
斯坦福 CS106A/B/L/X 编程入门笔记(十四)
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P3:[4]CS 106L Winter 2020 - Lecture 3_ Types and Advanced Streams - GPT中英字幕课程资源 - BV1Fz421q7oh
好的,我们开始吧。那么,今天的计划是,我们将完成对输入输出流的讨论,然,后我们会先做简单的 I-O,再讨论现代 C++ 类型。文件流你们在 106B 里已经学过了,所以我们不再重复讲,解。明白了吗?
但文件流的工作原理与输入输出流类似。我们会简单讨论一下它们的区别。好的,那么简单回顾一下,上周我们使用字符串流来解析字,符串,并将其拆分成不同的标记。例如,我们学习了输出字符串流。
然后我们可以利用字符串流的功能在字符串类型和我们想,要的其他类型之间进行转换。所以,在这个例子中,我们尝试将 STR 中的不同标记转换,为整数类型。大家,这个对上周的内容还记得吗?是吗?好的。
现在有关于这个的任何问题吗?这基本上是我们上周写的代码。唯一的区别是,上次我们有两个 if 语句。这次我把两个 if 语句合并成了一个,没有提供详细的错,误信息。明白了吗?很棒。是的。
我们还没有到达这个点。好的,那么让我们来看看这段代码。
所以,这段代码写在这里。明白了吗?写在这里。坏的欢迎程序。所以,我希望你们尝试运行坏的欢迎程序。在你的主函数中尝试运行它。然后,好的,尝试运行这个函数,随便玩一玩,看看你得到什。
么。所以,我猜大家都在玩这个函数。你可以对它有一个很好的了解。它是做什么的?它会询问你的名字,询问你的年龄。然后打印出“你好,名字”和给定的年龄。你想再试一次吗?你输入一些东西,它会告诉你你说了什么。
好的,这里没有重新提示。这只是一个简单的程序。现在,代码写在这里。
C 输入,读取到一个字符串变量中。C 输入,读取到一个整数变量中。然后我们打印出名字和年龄。在这个例子中,我添加了一个额外的空格。然后 C 输入响应读取响应。明白了吗?那么,作为一个快速示例。
假设我们来看看 C 输入和 C 输,出是如何工作的。C 输入和 C 输出,有点像输入字符串流,但有一个主要区,别,那就是当你尝试读取更多的标记时,如果缓冲区中没有,标记,换句话说。
如果你尝试读取并且显示 EOF,那么 C 输,入会使整个程序停滞,等待用户输入一些东西。明白了吗?所以,当你请求 C 输入,箭头,箭头,名字时,C 输入缓冲区,中没有内容。
这就是为什么当你尝试在程序中运行它时,程,序会冻结并等待你输入一些东西。好的,大家都遇到过这种情况吗?程序冻结了,然后你可以在键盘上输入一些东西,对吧?
作为一个明确的演示。
是的,请注意我们没有打印其他内容。它说“你叫什么名字”,然后在这里暂停,因为它在等待用户,输入一些东西。所以,如果我输入 Avery,那么它会进入下一个阶段,我们。
输入了 Avery。并且记住,当你在Avery中输入并按下回车键时,它也会将,那个新行字符包含到缓冲区中。一旦它能够将某些内容读入缓冲区,它就会尝试读取一个,标记。所以,它在这里读取一个标记。
它并不会读取到空白字符。它只会读取到遇到空白字符为止。明白了吗?在未来的CN操作中,它会跳过空白字符,然后尝试继续读取,所以,在这里,在这个调用中,它首先跳过初始的空白字符,并且注意到,哦。
剩下的什么也没有了。EOF(文件结束)已开启。
然后它能够提示用户输入另一个标记。所以,在这里,这就是为什么它现在提示你输入年龄。
明白到目前为止了吗?好的,提示和缓冲区之间的关系,你明白了吗?到目前为止有任何问题吗?好吧,是的,需要注意的是提取器,它会跳过任何前导的空,白字符,并且读取到空白字符,但不会消耗下一个空白字符,好的。
所以在这里,CN读取到空白字符,但不会消耗空白字,符。只有当你再次调用CN时,它才会跳过空白字符,然后尝试读,取标记,好吗?这将在稍后我们讨论常见的CN错误时出现。所以,假设我输入了20。
那么它能够读取20并且意识到它是,一个整数,因此成功将年龄转换为20。然后,当你能够打印CNAME和年龄时,它能够打印我的名字,和20。当它到达CN并且响应时,缓冲区为空,因此它能够提示用户。
输入另一个响应。所以,你输入“是”或其他内容,好吗?关键点,让我们看看。让我们通过关键点来回顾一下。程序何时提示用户输入?Andy?是的?等等,这是什么?我们将在下一个幻灯片上看到,是的。但确实。
这是一个非常糟糕的欢迎程序。好的,所以我们来快速回顾一下。程序何时提示用户输入?是的,那CN具体发生了什么?所以,当缓冲区中没有剩余内容时,CN才会被提示,对吗?这时它能够询问用户更多内容。好吧。
为什么说操作不会立即打印?我们可以跳过这个。位置指针跳过了吗?位置指针是在标记之前还是标记之后跳过空白字符?之前还是之后?之后。位置跳过空白字符,它跳过标记之前的空白,对吗?所以,在这个例子中。
如果你输入任何内容,一旦你输入,它,只有在这次操作中才会跳过空白字符,明白了吗?但当你输入其他内容时,当CN尝试提取年龄时,它读取到空,白字符,但不会消耗空白字符。只有在下一次操作时,才会消耗空白字符。
明白了吗?这是一个小细节,将来会成为错误的来源。很酷。跳过这些。现在,让我们尝试理解为什么这是一个糟糕的输入程序。
事实上,我希望你尝试一下。尝试输入一些内容来使程序失败。好的?所以,一个明显的就是,好的,不输入年龄,输入一些不是数,字的随机内容。这会失败。有没有其他方法可以让程序失败?空字符串。好吧,所以空字符串。
如果你输入空字符串,缓冲区仍然是,空的,所以没有任何反应。好的,很好。还有其他方法可以让程序失败吗?这里有一种方法。我要输入我的全名。好吧?现在,看看会发生什么。好吧,坐在你旁边的人,看看这个输出。
尝试解释一下,暂时,不要解释为什么。只需解释一下你看到的症状是什么。开始吧。是的,我想,大约20个。好的,所以,一步一步来。从最上面,你注意到的第一个症状是什么?好的,所以,注意到我输入了我的全名。
然后它就直接跳到,了结尾。程序从未询问我的年龄。好的,它确实询问了我的年龄,但从未提示我输入我的年龄,它也确实问了,是否要再试一次,但从未提示我输入任何内,容。好吧?还有其他吗?还有其他症状吗?是的。
所以,我们从未输入任何年龄,但它认为年龄是零。好的,所以,这有点奇怪。还有其他吗?还有一个。你注意到这个程序还有什么奇怪的地方?是的。对,它基本上跳过了所有其他的CN调用。好的,有趣。是的。所以,是的。
这就是最后一点,当我输入我的全名时,它只读,取了我名字的第一部分,好吗?为了可视化为什么会这样,我做了一个小动画。
好的,CN尝试读取一个名字。缓冲区为空,所以我们输入了我的全名。好的,此时,CN读取了什么?为什么只读取到Avery?为什么到Avery?确切地说,它读取到空格,但不消耗空格,所以空格之后的。
所有内容都留在缓冲区中。好吧?所以,这就是为什么在名字变量中你只看到Avery。好的,CN尝试读取年龄,它会去消耗前导空格。当它尝试读取一个整数时会发生什么?对不起?太棒了,所以,缓冲区不为空。
所以程序不会停止询问你输,入,好吗?当它尝试读取一个整数时会发生什么?不是一个整数,记住上周,如果你尝试读取一个整数,但不,能读取一个整数,会发生什么?失败标志位被设置为打开,对吧?是的,所以这里。
它无法读取一个整数,所以失败标志位被,设置为打开。好的,现在当你尝试读取一个名字时,能打印出名字Avery,年龄不像一个初始化的变量,所以它可以打印任何东西。在这种情况下,它打印了零。
它实际上可以打印任何东西。好的,然后最后一行,这里发生了什么?该死,我的答案在这里。不要看答案。好的,缓冲区不为空,所以用户从未被提示。它能成功读取响应吗?因为如果你考虑一下,从技术上讲。
响应是一个字符串,对,吧?那边是一个字符串,为什么它不能读取这个字符串?对,失败标志位被设置为打开,对吧?所以当失败标志位或 EOS 标志位被开启时,所有未来的操,作都被冻结,对吧?
所以我的总结幻灯片中,为什么 cin 和提取运算符的结合,是个噩梦。第一,cin,如果你输入一整行,它会将整行放入缓冲区,但,当你尝试提取内容时,它只会逐个标记提取。
所以即使你输入 Avery Wang,它提取到的标记就是 Avery,第二,如果你输入多个标记,如果你不主动考虑缓冲区中剩,余的内容,那些垃圾内容会导致 cin 不能在正确的时间提,示用户,对吧?
在这里,我们希望让 cin 再次提示用户,但由于缓冲区中,还有内容,cin 从未再次被提示。最后,当 cin 失败时,它就会放弃,然后每次你使用 cin,它也会失败,对吧?
所以如果你曾经在自己的程序中使用过 cin,你会发现如,果你尝试测试错误输入,基本上 cin 会冻结整个程序的其,余部分。所以让我们尝试实现简单的 IO。我们想要做的简单 IO 是,改用这样的方式。
看看,程序去。
哪了?
这里,糟糕的欢迎程序。好的,现在,有些东西你在周五的 CS106B 中学到的,可以,帮助我们解决其中一个问题。所以有三个问题,对吧?它只逐个标记读取。
第二个问题是什么?缓冲区中的垃圾不会在正确的时间提示用户,然后当 cin 。
失败时,所有未来的操作也会失败。你确实在 106B 中学到了一些东西,可以帮助我们解决这,些问题中的一些。在 106B 中,你使用了文件流,并且你学到了一种通过提取,运算符读取输入的方法,对吧?
Keith 没有讲解流?没有讲解流。好的,那真是太遗憾了。所以,是的,想法是这样,它逐行逐标记读取,但有一种方法,可以提取整行内容,对吧?Keith 介绍过 getline 吗?他在周五讲了什么?好的。
因为周五的讲座确实提到了流和字符串,所以这很奇,怪。他只是改变了它。他会讲解流吗?好的,那真是太遗憾了。是的,基本上,还有另一个函数叫做 getline,它可以让你,读取整行内容,对吧?
所以你可以用 getline 代替提取运算符,它读取到下一个,换行符为止,对吧?所以假设我们这样做。好的,这样可以解决这个问题,因为,看看。
是的,我们试着用我的全名替换第一个吗?好的,我仍然可以输入我的年龄,然后它可以读取我的全名。
,对吧?让我们快速处理一下这在动画中的意义。
Keith 在做什么?好的,我没有那个动画,不幸的是,但基本上,getline 读取。
到下一个空白字符。它读取到换行符为止,所以它可以移动整个,消耗整个缓冲,区中的换行符。好的,问题,嗯?在 parison 中,你说 C 读取到空白字符,但不会消耗它。那个空白字符实际什么时候被消耗?
所以当你使用箭头,当你使用提取运算符时,它读取到下一,个空白字符,但不会消耗空白字符。当你再次调用提取操作符时,它会跳过所有前导空白,读取,标记,并在遇到下一个空白时停止,好吗?你提出了一个很好的问题。
那就是,getline 是做什么的?getline 读取到下一个换行符,并且也会消耗换行符,好吗,你能想象一下,如果你将这两个混合在一起,会发生奇怪的,事情吗?
我有一个例子吗?我在最后有一个例子,所以我会回到那个问题,好吗?
很好,好的,那么我们可以修复这一行,也可以修复下面这,一行。你需要一个响应吗?好的,就把整个响应放进去。好的,目前有什么问题吗?有吗?嗯,这是来自这段代码吗?我认为 getline 在这些代码中都有。
它在 iostream 中,对吗?好的,因为我很确定我在最顶部包含了 iostream,所以你,能再检查一下吗?好的,你在使用小写的 getline 吗?所以,getline。
像小写的 getline 那样。好的,然后,对于参数,确保你传入,嗯,确保你传入流,即 ,cin,然后你还需要传入一个变量,它是一个引用变量。getline 读取到的内容会放到那个变量中。是的。
这有点奇怪。有些人认为 getline 应该返回它读取的内容,但实际上它,是将读取到的内容放入变量中。好的,有没有猜到 getline 的返回值是什么?是的,嗯?是的,差不多。
它和提取操作符做的是一样的事情。提取操作符返回流,这个流会隐式转换为一个布尔值,表示,操作是否成功,好吗?很好,好的,那么我们能够修复这个问题,但问题是我们仍,然不能修复这一行。
因为这一行我们尝试读取一个 int,但,我们不能直接做到这一点。所以,这里我们要尝试读取,我们要尝试使用我们的函数 ,getinteger。好的,我们将实现 getinteger。好的。
Keith 讲过 getinteger 的内容,对吧?函数 getinteger?是的,我很确定他讲过。是的,所以 getinteger 的原型看起来像这样,然后我认为,你还需要传入一个字符串提示。
好的,这听起来有点熟悉吗?有一个提示,你调用 getinteger,可以从用户那里读取数。
据。好的,所以实现 getinteger 的一个简单方法就是使用我。
们上次写的内容,对吧?上次我们写了什么?我们写了将字符串转换为整数。所以,你可以做的是,比如从 cin 读取一行,字符串 line,从 cin 读取一行,然后将其转换为整数。所以。
记得我们是怎么把东西转换为整数的吗?我们使用了字符串流,对吧?我们使用了 iStringStream,我们把那一行放入 ,iStringStream 中,然后怎么读取一个 int?int val,对。
然后你只需执行 iss val。好的,所以这基本上是使用我们上次写的内容,将字符串转,换为整数,我们只是将那一行的内容直接读入 ISS 中,好,吗?这有什么问题吗?与我们上次遇到的问题一样。对。
所以当用户没有输入整数时,这会失败,好吗?所以,就像我们上次尝试通过状态位修复那样,让我们把那,个代码复制到这里。所以,我打算把整个代码从这里复制到这里。我们肯定要改变这个。好吧,这是我们上次写的代码。
然后,这不是 s,对,这就是我们上次写的代码,然后如果你。
尝试将这段代码结合起来,你会得到像这样的东西,对吧?你打印提示,尝试读取令牌,将其转换为字符串流,尝试从,中获取一个整数。如果失败了,重新提示。好吧,一种重新提示的方法是使用递归,对吧?但你不想这样做。
更好的方法是使用 while 循环来进行重新提示。明白了吗?好的,你现在有什么问题吗?哦,对了,一般来说,除非问题本身具有自相似性,否则不要,使用递归。你有时会看到学生写这样的代码。
这将在 106B 中更详细地讲解,但除非你解决的问题本身,是自相似的,否则不要使用递归。所以,获取整数,嗯,这并不完全是自相似的问题。这不是你在课堂上学到的那种,像阶乘那样。所以,是的。
基本上每当有更简单的方法来解决时,尽量使,用 while 循环。在这种情况下,你并没有真正将问题分解成更小的部分。你实际上只是每次都重复做完全相同的事情。所以,这可能让你觉得更像是一个循环。
while 循环,而不,是递归,递归更像是将事情分解成越来越小的部分。好的,那么最后我们需要担心的是,我们已经提到过 ,getline,但当你读取 cin 并且你正在读取一个令牌时,它,并不会读取整行。
它只读取第一个空白符号的令牌。所以,你可以通过将其替换为 getline 来修复。好了,现在,我希望你们花两分钟时间,和你旁边的人聊聊,尝试回顾一下这里写的内容。好吧,尝试解释每一行。
并且尝试理解每一行为什么是必要,的。好的,开始吧。好的,当你们还在讨论的时候,现在有问题吗?你们有什么问题?哦,双和符号?那只是逻辑与。所以,我们要尝试读取结果。如果失败了,因为这是一个与运算符。
所以如果其中一个失,败了,那么你就跳过第二个。然后你只需跳过整个操作,那么返回值不会运行,你会重新,填充它。如果成功了,那么第一个部分是真的,所以你尝试第二部分。
并且你要确保 ISS 不能读取更多的垃圾。如果它能够读取垃圾,那意味着在整数之后还有其他东西,那不是一个有效的整数,所以你也不想读取 pop。只有当这两个条件都正确时,你才能返回。好的,还有其他问题吗?
好的。所以,我想,我们还需要讨论什么吗?你的问题,嗯?如果只有一行的话,你是否需要大括号?哦,如果只有一行的话?哦,这是个好问题。我认为你可能需要大括号。好的,我不太记得操作顺序是什么,但。哦。
你是否需要大括号,记住了吗?按照某种运算顺序,我认为这个比那个高。是的,逻辑运算的优先级比较低。不过,为了清晰起见,也可以在这里加上括号。很好,我们来运行一遍这个过程吧。假设我输入了20 lol,好吗?
那么,getline 会把20 lol 读入 line 中。ISS,我们将把20 lol 转换成输入字符串流,然后尝试读取,到结果中,所以你读入了20。这成功了,然后你尝试读取垃圾,你读取了L。
你能够读取到另一个字符意味着用户输入的内容无效。所以,我们跳过返回语句,重新尝试,好吗?假设用户输入了其他内容,比如负数2013。getline 读取了整个行,包括换行符,存入变量中。
我们将其转换为字符串流,你尝试读取结果,然后尝试读取,垃圾。没有垃圾剩下,所以这个操作失败了。所以,这个函数返回 false,你取反得到 true。因为这两个条件都为真,所以你返回结果。明白了吗?
这有帮助吗?有问题吗?哦,好吧,那么为什么允许限制开放括号呢?在 C++ 和 Java 等一些语言中,如果你的 if 语句没有加,括号,那么它就会假设下一行是 if 语句的专用行。是的。
现在从风格上来说,不要这样做。因为如果你需要在 if 语句中添加更多内容,有些人可能,会不小心在这里下方写入,导致 if 语句无法正常工作。所以,当有疑问时,还是加上大括号。我这样做是因为简洁。
通常情况下,如果你的 if 语句非常简短,比如如果这个返,回 true,那么你可以把它放在一行中。当你做递归时,我觉得很方便做“如果字符串为空,返回空,字符串”,当你没有太多要做的事情时。是的。
为了强调,这只适用于 if 条件只有一行的情况。即使你把所有内容写在一行上,但中间有几行,这样会变成,多行,这样是不行的。但是,是的,它不受空格的影响,而是受语句数量的影响。一个常见的错误是。
当你没有加大括号时,比如你试图调试,这个,并且在调试时加了一个 cout 语句,然后忘记在这里,加大括号,那样可能会出现“哦,为什么这个突然工作得完,全不同于我预期”的情况。有问题吗?
抛出域错误的部分是简单 IL 的语法的一部分吗?是的,这是个好问题。每当你进行从 cin 中读取的操作时,你通常需要检查返回,值以确认是否成功。现在,getline 什么时候会失败呢?你会注意到。
如果类型不匹配,提取操作会失败。getline 什么时候会失败?getline 需要一个字符串。那怎么会失败呢?如果内部类型为空,那么 technically,getline 会给你一,个空字符串。
所以,这没问题。不坏的猜测。是的,这确实是个比较难的问题。实际上,嗯,我想要深入探讨一下吗?我让你尝试随机操作,看看你能否让 getline 失败。是的,实际上 getline 很难失败。
但有一种情况 getline ,可能会失败,明白了吗?但一般来说,你要检查返回值,以确保没有返回任何东西。一个原因可能是 cin 在其他地方失败了。如果你有一个程序同时运行多个操作。
cin 可能在其他地,方被使用,如果那里的 cin 失败了,那么这个 getline 也,会失败,对吧?如果 cin 有一些失败标志位被设置了,那么这一行代码就,不会正常工作,好吗?
在这个问题的上下文中,cin 总是能正常工作,因为 cin ,在其他地方没有被使用。好,我们快完成了。那么,这是带有重新提示的完整实现。这是标准库中最难编写的函数,所以你刚刚编写了这个最,难的函数。
对于作业一非常有用。仅供参考。好,最后的流问题。我之前有提到过这一点,但如果你将提取运算符与 ,getline 混合使用,会发生奇怪的事情,因为当你尝试使用,提取运算符读取时,它读取到空白字符。
但不会跳过空白字,符。实际上,这一页上有一个小错误。好,换个例子。
去掉盎司。让我在这里写出这个例子。好,假设我们有这个例子。这是一个可能会出现该错误的示例。我们有一个字符串流。我们称之为,给它 16。9,然后是换行符,然后是 24,好吗?所以。
如果我尝试读取一个 double 值,ISS val,ISS 箭头,箭头 val 会给我什么?val 是 16。9,好吗。当这完成时,指针的位置在哪里?它在空白字符之前,因为它不会跳过之后的空白字符。
假设我在这里尝试做一个 getline。所以,getline ISS,假设,string line。line 是什么?是的,line 是空字符串,因为指针的位置技术上是在换行,符之前。所以。
如果你尝试使用 getline 读取,getline 会读取到,换行符并消耗它,对吧?所以,如果你从这里尝试读取 getline,它只会读取到换行,符,消耗它,然后完成。所以。
line 在这里会给你这个,这不完全是你想要的,对吧,好,明白了吗?所以,当 Keith 最终讲解流时,他会解释为什么你不应该,将提取运算符与 getline 混合使用。有一个解决方案。
我会在两秒钟内展示。是吗?是的,如果你再调用一次 getline,到这时,指针的位置已,经跳过了换行符。所以,如果你在这里再调用一次 getline,那么你会得到正,确的答案。所以。
line 等于 24。是的,getline 读取到换行符或缓冲区的末尾。问题,是吗?这会返回一个好问题,它会返回空格 24,因为提取运算符,跳过了初始空白字符,然后读取下一个标记而不跳过下一。
个空白字符。getline 做的正好相反。getline 从起始位置开始,读取到换行符。它消耗换行符,然后将换行符之前的内容作为新的行。所以,在这种情况下,空白字符会被包含在内。
好问题。还有其他问题吗?是的,幻灯片中有一个小错误。它应该读取到空白字符,即换行符。
是的,然后简单的解决办法是你可以让流向前移动一个字,符。所以,在这里,你可以使用 ISS。ignore。这会忽略一个字符。所以,当我们完成 16。9 时,我们忽略一个字符,这样我们,就跳过了空白字符。
然后,最后,当 getline 函数在这里运行时,这将给你空间, 24,而我们不需要这个。明白了吗?好,这是一种非常常见的错误,好吗?
我可以保证在 Windows XP 上的 Piazza 会出现一个帖子,问为什么这个不起作用?我使用了所有正确的构造,但原因是你不应该将换行符与 ,getline 混合,除非你仔细考虑并跳过正确的字符。
好的,很棒。好了,你周四的挑战。所以,这是你的挑战。编写一个函数提示用户输入文件。这对第一项作业也很有帮助,这也是一个非常重要的斯坦,福库函数。我不认为 CS106B 的第一项作业要求你调用这个函数。
但,我很确定第二项作业会要求你调用这个函数。太棒了。好的,我们还有 10 分钟。让我们快速讨论一下现代 C++ 类型。我不,好的,让我们快速回顾一下类型。关于类型的一件事是。
你会经常看到这种非常烦人的错误,信息。有人开始做 CS106B 的第一项作业并遇到这个错误信息了,吗?有吗?好的,C++ 中整数的问题是什么?在一些其他语言中也是一样,整数,它们实际上是有符号的。
这意味着它们可以是正数也可以是负数。还有另一种类型叫做无符号整数。这些只能是非负的,好吗?现在,有些情况下,比如你尝试查询字符串的大小,因为大,小不能是负数,所以它返回的是无符号整数类型。好的。
当你尝试将一个普通整数与无符号整数进行比较时,这可能不是一个好主意,所以你会收到这样的警告。明白了吗?是的,所以,我只是想解释一下为什么会出现警告。其他接受无符号整数的函数。
例如 dot size 返回的是无,符号整数。当你进行括号操作时,它也期望 i 是无符号整数,因为你,在索引字符串,好吗?解决这个问题的方法是什么?你可以自己声明一个无符号整数。
我们通常使用一种叫做 size_t 的类型,它表示一个表示,大小的变量,好吗?所以 size_t i = 0。这将清除所有错误,对吧?因为在这里我们比较的是无符号整数,所以这没问题。
我们不会深入讨论无符号整数到底是什么,那是 CS107 的,内容,但只要知道,如果你遇到这样的错误,这就是原因。你们有任何问题吗?有吗?右边,size_t。它是平台相关的,是四个字节还是八个字节?
好问题。这取决于你的平台。现在,几乎总是,它是八个字节,但如果你使用的是旧的 32, 位系统,那么,size_t 的大小会在这里指定。它在你的机器上定义的宽度是否和 long 相同?
所以它们有两个不同的定义,你的机器会告诉你你写的定,义,但也有一些默认值说它会有所变化。一些规格会说这两者几乎相同。有些会说它们是相同的。所以你可以自己查一下。如果你不明白这些字节是什么,不用担心。
在 CS107 中,它会被广泛覆盖。好了,快速挑战。如果你做面试、实习的编码挑战,这会是你常见的一个错,误。我因为这个错误而没能通过编码面试。找出这个错误。
这个函数的想法基本上是返回一个去掉第一个字符和最后,一个字符的字符串。有人发现错误了吗?是的。好的,所以如果你输入一个空字符串,哪里有问题?是的,因为如果你在这里输入空字符串,str。size 是零。
零是无符号的。零减一是负一,但负一不是无符号整型。所以你会得到一个非常奇怪的值。结果是你会得到一个巨大的值。所以你的程序崩溃了。好的,仅供参考,这是一种非常常见的错误。是的,所以在这种情况下。
你可能要单独处理空情况。酷,好的。我将快速浏览这些内容。这些只是一些需要注意的事项。类型别名基本上是,它们允许你给一个非常长的类型另一,个名字。当我们处理集合时,我们会常常看到这一点。
有时你会有这些巨大的名字,你只是想给它们一个较短的,名字。所以你可以使用 using something equals another ,type。
然后你可以将 map iterator 用作那个非常长的类,型的别名。好的,非常简单。你什么时候使用这个?当类型名非常长时,以及当你想要为每种类型使用一个常,见的名称时。所以我们将使用的每个集合。
vector、list、所有其他类,型,它们都有很好命名的类型。所以它们有 vector、vector iterator。有一个 map iterator。
有 vector reference、map reference。这些类型的名称实际上并没有被引用,但只是因为它们使,用一个名称,类型别名来命名所有这些类型,以便它们有相,似的名称。好的,酷。Auto。
这真的是 Keith 最喜欢的 C++ 特性。Auto 基本上是做任何之前的事情的一种超级懒惰的方法,当你使用关键字 auto 时,编译器会为你确定类型。所以你不必指定类型是什么。
编译器会确定 yin 应该是什么。好了,来个快速测试。我们逐行查看。Multiply equals 2。4。Auto,这是什么类型?Double,好的。第二个,name 等于字符串 Avery。
不完全正确。它是一个 char star。这是一个 C 字符串。所以,好的,至少 Keith 在周五讲解过这个,对吧?关于 C 字符串与常规字符串。没有,天哪,Keith 在干什么?好的。
在 C++ 中,有两种字符串。一种是 C 字符串,还有一种是 C++ 字符串。如果你只是写字面量,这将给你一个 C 字符串。通常,当你声明一个字符串名称等于Avery时,它会将C字符。
串转换为C++字符串,因此你不必担心它。但如果你在C字符串中使用auto,你必须确保明确地将其传,递给字符串构造函数,以便better name one是一个字符串,好的,明白了。好的。
我现在已经放弃了,但Keith谈到const了吗?哦,他谈到了?哦,好吧,很好。很好,所以你们都知道const是什么,对吧?const引用。所以如果你需要声明一个const或一个引用,auto会去掉所。
有这些。所以即使你的变量是一个const变量,如果你尝试将其赋值,给auto,auto会去掉其中的任何const。所以如果你想让const继续存在,你必须加上const,明白了,吗?
auto或copy的类型是什么,auto copy等于decode?这是一个vector string,明白了吗?不是一个const vector string,而是一个vector string。
auto引用,ref multiplier等于multiplier是什么?所以multiplier是一个double,我们将其声明为引用。所以ref multiplier是对一个double的引用。
好的,Keith可能没有讲到引用,没关系。好的,然后你可能会想,auto只是一个懒惰的方式,对吧?实际上有些情况你必须使用auto。在最后一个例子中,我们稍后会解释这是什么。这叫做lambda函数。
这是它的一个例子。这叫做lambda函数。lambda函数的酷之处在于你实际上不知道类型是什么,因,为发生的事情是编译器接收它,创建一个新的类,并给它起,个名字。问题是,你不知道编译器给它起了什么名字。
所以在这种情况下,你必须让编译器给你提供类型。我们将在几周后的讲座中详细讲解lambda函数。所以现在如果你不理解这是什么意思也没关系。是的,我们会指出auto在许多场景中实际上是有用的。是的,问题?
所以字符串a,假设你用那个绿色箭头做name加better ,name one。名称。名称加better name one。这将会使加号运算符隐式地将C字符串转换为C++字符串。我认为这是对的。
这些之间有什么区别?所以,不想深入探讨这个,但在CS107中,你会很快学习到,实际上你会很快学到指针。C字符串实际上只是一个指针,实际上是一个字符数组。你可以把字符串看作是最原始的形式。
它是一个包含大量,字符的数组。所以,C字符串基本上就是,这里是一个字符数组,你处理它,它本身没有任何方法,而字符串有它自己的方法。C字符串最糟糕的部分是你不知道C字符串的大小。你必须实际尝试弄清楚。
所以,C字符串,C字符串很奇怪。如果你尝试将两个C字符串相加,你会得到奇怪的结果。是的,所以避免使用C字符串,使用C++字符串,特别是当你,在C++中编程时。是的,在C++编程中,另一点。
可能是你最常遇到的情况之一,你会担心某些东西是C字符串还是C++字符串。通常情况下,这不重要。通常来说,几乎所有的时间,你通常只是说,比如,字符串 ,name 等于引号,每个引号,然后它就能正常工作。
也许有一种错误,你有时会看到,尤其是那些从 C 语言派,生的函数,因为 C++ 是最初在 C 语言的基础上编写的,这,些函数只接受 C 字符串。所以发生的情况是,你会有某种变量,比如 name。
你已经在,其他程序中使用了,为了使用那个函数,你必须记得将其显,式地转换回 C 字符串,以作为参数使用。但通常来说,你不需要这样做,通常你可以同时使用两者,并且你不会真正注意到它们的区别。好了。
我们快完成了。我们快没时间了。但你可以为返回类型使用 auto。这确实是有争议的。你也可以使用 auto,注意你不能为参数使用 auto。好的,这里有一个特殊的原因,为什么你不能在参数中使用。
auto。那么什么时候使用 auto 呢?一种情况是,当你不关心类型是什么时。通常有些情况下你只需要那个项,你并不在乎它的具体类,型。好的,稍后在两节课后,我们会处理一些称为迭代器的东西。
有许多类型的迭代器,但我们只关心它是某种迭代器。所以在这种情况下你可以使用 auto。好了,还有其他情况下 auto 是非常好用的。通常,我告诉学生不要在 CS106B 中使用 auto,因为大多。
数助教都不喜欢 auto。好的,但问题是 Keith 非常喜欢 auto,所以你可以使用它,你会在你的代码中使用 auto。现在,有些人说总是使用 auto,甚至对于整型也使用 auto。
另一派则是绝不要使用 auto。我们倾向于使用 auto,当类型非常长并且你不太关心类型,时。所以基本上如果它超过四五个字符,随意使用 auto。但如果是像 int 或 double。
使用 auto 也不是必需的。它可能使代码更具可读性。好的,你可以直接搜索 auto 使用时机,然后你可以找到互,联网上关于如何使用 auto 的争论。好的,这确实有争议。哦。
关于 auto 的一个酷点是,如果你在编译器中查看,你。
实际上可以看到类型是什么。所以假设我说 i 等于 16。9 盎司。我们说这是一个 C 字符串,你可以,如果你将光标放在 ,auto 上,它会告诉你类型是什么。
所以你可以看到它是 const char star。好的,这意味着 C 字符串。如果我将它包裹在字符串构造函数中,那么你可以,如果我,将光标移到那儿,你会看到它说,哦,这是一个字符串。好的?所以是的。
auto 很好用的原因是,现代编译器会告诉你类,型是什么。所以你能够使用 auto。问题,嗯?对于那个,和那个有多大不同?字符串字面量是奇怪的。所以是的,字符串字面量在它们具有奇怪的常量性方面很。
奇怪。所以我不想深入讲解,但在讲座后可以随时问我们。是的,因为字面量很奇怪。稍后,我们会讨论 L 值和 R 值。字符串字面量有点奇怪。
好的?是的,我觉得我们基本上完成了。哦,是的,你可以看看这些对和元组。你有没有想过怎么从一个函数返回两个东西?Python 让你非常容易地做到这一点。Java 不允许你这样做。在 C++ 中。
你通常会使用引用参数,但还有一种叫做对的,东西,它允许你返回值对,明白了吗?而我最喜欢的东西之一是叫做结构化绑定的东西。它两年前推出,基本上让你可以做 Python 中非常方便的,事情。
在 Python 中,你可以将价格的不同组件提取到两个变量,中。在这里,我们做的是一样的事情。Auto 会自动推断 A 和 B 的类型,并将价格的两个组件提,取到其中。明白了吗?是的,这就是结构化绑定。
是吗?对,并给它什么?嗯哼。是的,是的。我没有深入讲解这个。我有吗?哦,好吧。好吧,是的,我试图删除我的幻灯片,我觉得我删除了太多,幻灯片。是的,所以对是一种类型。是的,对是一种类型。
它实际上是一个模板。你可以在对中放入任何东西。你可以放入一个整数对。你可以做一个整数和字符串的对,如果你使用 auto,它会,为你推断出所有内容,所以你不必担心这些。明白了吗?要获取第一个和第二个组件。
你可以使用 。first 和 ,second。如果你有元组,你需要使用奇怪的括号来获取不同的组件,好吧,对非常常用,特别是在 STL 中。元组则不那么常用。结构体,它们有点像元组。Keith。
有 Keith 讲解结构体吗?可能没有,是的,可能没有。是的,所以结构体有点类似,但你可以给这些组件命名,这,使得 coupon 到 。expression date 更加清晰。好吧,这些叫做结构体。
它们是将不同类型打包成一个一致类型的变量。这正是类所做的,对吧?一个类有不同的成员,但结构体本质上只是一个轻量级的,类。结构体通常没有很多方法。没有隐私。结构体中没有公有和私有。它只是内存中分开的变量。
你将在作业 1 中使用结构体,无论是 106B 的“欢迎使用 ,C++”作业,还是在本课程中的作业 1,如果你选择使用括号,哦,106B 作业 1 吗?哦,哇,好吧。等一下,Keith 没教过吗?
他在作业中讲解了它。哦,真是典型的 Keith。好吧,当你完成作业 1 后,我们可以多谈谈,到时候你会了。
解我在说什么。好吧,我不想谈论引用。哦,好吧,这里有一个非常常见的图表,如果你想传递参数,你会看到一些通用的模式。在 C++ 中,记住,一个主要的目标是使代码尽可能自我文,档化。
我忘了确切的术语是什么了。你想在代码中表达意图,好吗?根据参数类型,你可以猜测它的意图。所以记住Keith提到的const something ampersand吗?那是为了处理大型集合。
当你想将集合传递给函数时,但这,些只是输入,不是输出,在这种情况下,你将它们作为const, x ampersand传递,以表示这是仅作为输入的。你不能改变它。与此相反,如果你传递像这样的东西。
这意味着调用函数的,那个人应该期望x被改变。你明白了吗?记住,当你阅读代码时,很多时候你只是在阅读头文件,你,在阅读函数的作用,所以没有其他方法可以记录这一点,除,了通过注释或者明确地说明。
这是仅作为输入的参数,这是,一个输入输出参数。你传递进去,然后修改它以便传递出去,对吧?还有一件事。在过去的CS106B中,他们喜欢使用这样的输出参数,但现在,更常见的是直接返回集合本身。
所以如果你在函数内部构建了一个集合并返回它,只需将,其作为返回值使用。明白了吗?是的,以前106B有这个争论,不想返回大型集合。现在完全没问题,特别是因为Keith在教我。是的,一个你可能记得的原因是。
为什么你应该永远只使用,这种值传递。我们稍后会讨论值传递。值传递应仅用于复制成本较低的情况,比如int、double,这些复制成本非常低。实际上,如果你传递的是int或double,使用引用的开销远。
高于直接复制的开销。所以如果你使用的是非常便宜的类型,直接使用值传递,这,样更简单。如果你传递的是昂贵的集合,使用常量引用。明白了吗?好的,是的。你会知道值传递会创建副本。在第七周,我们会讨论移动。
对吧?因为与其复制内容,你可以选择移动内容吗?你将看到这些更复杂的内容,我们会在第七周讨论。这个双重ampersand,就是在不复制的情况下进行移动。
好了,我们快完成了。我的意思是,我们实际上不需要这些。统一初始化,Keith会讲这个。对,我觉得Keith会讲这个。好吧,可能在第二次作业中。可能是第二次作业。所以是的,你也会自己学习统一初始化。
一个例子中包含所有内容。这是一个包含所有内容的例子。你可以随意查看一下。我们完成了。所以,只有几件事。星期四,Anna将讲解STL和序列容器。然后Anand将在下周二讲解,对吧?你想在星期四做吗?好的。
他只能在星期二做。他星期四有课。好的,我们看看,是的。是的,我们会有一位来宾讲师。是的,他找我说,他真的很想为106L做一个来宾讲座。所以,你们会听到Anand讲座。他是那个,如果你参加过CS106B。
他会在Piazza上发很直白,帖子的人。他是在Piazza上回复很直接的人。好的,我不确定他这学期是否在使用Piazza,但过去,他会,对Piazza上的问题给出非常直截了当的回答。在CS106B中。
每个人都会说,哦,哦,我不敢问问题,因为,Anand给出的回答很直截了当。好的,是的,他是个非常有趣的人。
他可能会在明年某个时候过来。好的,很好。谢谢你的到来。如果你想要巧克力,来前面拿一些吧。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P4:[09]CS 106L Fall 2019 - Lecture 3_ Sequence Containers (Screencast) - GPT中英字幕课程资源 - BV1Fz421q7oh
好的,这个已经开启了,然后 Qt 也开启了,序列容器也开,启了,Vim 也开启了,好的,是的,然后只是,然后,我是说,你知道怎么使用终端,所以在序列中,序列,是的,你的两个,文件是你的基本向量,然后。
是的,好的,酷,然后,好吧,然。
后你能按下那个吗?好的,你们,接下来我们将完成周二没有完成的部分,然后,我们会转到新话题,也就是标准模板库,这非常令人兴奋,但首先,完成序列的内容。好的,欢迎回来,每个人,所以你们以为在两节课后。
你们终,于结束了我,我还会讲大约 10 分钟,然后你们会听到 ,Anna,耶。好的,让我们完成一些类型,上次,我简要提到过结构体,我,真正谈论了 SCD 对的概念,对就是,你可以放入两种不同,的类型。
然后这允许你,将这两种类型打包成一个对,你可,以根据需要传递它们,返回它们。在 C++17 中,他们有一个新特性叫做结构化绑定,就是这,行,基本上,当你返回一个对时,你可以自动将变量解包到。
两个不同的变量中。此时,有问题吗?有吗?避免在 106B 中使用这个,是的,因为有些章节领导不知道,什么是 STD 对。这也是一个相对较新的特性,所以不能保证它甚至是一个,模板。是的。
假设 106B 自动评分系统使用的是一个比较旧的 ,C++ 版本,所以避免在 106B 中使用这个。是的,问题?这可以应用于自定义结构体吗?这是一个非常好的问题,这是我接下来的幻灯片。
让我们继续你的问题,预测接下来会发生什么。还有其他问题吗?有吗?你是说当我返回一个向量时,你能立即解包吗?是的,你也可以这样做。你需要小心。当你返回某物时,你必须确保你将其解包到正确数量的元,素中。
这最常用于对。顺便说一下,这就是一个对。还有一个叫做元组的东西,它本质上是一个对,但你可以有,尽可能多的东西。到目前为止有问题吗?让我简要谈谈结构体。如果你现在看看这个程序,返回一个对是很好的。
你可以一次返回两个东西。你能从这段代码中看到一个缺点是什么吗?这个程序只能返回两个东西,因为我们现在使用的是对。如果需要,你可以将其扩展为元组。你提到了一个很好的点,那就是对本身。你知道里面有两个整数。
但你怎么知道第一个是最小值,第,二个是最大值?阅读你函数的人可以说,哦,你返回两个整数,第一个一定,是最大值,第二个是最小值。没有自文档化的方式来说明哪个是最小值,哪个是最大值?
也许更好的办法是使用一个叫做结构体的东西。结构体是对或元组的更一般形式,其中每个组件可以是不,同的类型,你可以提供多种不同类型。此外,结构体中的组件是命名的,这意味着当你引用结构体,中的不同部分时。
你可以按名称引用它们。例如,对于一个结构体,我可以命名两个组件为 min 和 ,max,以便稍后当我想访问 min 或 max 时,我可以调用,好,的,这个结构体的 min 是什么。
这个结构体的 max 是什么,我看到这儿有一个问题。是的,问题。抱歉,在对或元组中,所有类型必须相同吗?我想我说错了。类型不需要相同。是的,在对或元组中,它们不需要相同。当我们下周深入 STL 时。
你会经常看到 STL 库返回一个,对,然后这个对通常是一个布尔值和其他东西。我认为 find,这个你会用到的比较常见的函数,它返回一,个布尔值和其他东西。布尔值表示是否实际找到了某个东西。
如果这个布尔值为,真,那么另一个组件就是你找到的实际东西。如果没有找到任何东西,那么就是假和一些随机的垃圾。好的,问题。还有其他问题吗?是的,问题。结构体是否较慢?我认为对和结构体。
它们的工作方式是一样的。我不是很确定,但我认为对可能在底层是作为结构体实现,的,所以我认为没有大的问题。你知道答案吗?比结构体更有可能一点。好的。它不需要存储在内存中的任何地方。是的。但在速度上。
它应该是。是的,应该是相似的。结构体是一个非常古老的构造。这不是现代 C++ 的事情。如果你在编程 C,你会经常看到结构体。所以结构体实际上是从 C 中借来的。现在。
如果你在编程 Java 或 Python,你会发现这实际上,和类很类似。一个类,你可以有实例变量,然后你可以引用这些实例变量,这有点像,但是结构体中的一切都是公共的。
你可以立即访问你创建的任何结构体对象的 min 或 max,结构体的工作方式和对象完全一样。问题,是吗?当你访问时,是访问变量的引用,还是访问那个的副本?你是在访问变量的引用。作为一个例子,看看这儿。
这里我们使用了一个结构体价格范围,其中价格范围有两,个组件,一个 min 和一个 max。然后你可以看到,我们可以通过说 p。min 和 p。max 来访,问价格范围的 min 和 max 字段。
如果你想改变 p。min,你可以说 p。min 等于某个值,因为,这是一个引用,它实际上在改变 p 内部的内容。问题,是吗?结构绑定?封装即私有变量隐藏。是的,我认为你不能说私有这个词。
结构体是类的一种非常轻量级的形式。如果你现在在上 106B,类将在学期中途讲解。结构体实际上是 C++ 被创建的原因,因为 C++ 的创建者,在使用 C 和结构体时,厌倦了只处理公共数字,所以决定。
让 C++ 成为一个更大的名称。还有其他问题吗?然后你可以使用结构绑定做同样的事情。当你返回结构体时,你可以访问变量并将它们提取到单独,的变量中,有点像我们对对所做的那样。我们不通过这个例子。例如。
另一个我们可以做的结构体,我本来打算做这个示例,但我们时间不够,是我们可以写一个叫做 course 的结构,体,在 course 里面可以有一个字符串 code,CS16L。
可以有一个开始时间和结束时间。时间是我们稍后可以创建的另一个结构体,它包含小时和,分钟。例如,开始时间可以是 3。30,结束时间可以是 4。30。
结构体还可以包含一个字符串向量 instructors 和一个,简要介绍。结构体的工作方式相同……,这只是另一个你可以使用结构体的例子。你可以拥有一个课程的向量,它本身就是一个结构体。
如果我们有时间的话,我稍后会详细讲解这些。我想简单提一下最后一个主题是初始化。在其他语言中,初始化并不是一个大问题。但在 C++ 中,显然,我刚刚查了一下,你可以用 26 种不同,的方法初始化一个变量。
然后这里有一个小表情包,或者这是一个小 gif,讲的是你,可以用多少种方法初始化。我不知道其中三分之二的内容。我们完成了吗?好吧,我们完成了。本质上,初始化的方法有很多,而且实际上不可能知道或记。
住所有这些方法。初始化的方法还取决于你使用的类型。具有讽刺意味的是,为了解决这个问题,C++ 决定再增加一,种初始化方法。有一种第 27 种初始化方法叫做统一初始化,它旨在解决,所有这些初始化问题。
你现在处理的许多对象本身都有一个叫做初始化列表构造,函数的特殊构造函数,它允许你通过给出一个参数列表来,初始化某些东西。例如,默认情况下,结构体,比如 course,我们可以通过将。
我们想要的课程的不同组件放在这些大括号内来初始化它,们。这将用课程代码 CS106L、时间 3。30 和 4。30 以及讲师,的向量(我和 Anna)来初始化我们的课程。有几个问题。是的,第一个问题。
第二个参数,15。30,应该是一个时间结构体。你不需要明确地称它为时间对象或时间结构体?好问题。请注意,我们从未提到 15。30,这里的蓝色部分,是一个时,间。原因是当 C++ 看到你的课程结构体时。
它看到你放置它们,的顺序。首先是一个字符串,然后是一个时间,然后是另一个时间,然后是一个向量。它进入你的初始化列表,就是这个。这不是初始化列表。它进入这里,并且说,第一个是字符串。第二个是时间。
要创建一个时间,我们可以进行另一个统一初始化。它会说,第一个部分是小时,第二个部分是分钟,其他一切,也是如此。它将第三项视为一个向量,因此将其转换为字符串向量。好问题。是的。你能改变……,哦,你是说。
我可以现在说 now。courseCode 等于某个值吗,像是改变它?是的。这将初始化一个课程,从这些值开始。你现在可以进去并说 now。code 等于 CS106B。是的,好问题。还有其他问题吗?
是的。统一初始化就是一次性完成所有操作吗?统一初始化的方式适用于很多不同的事物,不仅仅是结构,体。对不起,你能解释一下吗。我还是不太明白统一初始化的意思,这是一种特化。是的。例如,初始化一个向量。
这种初始化向量的方式会创建一个,包含这些元素的向量。现在,向量和课程,它们是不同种类的东西,但有一种通用,的方法来初始化各种不同的对象。如果你注意到,我们在初始化很多不同种类的对象。这是一个时间结构体。
这是一个字符串。这是一个向量。但是它们都可以用相同的方式初始化,使用花括号。问题,是的。所以OOP(面向对象编程)是一个话题,我们实际上会在本季,度晚些时候讲到。
结构体在某种程度上是OOP在C++中的前身,但C++有自己版,本的类等,因此不仅仅是结构体。好的。在第一次讲座中,我提到向量有很多构造函数。你会看到的构造函数之一是这个初始化列表,它会接受你。
给出的初始化列表,这是正在调用的构造函数。问题,是的。你可以传递一个结构体作为引用吗?是的。然后可以改变结构体中的特定值吗?是的,所以问题是你可以按引用传递结构体吗?答案是可以的。
它们的工作方式有点像对象,只是所有成员都是公有的。好的。是的,当你使用统一初始化时,它更倾向于调用带有初始化,列表的构造函数。所以在这里,如果你对3使用它,它会创建一个包含元素3的,向量。
如果你更愿意调用其他构造函数,你需要使用更常规的方,式,比如,传入3。如果你查看向量的STL规范,传入一个数字表示大小,即初,始大小。所以它会创建一个大小为3的向量,默认值为0, 0, 0。问题,是的。
等等,因为我以为Jeremy可能说过,比如,当你用大小3初始,化时,它不会自动默认为0, 0, 0。难道不是垃圾,垃圾,垃圾吗?
所以这实际上是一个棘手的问题。所以问题基本上是,C++是否会自动将你创建的对象初始化。
为0?事实证明,这实际上是一个比最初假设的更棘手的问题。我们能找到的共识是,当它是全局变量或某些变量时,你可,以依赖C++进行零初始化。有某些关键字表明它将自动进行空初始化。
但通常来说,如果它只是一个局部变量或对象,你不应该依,赖它进行空初始化。是的,这是个好问题。因为我认为这里有一个构造函数,它会指定某个类型的默,认值。对于int,是否有默认值?它是否只是给它0?是的。
所有原始数据类型也会自动进行空初始化。是的,所以我认为这里它有一个默认值。所以如果你没有给它默认值,它会说,好吧,你有一个计数,它的默认值将是0或类似的东西。好吧。
现在你知道了这一整件事情的一个小部分是什么意,思。你知道这意味着什么,初始化列表,以及你也知道什么是引,用。然后我们将在学期中覆盖其他所有内容。有问题吗?
那么我们需要自己创建一个带初始化列表的课程方法吗?是的。好吧,所以默认情况下,如果你不提供初始化列表,它所做,的是按声明每个变量的顺序实例化每个参数。所以我们做的顺序是一个字符串,课程代码。
然后是两个时,间,然后是一个向量。所以它就将每个匹配到不同的成员。是吗?所以如果你定义一个新课程而不指定任何参数,那就意味,着它会对每一个参数进行零初始化,或者像等效的零初始,化?会这样吗?对于整数。
它会这样做吗?我相信它对于整数确实会这样做。我们需要检查它是否对向量或其他东西进行零初始化。但是你完全可以先创建它,然后稍后再设置值。在创建时你不一定要设置值。好吧。
那我们就这样吧。太棒了。好吧,各位。所以我将休息一周左右。休息他的声音。他做了非常棒的工作。好吧,所以今天我们要讨论一些事情。不过,在我们进入新话题之前,有一件事我们想强调一下。
就是我们刚刚教过你们的这些话题,特别是,比如说,字符,串流,它是很多人其实没有听说过的东西,我们看到了一些,可以用它做的很酷的事情。然而,我们还想在这门课程中强调的是,不仅展示C++的独,特工具。
还要教会你们或者提醒你们总是要考虑好,我有这,些不同的工具。我还需要记住什么时候使用它们。所以我们刚教了你们字符串流,但我们想强调的是,它不一,定现在是你代码中所有字符串的替代品。
字符串流适合做一些事情,特别是这三种应用。比如说你想在代码中处理流。例如,那就是你文件目录中的一个路径。如果你现在不熟悉它也没关系。你会在107或者甚至106x中看到它,我相信。
如果你想将它简化为更短的版本,例如。格式化输入和输出字符串。所以我们没有机会讨论与输入输出流或字符串流相关的字,符串操控器,但像ndl或flush这样的东西,都是字符串操控,器。
像大写和十六进制也是如此。这些操控器的作用是,例如,如果你输出你的字符串,你可,以插入字符串操控器大写,它会将所有内容输出为大写。所以对于那些你想以特定方式格式化的流,字符串流也非,常棒。顺便提一下。
现在幻灯片的最后有关于操控器的幻灯片。所以如果你感到好奇,可以随时查看这些幻灯片。是的,如果我们今天有时间的话,希望能看到这些。如果没有,它们在幻灯片中等着你们。最后一点,例如。
当然是我们处理的字符串到整数。换句话说,当我们处理流中的不同类型时,这又是字符串流,的一个好用例。另一方面,例如,如果在你的程序中只是连接字符串,结果,是你实际上并不需要字符串流。
这是你实际上没有使用的额外功能,而你亲爱的老朋友 ,string。append 实际上是你可以使用的最佳工具。所以这是一个提醒,这确实是一个超级强大的工具。只要记住,例如,字符串也是有其存在的理由的。
好的,那么我们在开始新的话题之前想做的另一件事是调,查结果。感谢大家填写调查问卷。我不知道你们为什么会在没有某种动机的情况下这样做。不,我只是开玩笑。所以有几件事只是让你们知道一下。这就是课程的情况。
不同班级之间的分布很均匀。很多新生,但我们也有相当数量的二年级学生和研究生。听到这些真是太好了,这对我们非常有帮助,因为这让我们,知道我们实际上在处理更广泛的背景。与此类似,我们的专业当然是计算机科学。
但也涵盖了许多,其他专业,还有更多。最后,如果你们感兴趣的话,这就是你们今天坐在这间教室,里的原因。你们想学习 C++ 如何在工业中使用。你们想更多地练习 C++ 语言。你们想补充 106B 和 X。
这也是一个非常有效的理由。然后是具体应用。所以对于那些因为想学习如何将 C++ 应用到具体工作或,其他应用的人,我们不会直接在课堂上讲解这些,但我们鼓,励你们在办公室时间来找我们。
我们会在明天或今天发布,作业时公布。这样我们也可以与你们讨论你们感兴趣的具体内容。所以不会直接讲解这些,但我们会尝试专注于这三种应用,使课程对你们有用。酷。这很令人兴奋。
所以到目前为止你们学到了流和 C++ 类型以及 C++ 库中,的许多其他非常酷的新构建块。如果你们还记得,这就是 Avery 在学期开始时为我们制作,的地图。现在我们要做的是退后一步。
回到 C++ 中最早创建的东西,之一,即标准模板库,在右上角。这不是 C++ 的内置功能。在 1972 年,或者我希望我记对了年份,当 C++ 首次创建,时,它并不存在。
但在 C++ 的第一次广泛发布中,标准模板库是语言中最早,内置的功能之一。我们很快就会看到原因。所以今天,我们将专注于几个不同的方面。在我讲到这些之前,我认为这是标准模板库的一个很好的,总结。
正如数学家们试图将定理提升到最一般的设置一样,这位 ,Alex Stepanov 希望将算法和数据结构提升到它们最一般,的版本。所以,再一次,为了避免每个程序员都要自己实现一个查找,或排序算法。
这位先生想把它放到一个标准库中,人们可以,在一个源空间中工作并使其更高效。而这在现在我们有点视为理所当然,但在当时,绝对是一个,新特性。所以在这次讲座中,我们将覆盖标准模板库的两个部分:容,器和适配器。
这些词似乎很陌生,但你们会发现你们实际上已经知道大,部分内容。这将是好的。接下来的五次讲座中,我们将深入这三个标准模板库的核,心部分:迭代器、算法和仿函数与 Lambda 表达式。实际上。
这三件事就是为什么很多人,或者说,为什么我们,在向你们宣传这门课程时,说 C++ 是魔法的原因。我现在就给你们一个提示。但是,是的,一旦我们进入那三次讲座,你们会真正看到标,准模板库是多么强大。
我保证你们会感到惊讶。在 STL 中还有一些其他内容,但这些是更小的概念,我们,在这门课中不会涉及。我只是想补充一点。即使 STL 是 C++ 最古老的库之一,它也在不断演进。如果你现在查看文档。
你会看到有日期,比如自 C++14 以,来,自 C++17 以来。它们在不断增加更多功能,这些功能补充了 C++ 的新语言,特性。所以不要认为这是一个没人使用的超级古老的库。每个人都在使用这些。
并且它变得越来越复杂。是的,是的,绝对如此。是的,那里。再说一遍。是的,这是一个很好的。是的,这是一个很好的问题。是的,好的。所以他们有一个 C++ 大会,由。这个大会非常非常频繁地举行。
你可以在网上看到他们的会议记录,那里讨论了基本上要,添加到 C++ 中的新特性,并讨论 C++ 目前的问题,如何解,决它们。志愿者,这也是。你提到了一个关于志愿者的好点子。
这是一个关于 C++ 的重要点,即与一些其他语言(如 ,Java)不同,C++ 不属于任何特定的公司或行业。开源的。Java 属于。 Oracle。Oracle,是的。所以与那些不同。
C++ 可以在任何地方使用。如果你想在你的业务中使用它,没有许可问题。确切地说,这也是。这一直是 C++ 发展的核心特征之一。因为它是开源的,人们一直在为语言编写扩展。有一组库叫做 Boost 库。
也算是。它们有点像 STL++。它们像是 STL 的扩展,还提供了很多独特的功能。所以我忘了我们是否覆盖了 Boost 库,但只是为了说明 ,C++ 是超级可扩展的,这也是它的一大特性,部分原因是它。
是开源的。所以给你们一个提示,展示你们五次讲座后的样子,这里是。
STL 能做的一个例子。
好了。好, 所以。
如果我们进入 STL 预览。好,这是一段我写的代码,用来做一些简单的功能。不要担心细节,但本质上,这段代码做的就是这三件事。它生成了 n 个随机数,并将它们放入一个向量中。
它使用某种随机排序算法对数字进行排序,在这个例子中,是冒泡排序,然后最终将它们打印出来。你会注意到,如果你们写过类似的程序,这可能看起来有些,熟悉。例如。
你使用 for 循环来遍历向量中的所有空间以填充它,们,然后你。不要在意冒泡排序。这只是这种特定实现的排序方式,但如果你实现过排序,那,可能就是你做过的排序之一。然后,再次,打印向量时。
向量没有内置的字符串,点到字符,串函数,所以你需要逐个遍历向量中的元素来查看。好的,我们来看看是否有效。
所以我们将。完美。看起来我们的程序做的就是生成了 20 个随机数,然后按,排序顺序输出它们。所以,如果你还记得你刚刚在程序中看到的所有内容,我们。
现在将其压缩成五行代码,看看会发生什么。所以,再次,我们从这个变成这个。让我们看看会发生什么。哎呀。让我。
完全相同的事情。所以,实际上,这真的在做的就是使用标准模板库中的算法,和预构建函数,将我们通常编写的代码转换成超级简单的,东西。所以,无论如何,这只是一个暗示,表明你们在接下来几节,课中将能够做的事情。
我想强调的另一点是,你可能会。你可能会想,既然我将其压缩成这些其他函数,那会不会比,直接写更慢呢?因为现在你不再像之前那样。 像谁知道它们下面发生了,什么?事实证明,STL 的另一个好处是。
由于它已经成为 C++ 的,标准库,人们一直在努力使这些函数的实现尽可能高效。所以,即使你想重写其中一个函数,例如你可能自己见过的,生成函数,它也可能不如 STL 版本高效。
所以。我可以再加一句吗?可以。你能给我那边的代码吗?
可以。所以,代码,是的。如果你看看之前的实现,这是一种非常机械的代码编写方。
式,对吧?你实际上是一个一个地遍历元素。你在移动它们。你一个一个地打印它们。但如果你看看 STL 库所做的事情,比如仅仅看代码,你可,能不能完全看出它在做什么,但你可以大概猜测它在做什,么,对吧?它是。
好的,你创建一个向量。你生成一堆数字。你对它们进行排序,然后将它们复制到某个与 CL 相关的,东西中。对吧?我们从一个非常机械的、非常低级的过程转变为能够利用,这些算法来解决问题,专注于更大的问题。
而不是机械地移,动数字。这也是 STL 的一个好处。是的,既然我们在赞美 STL,我们还要预览一个其他好处,那就是,实际上,如果我们想的话,我们可以完全用比如栈。
或者我们自己定义的随机对象来替代这个整数向量。STL 的最大好处之一是它具有通用性。这个具体的函数可以在任何满足特定条件的数据结构上工,作。不过,这只是 STL 的一个预览。接下来的几节课里。
我们将详细讲解这些内容。但这只是给你们一个例子,让你们了解为什么我们要花时,间讲这个。酷,所以,这就是我们要去的地方。还有我们喜欢的 Alex Stepanov 的一句话,再次提到 C++。
的目标之一就是追求效率,这真的帮助我们理解问题。是吗?你必须把它加上去吗?是的,绝对是。所以这些函数特别地出现在一个名为算法的头文件中。但我们也会在另一节课中深入讲解算法。是的。
我们会在那里展示给你们看。太棒了。所以在我们可以进入所有这些酷东西之前,我们也想专注,于序列容器、关联容器和适配器,所以请耐心等待,因为其,中很多内容你们可能已经很熟悉了。所以我想强调的是。
C++ 版本的这些熟悉对象与你们在 ,CS106B 和 X 中见过的东西有何不同。序列容器实际上只是提供对元素序列访问的数据结构。序列容器是容器的一个子集,而容器是 STL 的一个方面。
容器实际上就是存储其他数据集合的任何对象。序列容器是一种特定类型的容器,其中数据是按顺序排列,的。例如,你可能对 106B 或 X 中的向量非常熟悉,它刚刚介,绍过。
然后这些实际上是 STL 定义的其他四种序列容器。我们不会讨论这底下的两个,但我们会讲解前两个。所以 C++ 向量实际上与你们在 106B 或 X 中熟悉的非常,相似。向量,依旧。
表示任何类型元素的序列。再说一次,向量可以是任何类型对象的向量,无论是 int、,string、struct(正如 Avery 刚刚教过的),甚至是其他类,型容器的向量。所以,好吧。
在我继续讲解下一个幻灯片之前,我想强调的一点是 C++ ,向量比 Stanford 向量实际上有更多的函数。所以我们在下一页提供了一个很好的表格,以便你们真正。
了解 Stanford 的向量与 C++ 向量的不同,但要知道我们,的向量还有更多内容在页面上没有展示。所以不要担心屏幕上有多少文字。我们主要是为了给你们提供一个参考,以便你们以后需要。
时可以查看幻灯片。我们想突出的几个不同点是,与 Stanford 向量相比,你们,在向量中添加元素时,调用的是 v。addk。在标准向量中,类似的函数叫做 pushback。
你会发现 pushback、pushfront、popback 这种操作在其,他标准 C++ 数据结构中也会看到。另一个我们想强调的区别是,从向量中检索元素,在斯坦福,你使用 。
geti 函数或括号表示法。在 C++ 中,标准 C++ 中,get 的对应方法是 at。但是,括号表示法也有细微的差别,我们会很快讨论到这个,问题。最后,类似地,存储值到向量中时,使用的是 at。
而不是 ,get。在我继续之前,有没有什么问题?是的,请说。一般来说,你知道为什么在 B 和 X 中我们要使用斯坦福,的库吗?是的,所以问题是,为什么在 B 和 X 中我们使用斯坦福的,库?实际上。
这对讲师来说是非常有意义的,因为像 106B 和 ,X,甚至 A 这样的课程的目标,是教你 C++ 的通用设计原,则,或者像递归这样的概念,你们将在本季度稍后学习,这。
样他们不希望你被语言的具体细节困扰,因此他们提供了,一个比标准 C++ 更小但更清晰的接口,而标准 C++ 当然,是一个非常混乱的环境。我还要补充两点。一点是,要正确使用 STL 库,你必须理解迭代器。
安娜将在,下一节课中讲解。我首先要说的是,下一节课我认为是 CS106L 中最重要的,一节,关于迭代器。如果你没有听到,下一节课是 CS106L 中最重要的一节课,而且我们不会录制它。我们可能会录制。
我们可能会录制。所以,是的,要理解迭代器,你必须理解迭代器才能完全正,确地使用 STL。所以,迭代器可能需要一周时间来讲解,这就是我们正在做,的事情。所以,当你想要成功时,你只有 10 周的时间。
你没有一周时间来讲解这个内容。另一个问题,对不起,我还有一个问题。哦,STL,安娜会谈到这个,但根据 C++ 的理念,如果你做一,些事情,C++ 相信你会做得对。如果你做错了什么。
C++ 是非常不宽容的。STL 库如果你做错了什么也是非常不宽容的。所以,是的。这实际上是一个很好的引入,因为我们现在将看到一个例,子。我们时间怎么样了?哦,等一下,好吧。好的。
所以我提到有一个关键的区别,就是这种括号表示法,或者更普遍地说,是在向量中获取和替换元素的方式。所以,让我们快速看看原因。
所以,你想看到的主要是,我实际上要删除。好的。所以,我已经写了一个函数,专门为我们打印向量。你会注意到我没有使用我们之前用过的漂亮的算法。这只是因为,一方面,展示另一种方法,另一方面,因为我们。
还没有讨论过它。所以,我们暂时保持原有的方法。你会注意到我使用了 Avery 刚刚教的 auto 关键字。所以,再次,如果我们想创建一个向量,称之为 vec。然后,实际上,我在这里展示给你。所以,再次。
就像斯坦福库一样,我们使用 pushback,而不,是 add。让我们做这个,让我们做这个。是的,谢谢。非常感谢。酷,然后如果我们想,我们可以打印出来。但在我们这样做之前,我还想展示一下,啊,好吧。
如果我想的话,我们还可以看到 vec。get zero。
那么,当前向量位置是什么?所以,它是 names。
实际上,谢谢你。
哦我的天,我骗了自己。实际上,这是一种,讲师们经常说的,但的确,这是一场突击,测试,你们。干得好,抓住了它。如果我们在这一点上可以的话。
太棒了,所以我们再次看到,pushback 和 get,哦,对不起。
按预期的方式工作。好,那么现在我们可能会有一个问题,那就是如果我们尝试,在一个没有任何东西的索引上使用 at 会发生什么?所以,如果你在 106B 或 X 里试过的话,你会知道它抛出,的是什么。
那就是所谓的数组索引超出范围异常。那么,让我们用这个试试。我们使用 names。at2,这个索引应该不存在,因为目前程序。
中只有零和一这两个索引。
好的,所以事实证明,当我们使用 at 函数时,标准 C++ 的,做法与斯坦福库做的非常相似。我们得到一个超出范围的异常,如果我们愿意的话,我们可,以捕捉这个异常,并对其进行其他处理。
我们要强调的一个重要区别是,这个区别会让很多人在从,斯坦福切换到标准 C++ 时感到困惑,那就是注意有两种方,式来访问和存储向量中的内容。在斯坦福库中,你可以使用 names。get 或仅仅使用 。
names。bracket 符号。在 C++ 中,这两种符号仍然有效,names。at 和 ,names。bracket,但有一个关键区别在斯坦福库中是不存在,的。所以,如果我们尝试使用 names。
to,然后只是为了确认,我,们可以像这样说,嗨,我到了这一点。
结果 C++ 做的正是 Avery 之前谈到的那样。
与斯坦福库抛出异常不同,当你超出范围时,C++ 不会这样。
做,这是不应该发生的。再给我一点时间。这是正确的。它打印了补充会议。是的。实际上,这里。
我们会更加小心。
好的。所以,我会回去调试这个,但本质上标准 C++ 库中的 ,names。bracket 符号不会检查你是否超出范围,实际上,更,糟糕的是,它会悄悄地失败。所以。
类似于你们在 Avery 的直播讲座中看到的一些内容,它不会让你知道出了问题。即使在这种情况下,你也会注意到它实际上没有让你知道,实际上它不是向量的一个元素。我实际上对为什么这个会工作有些怀疑,但是的。
你们有问,题吗?这是一个很好的问题。那么,当你超出范围时,为什么不会出现所谓的分段错误呢,我相信这里可能发生的是。所以,不一定。分段错误会发生在你访问受保护的内存时,但在某些情况,下。
例如它是一个局部变量时,可能那段内存实际上是空闲,的。所以,你实际上是在获取垃圾数据。但的确,我会回去调试这个,不过从中得到的教训是。
一个关键区别是,当你在标准 C++ 中使用括号符号时,要,知道你必须先检查是否在范围内,因为 C++ 不会为你告诉,你。好的,所以我想问你们一个问题,为什么 std 向量默认不,检查边界?
有人有快速的猜测吗?是的,在后面。如果你在检查这一点,或者如果你正确地设置你的东西,那,么你就不需要再检查它了。完全正确,C++,就是这样,完全对。所以,再次强调一下,C++ 真的不会手把手教你。
这在你想,做一些非常具体的事情时是很好的,因为你可以用它做很,多其他语言做不到的事情,但不好的地方是它不会告诉你,什么时候做错了。所以,是的,回到哲学上。是吗?
如果你使用括号表示法来设置向量的第400个元素,会发生,什么?比如,它会对中间部分做什么?是的,很好的问题。所以,事实证明,根据底层的实现方式,它实际上不会检查,中间部分。它所做的只是。
你可能会在107中学到更多,基本上是从开,始位置加400,然后只检查第400个元素,或者设置它。所以,当你遍历时,它会跳过所有的元素吗?所以,如果你遍历它,那将是另一个问题,因为你在尝试遍。
历一个不长于两个的向量。所以,你实际上是在遍历垃圾。是的,最后一个问题,然后我将继续。所以,这是否意味着括号表示法的运行速度会更快?没有人,或者实际上,Avery,你知道吗?我认为答案是肯定的。
我的意思是,因为如果你考虑一下边界栈在做什么,它实际,上就像一个if语句。是的,对,这是真的。所以,是的,括号表示法将少一个if语句。它不会,实际上,它不会抛出错误,而点表示法则会。括号表示法。是的。
完全正确。是的,在大多数C++代码中,你几乎不会看到有人使用点表,示法。是的,是的,不,实际上这是一个很好的观点。太棒了。好的,关于向量还有最后一件事。我相信Cynthia在106B中讲解过一个概念。
就是在向量的前,面添加一个元素。结果是,这个问题在C++标准向量中仍然存在。那么,有人可以告诉我,尝试在向量的前面添加一个元素与,在后面添加有何不同吗?有人知道吗?我们来讨论一下。
然后你必须移动所有在之后的元素。完全正确,他说的是,你必须移动所有在之后的元素。所以我们可以看到实际情况。如果我们想尝试移动七号,我们只需要在做之前将所有元,素向后移动。所以,是的。
这确实不是我们想做的事情。所以,事实证明,这也是一部很棒的电影。我认为,是的,如果你还没看过,《疯狂动物城》很棒。所以,事实证明,C++也考虑到了这一点,实际上构建了一个,很多人没听说过的数据结构。哦。
对不起,好吧,在我们做这件事之前,再次强调一下,何,时使用每种东西。你会发现,向量几乎总是正确的选择,因为向量是如此基础,以至于它非常快速、轻便、易于使用。然而,有时我们确实需要这种前插的额外能力。
所以,回到我的讲解,C++实际上发明了一种叫做双端队列,的数据结构。所以,双端队列,发音为“deck”,是一个双端队列。本质上,它只是一个具有这种高效地推到前面能力的向量,所以。
如果这个术语对你有意义的话,它实际上可以在常数,时间内推到向量的前面。我实际上会跳过双端队列是如何工作的,但可以在之后查,看幻灯片。本质上,它是其他固定大小数组的数组,这就是它能够不需。
要移动每个元素的原因。但是,是的,我推荐你回去查看幻灯片。你实际上不需要知道它是如何实现的就能使用它。这只是有点有趣。在 CS106B 中,你会在第六周或第七周学到更多关于它的,内容。哦,他们会这样吗?
是的。好的,是的,所以这有点酷。他们现在引入了双端队列。有时它会出现在考试中。啊,好吧,那很有趣。所以,你们现在有了一个提前了解的机会。所以,好吧,在我刚刚告诉你们的双端队列的描述中,它本。
质上是一个向量,但具有以非常高效的方式推到前面的附,加功能,可能会有一个自然的问题,那就是,我们为什么还,要使用向量呢?事实证明,就像生活中的一切一样,一切都是权衡取舍。所以,为了获得额外的速度。
双端队列在访问向量中的元素,时不如向量那么快。所以,像 names[i] 这样的操作在双端队列中的速度不如,在向量中快。所以,是的,实际上,我认为这有点酷。所以,我要给你们展示一个例子。
这实际上是为了强调向量和双端队列的不同优势,通过展,示时间来说明。所以,我们实际上可以。我明白了。我会为你们做插入操作。点击这里。点击它,是的,它会变成。哦,我明白了。是的,然后按运行。好的。
我们还想这样做,以展示我们实际上在使用 Qt Creator。命令行界面有点轻量级,但是。好的,所以这段代码是在向一个向量中插入大约一百万个,元素,并比较推到后面和推到前面的速度。
你可以看到紫色线代表推到前面,绿色线代表推到后面。所以,你可以从中看到向量实现推到后面和推到前面的速,度差异。这只是一个指示,哦,好吧,实际上,也许我们确实想要一个,特别专注于加快推到前面速度的东西。
所以,如果我们尝试使用双端队列。
是的,然后推到后面。是的。太棒了。再次,实际上是向一个双端队列中插入大约一百万个元素。
并比较推到后面和推到前面的速度。你会注意到推到。对不起,你会注意到。首先,你会注意到 y 轴非常小,而另一个的 y 轴上限大约,是 400 之类的。这个图表的推到前面和推到后面都在 20 的尺度上。
所以,这几乎快了 200 倍。是的。是的,很好的问题。y 轴的单位是毫秒。是的。是的。对于向量的情况,我想知道,为什么它不是完全线性的?啊,为什么它会呈指数增长?我意思是,它甚至不是指数增长。
我觉得这有点像是与你的问题在分段上有所不同。明白了。你必须遍历每一个吗?像,不应该直接根据元素的数量来进行吗?啊,所以,问题有点像是,为什么图的表现是那样的?我得再看一下图,但我的假设是图呈指数级增长。
因为随着,元素数量的增加,每次推送到前面,你每次都需要推送更多,的元素到后面。因此,它所需的时间应随着元素数量的增长而增加。指数级的二次方。是的,相同的概念。是的,明白了。所以,这再次向我们展示了,啊。
好吧,双端队列正在按预期,工作。它的插入尾部和插入头部都非常非常快。那么,最后,在我们的时间讨论中,为什么我们不总是使用。
双端队列呢?
嗯,因为如果我们尝试,这个程序将会比较向量和双端队列。
在插入,即访问向量或双端队列的元素时的表现。所以,在这种情况下,我们很快就会看到,你已经可以看到,它将要花费多长时间,我可以告诉你,花费长时间的原因是,因为双端队列。所以,再次,我们有一个向量。
这个程序告诉我读取该向量,或双端队列每个索引的值需要多长时间。哦,我明白了。对不起,你们,我实际上以为我去了五的地方。所以,之后,我会让你们离开,我可能会在周一抽出一些时,间来讲解最后几件事。提醒一下。
下一节课可能是整个学期中最重要的一节课。
是的,再说一遍。在这种情况下,你可以看到我们在1500的数量级上,双端队。
列比向量访问要慢很多。所以,从中得到的结论应该是这样的。默认使用向量。当你真的只处理数据结构的前端或后端时,使用双端队列,太好了。课外问题。课外问题,并且作业已经在网站上发布了。祝你们好运。
我能问一个很快的问题吗?是的,问吧。所以,我今天刚来第一个课,并且我在Access上添加了它。除了复习以前的讲座和将自己添加到Piazza上外,我还需,要为我的课程做其他准备吗?是的,对不起。再说一遍。
除了将自己添加到Piazza上和查看讲座外,我还需要做其,他事情吗?这是个很好的问题。你应该已经准备好了。实际上,作业应该完全依赖于我讲解的内容。我们开始吧。哦,你们要离开了吗?我们必须出去。
但我们不想等到第二天早上再上课。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P5:[11]CS 106L Fall 2019 - Lecture 4_ Associative Containers (Screencast) - GPT中英字幕课程资源 - BV1Fz421q7oh
好的,然后,等等,让我再检查一下,嗯,没问题,然后,嗯,我。
们也可以尝试运行所有示例,以确保它们能正常工作,虽然。
由于我们没有这些 SAMHSA 库,它们应该会编译得非常快,等等,等等,祈祷吧,是的,我不知道,好吧,我不知道,好吧,酷,那你怎么退出?
好的,好的,如果它不工作,就再运行一次,好吧,如果有人。
问,那个错误是什么?我们会说,Qt Creator,给我一点提示。好的,这样可以,很好。
是的。
它已经在那里了。是的,但我有点想改一下,我有点想做。哦,等一下,好吧,我会重新上传 PDF。哦,它在这里?是的,它在那儿。好的,好的,没问题,我会在 Piazza 上宣布的。它在幻灯片中。它在幻灯片中。
好的,没问题,我会在 Piazza 上更新的。是的,我可能今晚会有办公时间,我会看看是否有人来,因,为我在考虑周四晚上,谁会在周四晚上来,因为 Windows ,XP 是周五的新作业。哦,是吗?是的。
哦,这样说得通。是的。明白了。但是,他们可能会在周四早上来,肯定会,然后我可能会有,一个像。今晚。是的,我会看看是否有人今晚想来,然后。明白了。是的。我会问一下。所以,关于办公时间的奇怪问题是。
如果我们要有办公时间,你会来吗?人们想要交流。因为这是一个重要的问题,我们正在尝试确定何时安排办,公时间。比如说,如果没有人来,那对我们来说会很尴尬。所以,你真的会来吗?是在晚上,是的。
Avery 的会在晚上,我的是周四早上,以防一个时间对人们,不合适。好的,好吧,好吧。我们会看到的。太棒了。所以,我在考虑做的事情是,因为周四晚上,我在想实际上,没有人想要在周四晚上做。
Windows XP 作业是周五到期的,Windows XP 什么时候到,期?周三。周三?周三,下午 5 点。好的,那么也许。所以,周三晚上。你说了什么?本周末下午 4 点。好的,我们来。周四。好的。
是的,因为那将在周四晚上。好的,我今晚会有办公时间。我会确定时间,然后可能,应该不是。哦,好的。好的,我今晚肯定会有办公时间,然后让我更新一下周四晚,上。周四早上,Anna 会有办公时间。是的。
周四早上,我会有办公时间,但 Avery 的是到。所以,我今晚会有办公时间。我会尽快在 Piazza 上发布。今晚大约 5 点到 7 点左右。好的,应该会很轻松。我会在那里做我自己的事情。
除非有人来找我。好的,即使你还没有开始做作业,也可以来。你不需要准备好任何问题。你可以直接来,你可以坐在那里做你的事,如果你有问题,我会就在那儿回答。确实,这点很好。是的,这点很好。是的。
如果你对我们讲过的任何内容有疑问,我知道我们在,过去两周中讲了很多东西。随时过来也可以,好吗?Anna 的办公时间是周四早上 9 点到 11 点?早上 9 点到 11 点,早上 9 点到 11 点。
好吗。然后,记住,你可以随时在 Piazza 上发帖。好的,我们接受私人帖子。如果里面有代码也没关系。只要确保是私人帖,我们才能私下回复。是的,我们今天会在讲座中快速概述作业 1。所以。
你们会觉得准备充分。今晚,我可能会在像 Old Union Pub 这样的地方做一些事,情。我通常是这么做的。好的,我知道我的一位老分组成员在这里,但如果你曾经参,加过我的分组。
我会在 Old Union 举办办公时间。我在 Old Union 举办办公时间,并且我会在 Realistic ,购买他们。看看。你在那儿吗?不,我不在那儿。你不在?好的,你错过了很多。是的。
我们在 Old Union。所以,由于作业在这两周到期,所以我们会有更多的办公时,间。我今天会有办公时间,从 5 点到 7 点,或者到人们离开为,止。然后 Hannah 会在周四有办公时间。周四,是的。
9 点到 11 点。如果你想在其他时间见面,请告诉我们,我们会想办法解决,是的,这些只是为了安排每周的时间,但我们非常灵活。我的办公时间现在是在 Trusseter 二楼的露台上,面朝 。
Dinko Street 大礼堂,虽然名字很长,但非常值得。视野非常好。但如果人们找不到那里,可能会有变化。你介意我再用一次你的浏览器吗?我会迅速写一个帖子。你想以你自己名义登录吗?
我可以以你的名义发帖吗?是的,你可以以我的名义发帖。好的。我会说我的办公时间是。我会用第三人称提到自己。我还想统计一下有多少人来。23 人。23 人中有 60 人。你呢?是的。比。 229 要好。
可能比 106B 好。比大多数要好,我认为。嘿,106B 的同学们,礼堂还爆满吗?不。不,还是有人到场。好的。礼堂有多满?半空。半空?好的。我们正在做。实际上,他们已经超额注册,所以我们做得稍微好一点。
在这方面我们做得稍微好一点。干得好,你们。是的,嗯。我会在大约一半的地方。大家好。对不起。好的。我们开始吧。正如你们从上节课可能记得的,Avery 说这节课是最重要,的课,所以你们已经做得很好了。
但这是真的。今天,我们有一些非常令人兴奋的主题要教你们,所以我们,将开始,希望能覆盖所有内容。首先,如果你们还记得,我们在调查中询问了关于任何糟糕,的爸爸笑话的问题,所以你们将听到一些来自同伴的精彩。
片段。如果你是写这个笑话的人,请随时自我介绍。否则,我只是用首字母给你们归功。好了。今天,我们将覆盖。我们会迅速回顾一下上次没完成的主题——容器适配器。然后我们有几个公告要宣布。
之后我们将深入探讨关联容,器和迭代器,这将基本上是你们在这门课程中学到的最基,本的概念,几乎高于其他任何内容。好了。简要回顾一下,如果你们还记得上周四,也就是第二周,我,们讨论了序列容器。
在标准C++库中,有五种类型的序列容器。向量和双端队列是我们周四讨论的两种类型。还有列表、数组和前向列表。这些仍然很有用。如果你们有兴趣了解更多,我强烈建议去cppreference网,站查阅。
但我们不会在这里讨论它们。作为提醒,斯坦福的向量和标准向量之间的主要区别是。at,和括号函数之间的区别。请记住,at会在访问超出范围的索引时抛出异常,而括号,表示法会导致未定义行为。
你们还记得上次的例子其实有问题。当时我说它不会工作,但不知怎么的,它却仍然工作了。事实上,我们都不知道,那正是预期的行为。事实证明,当你尝试使用括号表示法访问超出范围的索引,时。
它实际上会导致未定义的C++行为。这个术语,未定义行为,是你们在许多C++文档中会看到的,基本上,这意味着事情出了非常大的问题。原因是因为行为实际上是不可预测的。例如,在像我这样的Windows机器上。
调用vec i超出范围的,索引后,你的程序可能会直接停止运行,但也不会告诉你有,什么问题。在Mac上,例如,它可能会继续运行,好像什么都没有发生,这意味着你不能预测它,这也是需要记住的一个重要区别。
我们讨论的另一个部分是双端队列。简要回顾一下,双端队列做的所有事情都与向量相同,但它,增加了一个功能,就是你可以非常快速地将元素推送到数,组的前面。但它的缺点是访问中间元素会比较慢。再次强调。
如果你想学习你拥有的工具并了解何时使用每,一个,现在你们知道了双端队列和向量之间的权衡,因此你,们知道在不同的应用场景中该使用哪一个。再一次,使用我们最喜欢的思考者雕像,默认使用向量,如。
果你主要在开始或结束时工作,则使用双端队列。在我们继续之前,有谁对上节课的内容有任何问题吗?有吗?我知道斯坦福库里有一个叫做栈的东西。栈是否与双端队列有关?再说一遍。栈适合这个吗?对,栈。
你们真是太贴心了,因为这正是我们接下来要讨论的内容,很好的问题。在我们继续讨论序列容器之前,还有其他问题吗?完美。谢谢。这是一个很好的问题。你们可能从 1。6b 或 x 里记得你们学过一些叫做栈和队。
列的东西。事实证明,标准 C++ 库中也有这些,只是被包装在一个不,熟悉的名字——容器适配器中。稍后我们会看到为什么会叫这个名字。简要回顾一下,你们应该最近刚刚学习过这些内容,但如果,已经有一段时间了。
栈有两个主要功能。你可以将一个元素推入栈顶,或者你可以从栈顶弹出它。后进先出。另一方面,队列,你可以添加。哦,底部有一个 PDF 的东西要出现。你可以将一个元素添加到队列的末尾,或者从队列的前端,弹出。
就像你在商店里看到的排队一样。其实我有一个问题要问你们。鉴于你们对 C++ 中的 vector 和 deque 了解的内容,标,准 C++ 中,实际上没有同时存在的魔法,一切看起来都像,是魔法。
但你们认为 C++ 是如何在底层实现栈和队列的?有人有想法吗?是的,最后面的那位。再说一遍。是的,这是一个很棒的猜测。事实证明,C++ 的栈和队列在底层其实就是向量和双端队,列,但功能有限。
如果你查看在线规范,它的样子,我们实际上会在下一张幻,灯片上看到,但只是确认一下,栈实际上就是一个向量或双,端队列,但仅允许我们看到的 push_back 和 pop_back 函,数。
而队列实际上就是一个双端队列,只允许你将东西推入,双端队列的后端,并从双端队列的前端取出东西。此外,栈和队列都确保你只能访问栈和队列的最顶部元素,是的,Brian?如果它们实际上是作为向量实现的。
那它们真的会更好吗?也许因为我以为我们被告知由于功能的限制,它们更好,但,如果它们在底层实际上是向量,那。很好的问题。问题是,既然它们实际上只是向量和双端队列在底层,它们。
的速度或效率是否与向量或双端队列基本相同?我将在下一张幻灯片上回答这个问题。做得好。我们继续预测接下来会发生什么,正如 Avery 所做的那样。
。非常快,你们可以自己看到这一点。再次强调,我们希望在这门课程中教会你们如何阅读 C++ ,文档。如果你查看栈或队列的 C++ 文档,你会看到在你们笔记本,上的小字体中。
实际上写着栈和队列实际上只是建立在向,量和双端队列上的。正因如此,它们被称为容器适配器。它们实际上不是容器本身。它们实际上只是将向量和双端队列容器适配到这个特定需,求上。我认为这是个很好的问题。
我的问题是,既然如此,当你有一个向量或双端队列时,为。
什么还要使用栈或队列?为了解答这个问题,我们再次回到我们最喜欢的 C++ 设计,哲学之一,那就是我们希望在代码中直接表达思想和意图,当我们有类似于星巴克的排队情境时,我们知道我们需要。
的唯一功能是将某人添加到队伍末尾或从队伍前端取走某,人,我们希望在代码中直接表达这种意图。这就是为什么像队列或栈这样的常见用例最终有了自己的,类型,称为容器适配器,其中有我们可以直接使用的类型。
另一种风格是,我们希望向开发者展示,我们实际上不需要,向量或双端队列的其他功能,我们只需要这种特定的功能,那么来回答Brian的问题,哪个更有效率?事实证明,因为我们保证在栈或队列上唯一可以使用的操。
作是pushback、popback和popfront函数,这些函数实际上,都是常数时间函数,如果这对你来说没有意义也没关系,但,它们实际上比向量或双端队列要快,因为它们只使用了向。
量或双端队列上的最快函数。向量或双端队列对这些特定函数的效率是相同的,但栈或,队列确保你只使用这些快速函数。是的,然后Avery想要补充。我可能会补充一些,即你看到的第一个哲学给程序员完全,的控制权。
如果你回到前一张幻灯片,再看一张幻灯片,是的,如果你,查看文档,你会在那模板的部分看到,哦,是这个你看到的,红色框,这个框。你打算谈论这个吗?不,你说吧。好的,是的。假设你正在编写自己的程序。
你不喜欢栈或队列使用双端,队列的事实。假设你想要栈使用其他数据结构。那么你实际上可以在创建栈时,指定不使用双端队列,使用,其他数据结构。如果你喜欢向量而不是双端队列,当你声明栈时,你可以说。
栈括号T逗号向量。那是你看到的第二个参数,即容器。默认情况下,它给出的默认值是双端队列,但你实际上可以,给它任何你想要的容器。
这是一个很好的观点。关于C++的一点是,这些类尝试隐藏它们下面的内容。如果你不想考虑它们,你可以像你期望的那样使用栈。但如果你关心的是挤出最后一点性能,你可以自定义它。
你可以创建自己的特殊数据结构来作为栈使用。是的,实际上这是一个很好的观点。是的,那么在我们继续之前,有人对栈或队列有问题吗?
是的,问题很棒。问题基本上是,你如何使用自己定义的数据结构?事实证明,C++有,或在这个特定情况下,您的自定义数据结,构需要满足特定的要求。在这种情况下,它们必须满足称为序列容器的类型的要求。
只要你满足这些要求,在这种情况下,例如,栈的pushback,和popback功能,你就可以使用你自己的类。但是的,你需要为你的定义它们。是的,这是一个很好的问题。还有其他关于栈或队列的问题吗?
太棒了。好的,到目前为止,我们实际上已经涵盖了标准库中的两个,大部分,序列容器和容器适配器。所以实际上还有另一种类型的容器,称为关联容器,它们也,是一种你可能在106B或X中已经熟悉的类型。事实上。
如果你现在正在上这门课的话,我相信你们上周五,刚刚学到过这个内容。所以再次强调,关联容器是一种没有序列概念的容器类型,你不能通过索引0、1、2、3、4等来访问它们。相反,数据以键值对的形式存储。
在C++标准库中,有四种类型的关联容器,映射和集合你可,能已经很熟悉了。还有两种叫做无序映射和无序集合的东西,我们今天不会,详细讨论它们,但会简要解释一下它们是什么。如果你感兴趣的话。
你可以自己去查找它们。在106B或X中,你可能已经学过映射或集合是什么了。那么有人能快速告诉我,映射是什么吗?映射的定义是什么?是的,请说。所以它基本上就是一个存储两种不同类型数据的大集合。
你定义这两个数据类型的集合组成是什么。然后当你调用映射时,你有一个键,即第一种数据类型,它,将检索关联的第二种数据。完全正确。这正是为什么它们被称为关联容器的原因。
你将一种键的数据类型与一种值的数据类型关联起来。完全正确。还有一个问题,你们认为在映射或集合中可以有多个相同,的键值吗?谁认为可以?谁认为不可以?好,好,非常好。是的,你们已经非常熟悉了。那么。
与C++版本或标准C++版本中的映射和集合之间的一,些区别。一个需要了解的事情是,在底层,映射和集合会根据其键的,一些排序属性来对元素进行排序。它们不一定存储在像我们熟悉的向量那样的数组中,但它。
们是以排序的方式存储的,这意味着,例如,回到Avery提到,的定义你自己的类的想法,如果你想创建一个学生的映射,类,那么你需要定义自己的小于运算符,以便知道如何比较,学生,以便映射可以保持它们的排序。
这是我们在讨论类时会更详细地讲解的内容,但需要记住,的一点。所以再次强调,你必须在类上定义特定的操作,以便能够与,映射或集合等数据类型一起使用。无序映射和无序集合在功能上与映射和集合完全相同,只。
是它们在底层不会进行排序,这也意味着,如果你试图遍历,一个映射或集合,你不能保证它会以字母顺序出现。这些是无序映射和集合的工作细节。再次强调,如果我们定义自己的类,如我们将在学期后期做,的那样。
我们需要定义特定的内容以便能够使用这些数据,类型。但现在,我们不会深入探讨无序映射和集合。还有一件事我们想提的是,什么时候使用每一种?所以,结果是,常规的映射和集合,键是排序的,并且它们在。
迭代元素范围时非常快,因为它们是排序的,而无序映射或,集合,通过键访问单个元素更快。所以,我们不会深入讨论,但如果你们自己构建东西并想知,道使用哪种数据类型,那就是应用场景。我看到后面有两个问题。是的。
Manuka。是的,你提到运算符是为了小于运算符进行预测的。是的,这是一个很好的问题。所以,我得再看一下,看看是否有其他的运算符,除了小于,运算符。我相信对于映射和集合来说,小于运算符是唯一的要求,但。
我需要查找一下以确认。是的,这也是我们下周会讨论的一个概念,比如每个类是否,具有小于运算符这样的特殊属性。它们是否具有一些特定的属性,使得你可以将它们放入映,射和集合中?
我们将在下周讨论模板时讲解这个问题。是的,因为我认为映射和集合还要求元素是可复制的,类似,这样的要求。是的,确实如此。确实如此。是的。标准库中的集合是否也包括像引用所有运算符那样的操作,不是。
但下周我们会讨论一些算法,其中有通用算法会处理,这些功能。以重现这些确切的功能。是的。还有其他问题吗?是的。C数组是序列容器的一种吗?是的。所以,序列容器往往描述标准库中的东西,而C数组是C的一,部分。
实际上不属于STL。但它本质上是一个序列容器。好的。向量是建立在数组上的吗?这是一个很好的问题。这实际上与实现方法有关。这确实是C++讨论中的一个棘手问题。C++其实只是一个语言。
不同的组织可以选择以不同的方式,实现C++语言。也就是说,大多数人倾向于使用许多标准实现。例如,你可能听说过GCC,那是一个非常常见的标准实现。所以这取决于实现,但数组是最常见的实现方式。是的。
CS106B或X涉及数据结构的实现,可能会在两周内讨论。我们不会涵盖这个,但在两周内探讨这个是个好主意。是的,好问题。还有其他关于集合或映射的问题吗?社交容器。是的,再一个问题。Indica。关于映射。
是什么使得它们的查找时间如此快?这是个好问题。所以,这实际上是你将在其他课程中也会探索的问题。问题是,为什么映射中的查找比其他数据结构快得多,这是,你可能在106B或X中学到的内容。
所以你实际上会在像CS161这样的课程中探索这个问题。106B的最后一周,有时会涉及这个问题。有时106B的最后一周会讨论这个问题。但为了给你们一个初步了解,实际上映射和集合是通过一。
种叫做二叉搜索树的结构来实现的,如果你们听说过这个,的话。你们将会学习到更多的内容,但基本上这些结构被设计成,具有相对较快的搜索和访问时间,你们会学到更多的内容,并且在课后随时可以问我们更多问题。是的。
那是106B,第七周。明白了。是的。是的。抱歉,我只是想到,嗯,我的朋友说集合实际上是无序的。那么你说的“是的”是什么意思?所以,这实际上涉及到集合和映射的基本思想之一。我们教给你们的是,与向量相比。
它们并不是按顺序排列的,你不能说索引零包含这个元素一、二、三。我们把它们教成像云一样,基本上,集合中的一切都在某个,地方。但是你会注意到,如果你尝试打印集合中的内容,它们总是,以排序的顺序返回。例如。
如果你记得你学过一个叫做“每个循环”的东西,你对,集合中的元素进行循环,打印出这些元素,你会发现它们总,是以排序的顺序返回。这是因为我们不能通过索引零、一、二或三来访问元素。
但它们在底层仍然保持排序状态。所以它不是线性序列的桶,而是有序的。所以,这是一种奇怪的区别,这让我在第一次学习时感到困,惑。很好的问题。好的。太棒了。如果我们有进一步的问题,我们会在讲座结束时继续提问。
但是,作为C++映射的快速示例,我们想展示这个例子,部分。
原因是它与你们的作业也是相关的,作业一。所以我们在这里做的是,我们实现了一个叫做get line的,函数,它会要求用户输入响应,这个函数类似于你们在作业,中需要做的事情。如果你读过作业说明。
在建议提示和技巧部分,它会说类似,于参考讲座中的get line。就是我们提到的那个get line。然后对于这个程序,我们想要做的是,大部分程序已经写好,了。
我们只是想再次指出一些映射在实际使用中的例子。所以再一次,就像其他任何对象一样,这就是如何声明一个,映射,未初始化的映射,以及我们想指出的一些其他功能。那么如何访问映射的值,你可能在106B或X中见过。
是你做,类似frequency map,然后是键,然后设置它,你会说类似于,A,或者访问它,你会做类似于,呃,int,呃,int A等于,frequency map点单词。
所以我们想强调的一个重要区别是,哦我的天,就像使用向,量一样,frequency map点at,呃,对不起,点get函数与,frequency map括号单词函数之间的区别。所以在斯坦福库中。
我很确定这两者做的是一样的事情。但是在 C++ 的标准库中,点取函数会做,它会做的是它会,搜索映射以查看键是否已存在,如果不存在,它将抛出一个,错误。而在括号表示法中,它会检查是否存在,如果不存在。
它会,自动创建一个条目并进行默认初始化。大家明白括号表示法和点取表示法之间的区别吗?好的,非常棒。然后,我们要强调的最后一件事是,是的,你可以回去阅读,这段代码,它已发布在网站上。
但这是我们要强调的一个大,区别。是的,Manuka。是的,这是一个很好的问题。所以你使用点取函数还是括号表示法取决于你的功能需求,如果你想要它在条目不存在时自动创建条目,那么可以使,用括号表示法。
如果你想在添加之前检查它是否在其中,那么这实际上是,我们接下来要讲的内容。所以,这是一个很好的问题。如果你想知道,如何检查一个元素是否已经在集合中?你可能还记得从 Stanford 库中。
有一个叫做 map dot ,contains key 的东西,你可以用它来检查映射是否已经包,含一个键。我们实际上会教你 Stanford 库是如何实现这个的。而且事实证明有几种不同的方法。
其中一种方法就是你在这里看到的。所以映射有一个叫做 count 的函数,计算某个东西在你的,映射或集合中出现的次数。所以有人能告诉我 count 在映射集合中可能返回什么值,吗?所以再次说。
它是在计算一个键在集合中出现了多少次。你说对了。干得好。是的。所以这看起来有点奇怪。为什么会有一个只能返回零或一的函数呢?事实证明,C++ 确实有数据结构,可以有多个相同值的键指,向不同的值。
所以这就是 count 函数存在的原因,但正如你所说,对于,映射或集合,在无序映射或无序集合中,count 始终返回零,或一。所以在这种情况下,我们将其用作隐式布尔值来表示,如果,它存在,如果返回一。
那么我们知道它在函数中存在。否则,它不存在。大家明白了吗?有什么问题吗?
我再加一个超级不重要的旁注。如果你不喜欢 count 函数,我认为 C++ 20 正在发布一个,叫做 contains key 的函数。是的。真的吗?好的。太好了。
如果你不喜欢这个 count 函数的名称,你认为它返回一个,整数,但实际上你把它当作布尔值使用,那么我认为是 C++, 20 的 contains key。使用隐式布尔值,比如返回零或一,这最终会发生。
对吧?这是个很好的问题。问题是,使用隐式布尔值是否比手动检查是否等于一更快?我猜实际上设置了高优化级别的编译器会使这些结果一样,嗯,不过在这种情况下,使用隐式布尔值的方式是非常常见,的风格构造。
所以可以随意使用它。嗯,最后,它很可能会对你的程序产生影响。是的。很好的问题。是的。是的。是的。很好的问题。所以当你使用括号表示法而找不到时,它会自动添加默认,初始化,无论值的类型是什么。所以对于整数。
它会自动将值设置为零。是的。好的问题。好的。所以,这只是执行程序。你们可以去网站上查看代码,大家可以自己玩一玩。
嗯,不过只是为了展示一些函数的练习,嗯,是的。在我们继续之前,还有其他问题吗?太棒了。好的。所以,嗯,再总结一下关键点,嗯,再次,那应该是“get”而不,是“at”,嗯,获取和括号表示法的区别。嗯。
然后,嗯,如何判断它是否包含一个键。我们看到了“count”,嗯,在C++ 20中,将会有一个,“contains key”。实际上还有另一种方法来确定一个元素是否在映射或集合,中。嗯。
我们将在下节课学习那个方法,使用迭代器,这就是我,们今天晚些时候学习的概念。嗯,所以你们会看到多种不同的实现“contains key”的方,法。嗯,然后,嗯,集合,我们没有看到它的实际使用,但它的工。
作方式与映射非常相似。事实上,你可以把集合看作是一个映射,其中隐含的值是“,真”或“假”。如果它在集合中就是真的,如果不在集合中就是假的。所以如果你想知道什么函数在集合和映射上工作,嗯,几乎。
每个在映射上工作的函数也在集合上工作,除了“dot get”,和括号表示法,因为集合中没有值的概念。所以给你们的一些实用技巧,不过,嗯,我们也发布了一些,代码,如果你们想看看集合是如何工作的。嗯。
但我们会把这些在线保留,如果你们想稍后再玩。很酷。在我们进入下一部分之前,有关于我们所涵盖内容的任何,问题吗?好的。是的。一个问题。是的。我们实际上会讨论哪个方法检查键是否在映射或集合中更,有效。
我们将在覆盖时讨论这个问题。是的。不过这是一个很好的问题。很酷。所以我们时间安排得很好,你们。嗯,所以我们有几个公告,让你们暂时从C++中休息一下。首先,第一个作业已经发布了。嗯。
截止日期是这个星期四之后的那个星期四。嗯,但记住你们也有三个延期天数可以使用,其中两个可以,用在任何作业上。还记得你们需要完成三个作业中的两个。嗯,前两个作业通常比第三个作业短得多。嗯。
不过第三个作业真的很有趣。所以由你们决定。嗯,Avery和我会有办公时间。嗯,我的办公时间是每周四上午九点到十一点。嗯,在信托,大楼二楼的露台上,面对丁克拉格球场。请随时在 Piazza 上留言。
如果你不知道 Piazza 在哪里,嗯,那是一个很好的学习地,点。嗯,还有一个小修改,嗯,这周可能仅限于这一周,嗯,我会,有办公时间。我想我说了,嗯,今天是周二,下午五点半到七点半,嗯,在。
老联盟大楼内。虽然如果你没看到我,我可能在其他地方。嗯,然后我会在周三在那里。我说是什么时间来着?某个时间。我在 Piazza 上说了些内容。那是关于周三的 Piazza 帖子。是的。
所以幻灯片有些过时了。Piazza 上有我们的最终办公时间。嗯,酷。还有一件事,我们实际上想告诉大家,嗯,申请助教和担任,助教的申请现已开放。嗯,Avery,如果你想要提供帮助的话。哦,对不起。是的。
所以,嗯,每学期,嗯,CS1 项目,嗯,会联系到当前的学生以,及那些完成了 CS1 的学生,看是否有任何有趣的申请者。现在,既然你们都在 CS106L 课程中,想学习 C++,嗯,我相。
信你们所有人都会成为优秀的助教,教授,嗯,CS106 课程,现在,嗯,基本上,这个过程包括,嗯,你们可以在线申请,然,后,CS106L 协调员会联系你们获取更多关于面试过程的信,息。嗯。
如果你被选中进入这个,嗯,令人惊叹的社区,那么,嗯,这是一项为期两个学期的承诺,担任 CS106 课程的助教。是的。一个六的课程。好的。是的。可以在课后跟我或 Avery 讨论。嗯。
这真的是一个很棒的社区。嗯,这实际上是我们了解到这个工作的途径。CS106L 讲师。嗯,然后你会有很多很酷的机会,比如和教授、我、行业人,士在特别活动上交流,诸如此类的事情。所以绝对推荐。是的。是的。
所以,如果你在 CS106 课程中,或者你刚好在 CS106A 或 ,X 课程中,你的助教,嗯,就是这样做的。这正是你将要做的工作,就是教授你自己的一节课。是的。哦,对了。我们有点奇怪的术语。嗯。
TA 在技术上指的是,嗯,是 Catherine 吗?Catherine?是的。是的。所以,嗯,是的。所以你可能会听到一些人交替使用这个词。这个工作是为了担任一个 CS106 课程的助教。是的。去做吧。
你想知道,嗯,它是否与 CS1060 一样,即一个迟到的天数,是下节课的时间。很好的问题。所以问题是,迟到的天数是 24 小时还是下节课的时间?因为我们在周二和周四上课,嗯,将迟到的天数从周四到周。
二有点奇怪,因为这段时间非常长,而从周二到周四则正常,嗯,我们决定设定为 24 小时。也就是说,我们这是一门单元课程。在截止日期方面,我们非常灵活。所以如果你有问题,可以先跟我们沟通,我们总是可以找到。
解决办法。但确实,延期天数是24小时。而且你有三天延期时间可以使用。是的。实际上,这也是一个很好的引子。所以今晚我们还会发布一份反馈表,因为我们希望听到你,们的意见。我们如何改进?比如说。
我们的进度是太慢还是太快?你们还想学些什么?类似的东西。所以,按照惯例,如果你填写了反馈表,你还可以再获得一,天的延期时间。是的。所以再次强调,我们在这里是为了与你们合作,但我们希望。
听到你们对这门课的想法,以及你们对我们的任何反馈。另外,那些通过视频观看的朋友,也请你们填写这份反馈表,是的。对视频观看的人致敬。唯一一个不应该写的评论是,视频质量差。好的。但其他的呢?是的。
请评论我们真正想听到的内容。这门课的内容也是灵活的。所以我们真的希望知道你们想学什么。完全正确。太棒了。是的。还有其他问题吗?如果没有,那就好。所以第一份作业在这个周末发布了。我们非常快速地想做的是。
让你们了解你们到底在做什么。
。你们可能已经在网上看到过这个,但如果你去106L网站上。
的作业页面,你会看到,如果你点击作业,它链接到一个实。
时网络演示。所以,这与你们将要构建的东西有些许不同,因为这个是在,网络上实现的,而你们的是用C++实现的。但这基本上是你们编程的输出结果。所以,为了给个概念,我们可以点击查看,我会把它调小一,点。
你们可以看到像这样的三网格,它开始时是一些随机的点,排列,最后变成一个美丽的图形可视化。所以如果你还没有阅读作业说明书,一定要去看一下,但你,们也可以在网上找到这个并进行操作,了解你们最终的程。
序应该做什么。这也是为了激励你们,看看你们所构建的东西——虽然是非,常简单的标准C++工具——是多么酷。还有一件事我们想提的是,嗯,顺便看看列表上的内容,看,所有的东西如何工作也挺有趣的。
但我们还想提的是,作业规范看起来比较长。大约有14页,我相信。实际上作业本身并不长,我们向你们保证。实际要写的代码行数不会太多。所以如果你遇到任何困难或者规范中有不清楚的地方,绝,对要联系我们。
我们在这里是为了回答问题,帮助解决问题。是的。有问题吗?
实际上,在我开始之前,有几个我想强调的点,以防它们对,你们有用。在作业的底部,有一个叫做建议、技巧和窍门的部分。他们提到有一个建议的循环结构,你应该在课程阅读材料,的第三章中使用。嗯,有些人可能会觉得。
课程阅读材料在哪里?所以这个公告的目的部分是宣布,实际上,我们有课程阅读,材料。这个课程阅读材料是由Keith Schwartz编写的。如果你上过他的课,你可以在网站的右上角找到它,在课程,阅读材料下。
这门课实际上并不完全遵循那个课程阅读材料,但它是一,个很好的参考。
这就是我们所说的课程阅读材料。然后,我们今天在我们的地图示例中刚刚看到 get line。所以这就是我们在作业中所说的内容。最后一点是,关于风格建议,一般来说,在这门课中,我们不。
会像第一个六门系列那样强调风格。不过,作为程序员,养成一些好习惯对你自己是很有好处的,其中之一就是分解,比如将你的程序分解成更小、更易读,的部分。然后,例如,在这个作业中使用常量。
比如很多随机数像 pi,嗯。所以,如果你们对这些有任何问题,一定要告诉我们。是的。好问题。我们可以使用斯坦福库来完成这个作业吗?答案是不能。所以,这个问题很棒。这可能是你们习惯的。
但这个作业的目的是让你们真正动,手使用真正的行业标准C++。所以,再次强调,斯坦福C++库在我们离开斯坦福后不会存,在于现实世界中。遗憾的是,它们对学习概念非常有用,但这部分不是挑战你,们的概念。
而是让你们真正挑战语法的正确性。所以,是的,我们确实希望你们使用标准C++。如果你们有关于如何开始的问题,请问我们,一定要问我们,是的。好问题。我会补充一点,就是在过去的四节课中,我们已经学到了足。
够的内容,实际上可以实现大部分斯坦福库函数,特别是像,文件IO和其他各种内容。在第二节和第三节课中,我们实现了 get integer。如果你记得的话,这可能对你的作业有帮助。而且。
网上的示例代码还展示了更多示例,比如如何实现像, prompt user profile 这样的功能。好了。所以我不推荐直接复制粘贴讲座代码。确保你理解代码,并能够在作业中编写类似的代码。太棒了。是的。
另一个问题。PowerPoints 都在 cs106l。stanford。edu 网站上。是的。点击讲座标签。是的。好问题。是的。嗯,是的,这是一个很好的问题。在这种情况下。
像 PI 这样的常量应该是全局的,因为我们,的东西只是一个单独的文件。我们没有与多个类进行交互。可以将它们设为全局。嗯,如果你对全局的意思感到困惑,可以来找我们。嗯。
我不确定 cs106b 和 x 是否涵盖了这个。没有。嗯,所以你可以将它们设为全局常量。是的。是的。如果有时间的话,我会在讲座结束时运行一个全局常量的,简单示例。但这确实是个好问题。
几周后我们会讨论命名空间。太棒了。好的。在我们深入之前,我想说的是,这一部分实际上是 C++ 的,基础构建块。所以要兴奋,因为它非常非常酷。再一次。嗯,这就是我们现在在课堂上的位置。
我们正在学习标准模板库。到目前为止,你们基本上已经学习了关于容器和容器适配,器的所有内容。你们已经学习了 SDL 中五部分中的两部分。嗯,你们会注意到容器和适配器都指向迭代器,也就是说。
它们都使用一种叫做迭代器的东西。我们会在稍后讨论这个。但所以,是的,我们正在逐渐完成这张图表。嗯,迭代器。嗯,有多少人之前听说过迭代器?快举手。好的。哦,好吧。所以这是一个相当不错的数量。嗯。
你们可能已经被震撼了,但我们今天会继续震撼你们。那么,迭代器为什么有用呢?嗯,你们可能注意到,当我们讨论关联容器时,我们不能像,使用向量或双端队列那样仅用普通的 for 循环来迭代它,们。比如说。
像 for i 等于零到长度。嗯,i 加加。嗯,再次强调,因为我们可以把映射和集合想象成数据的云,嗯,不一定以一种非常整齐的线性方式排列。嗯,那么问题是我们如何迭代一个映射或集合?嗯,首先。
这是一点小提示,来自我们之前讲师 Ali 的迭代,器。从现在开始,我们真的要深入研究 C++ 特别之处,那就是,它的通用性。嗯,为了强调,我们正在进行一段旅程。嗯,我们可以理论上飞到最终目的地。
直接告诉你们迭代器,是什么。嗯,但那样我们会错过经验,也会错过三本畅销书。所以,我们将慢慢来。即使你们可能已经听说过迭代器,我们仍然想强调它们的,具体使用方式和原因。好的。好的。所以迭代器。
迭代器是在斯坦福 C++ 库中允许迭代任何容,器的东西。所以你们刚学到的所有序列和关联容器,迭代器都可以用,于它们,无论它们是否有序。嗯,为了对迭代器的工作原理有一个心理模型,对于那些还。
没见过它们的人,假设我们有一个像这样的集合。它是一、二、三、四的集合。迭代器,嗯,是一种让我们以线性方式查看这个非线性容器,的工具,嗯。所以它允许我们把这片云当作是一种不错的序列,一个类,似向量的对象。
嗯,它们怎么能够做到这一点?对于这门课的目的,我们实际上并不关心,但这又回到了马,努卡的问题,呃,关于它们是如何,地图和集合在底层是如,何实现的,这就像是二叉树的遍历。如果您感兴趣,课后随时再问我们。
所以为了对如何使用迭代器有一个思维模型,嗯,所以,呃,在迭代器上有几个常见的函数,你们将会使用,嗯,基本上,对于从这门课往后你们将学习的所有函数。嗯,其中之一是点 begin 函数,它返回,哎呀。
它返回一个,迭代器,呃,幻灯片有个问题,但它返回一个指向集合中第,一个元素的迭代器。如何将其存储在变量中?所以这实际上给我们带来了一个问题,即迭代器的类型是,什么,嗯,如果我们想将其存储在变量中?嗯。
一方面,呃,所以如果你实际上,Qt 创造者的一个不错,的特性是,如果你开始在 Qt 创造者中输入函数,它实际上,会自动为您建议要使用的函数。嗯,在这种情况下,您可以看到,呃,它建议了一个名为 。
begin 的函数,其类型为迭代器,呃,begin。所以实际上迭代器的类型就是字面上的迭代器。并且迭代器取决于您正在使用的集合的类型。所以在这种情况下,嗯,它是一个整数集合的迭代器。嗯,很容易记住。嗯。
然后你们将要使用的另一个常见函数是解引用。呃,在这种情况下,呃,解引用这个指针意味着您将输出值,一,呃,这就是它所指向的。嗯,你们将要使用的第三个常见函数是++,呃,它递增一个,迭代器。嗯,等等。
所以再次,呃,解引用以读取值,递增以移动迭代器,呃,等,等。然后我们对迭代器做的最后一件常见的事情,嗯,是通过读,取我的集合点 end 并将其与我们当前的迭代器进行比较,来检查,呃。
我们是否已经到达了集合的末尾。所以这是对迭代器的四个常见函数的一个非常简短的介绍,但为了让您了解迭代器是如何工作的。我看到后面有个问题。是的,说吧。这是个很好的问题。所以您可能已经看到的是,嗯,呃。
从一类如 1 到 7 或 1 ,到 10 ,是有这些被称为指针的东西,您也可以做。您通过引用获取值吗?嗯,我相信迭代器在底层可能被实现为指针。呃,四个向量。是的。对于四个。是的。四个向量。呃。
迭代器就是纯粹的指针。是的。所以对于顺序容器,呃,迭代器在底层实际上就是指针。嗯,我,我得为关联容器查一下。呃,不,不,因为,这个,这个,++必须做一些不同的事情。明白了。嗯,引用是一样的吗?
我不太确定。我们,我们,我们会查一下然后回复您,但是是的,为了使这,个,呃,迭代器函数通用,有一些小的差异。是的。问题。一个,什么,引用运算符和解引用运算符之间有什么区别,或者,嗯,好的问题。好的。
所以,是的。我们刚刚很快地讲过这个。嗯,对于那些之前没有见过这个的人来说。所以,解引用运算符就是这样一个语法,你把它放在迭代器,前面,本质上是告诉迭代器,我不想要箭头本身。我想要箭头指向的值。好的。
是的。所以这就是解引用运算符的区别。好问题。为什么在这种情况下你在开头使用加一而不是在结尾?是的,那么,为什么加一在左侧而不是右侧?嗯,这叫做前缀运算符与后缀运算符的区别。嗯,你可能在课堂上见过。
比如在 for 循环中,你经常看到, i++ 与 ++i 的区别。嗯,在这种情况下,它们做的是完全相同的事情。加一在迭代器的开头与在结尾的唯一区别就是你执行加一,的时机。这是一个非常小的细节。
但实际上对你的 106B 或 x 考试,很有用。所以我实际上会在课堂上说一下这个。嗯,加一意味着你在使用之前先递增迭代器,而当加一在右,侧时,意味着你先使用迭代器然后再递增它。所以在这种情况下。
使用迭代器意味着只是检索值。所以无论你在什么侧边加一,都没有关系。是的,还是个好问题。还有其他问题吗?是的,那里。迭代器在集合和映射上工作吗?再说一遍。迭代器在集合和映射上工作吗?好问题。
迭代器在集合和映射上工作吗?实际上,这正是迭代器被发明的一个动机原因。所以绝对是的,我们会在接下来的几张幻灯片中再次看到,这个。是的,后面的那个问题。是的。是的,所以问题是。
直接索引一个向量是否比解引用迭代器,更快?或者,抱歉,再说一遍。或者通过迭代器循环。我明白了,所以使用像 for 循环与使用迭代器的区别。嗯,我想想它们是如何实现的。我认为是类似的。
我相信它们基本上是相同的,因为当你在向量上进行 for ,循环时,实际上你是在做的是,我相信 C++ 可能实际上在,底层使用了迭代器来实现这一点。嗯,比如说。
当你做类似于 for element in vector 的操,作时,它实际上是在底层实现这个确切的迭代器循环。对于数组来说可能会有所不同,但在速度上,它应该几乎是,相同的,如果不是完全相同的话。
我还想提到标准库,它们确实使用了迭代器。只是我们没有向你展示这些迭代器。是的。但如果你实际上查看标准实现,它们到处都使用了迭代器,是的,对的。嗯,所以迭代器本身是盒子的内存地址,对吧?你可以这样理解。
是的,它- 它和指针有些不同,嗯,我们需要稍后再回到这,个问题上。嗯,但你可以基本上把它当作一个内存地址来看待。现在,关键的概念是迭代器是一种抽象。正是如此。你实际上不需要担心它- 它是如何实现的。
你只需要知道它指向某个元素,然后你可以在那个迭代器,上调用 next 或 previous 来获取下一个元素。好的。那么,在最后一分钟,我们想做的事情是,再次,迭代器通常,用于的四件事是。
创建、解引用、使用加法进行前进,然后,与结束迭代器比较以检测何时完成。
嗯,在最后一分钟,我要做的事情是,向你展示一些常见的。
迭代器函数。好的。所以主要的就是这个 while 循环。所以我们实际上,你可以看到这里,基本上有两种使用迭代,器遍历容器的方法。嗯,你可以看到我们在这里使用了它们四次集合。所以这部分回答了你的问题。嗯。
但如果我们要重新写一遍,我们想做的是,创建一个 ,int 迭代器的集合,等于容器的 begin,以获取一个指向该,容器开头的迭代器。嗯,我们会解引用它以打印出一个值,然后递增它以将其移。
动到我们假想的线性向量中的下一个元素,对不起,集合,集合,元素字符串。嗯,然后最后,我们可以通过将当前迭代器与结束迭代器进,行比较来判断我们是否完成了遍历容器。嗯。
你们会看到的另一个常见结构是使用 for 循环。所以做的事情和 while 循环完全一样,只是将其包裹在 ,for 循环版本中。所以我们想把这个放上去,以便你们有一个参考,然后可以,在家里运行这个。嗯。
那么,我要问的是,最后一分钟关于迭代器有没有人有,问题?是的,就在那里。使用迭代器而不是 for 循环,这样你就不需要搞清楚 for。
循环的开始和结束,这基本上是唯一的优点吗?是的,这是个很好的问题。那么,迭代器的优点是什么?所以我们真的只是讲解了如何使用迭代器的功能,但我们,还没有告诉你们。
为什么它们很酷或为什么 C++ 实现了它,们。所以实际上,让我在最后几分钟里讲一下这个问题。我不确定是否应该留到下一节课讲。那么,为什么迭代器很强大?嗯,很多场景要求我们查看某种容器。
而不实际知道容器的,类型。而迭代器允许我们抽象掉容器是什么。我会在一会儿向你们展示我所说的更多内容。嗯,再次强调,关键的概念是,它允许我们以标准化的方式,遍历任何容器中的元素。嗯,这是一个例子。
所以假设我们有一个整数向量,并且我们想计算某个特定,值的出现次数。嗯,你可以想象我们会写一个这样的程序。我们可以让它适用于另一个数据结构,比如列表吗?是的,我们可以。实际上,当我们使用迭代器时。
我们通过更改迭代器的类型,来实现这一点。嗯,我们可以做同样的事情。假设我们想使用集合而不是列表。我们做同样的事情。你可以真正看到,关键在于,除了数据结构之外,我们程序,的逻辑没有任何改变。
这为我们将在周四讨论的内容做了一个预示,也就是迭代,器使我们能够在任何类型的容器集合中标准化函数。所以这是一个关键的收获点。迭代器可以在任何容器中使用。接下来,我们将介绍模板。
那是- 你将看到另一个- 那是我们将概括所有这些的地方,是的。太棒了。好了。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P6:[3]CS 106L Winter 2020 - Lecture 6_ Advanced Iterators and Containers - GPT中英字幕课程资源 - BV1Fz421q7oh
今天你有没有提到无效的迭代器?
不用担心。如果最后有时间,也许我们可以提一下。否则,我下次开始讲。我会举一个快速的例子,比如说,假设你正在从一个向量中,移除东西。
几英里之外,对吧?
好的。好的,现在是1:30,我们开始上课,没有显示问题。感谢我们可爱的Avery。所以,今天的计划是我们将结束STL的前两部分。我们将完成容器和迭代器的部分。然后最后的两部分。
仿函数和Lambda表达式,我们实际上会,在下一次讲座后讨论。下一次讲座,我们将讨论模板,这实际上是STL中所有不同,部分的结合点。首先,回顾一下上次的内容,关联容器。到目前为止。
我们讨论了像向量或双端队列这样的顺序容,器。在上一次讲座中,我们讨论了像映射或集合这样的关联容,器。区别在于,关联容器不是按序列存储数据,而是将某种键数,据与某种值数据关联起来。因此。
我们对映射和集合使用相同的熟悉语法。然后,我们上次还学习了迭代器的概念。再次强调,迭代器的语法实际上归结为这四部分。那么,有人能告诉我我们怎么创建一个迭代器吗?
我们上次学习到的创建迭代器的一个函数是什么?有人记得吗?如果没有,我给你们讲第一个。我们还在重新进入状态。是的,我们创建迭代器的传统方法涉及调用容器的。begin,函数。在这种情况下,请记住。
迭代器的类型实际上是逻辑上的,std:set
数。我们今天稍后会看到从这两个函数创建迭代器的方法。谁记得我们如何引用一个迭代器?是的,说吧。星号,完全正确。非常好。如果你对指针比较熟悉,这可能看起来有些相似。
我们实际上会在今天讲座的最后讨论指针和迭代器之间的,区别。所以请记住这一点。但是确实,通过解引用迭代器,它会跟随这个迭代器的指向,并读取迭代器所指向的值。Avery在上次讲座后想出了一个很好的比喻。
就是如果你看,过《玩具总动员4》,或者即使你没有看过,你也知道有一,个爪子机去捡不同的物品,这实际上就是迭代器。你可以把迭代器看作是那个爪子,然后解引用它就像让它,去捡起物品并把它带回给你。
如果这对你有帮助,所有的功劳都归于Avery Wong。我只是想提一下,我还花了很多时间放了一个电影片段。这是真的。也许在课结束时我们会有时间。下次吧。下次。迭代器的进阶。所以类似的语法。
我们可以使用 iter++ 或 ++iter。再次,这被称为后缀和前缀操作符。当它们单独使用时,就像一行代码,实际上这两者是等效的,但事实证明,++iter 略微更快,因此在 C++ 风格中,我们。
传统上倾向于在没有区别时使用它。最后,我们如何与另一个迭代器进行比较?事实证明,我们可以使用等于等于和不等于比较符号。我们最常比较的一个东西,特别是在处理循环时,是这个 ,container。
end 函数。这感觉很熟悉吗?有人有问题吗?好的。那么为什么迭代器这么酷呢?我会在接下来的几张幻灯片中讨论这一点,并提醒我们为,什么。所以再次,这就是如何使用迭代器的语法。但迭代器实际上做的事情。
你可以把它们想象成是将任何,形式的数据表示,不论是向量、集合、还是云,迭代器让我,们可以在代码中将这种数据表示视为线性数据集合,这意,味着它会始终保证以相同的顺序输出,并且进一步地,根据。
我们提供的某种排序函数,它会以排序的顺序输出。再一次,我在课程的最后提到过这一点,这真的很酷,因为,这意味着当我们有一个单一的逻辑概念,比如排序数据集,合时,之前在了解迭代器和下节课的模板之前。
我们需要为,这些不同类型的容器分别实现排序函数,因为向量的存储,方式与集合的存储方式不同。所以为了能够排序,你需要了解它们在底层的工作原理。但迭代器允许我们这样做,它们给我们提供了这个语法,就。
是这个 for 循环,它让我们说,无论我传入什么容器,我知,道那行代码总是会有效,因为迭代器在我们学过的每个容,器中都能工作。所以为了演示这个函数,它做的事情是取一个元素并计算。
它在那个数据结构中出现的次数。在这个案例中,它计算一个元素在向量中出现的次数。在这个案例中,它计算一个元素在链表中出现的次数。在这个案例中,它计算一个元素在集合中出现的次数,依此,类推。请注意。
唯一改变的是类型,逻辑没有变化。所以当我们学习模板时,我们会看到我们甚至不需要改变,类型,这有点疯狂,但那将是下节课的内容。太棒了。是的。有人对基本的迭代器有任何问题吗?今天我们将学习更多。好的。
我看到一些人摇头了。那很好。好的。在我们进一步探索之前,到目前为止,我们仅仅在数据结构,的开头开始,然后“++”直到到达结束。当然,你可能已经知道迭代器实际上有更多的用处。所以今天我们将探索这些。
另一个我们要提到的有点棘手的细节是 map 迭代器。所以到目前为止,或者好吧。在我们深入探讨之前,我们想重新介绍一下这个类。Avery 在他类型讲座的最后提到过这个类,但我们会再讲,一遍。
因为那时讲得比较快。我们之前引入这个的背景是,当我们想从一个函数中返回,多个东西时,我们以前除了通过引用传递然后在函数内修,改之外,没有其他方法。但现在有了所谓的对,我们可以返回一对东西。例如。
如果我们有一个名为 time 的结构体,那么在一个函,数中,我们可以在返回语句中同时返回小时和分钟。到目前为止,这就是对的用途。在语法上,声明它的方式就像声明其他 C++ 类一样。std 对。
两个对的类型,然后你可以使用 p。first 和 ,p。second 来访问和设置值。
你可能见过这个。啊,结果是,我在做这个示例时。我查了一下,哦,斯坦福大学的电话号码是多少?斯坦福大学确实有一个电话号码。我不知道那是什么意思。但如果你碰巧打电话过去,我很想知道会有什么结果。
是 Leland Stanford 吗?
是校长吗?谁知道呢?好吧。其实还有几种其他方式可以创建对。我们传统上学到的一种是上面提到的统一初始化。实际上,还有一种创建对的方法,叫做 createPair。这两者之间的区别有点微妙。
但却是关键的区别,即使用 ,createPair 时,你不需要提前知道类型。这在我们开始处理像模板这样的东西时非常有用,因为我,们不会提前知道类型。所以有两种不同的方式来创建对。
这也是 auto 关键字非常有用的地方之一。把所有这些概念带入之前的讲座中。还有另外一点要提到。在上一讲中,我提到过多重映射的概念,再次强调,我们在,考虑关联容器时可以更改三种不同的特性。
我们有映射、集合。我们有无序映射、无序集合。我们有多重映射、多重集合,然后是无序多重映射和无序,多重集合。所以,随时可以去探索一下。但要记住,多重映射是允许具有相同键的多个条目的映射,通常。
映射和集合只允许每个键有一个实例。它们不允许有重复项。多重映射允许,但这实际上给我们带来了一些奇怪的语法,问题,因为这意味着我们不能真正拥有逻辑上的括号操作,符或点操作符,因为它们想知道返回哪个值。
所以,相反,我们将元素插入多重映射的方式是使用对。这又是 make pairs 非常有用的另一个例子。在这种情况下,它会自动推断 3 和 3 是整数,并正确插入,这是另一种将内容插入多重映射的方法。
这实际上不是一个对。这是一个被称为初始化列表的东西。所以如果你看到错误中出现初始化列表一词,你现在应该,知道它指的是什么。那么,现在的关键是,对于映射和集合,点计数函数总是返,回 0 或 1。
正如你们在上一节课告诉我的那样。在这种情况下,对于多重映射,它现在可以返回大于 1 的,值。很酷,所以我们回到映射迭代器。再次提到这种边缘情况,它们实际上与其他迭代器和容器,略有不同。
因为其他迭代器在解引用时,它们会给你一个单,一的对象,比如一个整数向量。当你解引用一个迭代器时,它会给你一个 int,依此类推。解引用映射是一个特殊情况,它实际上给你一个对。
所以你使用映射迭代器的方法是这样的。所以我们可以像往常一样,使用我们的 begin 和 end 来,声明它。注意,我们再次使用不等于进行比较,并使用加法运算符进,行递增。这里唯一的不同是。
当你解引用它时,你不能立即使用它。你必须使用点 first 或点 second,其中点 first 对应于,第一个 int 值,点 second 对应于第二个。大家明白了吗?有人有问题吗?好的。
还有一件事我想强调的是,这些括号的放置非常重要,因为运算符优先级的方式,即 C++ 执行不同运算符的顺,序。如果没有括号,它会尝试计算 I。dot first,然后解引用它。
但是 I 在这种情况下只是一个迭代器,所以没有点 first, 或点 second 的概念。有趣的是,另一种常见的语法是 I->second 或 I->first,这是这种括号解引用点的简写形式。
随着你上 107 课程和其他课程,这些都会变得越来越熟悉,因为它们与指针的语法是一样的。是吗?所以这基本上是打印出每一种语法?是的,绝对是。这是一个很好的翻译。
所以这个示例的作用是打印出映射中的每个键和值。是的,非常好。按键的排序顺序。太棒了。酷,所以给大家一个迭代器有多酷的预览,这里有更多的用,途。到目前为止,我们只是打印出整个数据结构中的所有值。
所以看看我们还可以做些什么。啊,哎呀,其实在此之前,我忘记放这个幻灯片了。这只是一个关于映射迭代器的简要示例。所以再一次,这个示例是我们用上次的相同示例。你会注意到我们有我们的 get line。
我们正在读取响应,并创建一个映射,统计每个单词出现的次数。上次,我们只是访问映射,查看是否存在。这次我们要做的是类似的事情。我们要做的就是打印出映射中的所有内容。这将正是那个讲座幻灯片所示的内容。所以。
这将是类似于,我们需要做的第一件事是创建一个到,开始的迭代器。所以这将是类型迭代器,类型 begin 等于,再说一遍。是的,谢谢,谢谢。非常好,确实很棒。是的,抓得很好,抓得很好。然后再次说一下。
我们可以有两种方式来做这个。我会这样做。所以假设当它的迭代器不等于 freak map 时,我们要做的,就是 cout。再一次,这只是演示我们必须先使用这个点。所以 cout dot first。
second,然后完美。所以我们来试试运行它。你们很棒,真是好捕捉。非常感谢。哇,天哪,你们真是太棒了。我甚至不能开玩笑说这是故意的,因为那真的很棒。好的,那么我们来做点这样的事情。完美。
它完全如你所料的那样。所以我主要把这个放在这里,以便你们可以自己玩弄程序,但到目前为止,有人对这方面有任何问题吗?关于映射迭代器?很好。哦,Avery 有个问题。
你能对多重映射进行基于范围的 for 循环吗?是的,你能对多重映射进行基于范围的 for 循环吗?对多重映射还是普通映射?对普通映射。是的,绝对可以。所以再次说,就像上次一样,我们可以。
代替使用迭代器对,整个映射进行 for 循环,现在我们可以对映射做同样的事,情,只需用 for, 在这种情况下,映射中每个元素的类型实,际上仍然是一个对。所以这是一个字符串和整数的对。
但这样输入有点麻烦,所以我就写 auto。所以对于 auto 元素 在频率映射中,我只想做的是 cout,在这种情况下,再次因为元素是一个对,你仍然需要调用 ,sort 的 lm。first 和 lm。
second。如果你尝试仅仅 cout lm,我不记得会发生什么。它要么抛出错误,要么打印出默认的对。所以我们来看看。
试试这个。完美。
好的。实际上,这是一个很好的点,因为它回到这一点,即我们实,际上不需要迭代器,那我们需要迭代器做什么呢?所以让我们探讨一下迭代器的使用。我实际上是几周前才学到这个的。你实际上可以在里面使用结构化绑定。
在 auto 内部。哦,是的。所以结构化绑定 key, val,然后 cout。
key 和 cout, val。
非常酷。
是的,因为我知道反复输入 item。first, item。second 非,常麻烦,所以你可以直接在 for eq 内部解包它。是的,这实际上是一个很好的点,也是对的的一个很好的特,性。很好。好的。
好的,所以我想在这次讲座中做的另一件事就是给你们一,个从头开始编写程序的感觉。在这种情况下,我们的程序不会做太多的事情。它只是做几个不同的示例,但这样你们可以看到你们可能,自己如何去做。
所以我们首先要做的,当然,我们基本上要做的三件事就是,对向量进行排序,找到一个元素,比如从集合中,然后我们,会看看接下来该做什么。我们会看看我们想做什么。所以我们来声明我们自己的向量,一个向量,当然。
如果我,们想声明一个向量,那么我们需要导入向量类。所以我们要包含向量,然后因为我不想在任何地方都写 ,std vector,我会说使用 std vector。很好。所以现在假设我有一个整数向量。
再次使用统一初始化,所,以我可以做,比如随机数,然后看看。好的。所以让我快速定义一个叫做 print vector 的函数,它将,接受某种向量。所以记住,你应该在106B课程或其他课程中学到的,因为我。
们传递的是一个比 int 或 bool 更复杂的数据结构,我们,总是想通过引用传递它,在这种情况下,因为我们不会改变,向量,我们将通过常量引用传递它。所以传递一个事件,然后我们可以做我们通常做的。
即对每,个 B 中的元素使用 for auto,输出该元素,然后我再输出,一次,或者实际上。另外,我会在课堂后发布所有这些代码,所以不用担心如果,你没有全部输入。好的,所以现在我们注意到几点。首先。
cout 和 endl 产生了问题,当然,因为我需要包含 ,iostream 类,它包括这两个。所以 iostream,再次,我可以在使用它们的地方说使用 ,std cout,但那样有点烦人。
所以我会改为使用 std endl,很好,到目前为止没有抱怨。好的,所以要排序一个向量,真正的关键是,实际上在这里。
我们可以测试它是否已经正确排序。迭代器之所以有用的关键原因是它们如何与函数、函数对。
象和算法等一起工作,我们实际上将在下一节讲座中深入,探讨,但这些是你将会经常使用的一些基本算法。所以让我将其更改为迭代器使用,然后很好。它打印出了我们期望的结果。经典示例之一是被称为排序算法的东西。
所以 STL 中的所有这些算法都位于算法类中,除了其他几,个在 numeric 类中,与数字有关的。但在这种情况下,你如何使用算法?它是一个叫做排序的算法。所以你可以在这里看到。
它实际上会告诉你它期望的参数,在这种情况下,它期望的是一个第一个迭代器、一个第二,个迭代器,然后是比较函数。所以我们会做的就是,当然,我们想排序的是整个向量,所,以 vector first。
vector end,然后我们将使用默认比较。
它会根据类型自动确定。所以让我们尝试一下,看看会发生什么。
很好,我们看到它已经排序了。这有点疯狂。
如果你记得,如果你在两节课前在这里,我展示了传统排序,与使用排序算法之间的区别,它将复杂的代码大约 10 到 ,15 行简化为这个算法。我们稍后会详细讨论,但 STL 算法已经优化为尽可能高效。
的排序算法。所以这就是我们从使用这些东西中获得的另一个好处。好的,所以如果我们想从集合中查找一个元素,让我们做同,样的事情。所以我们会做一个整数集合,再次,如果我想使用集合,那。
么我知道我必须包含集合库,并且我也不想到处输入 std ,set,所以我会直接使用 std set。请注意,这里我只输入了 std sort,因为我只用了一次。所以让我们看看。我们称之为元素。
然后我们将做同样的操作,3、1、4、1、,2、6。快速问题。你预计这个集合的大小是多少?多少人认为它会是一个大小为 8 的集合?多少人认为它会是一个大小为 8 的集合?好的。
多少人认为它会是一个小于那个的集合?好的。你认为它会是什么大小?你就在那儿。戴眼镜的你叫什么名字?哦。我想知道他的名字是有帮助的。哦,对。你叫什么名字?是的。再说一遍?你?好的,你。是的。
你期望它是什么大小?啊,正好。是的,完全正确。这有点棘手的问题。这只是为了展示,即使你用重复的元素初始化它,一旦创建,集合,它总是会移除这些重复的元素。所以我们可以看到 lms。size。
太棒了。好的。所以排序,是一个很常见的操作。另一个你会看到的非常常见的算法是,假设我们要查找元,素 5。实际上,上次我们确定一个元素是否在集合中的方法是使,用这个函数 lms。count。
然后它会打印出,好的,它在那个集合中找到了 1 次。我们实际上可以使用迭代器做同样的事情。这将会是这样,即再次使用这个来自算法类的 std find ,函数。在这里我们看到它需要一个第一个迭代器。
一个最后的迭,代器和一个值。所以,好的,我们的第一个迭代器将是 lms。begin。我们的最后一个迭代器将是 lms。end。然后我们要查找的值是 lm。find。
当我们说为了好的风格在赋值中使用像常量这样的东西时,这就是你将会使用的,像这样的全局常量。完美。所以如果我们然后打印出来,比如说,如果我们使用 find ,函数的方式是。
它将返回一个指向集合中那个元素位置的,迭代器,或者如果没有找到则返回结束迭代器。所以我们会说,让我们看看,如果 auto test test 迭代器,等于那样。所以如果测试迭代器等于 lms。end。
那么我们知道它不在,集合中。所以我们 cout 没有找到,否则。如果它在集合中,那么我们将 cout 找到了。然后我们将解引用测试迭代器。
所以让我们看看它是否正确地执行了。
哦,完美。并且它说找到了 5,它确实在集合中。
而 lms 大小是 7。是的,那是什么?是的,这是一个很好的问题。确切地说。所以 lms。end 实际上并不指向集合中的任何东西。它指向最后一个元素加一。所以它在集合之外。非常好的问题。是的,是的。
到目前为止还有其他问题吗?我想指出的一件事是使用这些常见函数的便利性。比如排序,查找。好的。我想展示的最后一件事是迭代器的多功能性。到目前为止,我们总是遍历整个向量或整个集合。所以这里的一个方法是。
我们可以通过像 vector。begin ,然后 vector。begin 加三来仅对前面三个进行排序,而不,是使用 vector。begin 或 vector。end。所以我们现在实际上来看一下这个。
所以我们不如对前四个进行排序。
然后我们将剩下的部分保持不排序。很好。看起来只有前四个被排序了,剩下的四个保持不变。
我们还可以做的另一件事是,即使在迭代一个集合或类似,的东西时,我们也可以使用,可以实际迭代数据结构的范围,而不是整个数据结构。所以你可能会这样做,比如取一个整数集合,然后创建一些,新的迭代器。
我们来看一下。这有一个函数叫做 lower bound 和 upper bound。lower bound 和 upper bound 的工作方式是,lower 。
bound 找到的是与给定元素相等或更大的元素。所以在这种情况下,它会找到 4,因为 4 是与 4 相等或更,大的最小元素。在 upper bound 函数的情况下,有点误导。
听起来它应该做一些类似的事情。抱歉。听起来它应该做一些与 lower bound 相反的事情,但实际,上它做的是非常类似的事情。所以与 lower bound 不同的是,我们来做六。所以。
lower bound 会找到大于或等于传入元素的最小元,素,而 upper bound 会找到严格大于传入元素的最小元素,所以这有一个小差别。可能会稍微让人困惑,所以你可以多想一想。但我们可以。
打印一下,看看。我们的开始将是那个数字。然后我们的结束将是 end 元素。然后我们可以从这里做之前我们会做的所有事情。所以这是 where the for loop。
即不是 for each loop ,实际上很有用的地方。因为使用 for each loop 时,它会自动遍历整个容器。当我们使用普通的 for loop 时,我们可以控制要遍历容,器的哪些部分。
所以在这种情况下,我们会有某种 iter。注意,由于我们已经声明了它,所以在 for loop 的第一个,语句中不需要初始化它。所以它实际上会是空的。第二个将是当它不等于结束迭代器时。最后。
我们将递增我们的迭代器。然后从这里,我们可以 cout,看看,cout 迭代器。
所以我们看看这会做什么。很好。所以注意到在这个集合中,它做了什么,就是找到 4 的 ,lower bound,结果是 4,然后找到 6 的 upper bound,结,果是 9。
因为 9 是严格大于 6 的最小数字。
然后在打印出来时,它从不包括最后一个迭代器。所以在这种情况下,它会打印出 4、5、6。所以挺酷的。现在有很多不同的方式来操作你的数据结构,无论数据结,构是什么。
我们本可以轻松地用 vector 替换 set,所有这些代码仍,然会有效。所以这就像是建立我们的工具箱。然后当我们谈论函数和算法时,我们将真正了解这些工具,已经如何被使用的常见方式。
太棒了。所以再次回顾我们做了什么,我们使用了排序算法。我们使用查找功能找到了一个元素。请注意,我们上次讨论了类似的内容。所以上次,我们讨论了使用计数方法来查找键是否在数据,结构中。在这种情况下。
再次使用映射集合,它只会返回0或1。所以如果我们检查它是否等于0,那么我们就知道没有找到,结果是还有另一种方法来确定一个元素是否包含在映射集,合中,那就是使用这个查找功能,你们现在知道这个,因为。
你们知道迭代器是什么以及它们是如何工作的。所以这是检查是否找到某物的另一种方法。它看起来有点长。结果是查找实际上比计数稍微快一点,因为计数是使用查,找来实现的。所以如果你非常在意这个差异。
你可以随时使用查找。即使它看起来更长,但实际上稍微快一点。当然,最重要的一点是,在C++20中,你将不必担心这些,因,为他们终于推出了一个被称为包含键的方法。太棒了。最后,我们讨论了迭代器范围。
所以使用像下界或上界这样的函数来隔离你数据结构中的,范围,在你的排序数据结构中。作为参考,你会注意到这里的迭代器和结束都有下界和上,界或下界调用。这些调用在数学中是等效的。所以如果你想说。
从5到26(包括26),那么你需要为你的开,始和结束使用下界和上界等等。这不是特别重要,但作为一个好的参考,因为这些在脑海中,思考起来可能会有点棘手。是的,继续吧。是的。
这就是斯坦福库和标准库之间的区别。所以在106B中,106B的映射确实有一个包含键的方法。是的,所以这两个实际上是斯坦福包含键的等价物,因为标,准C++中不幸没有看起来像那样的方法。是的。
所以这些是某种解决方法。然后最后,今年我们实际上会得到一个类似的功能。是的。你会注意到斯坦福库中的很多函数,它们隐藏了所有的迭,代器。在斯坦福库中,你不必处理迭代器,因为这就是他们的目标。
他们不想让你处理迭代器。对,完全正确。现在我们正在展开,向你们展示如何实际使用迭代器。是的。所以如果你们真的想让你们的分组领导感到困惑,你们可,以提到迭代器,但不推荐,除非是在课后。好吧,最后。
我们触及的最后一点是再次提到基于范围的 ,for 循环,这是你在106B中学到的东西。现在你们知道它,是如何工作的。
结果是,如果你实际去查阅基于范围的 for 循环的文档,它实际上是按照我们迄今为止学习的迭代器 for 循环的,方式实现的。所以这非常酷。你们实际上隐式地实现了标准库中的一部分。这很不错。酷。
在我继续之前,大家对迭代器有任何问题吗?是的,朱莉,怎么了?
好问题。这样做更符合 C++,还是使用范围基于的 for 循环更符合, C++?所以,当你遍历整个数据结构时,考虑到风格上可能没有强,烈的意见,但大多数人会使用 for each 循环,因为它看起。
来更漂亮。实际上,只有在你真正需要迭代器时,例如,如果你在这里,调用排序函数或查找函数,或者你只想遍历数据结构的部,分而不是从头到尾时,这种方式才会派上用场。非常好的问题。我再补充一下。因为你会注意到。
第一个循环更简洁,更好看。然后你会发现差别,第一个循环有点受限,因为你必须遍历,整个集合,这实际上是 C++ 社区的一个常见问题。在 C++20 中,他们实际上提出了一种新东西叫做范围,它。
本质上允许你在 for each 循环中改变你想遍历的范围。现在,默认情况下,范围始终是整个集合。但在未来,你将能够构造这些范围,这样你就可以遍历每隔,一个元素。你可以遍历向量的前半部分,等等。是的。
你提了一个非常好的观点。这是 C++ 正在努力实现的目标。是的,这个点很好。如果有时间,我们可能会涵盖它。这是对的。有一堆很酷的 20 特性,我们可能会在最后涉及也可能不,会。很好。如果没有其他问题。
那就快速休息一下进行公告。首先,Assignment 1 的办公时间已经在 Piazza 上发布了,所以如果你们有任何问题,请查看 Piazza 帖子。我们都在正常上课后的时间增加了额外的办公时间。
如果你不能参加这些时间,请随时给我们发消息,因为我们,很乐意见面并找另一个时间。另一件事是,我们在学期开始时承诺你们有三次机会来创,建延期天数。这是第二次。这是一个中间的反馈表。
在这里你可以告诉我们像我们进,度太慢,或者太快,像我仍然不知道 std: 是什么,这真的,让我很困扰,因此如果你能花五分钟时间填写,我们会很感,激。是的,不过我保证我们会在后面学习它。是的。
任何类似的内容。所以我们很乐意听取你们的意见。反馈表的回复是匿名的,所以一旦提交表单后,会有一个链,接,你可以在其中添加你的名字,以表明你已完成填写。所以是的,请在下周四之前完成。
然后我们会整合你的反馈,以便改进课程。表单非常简短,所以请务必填写。请帮助我们。是的,很好。然后我还想做的一件事是,快速回顾一下 Assignment 1 ,的一些语法,这在去年让很多人感到困惑。
就是结构体的快,速回顾。所以这应该都只是复习。你们应该都对来自 106B 作业或者阅读这个作业的内容比,较熟悉。再次强调,结构体是一种将多种不同类型的数据组合在一,起的方式。初始化它的方法是。
再次使用这种统一初始化方式,然后你,可以访问它的方式感觉有点像一个对。你可以通过结构体内那个数据片段的名字来访问它。所以 object。var1 给你一些新的值。这些都在幻灯片里。事实证明。
在 C++ 中,声明某物时 struct 关键字是可选,的。在 C 中不是。所以在 107 中,你总是要使用关键字 struct。好的,这是针对作业的。所以在作业中,你会看到几个不同的结构体。
一个是简单图结构体,一个是节点,还有一个是边结构体,后者没有展示出来。我只是想快速让大家了解一下这的语法。再一次,声明一个简单图,实际上,统一初始化器是可选的,但在这里只是作为参考。快速地。
基于我们刚刚回顾的结构体语法,和你的同学讨论,并回答这个问题,你会如何添加一个新节点?假设值是 x1 和 y1。你会如何将一个值为 x1 和 y1 的新节点添加到简单图的,节点向量中?那样的语法是什么?
所以想一想,然后用 30 秒的时间和你的伙伴交流,看看你,们是否想出了相同的东西。再一次,我们有一个简单图,我想将一个节点添加到这个简,单图的节点向量中。那样的语法是什么?一个简单图是一个结构体。是的。
简单图就是这个结构体,而这个结构体包含了一个节,点向量和一个边向量。是的,这是一个好问题。它有点复杂,这也是我想在讲座中提到它的原因。你会怎么做?这就是你在第一项作业中会做的事情,如果你做的话。好的。
有没有人有什么想法或主意?是的,去吧。完美。好的,所以这是一个很好的主意。所以我听说我们要做简单图。所以在这种情况下,变量的名字是 graph。nodes 用于访问,节点向量。
然后使用加等于来添加一个节点。一个提醒是,实际上加等于在标准 C++ 中不存在。所以我们需要做的是使用点 pushback。但这正是正确的想法。确切地说。所以我们取我们的 graph 变量。
使用点 nodes 访问节点,向量,然后用 pushback 函数将节点推送到简单图中。然后再次使用统一初始化器来创建 x 和 y 值。这对大家来说有意义吗?如果这有意义。
那么我认为你会对作业的结构有一个好的,开始。好的,我会把它留在幻灯片中,你可以再次查看它。酷。
好的,所以你可能注意到我们之前的迭代器示例中有一些。
事情。哦,对不起。其中之一是你会注意到在我说我想排序,例如,我向量中的,一部分元素时,你会注意到我做了一些有点独特的事情。我做了 vector begin plus four,这种方式直观上是有意。
义的。它在说,好吧,我想要指向这个三的指针或者指向这个三的,迭代器,然后我会加上四,所以它应该指向这个五。这正是我们看到的。但这个运算符实际上不是我们之前讨论过的。到目前为止,我们只讨论过加加运算符。
它会将值增加一。让我们看看如果我们尝试对集合做同样的操作会发生什么,在我们脑海中,这似乎是合理的。我有,比如说,如果我做类似的事情,先让我快速注释掉剩,下的部分。在一个集合中。
我有类似于 std sort,begin,end,然后我,会做四,然后我会做之后。为了好的风格,我肯定会将其分解开来。实际上,周四,实际上,这是为什么模板如此必要的一个例,子,这正是这段代码的用途。
唯一的区别是这是一个向量,而那是一个集合。所以下一节课,你将学会如何对两者使用完全相同的打印,函数。所以很酷,提前告知。但是我们可以查看一下它会做什么。
而且它实际上已经有错误提示了。这将引出我们下一个话题,即迭代器类型,但这种情况下为,什么这会出错,即使这不会。我们甚至可以将其改回 vec。end。为什么这个有效而这个无效,即使它们是不同的容器?
事实证明,sort 需要一种称为随机访问迭代器的东西。因为它实现排序函数的方式,它需要能够在容器中任意跳,转。而且事实证明,即使集合对我们来说似乎是有序的,由于它,们在底层并未排序。
你实际上不能在它们上调用 sort 函。
数。所以这引出了一个问题,那就是如何知道何时可以使用某。
些东西,何时不能使用某些东西?
最后我要提到的是,你会注意到当我输入 std sort 并查,看这些时,它告诉我们模板是什么。很难看到,而且它不会增加。但你会注意到这里说的是随机访问迭代器,而我们之前做,类似 std find 时。
这里只说是输入迭代器。所以这会告诉你它需要什么样的迭代器,然后我们将学习,这些不同类型的迭代器。
所以迭代器类型。到目前为止,我们真的只使用加加运算符来递增它们,但对,于像向量这样的东西,能够做到例如将大小除以二添加到,那个迭代器是有意义的。同样适用于双端队列。看起来对。
看起来应该是我们能够做到的事情。问题是对于像集合或链表这样的东西,链表如果你不熟悉,的话看起来可能是这样的,如果这看起来陌生,不用担心。但观点是你不能在访问下一个元素之前跳过前面的元素。
你唯一知道下一步去哪里的方法是查看前面的元素。所以在这种情况下,做类似 my list。begin 加三的事情并,不真正有意义,因为要到达加三,不仅仅是加上三。这类似于你必须跟随箭头三次。
这种情况下你实际上只是,做 dot begin 到 dot end。那么 C++ 如何解决这个问题?再一次,你会得到这样一个不幸的部分,那就是你会得到非,常晦涩的错误信息。所以这个错误信息。
有点像是无效的操作数,你真的不知道,它是什么意思。但这实际上意味着你正在使用错误类型的迭代器。确切地说。那到底发生了什么?结果是有五种不同类型的迭代器。不用担心,因为对于每种容器。
迭代器的类型是非常明确的,所以这只是为了让你对每种类型的使用有所了解。这算是一个预览。结果是随机访问迭代器是最强大的,输入和输出迭代器是,最弱的。你会在几分钟内准确理解这意味着什么。首先。
每个迭代器都可以执行这四种基本功能,我们在讲座,一开始介绍过,即它们可以从现有的迭代器创建,可以使用, C++ 进行前进,并且可以进行比较。我们在最开始时就看到了这些。什么是输入迭代器?所以输入迭代器。
你可以把它想象成用于输入流的迭代器,有两个特点。第一个是它是只读的,意味着你只能从中读取值。你不能写入任何东西到那个迭代器中。换句话说,它只能在表达式的右侧引用。第二个是它是单次遍历的。
稍后我会详细讲解单次遍历和多次遍历的区别。结果是,我们已经看到这些了。我刚刚在 find 函数中展示了,所有它要求的都是输入迭,代器。所以只要你有像输入迭代器这样的东西,你就可以使用 ,find。
这意味着即使是像输入流这样的流,你也可以实际使用 ,find 函数,这挺酷的。然后还有像。哦,同样的 count 也是如此。同样,只要求输入迭代器。是的,同样适用于输入流。输出迭代器。和输入迭代器一样。
只是它们是只写的。所以你不能从输出迭代器中读取值。你只能写入东西到它里面。是的,它们只能在表达式的左侧引用。再次,你会看到这种情况与输出流,如输出流流或 CL 相关,以及一个叫做 copy 的函数。
这是另一个你可以查看的算法,它会将整个数据结构复制,到另一个数据结构中。好的,那么前向迭代器。前向迭代器可以做输入和输出迭代器可以做的所有事情,并且具有能够进行多次遍历的额外功能。因此,由于第一点。
它可以做输入和输出迭代器可以做的所,有事情。这意味着它既可以从迭代器中读取,也可以写入。最后一点是进行多次遍历。这意味着,本质上,如果你有两个指向同一对象的迭代器,比如说迭代器 A 和迭代器 B。
如果你对 A 和 B 都进行加,一操作,这些加一操作保证会再次指向同一元素。例如,如果你考虑一个向量,如果你对向量的 begin 进行,加一操作,然后创建另一个 begin,进行加一操作,它们保。
证指向相同的第一个元素,这似乎很直观。但如果你考虑一下,例如,一个输出流或一个输入流,比如,我们有 C in 并且你对 C in 进行递增,那么如果你尝试,调用,比如 C in dot begin。
虽然不完全是这样,但从直觉,上讲,大致是这样,那么它实际上会指向这个。所以如果你递增它,这两个将不会是一样的。这就是传递的总体思路。我会说,在这一点上,我给你提供所有这些细节只是为了让。
你了解发生了什么。实际上,在实际应用中,当我们使用这些不同类型的迭代器,时,你需要知道的只是,它是前向迭代器、输入迭代器还是,输出迭代器,这取决于文档说明。文档将告诉你它是什么类型的。这样。
你就可以知道何时可以使用每种迭代器。是的。所以,是的。如果传递的那些细节从你的记忆中消失了,也没关系。无意中的双关语。很好。我想在这里指出的另一点是,这些箭头存在的原因是因为,每一个都是其前一个的超集。
你会注意到前向迭代器是输入迭代器和添加迭代器的超集,加上了一些额外的东西。这在实际应用中意味着,如果有一个函数,例如在 find 中,要求一个输入迭代器,这意味着你不必只提供一个输入迭,代器。
你可以提供一个输入迭代器或任何比它更强大的迭代器。换句话说,如果一个向量只包含随机访问迭代器,那么你就,不能在它上面使用 find,这将是没用的。所以,是的。再一次,从实际角度来看。
你可以使用任何比所需类型更强,大的东西。如果你在之前的课程中学过继承,这基本上是一个继承层,次结构。每个随机访问迭代器都是一个双向迭代器,而所有双向迭,代器都是前向迭代器。有点像正方形和矩形。
如果你听说过这个。是的,怎么了?所以,你之前说的,如果你创建了两个输入迭代器并将它们,放入相同的元素中,并递增其中一个,它们两个都会向前移,动?哦,是的。这是个好问题。所以,实际上不是这样。所以。
让我更清楚地解释一下。所以,对于一个输入迭代器,比如我们创建了一个输入迭代,器指向一个点,另一个输入迭代器也指向那个点,当你递增,它们时,它们不一定会到达相同的位置,这有点奇怪。但可以这样理解:
像输入流一样,一旦你递增某样东西,它,就已经被消耗了。所以,如果你再次递增其他东西,就没有逻辑上的保证,它,几乎是未定义的行为。例如,它会去同样的地方吗?它会去新的地方吗?所以。
即使存在两个独立的迭代器?是的。但一个的行为会影响另一个的行为?我会这样想,这是理解它的一种方式。也许我认为更清晰的理解方式可能是,每个独立递增它们,的行为都不会产生定义的结果。比如。
它们可能都去同样的地方,也可能操作系统随意让它,们去两个地方之后的地方。此外,对于输入迭代器,你不能复制输入迭代器,你不能创,建两个输入迭代器。是的,你可以重新创建这两个,但你不能从一个复制到另一,个。
如果这也有助于澄清事情。那么,这是否意味着所有的输入形式都是相同的?这是一个很好的问题。所以,这实际上涉及到迭代器何时会变成,那个词是什么?哦,迭代器何时会失效?所以,这实际上是一个很好的问题。
我要把它搁置。我认为Avery会在下节课开始时讨论迭代器何时会失效。而且我实际上还得再查一下输入迭代器。我相信这可能是正确的,但我们会在下节课讨论时确认。是的。所以,是的,这是一个很好的问题。是的。
怎么了,Tim?很好的问题。所以,最强大的类型是由容器定义的。所以,一个向量会定义它的迭代器是什么,如果我们查看一。
下,例如,向量CPP参考文献的话。如果你查看文档,查看一下叫做成员类型的东西,你会注意,到有一个成员类型是迭代器,它实际上会告诉你迭代器的。
类型是什么,类似的东西。所以,是的,它是由容器定义的。至于是否可以将一个向量迭代器、随机访问迭代器声明为,输入迭代器或前向迭代器,这涉及到继承的概念。所以,继承是一个完整的讲座,因为它非常复杂。
我们实际上可能不会在这节课上教授它,但我们会在最后,一节课上提供它作为一个选项,让你们讲授任何内容。所以,是的,理论上是可能的。只是这个陈述中有很多“但是”。是的,很棘手的事情。好问题。
好的。所以,那是前向的。快完成了,最后两个。所以,双向迭代器,除了前向迭代器能做的所有事情外,还,具有递减运算符。所以,前向迭代器,即使它可以前进,抱歉,即使它有多次遍,历,它也不能向后移动。所以。
是的,双向迭代器,你会在像反向函数这样的函数中,看到它,你可以传递一些东西,它会反转其中的每个元素。实际上,双向迭代器是数学和集合类定义的。这就是为什么我们不能使用排序函数,因为排序函数要求。
的是随机访问迭代器,这比双向迭代器更强大。这也是链表定义的。所以,链表也是只有双向的。然后最后,随机访问。这些是最强大的,你可以使用任意量进行递增或递减,例如,三或一半的大小,类似的东西。
这些是向量、双端队列、字符串定义的。实际上,指针也是一种随机访问迭代器。是的,所以这实际上涵盖了我们所有的五种类型。有谁有任何问题吗?如果没有,那么最后我想说一下,指针和迭代器之间的区别,稍微有些混淆。
这主要是如果你之前学过指针并且试图弄清楚迭代器如何,适应指针的概念。你可以把指针想象成一种类。指针就像你可以声明的特定类,比如一个语音星指针或类,似的东西。迭代器,你应该更多地将其视为一种接口。
迭代器更像是一组承诺。因此,随机访问迭代器承诺你可以以任意量增加或减少你,的指针。这意味着,你可以把指针看作是随机访问迭代器的一种实,现。类似地,向量迭代器及其他也是如此。它们都是随机访问迭代器的实现。
是的,太棒了。下周二我们将讨论模板。非常强大,非常令人兴奋。不要忘记填写反馈表,因为我们很想听到你的意见。但大家做得很好。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P7:[13]CS 106L Fall 2019 - Lecture 6_ Templates (Video) - GPT中英字幕课程资源 - BV1Fz421q7oh
好的,我想我们准备好了。
是的,我来开车。Facebook。Facebook。是的。好吧,我们开始吧。有人能检查一下摄像机是否在录制吗?是吗?好,太棒了。那么,大家欢迎回来。第四周过得怎么样?举个大拇指?好的。竖个拇指?好的。
所以,我昨晚睡到五点,我刚刚做了一个期中考试。但我非常兴奋来教你们。大约十分钟。那么,让我们看看。首先,去106L网站下载QT文件。根据你们的一些反馈,我们将进行更多互动式教学,这意味。
着我们将做一些示例,但我也会让你们尝试自己编写这个,函数。所以,我鼓励你们下载QT Creator文件。如果你没有笔记本电脑,没关系。找个搭档。是的,找个搭档。在你们这样做的同时,让我们快速谈谈这个。
所以,根据你们的反馈,我们今天主要不会使用幻灯片。我将通过代码来激励一切。所以,是的,幻灯片作为参考是重要的,但你们在讲座期间,不必一定跟随它。如果对你有帮助,你可以在我讲解代码时把幻灯片放在那,里。
好的。如果这对你们来说没问题,请点头。即使你摇头,我今天也不会对此采取任何行动。好的。顺便说一下,这只是反馈的总结。你们提到的一些喜欢的方面。互动性。幻灯片看起来很不错。谢谢。
我们花了很多时间在幻灯片上,所以谢谢。我们真的很喜欢制作幻灯片。有人说我们,“”很友好。所以,我不知道这是什么意思,但希望不是讽刺。有些人说这门课真的很轻松。确实很轻松。这门课就是要轻松一点。
你们不应该感到压力。还有一些人很喜欢课堂上提问的数量。一些不喜欢的方面。有些人觉得课程有点快,内容有点多。幻灯片太多了。是的,不幸的是。有些人希望有更多的练习。所以,除了作业,也许还有更多的练习题。
更多互动式讲座,我们今天一定会开始这样做。还有些人不喜欢提问的数量。好的。所以,这里是一些我个人将在本次讲座中做出的更改总结,所以,今天,我会在Piazza上发布几个练习题,如果你们想,练习的话。
我减少了今天内容和幻灯片的30%。但幻灯片仍然在里面。只是我会跳过它们。好的。所以,你们都能看到你们错过了什么。然后,今天我们将有时间来处理问题。但在那段时间里你们也可以提问。所以。
希望这能平衡问题的数量。有问题吗?好的。哦,还有多人提到我应该多准备糖果。所以,我带了糖果。好的。我想我会在停下来回答问题时,拿几块糖果。然后我们会继续问问题,直到糖果用完。当糖果用完时。
那就是我们该继续的信号。这听起来公平吗?好的。我还没有真正练习过拿糖果这件事,所以我可能会拿太多,只是说说而已。所以,这是我们到目前为止所覆盖的内容的路线图。我们正在学习标准模板库。今天。
我们将进入一个叫做模板的小领域。你可以看到这是更大领域中的一部分,叫做泛型编程。所以,编程中有几种范式。你听说过哪种编程范式?好的。所以,我听说过面向对象编程。这就像是Java中的一个最大热门词。
因为Java是面向对象,的编程语言。这也是你到目前为止大部分使用的东西。你听说过其他这些范式吗?是的。函数式。函数式编程。所以,C++确实有函数式编程的能力,但我们不会涵盖这些,因为在我看来。
我认为有更多适合函数式编程的语言。好的?是的。编程范式本质上是解决问题的不同方式。一种方法是将问题分解为类,将这些东西在计算机科学中,可视化为对象。这就是我们称之为面向对象编程的原因。
这在类中占有很大一部分。我们将学习C++如何实现面向对象编程。但还有其他类型的编程。一种叫做过程式编程。这在C中主要使用。对吧?C基本上是,好的,这里有一个指令。做这个。做那个。做那个。
这叫做过程式编程。C++很酷的地方在于,人们称它为多范式语言,因为它结合,了所有这些不同的范式。你可以进行过程式编程。这就是C++从C派生的原因。你可以进行面向对象编程。我们很快会谈到类。
然后还有第三种,叫做泛型编程。这可能是你在上课时甚至没有想到的,你可能没有想到自,己是为了学习泛型编程而来上这门课的。但我相信泛型编程是C++中最独特的部分之一。其他语言也有泛型编程。它们有模板的能力。
各种各样的东西。但我认为C++有一套非常独特的泛型编程工具。这就是我们要学习的内容。我们有四节关于泛型编程的讲座。今天是第一节。然后在星期四,我们将讲解函数和算法。现在,你们可能会想,是的。
我知道什么是函数。我知道什么是算法。但那节课非常重要。确保你完成那节课。好的,今天我们将学习模板函数。所以这是我们要讨论的第一件事。我们将跳过可变参数模板,这是我觉得非常酷的内容,但我,们没有时间讲解。
不过幻灯片里有相关内容。概念提升,这是一个重要的讨论——动机是为什么我们需要,模板函数。然后我们会讨论隐式接口和概念。这将直接引入我们下周关于算法和函数的讨论。好的,我现在要切换过来。
我们不再使用幻灯片了。我们将讨论模板函数。我怎么放大?命令什么?哦,这样就好了。好的,所以我们要编写的第一个函数是我们将编写一个名,为myminimax的函数。现在。
我真的想把这个函数叫做minimax,但不幸的是C++里,有一个minimax函数。我们基本上要实现那个函数。但我会称它为myminimax。基本上,myminimax的工作方式是你给它两个参数。
比如说a,和b。然后,例如,这里我们说myminimax 3和负2。然后它应该返回一个对,其中第一个元素是较小的,第二个,元素是较大的。到目前为止很简单吧?好的,作为一个快速测试,你们都能看到这个吗?
好的,min1应该是什么?负2。Max1应该是什么?3。好的,太棒了。你们都能比较数字。做得很好。好的,所以这基本上就是这个函数的功能。为了使代码更简洁,我还编写了这个函数printminimax,它。
接收最小值和最大值,并打印出最小值。所以如果你运行它,它的行为正如你所想。为了证明给你们看,min是负2,max是3。如果两个数字相同,哪个是最小值哪个是最大值并不重要,到目前为止有任何问题吗?所以。
好吧,我们快速讨论一下这个。这个函数对整数来说很好用。但如果我想做类似的事情,比如说,QtCreator不是最新的,版本,所以它不识别自动缩进。它不识别结构绑定或有效的东西。所以你会看到奇怪的自动缩进。
好的,如果我们想做这样的事情呢?8。3和7。4的最大值。所以这不起作用。有人想猜猜为什么它不起作用吗?双精度浮点数转换成整数。对。你想要一颗糖吗?对。好吧,所以对,8。3是双精度浮点数,这不是整数。
现在,你会看到它仍然会编译。它会给你一个非常严重的警告。是的,你在尝试将双精度浮点数转换成整数。但它会编译。猜猜它打印什么?对,它打印8和7。它基本上将这些数值转换成整数,然后尝试做其他整数操,作。
这绝对不是你想要的结果。好的,明白了吗?当它说隐式转换时,是指编译时,还是在运行时它看双精度,浮点数,将其转换为整数,然后给你一个意外的结果?是的,隐式转换是在编译时完成的。所以当你按下运行时。
当它在编译时,它会看到哦,你在尝,试将 double 转换为 int,所以它会为你执行转换。然后在运行时,你会发现哦,它什么也没有打印。所以,另一种方法是,它们可以显示一个错误,以便我们意,识到这一点。
对,是的,完全正确。所以 C++ 可以在那儿显示一个错误,但记住 C++ 的一个,关键理念。不要,不要,这个理念是什么?不要,是的,基本上是给程序员完全的控制,如果他们想做,任何他们说要做的事情。
即使是错误的,C++ 也会让你这样,做。模板的一个重要特点是它会为你进行编译时检查。如果你输入错误,它有时会发现并告诉你,嘿,那不是你输,入的内容。问题,对吗?我知道这是一个隐式转换,它会起作用。
但如果你传入字符,串 "8" 和字符串 "7" 会发生什么?好问题。嘿,巧合的是,我这儿有这些东西,所以我们拿出来看看。好的,你可以看到这里,比如说,我们尝试使用字符,所以技,术上字符可以转换为整数。
所以这不是问题,即使这仍然有,点奇怪,但你可以这样做。如果你尝试使用字符串,那就不行,对吧?字符串不能隐式转换为整数。所以,不用担心模板是什么,如果你对模板一无所知,你会,怎么解决这个问题?
我们可以将其做成一个函数,并传入一个比较函数来比较,两个元素。传入一个函数。好的,所以你是说我们传入一个比较函数?是的。好的,这有点更高级。我们下周会讨论这个。但是,是的,我们很快会讨论这个。这有点。
假装你不知道它。那也需要模板,所以我们不要讨论那个,但,是的,你绝对正,确,这就是我们下周会做的事情。是吗?哦,这符合超时要求吗?哦,是的,一次。
你想要 Reese、Kit Kat 和 Hershey's 吗?Reese,Kit Kat。你想要 Kit Kat 吗?哦,你想要 Kit Kat,还有什么你想要的?好的,我们会给你一个 Reese。
哦,你可以重载那个 print、min 和 max 方法。当然。min 和 max 方法,然后分别为字符串、双精度数和字符串,提供一个。当然,好主意。这就是你在 C 中会做的事情。如果你在编码 C。
如果你们,中有些人正在上 107 课程,你将会这样做,因为那个函数,不起作用,所以我们写一个处理字符串的函数。那么你会如何编写函数本身?是的,所以如果 a 小于 b,你可以使用字母顺序比较字符,串。
然后返回 a,否则返回 b。好的?很好,这解决了这个问题。是的,这解决了这个问题。我们还需要为 print min 和 max 做同样的事情,所以我,们不做那个。那么你如何解决这个双精度数的问题?是吗?
在另一种方法中,你可以将你的输入或函数声明为 auto ,吗?有趣,所以你是说将这些做成 auto 吗?是的。这是个好问题。auto 可以用于许多地方,但它不能用于参数。所以,是的。
这是一个非常好的问题,基本上你不能在这里,使用 auto,否则会报错。你可以在返回类型中使用 auto,你可以在其他地方使用 ,auto,但你不能在这里使用 auto。但我们要做的是,模板。
就像在打印中使用 auto 一样。我可以因为那个问题要一个糖果吗?当然可以。好的,那么。Kit Kat。另外,我会指向你,如果你不说你想要什么,我就随便扔一,个东西给你。好的。我还想确保你们都保持警觉。
好吧,是的,这很不错。我们如何处理 double 类型呢?一样的处理。好的。好的,你知道吗?我就直接复制粘贴这个。是的,就这样。是的,这是个好点子。所以,幻灯片真的很漂亮,所以我会。好吧。
这就是我们写的,对吧?实际上,好吧,这就是我们写的。顺便说一下,有时候你会看到这样的情况,每个你调用的函,数,好吧,这些是 int 类型的,这些是 double 类型的,还,有字符串类型的。这很糟糕。
因为你必须在函数中实际写出 int。为什么要这样?哦,在 C 语言中你必须这样做,因为你不能重载?在 C 语言中你可以重载。是的。在 C 语言中你可以重载,但这是最基本的情况。如果你不知道重载函数。
你可以这样做。有时候对于星号来说。是的,我不打算讨论 void 星号,但这确实是一个使用它的,地方,对吧?所以,是的,这就是我们写的,对吧?好的,你注意到了什么?它们是如此冗余。好的,它们是冗余的。
对吧?好的,这是一个观察,这也是为什么我要有幻灯片,因为它,看起来真的很漂亮。注意那些高亮的部分。你注意到了什么?它们完全一样。就像,没什么不同,它们完全一样。你注意到的唯一区别是时间。所以。
让我们做你可能自然会做的事。好的,让我们用 T 来替代它们。好的,如果我用 T 替换它们,你注意到整个情况有什么不,同吗?一切都是一样的。是的,所以我们就把它们合并成一个吧。这样就有了一个通用函数。
现在,C++ 不知道那是一个通用函数。它查找类型 T,然后它想,哦,T 是什么?我实际上不知道 T 是什么。所以你必须实际告诉它,哦,这是一个模板。就是这样。模板非常复杂,但最基本的模板形式,就是这样。
我们告诉 C++ 我们在声明一个模板函数,它有不同的模板,变量。我们有一个模板叫 T,然后我们在不同的地方使用这个 T,是的?有没有不能模板化的东西?例如运算符?你可以模板化一个运算符。是的。
我认为可以。但我认为这实际上是它们的实现方式。有一些非常高级的 C++ 特性是通过模板化的运算符来实,现的。我们会讨论运算符。但一般来说,有没有不能模板化的东西?你可以模板化 Lambda 表达式。
你可以对类进行模板化。你可以模板化……,我遗漏了什么吗?你可以模板化很多东西。我们会讨论很多,但我现在想不到什么……,我的意思是,你不能模板化一个变量。虽然,是的,那些通常会这样做。
所以我会再回到这个问题上。我知道你会提到这一点,所以我们来看看。是吗?你能在 T 之外放入别的东西吗?或者放 B 吗?当然可以。或者在这种情况下用 B,但 T 小写。很棒的问题。
那么让我们过渡到输入代码,试试看。所以模板,类型名称,然后为了好玩,我们叫它……,你想叫什么?小写 T。好的。当然。然后在这里,我们会说,不是 int,而是 T,然后我们不需要,其他东西。是的。
这样可以。哦,哇。这很完美。关于这一点,还有一点就是,一旦你学会了这个结构,人们,会告诉你,他们其实希望你给类型名称起个能提供信息的,名字。所以,比如说,类型名称……,是的,你可以起任何你想要的变量名。
是的,不过……,我只是打算叫它 T。但我们以后会给它们更具信息性的名字,因为之后我们会,处理多个类型。所以你不想要 T……,T 后面是什么字母?T……,U。T-U-V-W。是的,你不想要那些。
所以作为一个例子,我们现在用 T,但以后我们会用其他名,字。是吗?是的,我有个问题。如果你尝试模板化一个不可比较的自定义结构体,那会不,工作吗?这是个很好的问题,我们会在 20 分钟后讨论。不。
不是 30 分钟,是在课堂结束时。20 分钟。是的,这和隐式接口和概念有关。但你提出了一个很好的点,对吧?这个函数假设你可以将一个 T 与另一个 T 进行比较。但,有些东西实际上你不能这样做。
所以我们会讨论这个。糖果?名字?任何人。好吧,没有人喜欢这些,所以我要给你这个。这些有……,它们很好吃。好的,等等。如果你有过敏症,你不应该吃这个,对吗?是的。这是一个好的类比,是的。你还好吗?好的。
如果 A 是 double 而 B 是……,很好问题。我们会在五分钟后讨论。糖果?不,我没事。好的。是吗?那么模板 T 的定义时间有多长?对于 printmin 和 max,我也可以使用 T 吗?
还是我需要再次声明模板类型名称 T?好问题。我认为……,安娜,如果我错了请纠正我。我认为你只能在这个地方使用它。你必须为每个函数重新定义。虽然我不确定。是的,我认为因为这是一个函数模板。
所以它是为紧接着的,函数定义的,当我们谈到类模板时,你可以在整个类中使用,它。糖果?我要去做了。当我说糖果时,你必须立即说出你想要什么糖果。否则,我就把你放到别的地方。是吗?
这是否具有类似于 Java 泛型的功能,你可以指定。你只能指定。它只会接受某些东西。这是一个很好的问题,我们很快会讲到。这与所谓的概念有关,这是 C++20 的特性。我们会讲到那个。在 Java 中。
他们称之为接口。接口,或者。我对 Java 不太熟悉。在 Java 中他们叫它什么?接口,对吧?是的,有类似的东西,但我们很快会讲到这个。你问的问题非常好,这些正是我将要讲解的内容。还有其他问题吗?
是吗?你能详细讲解一下这行是什么意思吗?看起来类型名称是变量类型,然后 T 是一个变量类型名称,什么是模板?好问题。模板本质上就是声明,好吧,接下来的是一个模板。然后类型名称意味着任何。
我们正在使用某种类型对其进行模板化。你实际上可以使用其他类型进行模板化。这就是说,好吧,T 是一个大小。你可以说,这就是你想要实现的东西。你也可以使用值进行模板化。但我们大多数情况下,对于我们的模板。
我们主要是使用类,型进行模板化。这就是为什么我们在这里说类型名称。对于类型名称,这意味着你可以放入整数,你可以放入双精,度,你可以放入自定义结构体,你可以放入任何东西。稍后我们会看到,我们会讲到类。
这意味着 T 必须是一个,类。所以整数和双精度将不起作用。你想要糖果吗?黑巧克力?没有黑巧克力。那就 Kit Kat 吧。好的。我们能否继续。我们先完成代码,然后再问更多问题。这一切都不起作用。
我们能否制定一个规则?你只能请求桌上现在有的糖果。让我先完成这个函数。我们进行了模板化。我们还需要模板化这个。所以 G,T。和。几乎所有的都有效。让我们试着运行它。你注意到了什么?最小值,最大值,很好。
最小值,最大值,很好。最小值,最大值,很好。最小值,是的,B 小于 L。这是正确的吗?哪个按字母顺序排在前面?所以 Anna 应该是最小值,然后 Avery 应该是最大值,对,吧?奇怪。
但我不会告诉你原因。我们先来谈谈实例化。那么什么是实例化?我们会做一些幻灯片,因为这些幻灯片也非常漂亮。好的,所以有两种方式。当你调用如 minimax 时,最清楚的指定 T 的方式是显式,实例化。
其中你在函数后面用括号明确指定类型。这种表示法,你可能见过类似的,当你尝试声明一个向量时,对吧?当你声明一个向量时,你把类型放在里面。所以你可能猜到,向量也是一个模板。这里,函数是一个模板。
你在里面指定 T 是什么。取消这些注释。顺便说一下,我真的很努力地弄清楚哪个部分应该注释,哪,个部分不应该。好了,我们这里有一个 int,double,string,int,double。好的。
这很有趣。一个 int 向量。所以 T 是一个 int 向量,然后我们传递的向量是,我们使,用统一初始化,1,2 和 3。我们还可以声明 times。这是开始时间和结束时间。
我没有打印向量的 minmax,但我确实打印了时间,你会看,到的。那么,让我们看看。这一切都很好。你在这里注意到了什么?这是正确的。发生了点奇怪的事情,对吧?Minmax 3,这很有意义。2 和 2。
3,这也很有意义。时间,它甚至能判断 3。30 在 4。20 之前。现在,稍微提一下,唯一能够编译的原因是如果你查看 ,temp。h,我定义了比较两个时间的意义。
因为 C++ 实际上不知道你说的比较时间是什么意思。我在这里定义了这些。我们还不会谈论这些。我们会在几周后讨论它们。但是问题是,如果你告诉 C++ 如何明确地比较时间,你可,以比较时间。有问题吗?
到目前为止我觉得很有意义。今天我们要讲的内容不多,所以我们可以稍后再深入讨论,有问题吗?如果你去掉那一行,模板,类型名称 T,因为我们已经提到,我们在内容中使用的变量类型,这样做有效吗?
你是说去掉这部分?去掉模板类型,类型名称 T。这一部分?问题在于,C++ 不知道 T 不是一个真实的类型。它是一个模板。这就像一个模板。你必须明确地说它是一个模板,才能让它知道,哦,T 不是。
一个真实的类型。它是一个在调用函数时会被填充的东西。到目前为止有问题吗?你想要更多糖果吗?我知道这可能有点泛泛而谈,但你能定义一下模板吗?模板一般是什么?模板是什么?它在内部如何结构化?它的目的是什么?
好问题。今天我们主要关注模板函数。我们只会模板化函数。还有更多的东西可以模板化,但这需要更深入的 C++ 知识,我们会在几周后讲到关于模板类的内容。你可以模板化很多东西。今天我们主要关注函数。
但我们会讨论这些。模板化某物是什么意思?这意味着在编写代码时,你在不确定的情况下以某种方式,编写代码,具体细节将在调用函数时确定。例如,让我们快速浏览一下……,再去看一下幻灯片。例如。
当你第一次调用 my minmax double 时,当编译器看,到这个时,编译器会说,好的,my minmax,我知道这是一个,函数,而这个函数是一个模板。它会尝试确定,好的,我的 T 是什么?
T 是 double。它的作用是将 double 替换为所有的双倍数。代码就是这样运行的。有趣的是,这一切都发生在编译时。当你编译代码时,所有这些函数,这些包含 T 被替换的函,数,都是在编译时生成的。
这是一个编译时的过程,当你按下编译时,所有这些函数都,是从那个模板中生成的。在运行时,一切都只是一样,就像我们自己写的一样。不过要注意,这仅发生在显式实例化时。当你进行隐式实例化时,一个好处是。
或者说好处或缺点是,它在运行时创建这些函数,因此你只会获得你在代码中直,接调用的函数。这可以是一个好处。如果你在代码中有很多额外的显式函数,它可能是一个缺,点,因为它会使你的编译时间更短。抱歉,对。
编译时间更短,但运行时间更短。我刚刚错过了。等一下,因为如果你用错误的类型实例化它,它会编译它。它会检查,但不会生成它。所以,是的,这基本上就是问题所在。是吗?你能给出每个 ID 的例子吗?
因为我们以后不会涵盖那个。我想在以后涵盖那个。好的。那边有一个问题。我有两个简短的问题。第一个是更机械化的问题。你说模板是整个函数,但我认为模板是类型。所以类型 T 不是模板吗?好的,让我想想词汇。
整个函数是一个模板函数。函数是一个模板。那个词不正确吗?它是一个……,是的,所以整个东西现在只被称为函数模板。它不再是一个函数。那我们怎么称呼 T?然后 T 是模板参数?好的,模板参数。
因为这些有点像参数。你用某个东西填充它,然后它变成箱子里的东西。第二个问题,抱歉。你能回到代码上吗?是的,所以如果你想放两个不同的类型,比如第一个数字是, double,第二个是整数,我们怎么做?哦。
好吧。所以你说给出两个不同的类型?是的。我们可以做这样的事情。我们叫它 T……,好的,我不知道我的字母。你可以这样做。所以你说,好的,T 是模板,U 是模板。是的。那么问题是,这样不太合理,因为你说。
我可以比较一个 ,int 和一个字符串。你怎么确定哪个是 int,哪个是字符串?所以这个函数实际上没有意义,因为它允许你比较不同的,类型。好的。我以为你只是一般性地向她展示一些东西,而那样的话,通。
常也会带到那里。我不知道你试图将它应用到那个函数中。是的。所以语法是这样的。你不能用这个函数,因为两个不同类型之间没有 min 和 ,max。但是语法会是这样的。我们很快就会做这个。
我们可能不会做到这一步。听着,你能不能添加第二种类型并用逗号调用这个函数?好的。就是你给我的那个。好的。嗯?那么这些类型名称会沿文件一直跟随吗?所以你在一个文件中有多个函数吗?是的。它只跟随函数。好的。
还有其他问题吗?我没有很多问题。嗯?当你使用类型名称 p 然后是类型名称 u 时,t 能否和 u ,一样?可以。你可以模板化函数吗?哦,意思是你传递一个函数?是的。可以的。因为那样你可以接受任意的函数。
是的。这就是本讲座的全部内容。好的。好问题。这里有问题吗?有。那么,当你进行显式实例化时,为什么 string 函数返回的,值会是?好问题。我本来打算稍后讲这个,但我现在会快速讲一下。
原因是字面量 Avery 的类型是什么?这是一个 C 字符串。所以要记住,它是一个 C 字符串,对吧?记得,106B。OX 的第一周,Cynthia 讲过 C 字符串与字符,串本身的不同。
字面量本身是一个 C 字符串,而比较 C 字符串是不好的,你真的不想比较 C 字符串。所以这里的原因是因为我之前声明了 Avery,这就是为什,么 C 字符串 Avery 小于 C 字符串。
但是我们可以通过显式指定字符串来解决这个问题,它会,查看这些 C 字符串,但它会说,等等,你想要一个字符串。T 是一个字符串,而不是 C 字符串。所以它将 C 字符串转换为字符串。好问题。
还有其他问题吗?是的,任何人都可以问。如果我们重载你有的函数,比如 my。min。max,使其适用于,所有类型,除了 int,然后用另一个定义重载 int,那它会,选择哪个?这是个很好的问题。好的。
这些问题都很棒,也很难。所以 C++ 有一些东西,当你创建一个模板时,它首先查看,所有可能的函数,包括所有模板和所有重载函数。我认为这叫做重载集合或其他什么东西。这些都是所有可行的函数。
然后它有一系列规则来确定哪,个是最好的选择。是的,但在这种情况下,当它试图解决这些冲突时,这两个,函数的签名完全相同。那么它会如何决定使用哪个?在 CppReference 上有一系列规则。
简短的答案是,不要这样做,因为这真的很混乱。但有一系列规则来确定哪个模板是最好的选择。而且它很可怕。我不理解这些。所以如果你不显式指定模板是什么,它会按照这些规则来,确定模板是什么。没有人会记住这些。
然后它会查看所有可能的类型。是的,确实有一种结构化的方法来做到这一点。还有其他问题吗?好,我们可以继续吗?我们继续吧。如果有问题,我们可以稍后再讨论。你现在的任务,非常简单。有人认识这个函数吗?
GetInteger。所以我们来将它模板化,现在可以用 get 代替 ,getInteger。所以现在在你的笔记本电脑上,尝试把它写成 get ,something。基本上就是模板化它。
不要返回一个 int,而是让它返回某种任意类型。这样说有意义,对吧?作为我们将如何使用这个的一个例子,我们将这里。作为我们将如何使用这个的一个例子,我们将说,好,getTypeValue 和 int。
这会提示用户输入一个整数。GetValueType,int,好的。GetValueType,double,提示用户输入一个 double。GetValueType,time,它提示用户输入一个时间。
好的,所以现在尝试将它模板化。应该很简单。那么你做了什么?你在顶部声明了模板,然后用 t 代替 int 来获取那个值,然后 typeId int,你也会将其更改为 t。结果呢?对,结果也应该是 t。
一切都编译通过了。现在试着运行一下。顺便说一下,你不必了解 typeId 的作用。在周末,我花了很多时间尝试让它打印 enter 和 int。但问题是,我的编译器对 int 做了奇怪的处理,所以它显。
示的是 i。假设是 3,因为它有两个 int。假设我们用了 6。8 的有效格式,因为它知道 t 是 int,它,不能将 6。8 读入其中。输入 double,3。0,输入 a,它称之为时间。
并且这是一个结,构体。然后我特别指示它,可以像这样输入时间。它会选择一个类型 int,还是你自己选择的?我在这里选择了它的类型。我说,要求用户输入一个 int、一个 int、一个 double 。
int。这是一个显式实例化。我们跳过很多隐式实例化的部分,因为没有太多内容需要,讲解。唯一需要讲解的是,有时候你会遇到歧义,这就是隐式实例,化问题所在。所以这里,编译器足够智能,知道 t 需要 4。2。
一个 ,double,这里 t 是一个 double。这里的 t 是什么?t 是一个 C 字符串,所以这将不起作用。你可以做几件事,你可以传递一个字符串,这样它就会转换,为一个字符串。
或者你可以显式实例化它。问题,是吗?如果你有一个情况,你可能传递一个整数或一个向量,你会,一直传递引用,还是有其他方法可以在之后选择?好问题。如果是大集合,我会这样做。我想确实有一种方法,你可以说。
如果是类,而不是输入类,那么它将按引用传递。但我认为这肯定会有效。还有其他问题吗?那么如果你传递了一个 int 和一个 double,会发生什么?它会将它们都变成 double,还是都变成 int。
还是会保持,它们的类型?好问题。所以在这里,你会看到它给你一个编译器。它说,好,t 是一个 int,但它意识到 2。3 不是 int,所以,这不起作用。所以在这种情况下,你会添加另一个类型,对吧?是的。
但你不真的想对 minmax 做那样的事,因为这允许你,给出不可比较的类型,比如 int 和 string。是吗?为什么不会自动将 double 转换为 int?模板有关于特定转换的特殊规则。
所以我认为它尽可能严格。当你使用模板时,它不会做任何隐式类型转换。好问题。好吧,你能猜到为什么这不工作吗?为什么这两个不工作?哦,这个也不工作。为什么这些不工作?Vector,time。
还有这个是一个 patient string。如果你上过这些课程,你已经知道那是什么了。是的,为什么那些不工作?什么是 patient 结构体?是的。好,首先,光看这些,你知道哪个是 time 吗?
不是特别清楚,对吧?是的,这正是重点,我们使用这个统一初始化的方式,所以,它不知道是什么类型的。所以让我们把这些拿出来。让我们快速进入下一部分。好吧,显然我们没有谈论真正有趣的内容。可变参数。
如果你想了解更多,跟我说。好,我们来谈谈泛型编程。所以我们将编写泛型函数,我们为什么要编写泛型函数?泛型函数让我们可以编写可以在多种不同上下文中使用的,函数。它足够灵活。
无论你有何种问题或想进行何种操作,你都可,以使用类似的单个函数来解决。好?你可以想象这有多有帮助。它一个接一个地重复。它也给你一个非常强大的函数,可以在任何上下文中使用,例如,我们将编写一个单一的函数。
可以解决所有这些问题,例如,计算某个元素在某个列表中出现的次数。计算 5 在列表的后半部分中出现的次数。计算一个元素在列表的后半部分中出现的次数,最多为 5 ,次。好?有一种叫做概念提升的东西。
本质上我们将从一个函数开,始,然后我们将质疑我们对函数的假设。利用这些假设,我们将尝试给函数添加模板,以使其更有趣,举个例子,看看这个函数。所以这个函数本质上计算一个整数值在整数向量中出现的,次数。好?
明白了吗?这个函数很简单明了。这个函数对自身做了一些不必要的假设。当然。所以你可以查看一下,好的,整数。它必须是整数吗?它可以是其他任何东西的向量吗?好。所以这是一个我们可以放松的好假设。
为了放松这个假设,我们可以给它添加一个模板。所以不必说它必须是 int,我们可以说它可以是任何类型,的 int。所以如果数据类型在这里,数据类型是。明白了吗?你们当中有没有些是 Jerry 学生?
我简直不敢相信没有人发现这一点。所以它必须是一个向量吗?好问题。那么它必须是一个向量吗?好的?让我们将其模板化。所以,代替向量,你有一个数据类型的集合。哦,只是一个问题。当然。哦,抱歉。没关系。哦。
好吧。只是一个问题。如果你在迭代一个映射,你难道不需要使用迭代器而不是,使用 size_t 吗?很好。这段代码不起作用。为什么?因为如果集合是一个映射或集合,它们是不可索引的。
你不能使用 size_i 等于 0,i 小于它的大小,i 加上我的,大小。你不能那样做。这就是迭代器的强大之处。迭代器提供了一个统一的接口,用于遍历集合中的元素。例如,如果我们使用列表。
列表是不可索引的,所以这将无,法编译。原因是因为我们使用了这种数组表示法,而在。基本上,它只允许在向量中使用。所以,我们可以通过一种方式解决这个问题,即我们需要迭,代器。这里有一个更通用的类型。
即我们不是遍历索引,而是遍历,迭代器。这仍然做出了一个假设。你看到这个假设是什么了吗?你确定吗?是的。这是一个常量吗?好点子。所以,如果它是常量,那即使你传入的是非常量或常量,两,者都会起作用。哦。
我明白了。如果你将它设为非常量,那么你不能传入一个常量。不过,你提出了一个好点子。我可能应该做。数据类型不必是常量。是的,这是一个好点子。如果我们去掉常量,那么我们不能传入常量集合。是吗?
它是否假设双等号有效?双等号,好吧。好点子。所以,让我们看看。哦,双等号是否一定有效?你是在说这是。这是一个问题还是一个假设?我只是说,如果双等号在其他情况下不起作用,那么你需要,使用点等号方法。
好点子。好的,那么,让我们看看。我怎么回答这个问题?是的,这个函数确实假设双等号有效。我不确定是否应该使用点等号,因为整数可能会无效。但我们下次会更详细地讨论这个问题,在那里你传入的集。
合的数据类型必须是可比较的。这实际上是在 later slides 中关于 inless 和 ,ineffects 的内容。假设,是吗?这不假设你遍历整个集合吗?是的,太棒了。
这假设我们正在遍历整个集合。记住,如果我们回顾之前的问题,有一个问题是,我们能否,计算列表的第二半部分有多少个?那么,我们怎么做呢?我们怎么可以传入集合中的一个范围,而不是整个集合呢?你先举手了。是吗?
你能传入一个对吗?很好。让我们传入两个迭代器。所以,我们有一个迭代器开始,一个迭代器结束,以及一个,数据类型。快速问题。我把这个命名为输入迭代器。为什么它必须是输入的?
为什么我不说随机访问迭代器和输入迭代器?是的。因为并非所有集合都有输入迭代器,因此这将使我们的函,数仅能用于具有随机访问迭代器的集合。例如,我本来打算给你展示这个,但我们时间不够,这里是。
写这个函数的一个不好的方式。写这个函数的一个不好的方式是这样。这是写这个函数的一个不好的方式。在这里,你可以看到我们在移动索引,然后我们尝试计算起,始位置加上某个数字。本质上。
是将那个迭代器前移一定的量。这不起作用,因为这对这个迭代器有什么假设?假设它是随机的。是的。假设这个迭代器是一个随机访问迭代器。我们希望尽可能少地假设关于那个迭代器的内容。
迭代器唯一需要做的就是能够读取元素本身。它不需要做其他任何事情。这就是为什么我们指定这个应该是一个无限迭代器。关键在于,这只是一个名称,所以它不强制执行。你会在编译时遇到奇怪的错误。如果需要。
你可以这样做。我只是这样做。你怎么解决这个问题?并发到最小值。你怎么解决这个问题?你怎么解决这个问题?等等。如果你想要第二部分,那么你可以传递中间迭代器。我们还不能解决这个问题。
因为这需要一个叫做信用工具,包的东西。如果有人问这个问题,你必须传递一个函数。我们下次会讲到这个。这匹配了一个非常酷的函数。我在周末发现了它,但我们没有时间去讲这个。我们今天基本上讲了我们想讲的内容。
我在想,也许我们可以讲到这个,但我们下次会从这里开始,因为这个隐式接口基本上涉及到你们很多问题,即我们对,不同类型有什么假设?我们对传入的类型有什么假设?因此,我们需要思考这个问题。下次会很酷。
辛迪在第一天的课堂上做了一个汉密尔顿示例。我要做一个更好的汉密尔顿示例。是的,我要写一个程序,屏蔽汉密尔顿中的所有坏组,我不,会使用任何公式。一个公式都不用。如果你想要糖果,就过来拿。谢谢你们。好了。
你们想要选择。你们都想去外面吗?等一下,安娜,不要就这样出去。我们就出去吧。
好吧,就在这里吧。小心点。
斯坦福大学《CS106L: C++编程| Stanford CS106L C++ Programming 2019+2020》中英字幕(豆包翻译 - P8:[14]CS 106L Fall 2019 - Lecture 7_ Templates and Functions (Video) - GPT中英字幕课程资源 - BV1Fz421q7oh
你可能会问,好吧,我怎么编译代码?我怎么运行代码?要编译代码,你可以选择你想使用的编译器。我想使用 G++,所以我会输入 G++。这指定了我想运行的程序。然后我会给它提供几个标志。记住之前的内容。
标志就像是参数。你可以准确指定你想让程序如何运行。我将指定我想要 STD,即哪个版本。我想要 C++17 或 20。然后在这里指定你想编译的文件。我想跳过编译文件的长而复杂的过程。
我们只是做一些简单的事情,我们将使用标志。编译后,发生的事情是它会将 CPP 文件转换为一个可以运,行的二进制文件。我将把二进制文件命名为 HelloWorld。G++ 意味着我想使用编译器 G++。
STD17,破折号 STD 等于 C++17,意味着我想使用 C++17 ,标准。HelloWorld 是我想编译的文件。破折号,你不必过多了解这一点,但编译过程有很多阶段。我只是让它跳过所有内容。
我只想编译文件并给我二进制可执行文件。所以只需在这里添加破折号。然后 HelloWorld 是输出,即我们的二进制文件名称。然后我运行它。一旦你运行它,如果你再点击一次 LS,你会看到有一个 。
HelloWorld 二进制文件。基本上发生的是编译器把 HelloWorld CPP 文件变成了一,个可执行文件。如果我运行这个可执行文件,它就像你下载的任何可执行,文件一样。如果你运行可执行文件。
那么会弹出一些东西,并运行程序, HelloWorld。如果你想从终端运行程序,你需要指定你想运行的目录,即,然后我们说斜杠 HelloWorld。这将运行程序。酷,对吧?酷。
让我们运行一个更复杂的程序。记住,我正在使用 Vim 访问 HelloWorld。cpp。我们把它放进去。数字,C8。你真的不想在鳄鱼中使用 C8。你应该使用什么?GetLine。你还应该使用字符串。
字符串。但仅仅作为示例,我们输入这个。有没有人想提醒我,我们需要输入什么来编译它?第一件事是你使用的编译器,C++。第二件事?我们要输入我们使用的标准。你不必输入这个,但如果你不输入,C++。
取决于你的编译器,将默认为 1998 年发布的版本,这很糟糕。我鼓励你输入版本。你还可以使用 14,17。为了好玩,我们试试 20。我不知道他们是否发布了,但也许。然后,答案是什么?
HelloWorld。cpp。你要编译的文件,答案是什么?破折号 O。破折号 O 基本上只是跳过你需要编译的复杂阶段。我只是跳过所有内容,从文件到可执行文件。破折号 O 意味着目标文件。
有一个中间目标文件。我们只跳过所有内容,直接到最后。最后一件事是什么?C++ 20 尚未发布,但你可以使用 C++ 2A 作为可用版本。我们试试看吧。这很酷,对吧?你实际上可以运行 C++ 20 代码。
虽然它还没有发布。我们如何运行程序本身?我们要求 cin,所以它在等待我们输入一些内容。我们输入了 15。HelloWorld。dash O 是你为文件起的名字吗?这个吗?
最后一项是你希望可执行文件被命名为什么。如果你查看实际的文件夹,你会发现 HelloWorld 实际上,是一个可执行文件。有点像你从互联网下载的任何东西。你可以点击它,它就会运行。它应该能运行。哦,不。
我需要输入一些东西。15。哇。一个简单的建议。不要这样做。不要做点、句点、斜杠,然后是你随机从互联网下载的可,执行文件,而你不知道它是什么。因为那只是运行一个程序在终端里。不要下载某种文件。
然后在不清楚它是什么的情况下运行,它。不要随便运行可执行文件。如果你要运行可执行文件,就直接运行它。不要在终端里运行它。这要看情况,例如,我可以使用 root 权限运行一个可执行,文件。
这意味着使用 sudo。sudo 的意思是 root 访问权限。你知道你在电脑上有不同的用户吗?还有一个 root 用户,他有管理权限。在这种设置下运行程序是相当危险的,因为你的程序可以。
在每个设置中进行更改。它可以基本上更改你的笔记本电脑。这非常危险。所以不要输入 sudo。不要使用 sudo。有问题吗?只是一些有趣的命令。不要记住这些,因为你会习惯它们。但有一个命令你可以使用,比如。
我们来文档一下。我们创建一个文档文件夹。它目前只有讲座视频。你可以创建一个目录。M-K-D-I-R。这意味着创建目录。我们创建一个名为 code 的目录。哇,它出现了。L-S-M 意味着删除。
所以我可以说 R-M code。这不起作用。R-M,你可以删除一个目录。如果你想删除一个文件,你可以用 R-M 删除那个文件。如果你想删除一个目录,你可以使用 R-M 带有连字符的递,归删除。
R-M code,意味着删除目录。但因为这是一个目录,当你删除一个目录时,你还必须删除,目录中的所有内容。这就是为什么它叫做递归。你必须进入目录,删除那里所有的内容,然后再返回。
你们都听说过可以输入的非常危险的命令吗?Sudo。好吧,我不想输入它。但你们都听说过那个命令,对吧?我不想输入它。R-M。R-M。点。好吧,所以。这是真的吗?它会清除你的电脑吗?不会。
因为我没有说 sudo。因为它不允许清除所有东西。它只能清除它可以访问的内容。所以我的用户只能访问我自己的内容。但如果我想,我可以删除自己的账户。好的,现在,删除。等一下,等一下,等一下。删除,删除。
好了。所以 R 代表递归的。这意味着进入目录并删除所有内容。这意味着进入目录,删除所有内容。如果你有更多目录,那么进入那些目录并删除所有内容。所以这就像删除你的整个目录一样。
然后 R-F 的意思是如果你只使用 -R,它会问你,是否确定,要删除这个?它会逐个文件地询问你,是否确定要删除这个?你猜 F 代表什么?F 代表强制,这意味着你告诉它,直接删除并强制删除。不要询问。
你可以强制执行,但你是在说一切都删除。是的。然后记得点号是什么意思吗?当前目录。当前目录,是的。所以那一行的意思是删除当前目录中的所有内容,并递归,地删除,这意味着进入所有目录并删除那些目录中的所有。
内容。并且强制执行,所以不要问我。在继续之前,让我先。等一下,我没听懂那个点号。好的,是的。所以如果你进入,L-S 的意思是列出当前目录中的所有文,件。哦,桌面。哦,那就是那个。是的。
所以如果你做 L-S,它会显示这个文件,对吗?这是一个文件。我可以创建另一个目录代码。是的。如果你做 L-S,它会显示代码和。好的。当然。好的。让我开始,好吗?但如果你对这个有更多问题,告诉我。
希望这对你有帮助。你可以运行你的程序。希望如此。如果你来晚了,那是你的错。好的。我们开始吧。我的。在幻灯片中。好的。好的。正如我在 Piazza 帖子中所说的,如果你在讲座期间有问,题,随时问。
但我要做的是我会有一个特别的。我会有特定的幻灯片,上面写着问题。所以把你的问题留到那些幻灯片上,好吗?我有点想在回答问题之前完成一个完整的块。其次,如果你。确保你的问题与我们正在编写的材料直接相关。
或者是类,似于之前幻灯片的内容,好吗?如果你的问题是这样形式的,为什么不这样做呢?或者你不能用其他方法做到这一点吗?我非常喜欢这些问题。把这些问题留到讲座后再问。好吗?这只是为了确保我们都能完成材料。
并且保持我的流程进,行。好的?酷。好的。我们今天不会涵盖所有这些内容,但会涵盖其中一些。所以简单回顾一下。上周我们写了一个叫做通用 Minmax 的东西。不是 Minimax。Minmax。好的?
所以简单回顾一下。模板类型名称 T 是做什么的?是的?它定义了一个自定义类型。是的。所以我们指定我们要编写的是一个模板函数,然后具体来,说它将使用这个模板参数 T。
然后我们只需将所有假定我们类型为 T 的实例替换掉。你们有多少人做了作业?好的。所以是四个?好的,因为反馈中有超过四个人说他们想要更多的练习题,但只有四个人做了作业。所以作业是查看幻灯片并完成。
基本上让函数不匹配变得,更加通用。你们看过了吗?然后我查看了三张幻灯片后的答案。所以我得到的结论是我不应该发布答案。好的。我忘记做整个录屏的事情了。你们今天只想看视频吗?好的。好的。好的。
我不会在这上面花时间,因为你们应该自己做这个。所以我会跳过它。但基本上你们得到的是一个针对向量的不匹配函数,你们,需要通过使它尽可能通用来提升概念。
所以这里的 mismatch 函数可以接受一个范围的输入迭代,器,它还接受第二个范围的输入迭代器。为什么你们不需要传递最后两个?我们需要传递第一个和最后一个。为什么我们不需要传递最后两个?好的。实际上。
由于你们中的大多数人没有做作业,我不知道为什,么我会问这个问题。好的。所以原因是因为 mismatch 函数基本上遍历两个范围,找,到范围不匹配的第一个实例。问题是如果你已经知道一个范围的长度。
你应该知道另一,个范围的长度。这就是为什么你只需要传递第一个和第二个。今天会多次出现这个问题。然后这个函数基本上只是遍历这些范围。它只是遍历两个范围,找到哪个不匹配。这段代码,对吧?但是 2 较短。
在指针的情况下,对吧,如果你访问一些新的东西,看起来,是错误的,对吧?那么你怎么知道它不是错误的呢?记住,C++ 的一个哲学是它会给你接口并告诉你它期望什,么。如果你决定不遵循它,那么它不会试图阻止你。
所以它基本上告诉你——如果你查看文档,它会告诉你两个,范围必须是相同大小的。如果两个范围大小不同,那么比较这两个范围其实没有意,义。然后显然会出现不匹配的情况。所以它假设这样,如果你不遵循它。
那么——哦,好吧,如果你,不遵循它,它只是读取到——如果你的第一个范围比第二个,范围短,如果你的第一个范围比第二个范围长,那么它只是,尝试读取那边的内容。今天我们将重点解决这里的最后两个任务。好吧。
我们已经解决了倒数第二个任务,我们将重点讨论如,何解决最后一个任务。因为上次我们已经写了一个通用函数,count ,occurrences,它可以接受任何范围,比如一个字符串列表,的后半部分。
以及一些数据类型,比如 5。字符串 5。问题是,好的,嗯——这里有一个错字,应该是 8。但是我们要尝试解决一个更通用的问题,即计算某个范围,内的元素数量。但是我们不仅仅是检查是否相等,我们说。
好的——但在我们,进入这个问题之前,我们快速谈谈泛型编程和概念提升。概念提升是我们查看对参数的假设,并质疑这些假设是否,真的必要的过程。还记得上次吗?我们从计算整数向量中的出现次数开始,它尝试找出该整。
数的实例有多少。我们将它泛化到可以是任何类型的向量。然后我们进一步泛化,使其可以是任何类型的容器。我们更进一步。它可以是任何范围。我们可以计算任何范围中的出现次数。在你这样做的时候。
你必须小心你对参数的假设。这就是隐式接口出现的地方。假设我们尝试使用之前做的事情来调用这个函数。我们有一个 v1,我们有一个 v2。然后我们想要计算——我们在这个上调用计算出现次数。这没有意义。
因为我们有一个范围和另一个范围,但我们还,是会尝试调用它。这段代码有什么问题?让我先说说我试图做什么。我试图计算 v1 中第一个元素的出现次数。因为 begin 指向第一个元素。为什么这个无法编译?
作为提示,让我们看看代码。编译器所做的是,它基本上试图推断类型是什么,然后将其,直接放入代码中。如果我们仅仅查看类型,v1。png 的类型是什么?它是四个向量位的迭代器。v2。png 的类型是什么?
也是一样的。如果我们进入这里,它会直接尝试推断这些类型是什么。它在下面的代码中造成了一个问题。你能看到这个问题是什么吗?注意在这一行,我们说解引用迭代器。这应该给你什么?一个 int。
然后我们比较它是否等于 val。val 是什么?我们在比较一个 int 和一个迭代器。这就是隐式接口出现的地方,即当你声明模板时,你可以直,接尝试将任何类型插入模板中。有时候它会工作,有时候它不会工作。
编译器会强制执行哪些类型不起作用。它是通过查看你对这些类型进行的操作来确定的,它查看,这些类型必须满足什么条件。你刚刚指出了一个好的类型,即当你解引用 iter 时,它必,须能够与 val 进行比较。
这是这些类型的一个要求。当你解引用这些类型中的一个时,你必须能够通过相等性,比较来比较它。如果这是一个迭代器,而这是另一个迭代器,那是不行的。如果这是一个迭代器,而这是一个 int,那是可以的。
明白了吗?让我们看看输入迭代器和数据类型还有什么其他要求。这个函数施加了一些其他要求。这些要求是什么?好问题。这里说 begin 必须是可复制的。不是所有类型都是可复制的。我们稍后会学习这一点。
这里我们有一个强制要求,我们必须能够复制任何输入迭,代器。这很重要。迭代器 iter 必须可以与 int 比较。在这里,我们还看到,为了使这段代码编译通过,你必须能,够递增 iter。
你能想到不能递增的类型吗?哪些类型不能递增?对,字符串。你不能递增一个字符串。如果我们尝试传递一个字符串给 iter,那将不起作用。你看到这些是如何导致这些隐式要求的吗?我们没有明确说明要求是什么。
但代码本身强制这些要求,我们来做一些问题吧。你在说 begin 并不一定对所有类型都可赋值。字符串会是一个例子吗?好问题。一个不能被复制的东西是字符串。如果我让你复制 cout,那将不起作用。
想象一下如果可以,这会造成什么样的混乱。你会有多个 cout 的副本在四处流动。有些东西就是不可复制的。如果你学过 C、S106B 或 X,你会知道你必须通过引用传,递文件流。原因是流不可复制。
如果我们尝试传递一个流到这里,这个隐式接口将不起作,用。这就强制你必须能够从这里复制东西。这里的另一个问题是你必须能够将你的迭代器与 int 比,较。在这里,我们强制 iter 必须在其中。
如果你给它奇怪的限制,它真的会尽力寻找符合所有约束,的东西吗?是的。假设我们给它一个字符串。这将有效,因为 auto 推断你传入的是一个字符串。你提到,auto 会尽力找出是什么类型。
你提出了一个很好的观点,即 auto 会尽一切努力找出是,什么类型。即使类型是错误的。这个 auto 本身不是隐式接口。但是,可赋值、可复制的部分是接口的一部分。我们说 auto 好像它是一个坏东西。
但想象一下,如果不用 auto,你必须在这里写什么类型才,能使其编译?如果我们不使用 auto,你必须在这里写什么类型?输入迭代器,对吧?因为这个类型应该是一个输入迭代器。
它是从一个输入迭代器复制过来的。在这个例子中,直接插入它是可以的。但有时你的类型会更复杂,这时候 auto 是一种很好的方,式来说,好吧,帮我找出那个类型。真正的接口在你尝试找出其他部分时才会出现。
之前我们提到,这是隐式接口失败的地方,我们尝试解引用,输入迭代器时,它必须能够与你的数据类型比较。模板对类型施加了这些要求。问题?是的。当你将迭代器传递给那个函数时,它是否自动按引用传递?默认情况下。
我们并没有按引用传递它。我们只是按值传递。你也可以用 & 来指定,我希望它按引用传递。我们实际上是对那个迭代器进行了复制。你提出了一个很好的观点。在这里,我们已经在复制迭代器了。
当你执行 auto iter = begin 时,这也会创建一个副本吗,是的,它会创建一个副本。好问题。这一行确实会做一个副本。对于大多数类型,迭代器的复制相对便宜。通过值传递它们是没问题的。
还有其他问题吗?为什么不能递增指向字符串向量的迭代器?让我们看看。为什么不能递增指向字符串向量的迭代器?如果输入迭代器是字符串的向量的迭代器,那是可以的。但如果begin本身是一个字符串。
那我们尝试将迭代器设为,begin,这样应该是一个字符串。然后我们试图说,好吧,拿一个字符串。我不会做那个例子,但如果你尝试编译这段代码,你会得到,一长串难看的错误信息。曾经有一个编程在线竞赛。
其目标是写尽可能少的代码,并,尽量生成最多的错误。赢得比赛的方法是使用模板。模板的作用是,如果它发现不匹配,上周我提到的这些自动,推导规则。它会尝试每一种可能的类型,并会基本上尝试所有可能的。
方法来看看是否有任何有效的。如果不行,那么它会说,哦,这些都不行,所有这些都是错误,的。所以你会得到这些非常长的消息。我已经调试过一些学生的代码,你会看到这些巨大的错误,信息。最糟糕的是。
错误信息非常糟糕。这些信息一点帮助都没有。这就是模板的一个困难之处。我们应该注意哪些问题以便在错误代码中识别出这是一个,迭代器问题?好问题。我现在没有例子,但如果你去Qt Creator。
有时错误信息会,显示错误代码1。有没有人遇到过错误代码1?类似的错误。如果你遇到这个问题,你应该去“编译输出”标签页,在那里,你会看到所有的错误。并且查找你写的代码行。因为有很多错误。
其中很多来自于迭代器库。所以你应该查找你写的代码。你应该能看到main的内容,也就是你写的内容。这就是我开始调试的方式。这些问题很难调试,这就是为什么C++20有一个新特性来帮,助处理模板。
而且它实际上是C++20中最重要的特性之一。它叫做概念。我接下来会谈谈这个。嗯,可能不会。我可能会跳过它。好的。再举一个例子。好吧,拿这个。和你的伙伴讨论一分钟。讨论这个函数的隐式接口是什么。
对集合和调试有什么要求?好的。好的。所以对那些想要糖果的人,谁能告诉我对集合或数据类型,有什么限制?在那边。是的?数据类型是否需要实现比较函数?让我们看看。数据类型是否需要实现比较函数?那么在哪里呢?
这里,对吧?是的。所以当你做list bracket I时,应该给你一个数据类型的,元素。它们是否自动知道比较数据类型是否等于数据类型?应该是的。应该能够的。尽管,你应该 - 如果你定义了自己的类的话。
是的,如果你定义了自己的类,它可能会或不会是可比较的,所以,是的,这是一个很好的观点。这绝对是一个接口。嗯,糖果?你选择什么都可以。太晚了。我已经为你选择了。好的。是吗?
你需要能够使用括号表示法访问数据类型吗?确切地说。好点子。所以列表,这个集合,它必须具有括号表示法。好的?那么,什么类型没有括号表示法?映射肯定有,但它不是你期望的那样。像列表、集合,是的。糖果?嗯。
当然。什么类型?栈。是吗?它必须具有点大小方法。是的,好点子。所以列表必须有一个点大小方法。哪些类没有点大小方法?栈。当然。栈。嗯,栈没有 - 字符串。是的。好的。酷。糖果?当然。好的。
如果你说“当然”,我会假设那意味着任何东西。好的。还有其他的吗?你必须 - 有些数据类型,如,不 - 是的。像,即使是现有的,不一定是你自己创建的,有一些数据类,型适用于它吗?这是个好点子。
所以有些 - 所以当你处理一个集合时,比如向量,每次你,把东西放到向量中时,它会复制你放入向量的内容。所以数据类型必须被复制。哦,对不起。刚才说的,是的。对不起。
集合必须能够 - 必须有大小 T 的索引,这也意味着它必,须已经是有序的?是的,好点子。它必须是一个序列容器,对吧?嗯,不只是任何序列容器,但这基本上仅存在于向量和索引,中。列表没有这些。是的。所以。
是的,好点子。它必须是可索引的。糖果?奇巧。奇巧。好的,这行不通。这行不通。这行不通。差不多。是的。第三个记录数据类型,是否已经暗示它是某种集合?比如,里面有多个东西?第二个记录等等?这是个好点子。
我认为这可能是个错误。我可能确实需要删除这个。没错。我应该删除这个。是的,好的。是的,好点子。这不应该在那里。然后集合本身应该能够推断它是一个 - 然后这确实提出,了一个要求,就是集合,集合中的东西。
必须是类型 A。所以如果你那样输入,它是否仍然能够编译,一旦你有 - ,我认为这不会编译。好的。我知道你仍然需要保留引用。所以模板不假设引用。好的。我会修复这个问题。你能再次看到那个问题在哪里吗?是的。
所以这里我们不应该放这个尖括号,因为编译器会试图弄,清楚集合是什么类型的。它会说,哦,这是一个字符串的向量。是的,所以我们不需要尖括号。现在,这是一个额外的要求,即集合中存储的内容必须是数,据类型。
是的,你不能让这个是一个字符串的向量,而这个是一个整,数。或者让它可通过等号与数据类型进行比较。所以如果我们看到另一个等号。那么我们就来逐一查看它们。大小。是的。而且你们还发现了一些其他问题,这很好。
让我快速讲一下 C++20 的一些内容。C++20 允许程序员显式指定接口是什么。所以这里我们显式地说明它,无论是什么,都必须是一个输,入迭代器。而且它必须是值类型。如果我们解引用迭代器。
它应该可以与类型进行比较。我认为这就是它的意思。再次强调,这是 C++20 的特性。这还没有正式化,但这就是 C++20 将要做的事情。这允许的是,如果你打破了某个接口,错误信息不会是一大。
堆恐怖的错误信息。错误信息将源于此。所以这不是隐式接口,而是显式接口。显式总是比隐式更好。然后在文档中,你会看到这个,它是自我文档化的。你确切知道它作为输入迭代器,并且在解引用时将是可比,较的。好的。
酷。我们来做一些问题。有问题吗?你能解释一下隐式接口和显式接口之间的区别吗?当然。所以这里,看着这段代码,我们只是尝试弄清楚隐式接口是,什么。我们尝试弄清楚关于输入迭代器和数据类型的要求是什么。
但它没有明确写在这里。你必须实际查看函数,一步步弄清楚这些类型必须符合什,么要求。这不是很好,特别是当你只有文档的时候。你不完全知道输入类型和数据类型必须满足什么要求。所以为了使其更明确。
现在你可以像这样做,明确说明它必,须是一个输入迭代器,可比较等等。我们在代码中明确写出了要求。有什么问题吗?好的,我们继续。函数到 Lambda 表达式。我们今天的进展很快。我们讨论了概念提升。
这是我们写的函数,它解决了一个问题,即某个类型的某个,值在一系列元素中出现了多少次?我们可以使这个问题更加通用。一种方法是我们可以查看这一部分,将其重新表述为,在一,系列元素中。
元素满足等于值的情况有多少次?明白了吗?是一样的,对吧?这是在做相同的事情。同意吗?好的。现在,我们可以更进一步地概括。因为等于值,我们可以用其他东西替代它。我们可以将其替换为。
元素在一个范围内满足小于5的次数,是多少?这不一定要是等于值。可以是任何一种真或假的条件。这被称为谓词。谓词是一个接受一些参数并返回布尔值的函数。例如,是否等于3?我们有一个函数。这是一个一元谓词。
它接受一个值作为参数,并返回一个值是否等于3。你也有二元谓词,它接受两个参数,然后返回一些真假值。与其说,元素有多少次满足等于另一个元素,我们可以用一,些通用的谓词来替换。更好的是。
现在你可以传入一些通用的谓词。你能看到这比我们之前写的计数出现次数还高一层吗?之前,它必须是有多少个元素等于另一个元素。现在是有多少个元素满足这个条件。然后我们可以这样做。这非常简短,所以我不想写代码。
但你可以这样做,你可以定义一个函数,判断是否小于5。然后你可以在 v。begin、v。end 上调用 count ,occurrences,并传入条件。发生的情况是,它会尝试运行这个条件。
如果谓词对这个元,素有效。它会尝试对每个元素调用谓词,如果谓词返回 true,则计,数加一。这非常重要。现在有问题吗?有吗?所以谓词在这种情况下是一个函数吗?所以你是把一个函数作为参数运行吗?没错。
是的。你需要列出它吗?如果你想使其更通用,你需要列出很多不同的谓词函数吗?你必须具体写出来吗?是的,所以有函数的语法。你可以显式地写出函数指针,这在 C 语言中很常见。但因为通常不需要这样做。
我们不这样做。但最好还是作为模板来做。我们也可以模板化函数。还有一种类型叫做 std:function,它像是任何类型函数,的通用类型。所以你可以尝试这样做。你也可以这样做。
然后我们可以使用这个函数调用,并且它应该返回 true。是的,我们可以创建很多不同的谓词。这些都是谓词函数。不用担心这个 inline。这不是特别重要。但我们可以写很多谓词。
唯一的要求是它必须返回布尔值,并且必须接受一些谓词,作为参数。回到 Andrew 的问题,注意即使在我们的谓词中,我们也可,以将我们的谓词函数本身做成模板函数。
所以我们在其他模板函数中使用模板函数作为参数。是的?所以谓词不一定要是布尔值,对吗?你可以有与成本相关的值,并仍然以相同的方式调用它们,吗?谓词必须返回布尔值。
因为我们将谓词用于调用某些东西并查看它是否返回 ,true 或 false。在这种情况下,但在其他情况下,它总是布尔值吗?是的,我们使用“谓词”这个术语来表示它必须是布尔值。它必须返回一个布尔值。
返回布尔值的函数。是吗?如果一个谓词需要若干参数,那这些参数的数量可以是零,吗?它需要一些参数吗?是的。它可以是一个已知参数的函数。我认为在CS110中,这些被称为惰性函数。T-H-U-N-K-S。
是的,惰性函数。你可以使用这些,但对于我们今天要做的情况,它们不会特,别有用。因为,我的意思是,你只是对每个元素调用相同的函数,但,没有传递任何参数。比如,是吗?
一个不接受任何参数的函数并不是特别可定制,对吧?它每次都运行相同的内容。回到Michael的问题,我觉得你可能在问,是否有任何情况,我们可以传递一个不是谓词的函数?确实有这样的情况。
我们实际上在之前的一个算法示例中,见过,我认为Avery可能会在未来讲解这个问题。在这种情况下,你可以将它称为谓词,但确实可以传递做特,定事情的函数,而这些函数不一定返回布尔值。在这种情况下。
你确实可以。好的,是的,这些是一些谓词函数。我真的很喜欢这个。我的就寝时间是4点,所以……,这就是我的就寝时间。是凌晨4点还是下午4点?当然是凌晨4点。好了,现在我们,可以这样做。这就像是一个数字向量。
这就像是我的睡眠时间是4点。所以你可以说,好吧,我可以传递计数出现次数,计算有多,少个小于2。我可以说我的第4周的睡眠时间中有多少超过了我的就寝,时间。一个,就是这样。好的,有什么问题吗?好的。
这种方法有两个主要问题。我们称之为函数指针方法,即我们本质上是在传递另一个,函数。有人看到问题了吗?有问题吗?是吗?是的,请继续。这不太明确。这很……,你是说它不明确,然后你必须说小于5。
然后它是特别的……,是这个意思吗?不,但现在是了。哦,好吧,好吧。抱歉,我没有明白你的意思。我的意思是,当你使用这个函数时,使用它的人可能不确定,如何使用它。是的,当然。例如。
一个问题是你必须编写这个函数。有人必须编写函数,然后你必须知道要调用哪个函数。这是一个好问题。另一个问题,我已经给出了答案,那就是,对于小于5,这种,方法有效。你必须做这样的事情,对吧?有什么……。
有没有注意到我在这个例子中没有尝试的一件事,你可能,想尝试?比如你在说,为什么我必须硬编码这些?传递第二个值。传递一个用于比较的第二个值。所以我可以尝试,不仅传递一个值,我可以传递一个参数,对吧?
这就是你自定义函数的方式。你可以传递更多参数。问题是,谓词的隐式接口是什么?当你调用谓词时,你传递了多少个参数?所以,如果你尝试声明另一个有多个参数的谓词,这段代码,将无法编译。覆盖一个函数。
就像一个名字相同,但参数数量不同的函数。问题是因为我们写了这个。这是你在调用的函数,你不能真的改变那个函数。我们在编写自己的代码,但你不能改变。谢谢。我可以在讲座后回答那个问题吗?因为我知道你在说什么。
但这是一个更长的解释。是的,问题?一个对吗?是一个对吗?如果你想发送到。为了比较?但问题是。所以你在说谓词接受一个对。让我对此进行反馈。我认为那不会奏效。是的,问题是,谓词函数不知道它接受什么。
我接受一个值,还是两个值?所以我不是。让我再回到这个问题上。所以你可以尝试做的是,好的,我们来设置一个限制,对吧?但你不能真正把这个限制传递进来。所以这是一个问题。你不能把它作为一个参数添加进去。
你不能把它作为参数添加进去,因为那样的话这个就不会,工作。我真的很想去写代码,但。 我不太想拉出代码。但如果你尝试这样做,它会再次显示很多错误。说。它会准确告诉你,这段代码无法编译。问题是。
这段代码通常在某个你没有写的库中。所以你会被告知。好的,我喜欢这样的 Piazza 帖子,指出斯坦福库有问题。错误告诉我它们是。错误信息告诉我,斯坦福库有问题。因为当模板错误发生时,它们会指向源头。
即不能编译的确,切行。问题是,你传递了无法编译的东西。而不是那行代码本身。我喜欢那些 Piazza 消息。然后一些名叫 Anand 的友好的助教会礼貌地告诉你你的,代码坏了。看看。
6B 的学生知道我在说什么吗?好的,没关系。他仍然回答 Piazza 上的问题。好的,算了。这是助教 Anand,他回答问题非常直率。而且非常迅速。好的,那么 pre-C++11 的解决方案是这样做的。
现在,这个话题对我来说非常重要,因为当我面试这个工作,时,这是我展示的内容。但这有点复杂,因为你必须了解类。而且似乎为了写一个谓词函数写整个类有点过头了。所以我们甚至不会去看这个,但如果你想了解这个。
请告诉,我。我们可以在以后的讲座中学习这个。但 C++11 的解决方案是使用一个叫做 lambda 的东西。这就是这一行。Lambda 基本上是你可以创建的一个非常轻量级的函数。这个函数是一个对象。
但它表现得像一个函数。我们来做一个例子。所以我没有真正准备好这个,但我们可以这样做。有很多代码。所以让我们创建一个简单的 lambda。假设它是小于的。不要担心语法。但是 int i。
然后返回 i 小于。所以我刚刚声明了一个 lambda。不要担心这个复杂的语法。现在,这个对象,这就是一个对象,对吧?这是一个变量,它存储了某种对象。这个对象的行为就像一个函数。那么你可以用函数做什么?
调用它,对吧?所以假设小于,那么你怎么调用一个函数?是的,所以我们说它小于 2。让我们放入 1。那应该返回 true,false。对吧?然后它小于 3。所以我们只是声明了一个像函数一样的对象。
有些人称这些为函数对象。然后你可以像使用函数一样使用它们。例如,这里我们有一堆成绩。我们需要一些学生的成绩,并计算他们中有多少人得了 A,然后在这里,我们传入的这个,就像一个函数。
然后我们传入一个函数对象。让我们更详细地分解这一行。我们来看一下语法。因为语法的某些部分可能看起来非常熟悉。其他部分则不然。lambda 函数的结构如下。所以你有,声明一个函数对象。
你有一个称为捕获子句的东西。然后你有一堆参数,这个返回类型,然后你有一个主体。在 C++14 中,这个返回类型是相当可选的。所以你通常可以省略它。通常对于谓词,它们返回布尔值。所以你不需要真的保存它。
参数,正如你从函数中所期望的那样。它接收一些参数。lambda 的一个很酷的地方是你可以为这些参数使用 auto,所以让我们用 auto。然后对于主体,它就像一个函数一样工作。
在你的函数中放入你想要的任何内容。然后,是的,到目前为止就是这样。我现在停止。到目前为止有任何问题吗?有吗?通常,我们希望如果可以的话就使用 auto。但是在这种情况下。
我们是否应该保存 bool 以使其明显,是一个谓词?你是说返回值?是的,返回值。嗯,我想一开始。是 auto。哦,你是说这个。所以有两个 auto。这个 auto 或者这个 auto?第一个。第一个。
好的。这是个很好的问题。所以问题是,这个是一个函数对象。它的类型是什么?C++?这在 Java 中有效,因为所有东西都从对象派生。但 C++ 没有对象类型。所以这很酷。当你写这样的代码时。
C++ 会将其转换为,它为你创建一个,类。很酷,对吧?但问题是你不知道这个类的名称是什么。编译器会给它一个非常奇怪的名称。你真的不知道名称是什么。这就是你必须使用 auto 的地方。
因为你真的不知道名称,是什么。好的,那么关于括号后的返回类型,是否使用它来明确表示,这是一个谓词比较好呢?人们的偏好不同,取决于你喜欢哪种风格。因为我们经常使用 Lambda 表达式。
通常用它们作为谓词,在这种情况下,返回是合适的,这是一种隐含的含义。还有其他情况下你可能需要使用 Lambda 表达式。如果你学过 CS,你会经常使用 Lambda 表达式。在这些情况下。
明确指出返回值可能是有帮助的。但随着 C++ 的发展,大家变得越来越懒。还有其他问题吗?好吧,这个编译不通过。这是之前遇到的相同问题。我们可以声明一个限制,但这会创建它自己的作用域。
所以这里面的任何东西都不能以任何方式影响它。在一些语言中,你会听到这叫做闭包,这就是原因。它像一个函数那样封装自己的变量。所以这些变量不会相互作用。有人想猜猜捕获子句的作用吗?它捕获变量。
所以我们可以明确地说,捕获这个作用域中的变量并使其,在 R 内部可用。这个代码工作得很完美。好吧,有问题吗?如果你在调用 func term 后改变了变量的值,然后调用类,似你的伪造列表的东西。
它会引用哪个值?是当前的状态吗?好问题。我认为是当前的状态。是你在这个函数中声明的状态。所以它基本上是提取值限制。但是。我们会再确认一下。我对此很确定,因为接下来会发生的情况是你可能会有。
如果限制超出了那个作用域呢?因为这会拷贝一份。哦,引用与拷贝。是的,好吧。好问题。这正好引出我们要讨论的内容。你可以通过引用或拷贝来捕获这些变量。所以在这种情况下,它会拷贝一个。是吗?
只是作为另一个参数传递给 auto-vowel 吗?好问题。你可以这样做,但你会遇到相同的问题,就是这里。谓词的隐式接口只接受一个参数。这是我们之前处理过的完全相同的问题。你不能添加任何额外的参数。
所以这就是我们特别设置这个东西的原因,它允许你捕获,变量而不将它们计入参数。让我们快速谈谈。这是另一个茶的例子。我在这里骑车时丢了我通常喝的茶瓶。所以我现在有点渴。是的。好吧。
还有更多方式来捕获这些变量。你可以通过引用来捕获它们。例如,一组字符串,我们不希望拷贝它们,所以你可以通过,引用来捕获。你可以指定哪些要通过引用捕获,哪些不通过引用捕获。有问题吗?是吗?
如果你用这种方式捕获所有东西,你会希望通过引用来做,吗?拷贝那些大的东西,比如 Ts 吗?我们来看看。所以在这里你想把与符号放在 Ts 之前,表示我想通过引,用复制这个变量。不是通过引用复制。
我想传递这个。我想通过引用捕获它。通常,如果你在一个类中捕获所有内容,因为我知道你可以,用这个捕获所有内容,你会用与符号 this 吗,以便你可以,复制所有内容?我们不谈这个。
因为当我们讨论类时会涉及到。但是列表默认是指针,所以你可以通过值复制它。还有一些方法可以直接捕获所有内容。如果你用等号,这意味着按值捕获所有内容。如果有些内容你特别想通过引用捕获,那么你可以在后面。
写出来。你也可以通过引用捕获所有内容,然后接受你所写的内容,不推荐这样做,因为这会让它变得全局化。它会捕获所有内容。所以这就是我们的目标。这就像是把你函数中所有可用的内容捕获到。我们下次会谈论这个。
但我基本上会给你一个下次会发生,什么的概述。我们将基本上使用 STL 算法库,利用这个关于函数对象的, API 的新知识。将会发生的是,你会看到我们今天写的计数出现次数,其实。
是一个叫做 countInOccurrences 的 STL 算法。还有大量其他算法,非常可定制化。你可以传入函数对象,传入匿名函数,它会处理库的操作。我们会做一些非常酷的事情。我有一个例子。
我有一个文件,里面记录了完成了作业 1 ,的每个人的名字、年份,以及你在 106B 的成绩。然后我们会做一些任务,比如,有人失败了吗?这是一个危险的文件。我只是在开玩笑。我确实为你的成绩生成了随机数字。
会有随机数字,但那些不是你的,特别是如果你现在在 ,106B 的话,那些不是你的预测成绩。也就是说,我们预测你们都能得到 100% 因为你们在上这,门很棒的课程。开玩笑的,开玩笑的。另外。
回答 Bish 的问题,函数对象在运行时使用章节类的,值。什么是整数?