呃,今天得到一个消防栓的感觉会有点像,因为这是一个特殊的场合,你并没有机会经常描述,但这是我们所有人将学习一种新语言的机会,确实这并不是我们每天都会做的事情。
从第一眼看去,它会看起来像,但请让我向你保证,马上看起来的这个C代码在根本上是与上周通过Scratch看到和实验的内容是相同的,也就是说,在这种更传统、以文本为基础、以键盘为基础的编程语言C中。
我们仍然会看到函数、条件、布尔表达式、循环等等,它们看起来会有些不同,但理念是一样的,就像第一次走进某人的家,了解环境,看到许多你之前未见过的事物。
你通常不会关心所有这些,简单地向前走坐下,或者开始你的对话,类似地,今天我们将看到大量的语言。必须理解并抓住那些熟悉的概念,实际上是编写计算机程序,我们如何编写它们。
所以让我提出,有几个指导原则应该指导代码的编写,其中之一当然是正确性,我们上周探讨过这一点,即你的代码的正确性是否能如预期那样工作。当你命令时,你或其他人编写的程序是否表现正确,是否做到它所说的那样。
但是写作、编写优秀软件以及撰写良好程序还有其他方面,这与设计有关,我们上周对此稍微提到了一点,但设计更是一种定性,更多的是主观的衡量标准,你的代码写得有多好。因此,当你需要写论文时,你当然可以提出非常正确的论点。
但是你可以通过写非常长的、冗长的句子来提出非常正确的论点,重复自己。通常来说,这可能是正确的,但在你的论文中可能没有什么价值。你可能说这是不正确的,但你可能得不到很高的分数,因为它设计得并不好,所以在这个方面也是如此。
编程世界中有一个概念,代码,但良好设计的代码让你不必重复自己。你编写的代码效率相当高,不会做多余的工作。最后让我提出,今天在这个课程中,编写良好代码时你应该考虑的第三个方面,与风格有关。
这更像是一种审美,因此在写论文的类比世界中就是:你是否使用了良好的标点符号、大小写,是否为新段落缩进,这些审美因素在根本上不会改变你所说的内容的正确性,也不一定会改变你论点的质量。
但你的论文风格就像你的代码风格,使你的代码更加可读。因此,当涉及到编写良好的代码时,你首先希望它是设计良好的,同时也是风格良好的,就像你写一篇希望能很好反映你能力的论文一样。
比如这个第一个C程序,简单地在屏幕上打印出“helloworld”。那么我们如何开始编写这段代码呢?上周我们通过拖放拼图块来编写代码,今天会有点不同,我们将使用不同的工具来编程,但为此我们要去进行。
本学期,这个被称为cs50ide的工具,是编程中“集成开发环境”的缩写,简单来说就是一个。
在这种情况下,cs50ide是cs50自己的基于网络的编程环境,并不特定于cs50,我们只是在一个第三方云工具上进行了功能增强,任何在互联网上的人都可以使用,而我们的版本位于这个网址ide.cs50.io,所以你可以自由使用。
所以在屏幕顶部是一个大黑矩形,稍后会被代码填充,就像使用谷歌文档或其他工具一样。
像这样的地方,你可以创建新标签,并创建新文件,这就是我将进行编程的地方。屏幕顶部,我现在将开始调用我们的终端窗口,实际上在这个终端窗口中,我可以运行命令,并且最终运行我的实际代码,但让我们继续深入,设置环境。
并意识到这个工具确实并不代表一种非常常见的流行编程文本编辑器,或者可以编写代码的标签窗口,以及可以实际运行命令的终端窗口。我们可以替代地编程,当然可以在你自己的Mac或PC,或者现在的任何其他设备上进行,但坦率地说,这往往涉及到一些。
早期会面临不少技术难题,尤其是当我们都有不同版本的MacOS和Windows等操作系统时,所以这个基于云的环境确保在第一天,我们都能有相同的编程体验。所以我现在要继续进行这个文件。
上到文件并保存,默认情况下我将把这个文件保存为程序的名称。在这个叫做C的语言中,我将我的文件命名为hello,但我将以一个名为.c的文件扩展名结束,这确实是编写C程序的惯例,它们应该以.c结尾,就像你可能记得的Scratch程序以点结尾。
点击这个绿色按钮,保存,实际上没有什么变化,除了名称和标签,现在我在左上方看到这个标签被称为hello.c。现在我可以开始输入任何我记得的内容,就是我们上周看到的第一个程序,刚才提到的,我将做的是包含标准io.h,不管那是什么。
我将做的是intmainvoid,不管那是什么。我要使用一个大括号,然后在此之后关闭那个大括号。在这里,我将继续做printf,"你好,世界",后面跟一个分号,现在这真是太多了。
这就是我脑海中立即想到的内容,这种肌肉记忆是你在编写程序时很快会发展的。我当然之前做过很多次,所以我能够一下子,分解出所有输入的内容。但我现在想做的是,打印“你好,世界”,但怎么做呢?
在我们的Mac、PC和手机的世界中,如果我们想要实际运行一个程序,我们都会点击一个图标,但今天情况并非如此。因为现在我们处于一种更传统的编程环境中,当前的环境要求我多用键盘,或称为命令行界面(CLI)。
这与图形用户界面(GUI)形成对比,图形用户界面描述的是macOS、Windows、iOS和Android,但在命令行界面中,我必须在所谓的命令行上做所有事情,我指的是屏幕底部闪烁的提示符,在我的屏幕底部报告,我将在这里输入命令。
这是我的命令行界面,我在这里输入的任何内容都会被发送到这台计算机,并希望它能够在自己的硬件上执行。因此,我该如何操作呢?当然,这里有一个问题。
当编写C、Python、Java或其他许多存在的语言代码时,这些代码实际上是为了我们人类理解而编写的。然而,上周提到的唯一一种计算机理解的“语言”是什么呢?布莱恩,我们能请人来解释一下吗?计算机只说什么语言?因为我觉得这里有一些误解。
在我们上周停下来的地方和我们目前所在的地方之间。
这位Cindy,计算机说什么语言,你会说,嗯,二进制,能稍微详细说明一下你所说的二进制吗?为了回顾一下,是的,他们使用零和一来表示所有内容,确切地说,他们用零和一来表示所有内容。上周我们只专注于数字、字母以及颜色等。
图像、视频、声音等等,但我们并没有真正利用内置功能。计算机也使用零和一来表示,也就是说,计算机当然现在有能力在屏幕上打印某些东西,而这种打印的概念在计算机内部是由一些零和一的模式所代表的。
也就是说,我刚才输入的所有内容,尽管看起来有点像英语,实际上是计算机刚刚输入的。为了让计算机理解我刚刚输入的内容,我需要将其实际转换为零和一。因此,这个过程的下一步是将我称之为源代码的内容进行处理。
这里用C编写的源代码,上周我们也看到了源代码,它只是图形化的,像是Scratch中的拼图块。这是我的源代码,尽管很难懂,但至少我这个人最终能够读写它。不过,我需要将其字面上转换为计算机能理解的零和一的模式。
现在我如何得出这一点呢?上周,这涉及到问题解决。这里有一个问题:我如何将称为C的源代码转换为计算机理解的零和一?我敢说,我的输入将是我的源代码,我们称之为机器代码。机器代码只是一个术语,用来描述这些零和一。
计算机能理解的,我们上周没有使用那个表达式,右边是C代码,左边是源代码和机器代码。如果我拿我的源代码黑箱,如何摆脱这个理解的黑箱?嗯,我需要一个特殊的程序,我们将开始称其为编译器,编译器是一个你可以免费下载的程序。
或者在过去你可以付费的程序,设计用来将源代码转换为机器代码。所以我只需要做的就是,如果我想实际解决如前所述的问题,我已经在C中编写了这段代码,现在需要将其转换为零和一,我只需要给自己访问一个编译器,结果在CS50IDE中存在这样一个编译器。
工具简单地称为程序,我将去前进并输入make.hello,然后我将用一点难懂的语法运行它,./hello,但让我们看看它在终端窗口或我的命令行界面中的实际效果,我将字面上输入hello。
注意我并不是在输入makehello.c,我输入的是我实际上想要制作的程序名称,我只是想从这个命令中调用这个程序。实际上我打算编译一个叫hello.c的文件,我将去前进并按下回车。接下来会有一些疯狂的难懂输出,关于这一点留到以后再说,但我没有看到任何大问题。
可怕的红色错误消息是件好事,这意味着我的程序成功编译,为什么呢?因为没有黄色或红色的消息说明相反,但现在如果我想实际运行这个程序,我需要输入一个不同的命令,这相当于双击。我将字面上输入././hello,本质上就像是。
瞧,helloworld,现在我再次看到一个美元符号和屏幕上的其他文本,我们稍后会解析这意味着什么,但注意这个美元符号只是一个常量视觉提醒。更多命令,计算机实际上完成了我要求的,它打印出了helloworld,现在它在等待我的第二个命令,所以这相当于只是打印helloworld出来。
可以做的事情远不止这些,但让我们将其与我们上周做的联系起来,这样这里的一切就不那么不同。回想一下上周我们有参数,尽管我们没有函数,它像一个迷你程序,是一个行动或动词,你可以在编写自己的程序时使用,做一些事情,我们看到了say块。
上周那些是,动词或动作,更普遍地被称为函数,但函数也可以接收。输入,回想一下,我们上次看到的调用,函数的输入参数,所以。换句话说,另一个术语是,参数,但就我们而言,它们是。对我们目的来说,参数是函数的输入。
那么让我们考虑一下,上周我们看到的这个say块。它只是想说,猫嘴里说出你好世界。好吧,让我去**转换一下,如果可以的话,把它对应的转成C代码。只是为了强调,根本上这两种语言是多么相似,尽管。
从语法上讲,C看起来绝对,呃,视觉上不同,结果是。如果你想说什么,我刚刚做了,你不会写se,而是会写print,这其实有点白lie,你实际上是要说。printf,原因我们最终会看到,它意味着格式化输出,C中的一个类似物。
在Scratch中说的,现在注意到在C中我,有这个开放括号和闭合括号,它们恰好呈椭圆形,并且注意到它们。模仿了上周我们提供输入的白色椭圆,因此在这两个括号之间。将是我对这个,函数printf的输入,或者说。
这行代码末尾的分号,就像在英语或许多。人类语言中,你结束一个句子,例如用一个句号。你也得在这种情况下用,分号结束你的想法,所以右边这个是。将左边的Scratch转换成C的最接近方式,分别。
所以这些想法依然相同,但语法看起来稍微不同,我们,最终。要弄清楚这些模式是什么,以及这些,人的约定是什么,但请注意,范式。和上周一样,但我们这周增加了一些,术语。上周我们将黑箱描述为,可能是算法。
最初我们开始称它们为函数,函数只是算法的编程版本,即在代码中实现一个算法。在软件中,所以函数也可以,称为参数,但事实是。函数在编程的世界中可以做至少两种不同类型的事情,我们已经。
这些东西我们已经见过,但我们并没有像今天这样具体地描述它们。当一个函数接收输入,也就是说参数,就像。你好世界是Scratch中的say块的输入,或者是C中的printf函数。函数可以有被称为副作用的东西,记住,当我们使用say块时,它确实输出了。
更技术性地说,它有一个副作用,一个视觉副作用,当我上周使用say块时,以及这周的printf函数时,你在屏幕上看到了某些东西。是的,这是一种输出,但这是我们上次看到的输出形式。因此,一个函数的副作用,在屏幕上显示,例如文本或音频。
还有这个函数的另一个特性,我们将看到并利用,函数实际上可以把一个值返回给你,它不会用声音表达,而是以一种程序员可以重用的方式返回给你。无论那个函数的输出是什么,理想情况下,甚至可以存储在一个变量中。
例如,回想上周我们问人类他们的名字,在这个白色椭圆形框中写着“你叫什么名字”,然后回想一下这个ask块上周是有点特别的,因为它让我们访问了人类最终输入的任何内容。也就是说,上周的ask块本质上返回了一个值,而不仅仅是盲目地。
显示人类输入的任何单词在屏幕上,不,它在某种意义上返回了它。比喻地说,并将其存储在一个特殊变量中,称为answer。所以下面就是区别,say块字面上在屏幕上显示某些内容,并且有一个直接的视觉效果,而ask块在你输入后。
你的名字并没有再次在屏幕上打印或显示,而是你的名字有点藏在一个变量中,就像数学家会将一个数字存储在x、y或z这样的变量中。最终要做一些事情的是这个叫做answer的变量中的值“我的名字”,所以我们将如何翻译。
上周的ask块在c中查看这个星期,做这个在c中,没有一个很简单,除非你使用所谓的库,库是别人写的代码,我们今天将使用的就是所谓的cs50库,这个库是我和我的团队写的,旨在让简单的事情变得更容易。这些可以算是训练轮,某种意义上会被取下。
在几周内完全掌握,但为了快速入门,会更容易做一些事情,比如从用户那里获取文本。所以字符串是编程世界中的术语,字符串就是文本,它可以是一个单词,一个字母,一个段落,或者一页文本。
文本在某种形式上,字符串是计算机科学家所称的文本。getstring是我们写的一个函数,我们会提供给你,它确实接受输入,注意这里在c中的括号,它可以接受输入。那么这个输入可能是什么呢?就像ask块一样,它将是一个提示,人类最终应该看到。
但是,涉及到的事情比仅仅使用这个函数要多。当你在c中使用getstring时,正如我们将很快在现场演示中看到的那样,你想对人类的名字做一些事情,而仅仅信任scratch会为你将它放入一个变量中是不够的。
在C语言中,与大多数编程语言一样,它更加严格。如果你希望某个值放入变量中,你必须自己来处理,MIT不会自动把它放入答案中。所以你只需想出你想要的变量名称,可以是x、y、z或者更有吸引力的名称。
答案你使用一个等号,一个单等号,即使在数学中这通常意味着相等,语言中的等号实际上意味着我们所说的赋值。它的意思是有效地将右侧的内容复制到左侧的内容中,所以如果右侧有一个函数,其目的就是询问人类他们的名字。
这个名字最终会从右到左复制到名为answer的变量中。在scratch中MIT自动为我们完成了这个过程,而在C中你必须自己来处理,但你还需要更具体一点。事实证明,在C语言中,你不能仅仅有变量。
事先告诉计算机你想要什么类型的变量,具体来说,我将告诉计算机我想要的变量类型是字符串,约定是你必须字面写出你想要的类型名称,字符串是我们看到的唯一变量,然后再次从右向左为该变量赋值。
我们在这里必须使用单等号,现在稍作停顿。布莱恩,如果我们能请某人来,在你心中牢记我早些时候的一条建议,想要完成这个scratch代码的翻译,以便在右侧查看缺失的部分。
之前编程时,奥兹自动会跳出,你需要回想我之前所说的,雅各布你觉得呢,呃,分号,确实只有一行结束。这是为了让计算机明确这是这个思路的结束,而我有点愚弄它,因为老实说。
学习编程时最大的挫折之一就是现在将要发生的事情,今天、这一周以及之后,最初你会忘记一些愚蠢的东西,比如分号、标点符号或括号之类的东西。我今天可以给出的最重要的建议是,尽量不要因这些小事而感到沮丧。
这些愚蠢的事更有趣,也更有用,尤其是在条件中。而且不要让自己因更微小的审美问题而感到沮丧,老实说,这些问题只会随着你的进步而来,比如缺少分号,可能需要你花10分钟或一个小时来搞清楚代码出错的原因。
然后,我们获取一些输出,接着进行了某种处理,使用了ask的返回值或输出。我们再次询问人类他们的名字,神奇的是,上周将名字放入了answer变量中,但我想区分一些副作用,这些只是发生在你身上的事情,比如在屏幕上打印东西,像猫在说话。
通过其输出与返回值相比,返回值是一条信息,比如一个人输入的名字,被存储在某个地方,以便你,程序员,之后可以利用它。这并不是发生在你身上的事情,而是为后续使用而交给你的。上周,为了打招呼,我们不得不堆叠这些拼图。
将各个部分叠加在一起,使join的输出成为say的输入,在C语言中,这实际上是一种语法,再次printf将是say的类比,我们刚才已经看到了,我们仍然会有括号,我们仍然会有输入。我该如何向C提供“hello,answer”呢?我将前进并输入“hello,”。
在双引号中,但这有些奇怪的新语法,%s这是我们将称之为格式的东西,printf不仅仅是打印东西,它也可以打印格式代码。这里插入一些实际的值,而不是字面打印出%s。这是一个占位符,表示将要插入的字符串。那么我想放什么呢?
这里还有一些新内容,在Scratch中,如果你有两个输入,可以在这里输入单词或数字,或将变量拖入,而在C中根本没有椭圆或图形的概念。因此,我们将采用老派的方法,只需使用逗号,如果在参数之间使用逗号。
括号,作为函数的参数或输入,来自右侧的那个,因此相当于在Scratch的世界中有两个椭圆。现在这里有一些潜在的视觉混乱,这里实际上有两个逗号,当然在这里,但请注意这个重要的细节。
我在引号字符串内放置的逗号,与编程无关,完全与英语有关。值得注意的是,这个逗号在双引号之外,这在名为C的语言中是有意义的,它分隔了第一个参数。
从第二个参数,因此以这种方式,我们现在是否有能力使用printf以这种稍微新颖的方式在屏幕上说些什么,所以让我来实际操作一下,回到cs50ide,我将前进并回到这个程序中。
只需片刻考虑一下,我们这个程序,它字面上只是打印“helloworld”,并不那么有趣,我可以整天运行它,它会说同样的话。那我现在如何从用户那里获取输入呢?结果是,我可以稍微增强一下这段代码,让我来做点像stringanswer这样的事情。
逐字输入我们刚才在c中看到的内容,我会记住我的分号在这里。然后我需要更改第二行代码,现在不说hellocomma。world,而是说hello,commapercents,然后在双引号外面,我会加一个逗号,然后字面上提供这个词,变量。
但我还没有完成,这有点微妙,你也会在某个时刻忘记这一点,为了使用getstring,我必须使用这个叫做cs50库的东西,工作人员编写的代码,你不需要这样做。我需要添加一行代码,稍后会更详细地解释。
但现在只要相信,通过在最顶部添加这行代码,包含cs50。h。getstring,功能,否则我将无法访问。好吧,现在我要回到终端窗口,注意这里的二分法,如果我只是,运行dotslashhello。稍微热情一些,让我们看看,我的新程序会做什么,我快要。
不幸的是让我失望,因为它仍然显示helloworld,你可能会直觉地意识到这里的问题是,当然我没有实际重新编译代码,因此每次你对代码进行更改时,仅仅保存文件并不足够,通过文件,保存或控制或命令,s我需要重新编译它。
好吧,我只需输入makehello回车,穿过我的,令人害怕的文本,这一切看起来都很好,似乎已经编译成机器代码,现在我可以重新输入dotslashhello和,回车,你会看到我的程序正在运行并等待我,让我继续输入我的名字david,回车,hellocommadavid让我继续啊**d和。
清屏后再次运行,让我再试一次。这次,假设我的名字是brian,我看到的是,hellocommabrian,这与我们在scratch中的做法非常相似,但现在我们更强大,至今为止,完全通过键盘完成。好吧,我们写了很多,helloworld非常迅速。
我的内存,然后增强到现在我,接收用户输入,让我在这里暂停一下,如果有任何问题,欢迎通过聊天提问,让工作人员或同学回答。但如果你想在zoom上举手,请随意,这样我可以澄清或详细说明这里的任何内容,聊天之前的字符串就是。
不是变量的名字,字符串que,你可以把它写成stringa或string。其他任何东西,只要在string后面的是变量的名字就行。真是个好问题,在scratch的世界中,你必须使用叫做。答案的变量,而在c中我们有完全的灵活性,可以做我们想做的,所以,正如ryan所建议的。
是为了明确被使用的变量是同一个,只是名字不同,这完全可以,但现在我们进入了更好的风格问题,变量名叫a并不利于代码的可读性,我现在可能看着我的代码,想知道变量a是什么。
当涉及到编写好的代码时,实际上更加冗长并使用实际的单词,例如在这种情况下的答案,虽然我不断描述x、y和z作为数学家的主要变量,但在笛卡尔平面之外它们并没有真正的意义。
在你用C、Scratch或其他任何东西编写的程序中,使用描述性的变量名是良好的风格,嗯,乔纳森,轮到你了。是的,我有个简单的问题,为什么我们要手动编译,嗯,与其他不同的IDE相比,确实是个好问题,为什么你必须这样做。
不断重新编译代码,简短的答案就是因为这是C语言的特点,嗯,这是一个已经有十年历史的老语言,所以在那时,一切都是非常刻意的,用户界面并不是首要任务,性能才是重点。
现在有一些更花哨的东西,有些人可能用过像Codecademy或Code.org这样的在线平台,用户只需点击按钮就可以运行程序,而我们在这堂课中要展示这些按钮的作用,因此如果你使用这样的自动化环境,这仍在进行,但为了我们的目的,尤其是在学期初,我们将手动完成。
在学期稍后,我们将引入完全不同的语言,例如Python,不再编译,但几周后会详细讲解,好的问题,索菲亚,轮到你了,我注意到字符串末尾的\n是必要的吗?即使只是单行,真的很好的问题,这个反斜杠n。
你在其他地方看到的,有点剧透,但即使我有点忽略它,你知道这看起来有点奇怪,美元符号,像那样并不是我想要的输出,我真的只想说你好,布莱恩,或者你好,戴维,这个可视化伪影这里的美元符号。
符号和波浪线以及斜杠与我使用的终端窗口有关,这是我正在使用的命令行环境,老实说,稍微挑剔一下,它应该在新的一行上,应该单独一行,这样就不会与我自己的输出混淆,正如索菲亚所说,我们有一个。
在编写算法和代码时,你必须非常精确。在我的代码中没有明确告诉这一行,所以我可以通过在%s后面立即加上\n来显式地做到这一点,但仍然在双引号内部。这是告诉计算机移动光标到下一行的简写符号。
现在你可能会想,为什么我不直接按回车呢?即使这些对我们大多数人来说可能都是新的,简单来说,如果你在代码中间开始按回车,事情会很快变得混乱,这可能不是正确的解决方案。因此,多年前的程序员们想出了这个方法。
决定提出简写符号,比如\n,它表示换行。这里\n是换行符,让我继续编译这个程序。在保存我的文件后,让我继续运行makehello,它似乎编译得不错,./hello。让我再次输入布赖恩的名字,瞧。
输出还是一样,但更干净了,所以我们在整理上做得稍微好一点。非常好的问题,呃,BJ。我有一个问题是,get_string,为了让它请求输入。它在你将其分配给变量时仍然会询问你的输入。
在这个上下文中,至少从右到左,当你有一个等号时。在我这里的代码中,这告诉计算机,你首先要执行右侧所述的内容,然后将该内容的输出存储在左侧。现在,把事情分散在更长的行上。
如果可以的话,代码就是这样,如果这样理解的话,get_string正在执行。坦率地说,我们对这些程序的一些细节已经习以为常。可能公平地说,开始提及main、void和其他许多语法,让我们拆解一些我记得的代码行,但实际上在每个地方都需要存在。
你写的C程序,至少让我们开始理解一些细节。回想一下,在Scratch中,我们总是从“当绿色旗帜被点击”开始我们的程序,就像是。当你听到事件或者有摄像头运动时,这实际上启动了我们在Scratch中编写的大部分程序,在C语言中对应的是什么。
使用这种类型的样板代码,呃,我们将开始更详细地解释这一点,但现在只需相信这是你开始编写程序的方式。当然,我们写的程序还有更多内容,特别是我们看到了一些称为头文件的东西。
这是一个专业术语,指的是一个用c语言编写的文件,文件名以doth结尾,而不是dotc。所以我们之前见过这些,像这样。回忆一下,这是我们上周在scratch中写的最简单的程序,它在你点击绿色旗帜时只会显示“你好,世界”,这就是更完整的模拟。
今天的那个程序有些东西缺失,可能在某些短小的地方特别明显。缺失的就是这一行,我刚刚从记忆中写下了它,它指的是一个名为stdio.h的文件,代表标准输入输出,点h,所以io是一个缩略词。
计算机世界通常指的是输入和输出,所以标准io.h是一个非常流行的文件,广泛用于c程序中,允许你从用户那里获取输入和输出,它通过提供printf来实现,这当然允许你通过这些副作用生成某种形式的输出。
之前我写的另一个程序中确实有,正如bj之前提到的getstring,我可以从用户那里获取一行文本。我需要的是完全不同的东西,这时我们刚刚添加了第二个头文件cs50.h,这些头文件让你能够访问更多。
函数,这些函数是你可能不会自动从你使用的语言中获得的,而这里是c。回想一下上周,当我开始研究scratch的扩展菜单时,我使用了翻译块和语音块,那些被埋藏的更复杂的功能,就像在c中使用头文件一样。
它让我访问了更多的功能,换句话说,就是说你得到了额外的功能。好吧,让我在这里暂停一下,看看是否还有其他问题,然后我们继续实际创建一些你们可能会遇到的问题,呃,我们将给你提供解决这些问题的工具。
解决这些问题,这样你在学习新语言时不会感到完全孤立。有什么问题吗,布莱恩?等布莱恩给我提示,好吧,让我继续提出,我在写这些程序的过程中有那么多不同的方式可能搞砸了,呃,我可能漏掉了一个分号,就像我。
这意味着我可能没有关闭我的引号,可能括号写错了,可能完全拼错了单词,我可能有很多不同的方式让那个程序出错,坦白说,我有点心存侥幸,希望我没有弄错我们一起写的第一个程序,但不可避免地。
在某个时刻,也许不是你的第一个程序,但在学习如何编程或学习如何用C语言编程的早期,你会出错,你会犯一些拼写错误,你的理解和你想让计算机做的事情之间会有一些脱节,这就是说有一些工具。
幸运的是,有一些工具可以帮助你解决这些问题,第一个叫做help50。任何以50结尾的工具,都是专门针对教育的,由CS50的工作人员编写的,是临时的训练工具,课程中使用,但最终可选地去掉,换句话说,你不会需要它们来排除故障。
在我的程序顶部的暗示是,现在printf没有通过标准io.h包含,所以让我们看看错误消息,希望它是一个非常自我解释的消息,完全有意义,让我重新编译这个错误的代码。天哪,我的错误行数比我实际需要的行数还要多。
这实际上是编程的一个现实,很多编程语言和工具坦率地说并不是为了易用性或用户友好性而设计的,而是为了简洁性和精确性而设计的。不幸的是,它们往往假设观众是程序员。
这个线索告诉你出错的文件名和行号,第三行意味着第三行,然后第五行可能暗示在那一行中的列或字符。
这实际上取决于特定的问题,如果这有用的话。因此在第三行我遇到了一个错误,printf,应该输入constcharstar,我的意思是,谁知道那甚至是什么意思,你最终会知道,但今天它只是意味着发生了错误,你可能看不到,也可能不知道,如果我没告诉你。
我故意删除了那一行,所以让我们看看能否通过使用这个工具help50来理解这一点。这个工具是由cs50工作人员编写的,它将帮助翻译晦涩的计算机消息为更人性化的建议和问题,供你的同学或助教参考。
所以希望如果我们能识别出问题,我们可以用这种修辞式的问题来引导你,让你意识到“哦,是的,我犯了这个错误”,所以现在我可以回到这里,移动到我的文件顶部,添加#include
例如,我省略了分号,现在让我去做hello,没有分号的情况下,我们将得到一个不同的错误消息。你将再次看到我出错的文件名hello.c,因为在我上面添加了更多内容后,行号向下移动,你可以看到。
在表达式之后预期分号,所以这个稍微简单一些,但你也可以在这个命令上运行help50。只是为了获得一些更明确的建议,因此help50将成为你的朋友,你的代码。
为了写出相当漂亮的代码,我缩进了printf这个词,我在这里添加了一些空白行,仅仅是为了让它清晰,我把这些花括号放在自己的行上。但是老实说,我的电脑或cs50ide并不那么挑剔,我从技术上讲可以去掉这一空白行,我可以把这个花括号移得更上去。
在这里我可以完全去掉这个缩进,将其移动到单独的一行,然后我可以将这个花括号移到这里。这样就可以写出一个现在只有两行长的程序,不是六行,但希望即使你之前从未编程,这也应该让你感到不舒服。
这就像现实世界中一些人不在社交媒体帖子、电子邮件或短信中使用标点符号,他们只是不断地说下去,是的,信息在那里,你可以推测他们想要传达的内容,但天哪,这太烦人了,难以阅读,可能。
更高的概率说明这里有个错误,而且它将变得更难以平衡。我的程序依然是正确的,但描述起来是糟糕风格。标准输入输出库io.h和分号都没问题,只是看起来有点丑,空白空间不多,空行也很少。
但是我可以运行另一个程序,我们称之为style50。这是一个教育工具,安装在CS50IDE中,可以帮助你改善代码的风格。当我运行style50时,我们会看到一些乍一看可能有点神秘的输出。
但我认为这会帮助我们最终理解程序。要运行style50,你只需运行style50,然后是文件名,在这种情况下是hello.c。所以你不需要再运行make,也不需要运行makehello,你只需做style50,按回车。你会看到一些有点神秘的输出,但用绿色高亮显示。
这个工具试图引导我改善这个文件,注意这里的反斜杠n。之前提到过,它表示将光标移动到下一行。因此,这里绿色的反斜杠n意味着我的天,在这个括号后按回车。另一个反斜杠n则意味着,人类,。
再按一次回车键,将大括号移到自己的行上。这里有四个绿色空格意味着人类字面上按了四次空格键或按了一次Tab键来缩进代码,最后再加一行新行。因此,style50并不会为你在风格上修复代码。
但它试图引起你对所有可能的改进点的注意,所以让我在这里添加一行,让我在那添加一个空行,让我在那缩进四个空格,让我把大括号移下来,现在,代码。希望它看起来确实不错,但它仍然给我提供建议。
它告诉我添加一些叫做注释的东西,很多人已经在Scratch的世界中发现了这一点。你可以在Scratch中添加小便条或便签,提醒自己某些功能或解释某些内容,C语言也支持这些。
比如说,如果我想在这里特别严谨一点,向阅读我代码的人清楚表明我的意图,我可以在现有代码行的上方写“问候用户,新行”。我已经写了一些,斜杠,表示嘿,编译器,这不是实际的C代码。然后我按下空格键,接着我只是输入了一个。
英语短语,这可以是任何一种口语,但我继续输入“问候用户”。为什么?因为这只是对我自己一个提醒,后面这行代码的目的是问候用户,这比说“打印你好,世界”要稍微好一些。让我问一下,即使你从未编程过,为什么第一件事情是。
你在做什么,确切地说,你在区分什么,如果你的注释几乎与实际代码相同,你并没有向读者传达更多的信息,更不用说将来对自己解释了。更一般地说,这行代码的目的,是问候用户,这样的描述要更清晰一些。
因为如此简短,即使风格50会更希望你添加一些注释。如果你的程序确实只归结为一行代码,你可能不需要注释。现在,然而,几乎我们接下来要写的每一个程序都会超过一行主代码,比如这行printf。
所以很快就会变得更有意义,我们将希望实际打印出来。给我们的代码添加一些实际的注释。好吧,让我介绍一个最终的工具,这将帮助我们解决复杂的程序,这就是检查50。这是一个特别的工具,你将会在实验室或作业中使用。
实际上检查你代码的正确性,而帮助50仅仅是帮助你编译代码。通常在它完全无法编译的时候,风格50帮助你改善代码的风格,检查50将会检查你的代码的正确性,针对你写的作业题目。实际上我们写了一些测试,以正确地符合我们的规格,所以我该如何。
运行检查50这将完全依赖于问题集或实验室。我们将在问题设置器或实验室中告诉你要输入的命令。你可能无法自己弄明白,我恰好记得我们有一个测试,叫做cs50/problems/hello,这恰好是一个独特的。
我在这里做的事情,所以我们将重新开始这个演示,因为现在你实际上可以看到我的代码,所以几乎没有错误地完成,所以check50是这个工具,它将允许你测试你的代码的正确性。让我现在继续并按如下方式运行,我将输入check50。
然后是cs50斜杠问题斜杠hello,概率是你将永远不会运行这个相同的命令。在问题集或实验室中,我们总会告诉你该输入什么,你不会知道该输入什么,除非我们告诉你使用什么测试,这现在将上传我的文件hello.c到一个叫做github的服务,它再次是一个流行的共享代码工具。
用它来收集提交,我接下来将输入我的密码。你看不到它,你会看到星号或像网页中的项目符号。我将会前进并按下回车,然后它将验证我的代码,它会进行一些思考。它正在上传,现在我们只是在等待互联网的服务器。
并确保是的,它实际上按预期运行,而你通常会看到,希望的是一堆绿色微笑脸,表示是的,你的代码存在。是的,你的代码可以编译,并且是的,例如,它打印helloworld。有时你可能会看到红色的皱眉脸,这意味着不,你的代码没有工作,问题集。
在这时,就得回到你的白板,弄清楚这里到底需要修复什么。有时你会看到黄色输出,只有一个简单的黄脸,这意味着我们甚至无法运行某个测试,因为某个其他测试,关于正确性提供了相对快速的反馈。
在提交代码之前,你可以检查一下,结束这一天并检查check50。说明将始终伴随问题本身,在实验室中,在这个终端窗口中,我可以做的不仅仅是运行make和dotslashhello或者我程序的名字是50并检查50。结果是我实际上是在cs50ide的形式中使用。
我自己在云中的服务器,所以是的,我正在使用一个网站,你自己的服务器或你自己的计算机在云中,在互联网上的某个地方。你有自己的用户名和密码,以cs50ide的形式,你编写的程序存储在这个ide中,还有一些我现在会介绍的其他功能。
通常被称为文件浏览器或文件树,就像是我的账户或这个案例中我IDE中文件的图形表示。看起来类似于macOS,看起来类似于Windows,这只是内置于IDE的图形用户界面。
因为请注意,如果我打开,天哪,发生了什么。这有点乱,有红色,还有点点,谁能告诉我这是为什么。我看到的这些是因为概率,或许你在某个时刻会不小心点击一个文件,比如hello,而不是像hello.c这样的文件,它是二进制代码。
机器语言不允许用户看到他们所写的代码之外的内容。是的,正如你在这个标签页中所尝试查看的,实际上是二进制代码,零和一。然而,这些零和一在技术上被误解为ASCII字符或Unicode字符,请回忆一下上周的内容。
ASCII是数字、字母之间的映射,数字当然只是零和一的模式,这看起来非常神秘,因为我们试图误解零和一,好像在ASCII和Unicode中有更多的字符,而不是a到z之间的字母和数字,还有一些不可打印的字符,实际上所有的怪异之处都是。
在这里看到的只是零和一的误解,这些是计算机的指令。机器代码被误解为文本,因此你无法编辑这样的二进制文件。因此,当你做类似的事情时,你应该关闭hello,并确保你已双击并打开了它。
你的实际源代码文件也是如此,所以我们看到了字符串,还有其他数据类型,还有其他函数,更多的,我觉得现在是个不错的时机,也许我们可以休息一下,让这些内容消化,休息一分钟,等我们恢复时,我们将介绍C的一些新特性,并将其与上周看到的进行比较。
在Scratch中,我们七天后再见,好吧,我们回来了,请回忆一下我们停留的地方,我们正在查看这个左上角的图形用户界面,文件浏览器,文件树,这给了我们更多的图形。现在让我们以老派的命令行方式在我的终端窗口中来做这个。
事实证明,使用我们的终端窗口,我们不仅可以编译代码并运行等操作。我们还可以操作文件和文件夹,即使这些文件夹存在于我的IDE中,也就是我在这里的云端计算机上可以访问的地方。我要提的第一个命令是,输入ls,ls是列出的简写。
非常简单的ls,当前文件夹,所以这就像在windows中双击你的“我的文档”文件夹或在macos中的“文档”。ls只是列出内容,现在注意,hello有点奇怪,它被高亮显示为绿色,并且后面有一个星号,那只是一个视觉提示。
在这里,但一般来说,我们今天将只在命令行上进行操作。这将为我提供一个更强大的命令行界面。因此,假设我改变主意,我知道我不喜欢这个程序,并且想重新开始,严格来说,我并不需要删除hello,我可以直接。
重新编译它,它会不断被修改,但如果我想删除它,我可以输入rmhello,然后按回车,接着会被询问是否要删除常规文件hello。这只是一个视觉确认,我可以回复y或yes之类的,如果我按y和回车,似乎没有发生什么,但注意顶部发生的事情。
左边注意,hello现在消失了,只留下hello.c代码文件。也许现在我想更改这个程序,不想写hello.c,而是goodbye.c。让我关闭上面的标签,是的,我可以右键单击或者控制单击它,但我们不需要使用图形界面。
让我继续,然后做mvhello.cgoodbye.c,mv是移动命令。尽管如果它叫重命名而不是移动会更好,但移动只是将一个文件移到另一个地方,移动hello.cgoodbye.c,注意左上方发生的事情。现在我的同一个文件叫做goodbye.c,如果我再次输入ls,我可以看到它确实是。
现在让我继续,把它移回去,因为我想保留hello.c程序,但假设我想要程序,因此我的账户会随着今天越来越多的文件变得有些混乱。假设你想创建一个文件夹,也就是一个目录,make。
目录,然后是我想要创建的目录的名称。例如,lecture,你可以随意命名,也许我想把今天所有的文件存储在lecture目录中,当我按下回车时,注意我的文件树上发生了什么。三角形是空的,因为我还没有移动hello.c到lecture文件夹中,mvhello。
c讲座,现在让我按下回车键,瞧,现在注意到它被嵌套在这个讲座文件夹中,实际上如果我现在输入ls列出文件,我只看到讲座文件夹。不幸的是,我现在没有访问hello.c的权限,无法在这个命令行环境中访问,除非我切换到那个目录,在mac和pc的世界里。
这个~讲座/,这个~只是我自己的账户,我自己的默认文件夹,像macos一样。这就是~在我现在所在的文件夹中的代表,仿佛我在macos或windows上双击讲座以打开一个文件夹。现在我在终端窗口中进入这个讲座目录,瞧,嗯。
我已经进入其中,现在让我撤销这个,因为我想保持简单一点,假设我想移动hello.c的语法。对于我们称之为上级文件夹的内容,有这种简写符号。因此,就像在家谱中,有父母和孩子的概念。
孙子和孙女等等,这在有文件夹和文件夹内有文件夹的计算机系统中也是如此,文件夹之间有层级关系,就像家谱一样。因此,如果我想将hello.c移动到上一级,我实际上可以使用mv,就像在说。当我这样做时,注意左上角发生了什么,现在hello.c不再在讲座文件夹中。
在下面,实际上如果我现在在讲座文件夹中输入ls,里面是空的,我该如何在这些文件夹之间移动呢?点(.),因此切换到我的上级目录(..),回车,现在我显然在~目录下,这又是你自己家目录的简写符号,你的我的文档,ls在这里。
为什么会这样呢?就像点点(..)指代你当前的目录,尽管这看起来有点傻,名为hello的文件夹就在我当前的目录中,点(.)代表当前目录,点点(..)代表上级目录,所以,我们终于明白了为什么我一直在输入./hello,这只是文本的类比。
在这样的计算机中,来自max的问题是,我不太明白hello程序和hello.c程序之间的区别,似乎没有c的那个没有用处。哦,在这个故事中,我们有源代码,就是我写的c代码,然后是机器代码,即零。
计算机理解的那些代码,我一直在写的所有代码都在一个叫hello.c的文件中。make程序会创建一个新的文件叫hello,技术上来说它只包含零和一,而这就是我实际运行的机器代码。我可以使用rm来删除hello程序,就像我之前做的那样,现在我们回到了最开始。
这是我们从零开始写代码的故事,如果我现在输入make,让我来试试。现在ls注意到我只有一个文件,现在让我做makehello,我看到那个神秘的输出,但如果我再次输入ls,只有绿色带星号的那个是可执行的,那是编译器生成的机器代码。
接下来剩下的就是你们的最终项目,我们迫不及待想看到你们今天的创作。我们想稍微回顾一下,也展望未来,所以能做什么呢,谢谢,当然我们在洛贝戏剧中心,感谢美国剧团,他们整个学期都是我们的出色东道主。
他们确实给CS50注入了新的活力、新的灯光、新的动画和新的声音。我们非常感激能够有这样的特权,与这里才华横溢的团队合作,确实让这个舞台在这个学期焕发活力。当然,还有CS50的团队,尽管我现在是唯一一个在这里的人。
我们有我们的视频、技术,以及所有的视觉元素,希望能补充你们所做的实践操作,因此非常感谢你们,这个学期。这一切都是可能的,可以说,这些都是比较特殊和困难的经历。
我们希望,无论你是现在直播观看,还是稍后某个时候看到这个视频,都能感到健康和快乐,并且确实希望我们能帮助你在这条学习新事物的道路上找到方向,当然还有比这更多的人在幕后支持着你们。CS50的整个团队,当我望向台上的每一个人,大家相距甚远,这学期。
这里的观众实际上并没有观众,后面的场景就像这样,这是一张我在每节课上看到的确切照片。如果我们放大一下,当我们有问题时,实际上就是我们和一些电视屏幕。在这一年里,但我们期待着不久后再次团聚,现在在幕后团队。
实际上,这里展示的只是CS50的教学助理们的大部分,而不是全部,他们在哈佛和耶鲁任教,没有他们,这一切都是不可能的,因为他们确实是我们获取每个人最终完成问题集、实验室等的支柱和支持结构。但值得注意的是,我们都是有缺陷的,确实有人告诉我,这相当于。
当我做错事或在台上有点困惑时,这会让我感到启发。我无法立刻弄清楚为什么自己的代码会出现问题,这种情况肯定会发生在我们所有人身上。因此,即使你在学期结束时感觉到并不是所有的东西都能很顺利地理解,而有时仍在挣扎。
准备那段关于TCP/IP数据包的视频,效果非常好,其中人们希望从Zoom的右下角到左上角传送三个TCP/IP数据包,但我们想给你一瞥,实际上在我们背后发生了什么。
甚至在那个演示中,我,[音乐],太棒了,乔什,[音乐],嗯,索菲,[音乐],太完美了,我认为,[音乐],真是太惊艳了,谢谢你们,哦,天啊,[音乐]。所以可以说,计算机科学对我们所有人来说都是困难的,这些感受和挫折确实是你们工具箱中的工具。
工具包的基础更加坚实,现在,你在学习语言时会感到有些不舒服。最终吸收新的想法和技能,但记住对于CS50而言,这门课程中最终重要的不是你相对于同学的最终位置,而是你自己最终的位置。
相对于你刚开始的时候,考虑到这并不久远。也许这是你在CS50中最大的困难之一,只是试图弄清楚。对的,是否选择了不那么舒服的版本,弄清楚如何打印空格,如何移动金字塔等等,弄清楚如何嵌套循环,更不用说让所有的。
分号和编译步骤都正确,然后快进到一两周前,当你建立了自己的网页应用程序,使用了第三方API并几乎实时拉取数据,控制着所有你所读取和写入的数据,像这样的进步真是巨大。
所有这些最终都会以某种形式过时,或者它们可能会作为旧语言继续存在,但更新更好的事物会接踵而至。我们希望的是,在过去的几个月里,你能够掌握基础知识,并建立一个可以自我提升的基础。
我们开始时,首先是原则,你可以推断出一些新系统。某些新硬件、某种新技术、某种新语言肯定会奏效。因为在背后,最终,某种程度上,仅仅是零和一,因此我们引入了。在零周回顾中,计算思维鼓励你更有条理地思考。
从算法角度来看,但实际上计算思维只是计算机科学家的体现。我们可能认为这仅仅是批判性思维,这一过程是将信息作为输入,并产生输出,即某种解决方案。在这之间,当然是我们的算法,那个黑箱正在做一些有趣且可能困难的事情。
但归根结底,这就是解决问题,这无论如何不会消失。与您使用或选择的语言无关,甚至在过程中遗忘某些内容,今天也是如此。二进制形式,或者仅仅是信息和决策,或事实和结论,这一过程,正确。答案、正确的结论,远比c或python或其他任何语言的细节要重要得多。
复杂系统或解决更复杂的问题时,你确实需要更清晰地解决问题。你不希望它们变慢,变得混乱,从实际角度看,你不希望你的代码完全难以理解,因为这只会在长期内妨碍你使用那些相同的工具和库来解决更多问题。
以一种清晰的方式与其他人沟通,使你的想法和解决方案被采纳,显然与这些其他理念同样重要。那么,关于其他跨越特定语言和任务的基本构建模块呢?抽象,这个将它们整合的概念。
所以你不必担心底层实现的细节,你只需专注于那个拼图部分或那座建筑。这样的思维无处不在,当然在代码中也是,抽象,我不记得getstring的具体实现,像printf那样,但我知道它有效。
但随后还有另一个概念,即精确,这在书写人类语言时至关重要,要明确你所表达的意思,考虑边缘案例,考虑可能不会发生的输入,这样你就不会犯错,避免一些意想不到的行为,正如我们在实际代码中多次遇到的。
有时候,抽象和精确在某种程度上是相互矛盾的。因为抽象让你在相对高的层次上思考和交流,而当涉及到给其他人讲解时,应该深入细节,逐步进行,或许可以通过几个例子让这一切变得生动。
我们会尽可能多地让人参与进来,你可以拿出一张纸和一支笔,或者铅笔,如果没有的话也没关系,你也可以在电脑上进行,如果你更愿意在记事本上画画,没问题。但理想情况下,拿出一些东西,我们将继续尝试应用。
一些计算原则,在某一层次使用抽象或精确是有害的。因此,我认为要做到这一点,布莱恩,我们需要观众的帮助。我想我们需要一位志愿者,来为其他人口头写指令,我们会把每个人都视为参与者。
屏幕稍后会有一张图片,如果你还没有看到,不要告诉其他人那是什么。你可以在一会儿使用任何你想用的词,但你不应该用手或任何手势,目标是为房间里的每个人逐步口头写出一个算法,这样理想情况下,他们就能画出你所看到的内容。
你可以说任何你想说的,但就是不要有身体手势,这样明白吗?明白了。你想在开始之前先向大家介绍一下自己吗?我叫丹尼尔,来自耶鲁大学的埃兹拉·斯图尔斯学院,我非常喜欢这个学期的CS50。哦,太好了,谢谢你的志愿服务,我们继续让大家准备好吧。
丹尼尔,大家第一步应该做什么,好的,所以我们要画的第一件事是一个六边形,嗯,它是一个规则的六边形,我们要确保画出它,确保六边形的底部顶点和六边形的最顶部顶点,所以你是这样做的,不要做手势,不要用手,不要用手。
好的,嗯,一个顶点在最顶部,一个顶点在最底部。你还有另外四个顶点,嗯,在侧面,嗯,所以一旦你有了六边形,下一步是找到六边形的中点,嗯,所以一旦你找到你的中点,你要从一个顶点画三条线。
到那个中点,你将选择的顶点,将是从最底部到中点,然后从中点到顶部。好的,还有最后的指示,嗯,我希望这就是了。那确实是很长的步骤一和二,但好的,好的,我们就开始揭示吧。
如果大家都能舒适地拿起他们的纸张或平板电脑,稳稳地保持五到十秒钟,我们将能看到每个人都能看到的确切内容。丹尼尔,希望你看到一些熟悉的画面。我想我们肯定有范围,你是否看到一个或多个与你心中的想法匹配的?
我敢说这实际上是一个立方体,但它实际上是由一个六边形组成的,然后,丹尼尔,我确实看到过,可能是在缩放窗口的第二和第三页上,并不是完全的立方体。呃,你当时在想什么,关于你提供的算法的?是的,所以我想要,嗯,我的第一反应是。
头是一个立方体,但我知道有很多方法可以画立方体。我不想把它描述为一个立方体,因为如果我说只画一个立方体,我知道我们会得到。很多不同的结果,所以我想,如果我能以某种数学的方式来描述它,嗯,用六边形和中点来描述,可以画出一个精确的形状,确实说得很好。
如果我们每个人的声音都在,你可能会听到一些轻笑,也许还有一点尴尬,所以我敢说并不是所有的。照片都完全呈现那样,但这是一个完美的例子。
也许抽象概念会让我们陷入麻烦,因为如果丹尼尔只是说画一个立方体,有些人可能会立即开始画一个立方体,但你们中的许多人会有一个问题,那就是方向应该是什么,大小应该是多少,精度变得越来越重要。
我有点被细节的数量压倒,结果失去了方向感,因为你们都在一个点上操作,但我认为我们确实让你们中的一些人,现在放松一下,谢谢,特别是丹尼尔,让我们看看,集体编程,如果可以的话,我将继续在我的屏幕上拉起。
我在这里可以用鼠标和光标绘制,而和其他人一起,我们将看到一幅我保证没有提前看到的图画,所以我现在是唯一一个在简历室里没有看到这张图片的人,但布莱恩已经为你们提供了网址,所以把它拉到你的屏幕上,然后布莱恩如果可以的话。
我会逐步征召一些志愿者,为什么不试着按照大家的指示逐步绘制呢,好的,所以我刚刚把照片发给你们,戴维没有看到这个,我大约五分钟前选的,嗯,你们都会举手,如果想给他一个指示。
先从接下来的步骤开始,好吧,所以你要先在屏幕的顶部画一个圆,好吧,屏幕顶部附近的圆。让我明确,我没有删除计算机上的能力,所以一旦我提交就没法更改了,所以画一个圆,好,谢谢乔治,布莱恩,第二步。
好吧,接下来让我们去索非亚,在屏幕的正中心画一个黑色的填充圆,大小大约是顶部圆的十分之一,好的,填充黑色圆。我听到的说是十分之一的大小,所以我将做这样的事,然后稍微调整一下。
把它涂满,好的,谢谢索非亚,是的,你将再画一个圆,但它实际上不是一个圆,而是更像一个椭圆。所以它会比第一个大,嗯,它会在中间,并且会包围那个填满的建筑圆,底部留一些空间,好的。
所以这是一个椭圆,它比第一个圆要大,但它包裹着更小的圆。好的,我听到一些这个,好的接下来去瑞安,第四步。想要,[音乐],在下面画一个更大的圆和那个椭圆。但实际上不要显示穿过椭圆的线。
但不让线穿过,它看起来好像会穿过边缘,好的,我有点担心,但我听到的。是的,嗯在那种中间的椭圆,非常像你知道孩子们像飞机一样表演,然后像是做飞机。
噪音,他们用手做那种奇怪的动作,所以画出那种手和那种。来自于大的中间椭圆的中间椭圆,这个更低的,外面的那个,哦这个更大的椭圆,是的,外面的那个,好的,所以我应该画。
一些手像小孩一样,当,好的,我不确定这会不会有好的结果。好的,我们需要更多志愿者来帮助大卫完成这个,让我们去加布里埃尔。好的,试着画,试试是个关键字,你在最底部有一个更大的列表,那个比上面和中间的都大,但不要显示。
中间的线与正在画的那个重叠。好的,所以哦不,重叠的线,所以我听到一个更大的椭圆,所以像。哎呀抱歉,这个好的工作,好的工作,谢谢,继续积极的强化。必须做的就是画一个小的实心圆,稍微小于你最初的那个。
你在顶部画的圆,好的,就在第一个圆的中心。好的,我觉得这开始成型了,我后悔我之前的一些决定。嗯,你想在最后一个你刚画的圆和那个圆的边缘之间再提供一个圆,所以,在那个圆的左边你要。
在这个圆的左侧画另一个圆,[音乐],好的。下面你要重复同样的过程,只是画一个在右边的圆。好的,循环我想我们可能还有一两步,让我们回到索非亚。嗯,在费尔登圆的下面,画两个这个圆的复制品在原来的下方。
一条有两个直线的宽“V”形,好的那部分我想我掌握了,我可以切换过来。并揭示这个网址,我相信你们都收到了,我还没访问过。现在嘿,这还不算太糟糕,一路走来。但这是另一个例子,如果你刚开始就画一个雪人。
这样的内容可能在精神上帮助我找到方向,诚实地说,类似于丹尼尔的设计,这将为你提供一个心理模型,帮助我理解我应该绘制的内容。因此,在这里,抽象是困难的,操作的合适细节水平是解决问题过程的一部分。
尽管现在我看着它,实际上也不算糟糕,我在这里确实做错了,但对我们所有在线志愿者来说,做得很好。因此,当你试图向某人解释某个过程时,请记住这些细节,即使是为了一些琐碎的事情。
就像去办事或在市场上购买物资。精准当然很重要,但你提供的越精确,越容易让人陷入细节之中。因此,有时一个更高层次的细节列表可能是某人所需要的,所以超越今天。
我们希望大家考虑的一个想法是,学习计算机科学课程不仅仅是你能用代码做什么,还有你是否应该用代码做某事,如果应该,你该如何做。事实上,我们认为在这次最后的交流中,讨论伦理与技术是很有必要的。
现在你们都知道怎么做,坦率地说,还有更多的事情你们肯定能够弄明白,即使你们没有在讲座、问题集或实验中见过。例如,考虑到你们现在已经有能力用代码发送电子邮件,坦率地说,没有什么可以阻止你作为他人发送电子邮件。
使用代码,因此诞生了我们现在都知道并在某种程度上受到影响的垃圾邮件世界。你肯定有能力在数据库中存储密码,而我们为cs50finance展示的内容,坦率地说,如果你自己在创建网站,没有任何东西可以阻止你,只需确保以明文形式存储,这样即使是你自己。
你可能会看到他们,当然我们人类会使用密码,所以如果你的用户在你的网站上使用相同的密码,也在其他账户上使用,这是一件你可以做到的事情,但你应该这么做吗?因此,你有能力使用代码,无论是Python、JavaScript还是其他语言,记录例如每一个击键。
有人可以监控你的网站或应用程序上每次点击的详细信息,坦率地说,这在科技界已经成为一种常态,使用数据库存储每一次击键和他们购物车中的每一个链接,这无疑对一些有用的内容有价值。
所以我会建议你在构建某个东西时,即使是最无害的命令行应用程序、Chrome扩展或网页应用程序、移动应用程序。仅仅因为你可以做某事,并不一定意味着你应该这样做,而“面部混合”就是一个过去15年多前的网站的众多例子之一。
由哈佛学生制作,这位哈佛学生懂一点编程,并且他们知道如何。
编写代码抓取所有的哈佛“脸书”,可以说在facebook.com之前,哈佛有一个字面意义上的“脸书”,即一本印刷的照片书,包含面孔,当然多年来哈佛像很多学校(耶鲁等)一样,开始将这些实体脸书转移到在线格式。
当然,一旦你把东西放到网上,就意味着它现在受到代码抓取的影响,你可以看到如何用javascript或python编写代码,基本上假装成一个浏览器,如果你能假装成浏览器,你就可以假装成。
下载或实际上可以从一个网站下载所有的图像。因此,这位特定的学生确实这样做了,下载了数百张甚至数千张其他哈佛学生的照片,当时他认为这可能是一个好主意,或者甚至没考虑过是否是个好主意。
两名学生同时进行,邀请这些学生点击其中一名,以表示他们是否认为对方“热”或“不热”,同样的学生随后创办了正是那个工具,马克·扎克伯格的facebook.com,因此,现在不仅具有校园意义,也对地方上的公平决策产生影响,类似于我们今天关于隐私和信息的讨论。
因此,我们当时想邀请一些来自哈佛哲学系的同事,和我们一起参加20分钟左右的讨论,给我们提供不仅仅是进一步的技术,还提供一些不是仅仅出于本能的想法,大多数人可能现在都会感到不安。
可能当时也会感到不安并说“不,你不应该实施像脸部混合这样的东西”,但是,超越你自己的本能和正义感,你可能在更正式的思考过程中会做些什么,决定在你能够做某事时,应该怎么做,如何进行正式框架以制定这些决策。
所以不再多说,允许我介绍我们的朋友们,米卡·米尼亚尼和苏珊·肯尼迪,他们来自哲学系,来这里讨论这些话题,以便最终为你提供一个更具哲学框架的视角,让你评估这些类型的决策。
决策并自行判断你或他人是否参与。因此,嗨,我是米卡·米尼亚尼,我是哈佛嵌入伦理学项目的哲学博士后。嗨,我是苏珊·肯尼迪,我也是哈佛嵌入伦理学项目的哲学博士后。在开始之前,我先说几句。
关于嵌入伦理学项目,我们是一个由哲学家和计算机科学家组成的跨学科团队,致力于将伦理学整合进计算机科学课程。这种方法的理念是将伦理推理的工具嵌入计算机科学课程中。
这样做的原因是,在做出设计技术的决策时,无论你是否意识到,都在做出伦理决策。这就是做出可能对社会、政治或人类产生影响的决定。在哈佛,我们认为计算机科学家应该具备这方面的知识。
思考这些的工具,技术带来了巨大的力量和影响,这意味着设计技术的人同样也有影响。现在你开始思考作为计算机科学家,你可能承担的责任,以避免一些显著的失误,比如脸书应用。
例如,研究显示,44%的美国成年人表示他们通过Facebook获取新闻。可以说,近年来由于技术发展,发生了很多变化。这在我们考虑公众参与的能力时显得尤为重要。
这种话语支持一个良好运作的民主制度,因此我将首先向你们简要介绍我们来自何处,以及由于技术发展我们现在所处的位置。然后考虑我们今天面临的挑战,在互联网之前,新闻和信息几乎完全掌握在少数几家主要广播电台和印刷媒体手中。
媒体机构也称为大众媒体领域,因为只有少数几家组织负责新闻。这些组织的信息本质上是从组织到广泛公众的传递,负责研究和撰写这些组织内容的记者都共享一种职业伦理,他们关心真相。
不幸的是,这一切开始发生变化,近年来新闻报道不仅转移到了在线,还转向了社交媒体平台,我们现在生活在一个数字化网络公共领域。因此,我们不再是少数几家组织向公众传播信息,而是拥有一个广泛的数字领域。
充满了改善公共话语的巨大潜力,以支持良好运作的民主。例如,得益于推特和脸书,我们看到了像“我也是”和“黑人的命也是命”等社会正义运动的动员,观点的多样性使得个别研究人员和科学家能够对CDC关于种族政治、经济政策和美国在世界中的角色发表看法。
新冠病毒,因此尽管CDC最初并未说新冠病毒是通过空气传播导致社区传播,但在科学家们在推特上提出证明这一点的证据后,他们最终修正了立场。尽管数字领域带来了某些改善,但也带来了。
一些问题加剧并创造了新的挑战,例如,由于任何人都可以创建内容,事实核查和监控变得更加困难。人们在判断网上阅读内容的可信度时只能自求多福,我们也看到了信息的增加。
关于新闻和信息的个性化,特定内容可以通过社交媒体上的策划新闻推送,针对特定用户。这种现象显得尤为重要,因为尽管我们获得了更多样化的信息,但最终却产生了一种矛盾的效果。
在数字公共领域中,社交媒体平台本质上是在火上浇油,假新闻的传播在社交媒体上激增。因为数字环境的结构从点赞到转发都是病毒式的,迅速到达全球数百万人的屏幕,关于假新闻的传播引发了严重担忧。
这在加剧政治两极分化方面发挥了作用,因此,尽管技术带来了独特的优势,但也带来了独特的挑战,现在的问题是,如何对社交媒体平台上的内容进行规范,如果有的话,考虑到问题的规模。一些人可能对此持怀疑态度,认为任何形式的内容监管都是不可能的。
在线发布内容的人太多,无法逐一进行事实核查。而且假新闻传播得如此迅速,在它已达到庞大受众之前,根本难以阻止。同时,人们也担心试图规范内容的举措可能会演变为一种侵犯言论自由的审查制度,但一些人则更为乐观。
关于如何设计社交媒体平台以促进和维护民主的可能性,特别是有可能通过负责任设计的算法和用户界面选择,减缓假新闻的传播,更广泛地改善信息在社交媒体上的传播和互动方式。
例如,一些人认为,像Facebook、Twitter和YouTube这样的公司有责任对内容进行监管,因为它们对我们的巨大影响。特别是,人们认为社交媒体平台有责任打击假新闻,并减少那些个性化用户体验的数据驱动算法的影响力。
我们将讨论我们在使用技术创造今天的内容时所做的具体设计选择。我们需要对设计选择做出明智的决定,这需要对伦理和哲学进行一些批判性思考,以找出最佳的解决方案。但我们希望像你这样的学生能利用你的创造力。
技术知识和伦理推理设计,现在将事情交给米卡,她将告诉你一些哲学概念,这将帮助你积极思考特定的设计选择和算法工具,这些工具可以被实施以构建和促进民主公共话语。
社交媒体和互联网,但现在我们面临一个假新闻泛滥的环境。公民似乎被极端化的信息所分化,信息在孤立的泡沫中旋转,仇恨言论达到令人震惊的恶劣水平。所有这些都威胁到有效所需的条件,或者人们如此推测。
Facebook和YouTube现在都在努力通过平台设计和功能来解决这些问题。从某种角度来看,它们正在为保护民主而服务,试图控制虚假信息的传播、仇恨言论的放大以及两极化的加剧。
然而从另一个角度来看,它们正在介入塑造信息的分布。根据具体的设计,可能会说它们正在监管或压制言论。这当然与对自由言论和讨论的民主承诺相悖。因此,本模块的目的是为你提供一些工具,以深入思考这些问题。
关于健康民主所需的条件,如果我们要对科技如何威胁民主进行主张,以及支持这一点的条件,方教授是哈佛的政治学教授,约书亚·科恩是一位正在与苹果大学的教师合作的政治哲学家,他们为我们提供了这些工具。
民主的理念是对政治社会应有状态的理想。方和科恩将这一理想简化为三个要素,首先是民主社会的概念。这是一个政治文化将个人视为自由和平等的社会,尽管它涉及到利益、身份和信仰体系,这些都需要通过反思和话语来实现。
使他们能够在尊重彼此自由和平等的同时共同工作,第二是民主政治体制的理念,特征是定期选举。参与权以及结合表达权的权利,使参与既知情又有效,第三最后是。
论证民主,政治讨论应该诉诸于合理的理由,作为自由和平等的人,因此在为政策辩护时,不能仅仅诉诸于你自己的宗教,因为其他人不一定持有相同的信仰。你可以诉诸于宗教自由的概念,但不能诉诸于特定的宗教信条。
宗教内部包含的信仰,因此民主基本上是一个理想的决策过程。这一决策过程尊重我们的自由和平等,这不仅包括投票、选举和立法的正式程序,还受到非正式公共领域的影响,即公民识别问题、表达意见和挑战问题。
观点和围绕事业组织起来,这是民主决策过程中的一个绝对关键的部分,在这里我们作为公众形成、测试、分散、交流、挑战并修正我们的观点。信息流动以及用户在Twitter上的互动,都是这一非正式公共领域的一部分,以便个人能够参与。
作为自由平等的公民参与这一公共话语的领域。科恩和方提出了一系列权利和机会,一个良好运作的民主所需的工具,这些是分析的工具。作为民主公民,我们有基本自由的权利。
表达和结社的自由,表达自由的权利不仅对个体的自由重要,以确保他或她不受审查。公民之间能够将自己的想法带入对话,并对行使权力的个人进行批评和追责,第二是表达的机会。
我们不仅应当免于审查,而且还应该有公平参与公共讨论的机会。不会因为强大的权力,他们就有更多参与的机会。第三,每个人都应该有良好和平等的机会,获取公共事务中的优质和可靠信息。
也就是说,如果我们付出努力,应该能够获取这些信息。有效参与决策需要知情。第四是多样性,每个人都应该有良好和平等的机会,听到广泛的观点。我们需要接触到竞争观点,以便拥有更为知情和理性的立场。
参与公共讨论,起初似乎需要跟踪很多事情,但如果我们要思考民主,尤其是平台设计如何促进或阻碍民主,这些都是我们可以使用的有价值工具,比如获取条件。这个观点认为我们都应该能够获取可靠信息。
作为分析的视角,我们的平台是否阻止某些群体或用户获取可靠信息,或者我们可以利用多样性条件。这个观点认为我们都应该能够接触到多种相互矛盾的观点,作为分析的视角。因此例如,我们可以问自己,我们的平台是否创造了信息茧房。
在个体不再面对对立观点的情况下,除了理解支持民主社会的条件外,我们还需要了解所谓的问题,然后才能提出有效的干预措施。考虑假新闻,为什么人们在假新闻面前如此容易上当,为什么他们经常在没有适当批判评估的情况下转发。
在发布信息时,就像在对话中传递信息一样。然而,另一方面,当唐纳德·特朗普因其中一条有争议的推文而被质疑时,他的反驳是“这只是条推文”,以此来对抗假新闻,因此里尼认为,我们需要稳定社交媒体的证言规范。
通过仔细分析我们为何如此容易相信假新闻,我将留给你考虑这一提议,结合各种背景因素。
民主公共领域所需的权利和机会。里尼的提议是否违反或威胁到言论自由?它是否促进或阻碍我们获取可靠信息的能力?获取多样观点的能力,还是促进或阻碍我们的沟通能力?
我们希望你在思考以下问题时,开始问自己这些问题,例如,极化等与民主对立的问题,推特、YouTube和Facebook等公司如何应对这些问题,以及社交媒体平台的某些设计特征如何促进或抑制这些问题。
非常感谢苏珊和米卡加入我们,确实在即将到来的实验室中,我们有机会在一些非常具体的现实场景中考虑这些问题,所以我们现在想展望一下你们在涉及更实际的方面时会想到什么。
仅仅具备计算思维,所以对许多人来说,编程当然是设计中唯一的计算机科学或编程课程,而这当然没问题,实际上我们希望你现在能够对艺术、人文学科、社会科学或自然科学产生兴趣,实际上能够有一个。
一套具体的实用技能,无论是在Python、C还是其他我们所讨论的技术语言中,实际上能够在你自己喜欢的领域解决问题。如果你对计算机科学本身感兴趣并在这个领域继续前进,我们希望你能带走一个坚实的基础。
为这个特定领域进一步的理论和系统探索打下基础,但从实用的角度来说,我们希望你不仅能编程,还能更好地提问,无论是在现实世界中,概率是如果你曾经在CS50的讨论论坛上提问,教学助理或我可能会很好的回应。
问题要求你澄清,或者更好的是,你能给我们提供问题。我们可能有的,如果你注意到模板,通过它你被引导去很好地回答,看看你所遇到的错误信息是什么,你尝试过哪些步骤来解决问题,因为如果你想象在现实世界中。
如果你掌握的信息比他们多,能更好地向人们提出问题,他们可以更有效地帮助你,同时寻找答案。我们完全理解,许多CS50的周数,可能都让人感到相当沮丧,因为在讲座中覆盖过的内容,或者我没有看到。
当然,最终项目没有具体要求,告诉你该做什么或使用哪些库,毫无疑问,在接下来的几周里,你会遇到错误,坦率地说,也许我,也许是布莱恩,可能教学助理和课程助理都没有见过这些错误,但课程的目标是让你。
在克服那些障碍的过程中,你可以找出如何做到这些,仅仅依靠互联网,无论是谷歌还是堆栈,找到答案,涉及编程的世界,或者更一般地说,问题解决的世界。我们希望这实际上是一个持久的技能,我们希望你能够做到。
这无疑是令人沮丧的,但在课程的安全网支持下,你们经历了这些月。希望从现在开始,你能更舒适地接受不适,在你探索新事物的过程中,阅读文档也是其中一部分,这里也是一个可能的沮丧源。
有些文档库永远不会消失,它们的质量很差,是由那些并不理解你或我思维的人编写的,缺乏你所希望的同理心,因此它们是在非常低的技术细节水平上撰写的,它们的功能也或反之则是在极高的技术细节水平上撰写的。
高级别的文档让我感到无奈,你必须先学习库,才能弄清楚如何使用它。你会看到两种极端,但适应阅读像Python文档、一些API文档这样的材料将赋予你更多能力,希望你能做出更酷的事情,解决更强大的问题。
因此,我们希望你从这样的课程中走出时,不会觉得哦,我学会了如何编程,或者说我学会了如何在Python中编程,因为事情还没有结束。但你现在肯定更擅长成为一名程序员,弄清楚你需要填补哪些知识漏洞。
为了弄清楚哦,这种方法在这个语言中的语法是什么,我在另一个语言中已经看过,这正是我们将许多语言并排比较的原因,以加强这一思想没有不同,尽管语法可能需要大量的搜索和询问。
这也是我们希望你能够独立完成的事情,当下一个最好的事物出现时,提到辅助轮,你可以继续使用CS50IDE进行最终项目,甚至在之后的其他课程或项目中使用它,但归根结底,这也是。
你应该为自己设定目标,IDE旨在代表一个真实的编程环境,但我们确实为你做了很多事情,我们为你安装了整个学期可能需要的所有库,我们有这些以数字50结尾的漂亮命令。
回到你自己部门,解决一些代码问题。帮助不会再长期存在,所以我们想给你一个感觉,告诉你一些你应该考虑使用的行业标准工具,或许在假期或未来几个月进行尝试。
这样你就知道如何做你本学期所做的同样事情。因此,例如,如果你想在你的mac或pc上查看,结果是有些工具已经存在。确实我在某个时候提到,macos在其应用程序文件夹中的实用工具中有一个终端窗口,windows也有类似的东西,但还有其他命令并不。
这并不一定会随你的mac或pc附带其他工具,因此我们鼓励你访问这些网址,如果你想在自己的环境中安装更多在cs50中看到并使用的命令行工具。我们还推荐你了解git。
git是版本控制的一个例子,是当今任何良好软件实践的基本构建块,我们在cs50中使用git,但我们对你隐藏了这个细节。每当你运行check50或submit50时,我们在背后运行一个名为git的开源命令,它将你的代码推送,从cs50ide到github.com。
这是几个流行网站之一,你可以在上面托管代码、共享代码、合作编写代码、运行自动测试等。但git本身可以终止你可能在Google文档中使用的常规做法,当你想保存某个东西或另一个文件的副本时,也许你只是。
将文件名的结尾更改为2,然后下次更改为3或4,或者你可能会做dashsundaynightdashmondaymorning,我的意思是,有时我仍然会犯这个错误。当我想给文件版本时,有更好的方法。如果你在未来发现自己在做一些你认为有更好方法的事情,git。
这是那些更好的视频之一,了解得更多会帮助你不仅维护多个版本,实际上是你自己代码的备份。它还将最终使你能够与他人合作。至于文本编辑器,当前最新、最优秀和最受欢迎的工具之一是叫做vs的东西。
code这是一个你可以在自己的mac上下载的开源工具,当然也可以在网上找到。但这绝对是目前最受欢迎的工具之一,但它只是一个文本编辑器,当然也有绝对的替代品,欢迎你查看。如果你对事物的网络感兴趣。
如果你想托管一个网站,比如一个静态,网站就是你自己的个人主页。GitHubPages是一个选择,Netlify也是,实际上还有很多其他的网站托管服务。基础帐户,因此它们不一定需要,甚至花费任何费用。但静态与动态是不同的,如果你想托管一个网页,应用程序。
这个实际上接受用户输入,存储信息在数据库中,做一些比静态网站更有趣的事情,你可能想用某个叫做PartyService的东西。它也有一个免费的入门级账户,可以用来开始玩最终项目。然后还有其他提供者,所谓的大型云服务商。
课程中你可能已经使用过解决,过去的学期,服务器故障在精神上与,管理相似。Linux类型的东西,以及TechCrunch是一个,受欢迎的地方,不仅仅是消费类。聚焦新闻,而是几乎任何,正在科技领域流行的事物,然后还有一个叫做新黑客新闻的,网站在YCombinator的网站上,这也是一个可以随便浏览的地方。
偶尔会看到最新的和最好的库,或者一些相当新兴的事物,所以如果你总的来说想了解,科技界的新鲜事和趋势,你应该了解的事情,即使你不关心深入探讨。
进行这些动手操作,这些都是很好的网站,当然还有其他一些网站。而CS50当然有自己的,在线社区,其中一些你们已经参与了很久,甚至是在高中之前,呃,请随时保持与我们的联系,或为你们的继任者回馈一点什么。
参与不仅是在这些社区中提问,同时也回答他人的问题,所以我们想少说点。现在把事情转向一种最终的,社区活动,感谢过去几周,许多贡献问题的你们。
好的,我们来看看结果,看来70个你们说了链接。布莱恩,你能告诉我们这是否正确?是的,这是正确答案,预处理、编译、汇编和链接,所有这些都是在后台进行的,确实是这些步骤。
fairmake可以说是所有这些的抽象,因为它启动了整个过程,但我认为更准确地说,回答两个步骤确实是那四件事。好,让我们看看记分牌,我们有很多嘉宾账号,嘉宾编号200在领先,但与其他人并列。
在CS50讲座中提到,驼鹿、Python、鸭子、猫,驼鹿、Python、鸭子或猫,在CS50讲座中最先提到的是哪一个?好吧,一秒钟,让我们看看结果。看起来猫刚好超过了鸭子,得到了51%,布莱恩,猫确实是正确答案,驼鹿出现在第一堂实验课,但他们。
在讲座中未提及,呃,鸭子直到调试时才出现。Python在讲座结束时被简要提及,但那是在我们自我介绍之后,Scratch中的主角当然是猫。好吧,我们可能很快会看到一点扩散,我们有。
一大堆人有3000,但名字开始变得不同,让我们继续下一个问题,每次你malloc内存时,你还必须确保。reallock返回free或退出,每次你malloc内存时,你也应该确保。reallock返回free或退出,大约还有五秒钟,malloc是很多。
学期中出现了分段错误,现在的回答是78,布莱恩,你同意吗?他们都是正确的,每当你动态分配内存时,完成后应该通过调用free归还给计算机,确实如此。布莱恩,作为一个可教的时刻,为什么我们从未有过callfree的getstring,我们现在知道。
在幕后使用类似malloc的东西来分配内存。所以getstring是CS50库中的一个函数,CS50的库为你处理内存管理过程,因此你不必担心自己释放所有的内存,确实如此,但每当你调用malloc时,你必须。
在这里,呼叫free完全正确,排行榜上看起来我们有来宾,问题是。什么是竞态条件,当条件适合赛车时。两个事情同时发生,产生意外结果。当一行代码执行得太快时,另一行代码也被执行。
SQL和数据库0秒,让我们看看,85%的人说当两个事情同时发生时。产生意外结果,布莱恩,这是正确答案。我很感激至少有1%的人说当外面的条件适合赛车,但在计算机科学的上下文中。
好吧,0秒,看起来93%的人说不,照片只包含一定量的细节,7%的人说是的。就像在《犯罪现场调查》中一样,布莱恩,你能帮我们调和这两个选择吗?嗯,这里93%的人是正确的,嗯,照片只包含一定数量的像素,你无法在那之前做到。
公平地说,这有点改变了,或者至少答案是机器学习或人工智能,算法会计算出什么样的细节可以或应该存在,但那实际上只是统计推断,实际上并没有存储在相机或其他设备上。
好吧,排行榜现在在6000分,这些人并列,以下哪个不是一个好的哈希函数的特征:确定性输出、随机性、均匀分布、效率。事情又变得真实了,以下哪个不是一个好的哈希函数的特征,回想一下我们在上下文中使用了哈希函数。
哈希表在谈论数据结构时,好吧,一秒钟,答案这次更分散了,应该是的,这才是正确答案。随机性不是一个好的哈希函数的特征,你希望你的哈希函数在相同输入下总是给出相同的输出。
那样你就可以依赖输出结果,如果是随机的,那将很难使用。确实考虑一个拼写检查器,它随机地说“是”或“不是,这个是个词”,这可能不是你想要的属性。好吧,现在排行榜上我们已经达到7000分,但终于开始看到一些差距。
FIFO代表什么?f-i-f-o,得分,先进先出功能输入文件。FIFO代表什么?让我们看看,80%的人说是先进先出,布莱恩,这是正确的,我们用这个来描述像队列这样的东西,队列中的第一个。
粉色的RGB值,我让你自己阅读这些,当然现在幕后可能正在进行一些谷歌搜索,但没关系,实际上谷歌相当聪明。如果你输入一个十六进制代码,轮子或色板,五秒后,以下哪一项代表粉色?好吧,让我们看看,选择ffd。
0e零,布赖恩,这样是正确的,所以这些RGB值是六个不同的值,其中每两个对应一种颜色。两个是红色,两个是绿色,两个是蓝色,这一切都是以十六进制表示的。粉色会是它们的很多,因为它非常接近白色,即所有红色、所有绿色和所有蓝色,但它的红色比绿色和蓝色多。
谁是对的呢?在这种情况下,少数的33%是正确的。记住malloc的参数是想要分配的大小。如果你有一个字符串,想要的事情是弄清楚这个字符串中的字符长度,但你确实需要一个字节。
我们有一个空终止字符,且需要一个字节的内存。所以字符串的长度加一,就是你需要的内存字节数。确实在C语言中,你没有什么是免费的,想要什么都需要自己去做,那个加一的部分,是你需要解决的问题。
有不同的方法来组织衣物,但结论是放入一个队列。因此,在你用完一个后,将它放到队列的末尾,然后在回去之前使用其他的。确实,好吧,现在排行榜上,看来猜测10破了10000分,但。
有很多其他人,接下来的问题是什么是段错误。当计算机内存耗尽时,当程序尝试读取一个空文件时,当程序尝试访问它不应该访问的内存时,当发生地震时。这些都可能非常接近,等两秒钟,让我们看看,似乎80%的人说当计算机。
程序尝试访问它不应该访问的内存,布莱恩,这是正确答案。段错误可能发生在你试图触及程序中不应接触的内存时,对于13%的人来说,他们说当计算机内存耗尽时,为什么这不是这里的正确答案。
所以计算机可能会耗尽内存,当你像调用malloc时,malloc可能会返回null,因为没有可用的内存来分配。但只要你检查这一点,我们在你调用内存时尽量鼓励你,检查这个值,避免这些类型的错误。好吧,让我们。
溢出可能源于递归,如果没有基准案例,堆溢出,整数溢出。栈溢出,缓冲区溢出,以及所有形式的溢出确实都会出现。其中一个当然也是一个流行网站的名字,但这些都是实际存在的东西。那么对于没有基准案例的递归来说,哪个是正确的,两秒钟,好吧,让我们看看结果。
比如61,60选择了栈溢出,布莱恩,这是正确的。每次你调用一个函数时,嗯,你最终会在栈上为那个函数占用一点内存,如果你不断递归调用那个函数而不停下,因为没有基准案例,那么你可能会耗尽栈空间,我们称之为。
好吧,让我们看看结果,布莱恩,这个很接近,33说是露丝、尤金和雷蒙德,露丝。得到的回答比其他任何人都多,这是正确答案,这些名字的背后并没有太多理由。我在故事本身上花了很多心思,但在名字上并没有太多的思考,那些名字有点是随意选择的,但确实是目击者的名字。
这些命令行程序中,哪个检查你的代码是否存在内存泄漏,Valgrind、Clang、Make,哪一个命令行程序检查你的代码是否存在内存泄漏,注意这些都没有50,这意味着这些都是你在自己的Mac或PC或未来会继续看到的真实命令。
让我们看看结果,我们有Valgrind,一个明显的赢家,得分78。布赖恩,这个答案是正确的,这个程序可以用来检查你的程序是否有内存泄漏,看你是否触碰了不应该触碰的内存,是否忘记释放某些内容。
以下内容在C语言中存在,但在Python布尔表达式中不适用,dowhile循环、递归函数和浮点数,哪一个在C语言中存在,但在Python中不适用,一切都好。68,正确,呃,Python有四种循环,Python有while循环,但它没有以C语言相同的方式有dowhile循环,你必须找到其他方法来实现。
这种逻辑思维确实存在,布赖恩,几周前我们讨论的是什么方法。是的,一种方法是有一个always重复,然后当你到达一个可以退出循环的点时,可以使用break命令跳出循环,继续程序的其他部分,确实,一切都好,我们来看看。
当发送私人信息时,应该使用什么HTTP请求方法,例如密码,是GET、POST、SELECT还是TEXT?发送时你应该使用什么HTTP请求方法?看看结果,好的,分布情况是很多人说POST,布赖恩74,他们是正确的,如果是GET请求,你最终会得到敏感信息。
在URL中,可能会出现在你的浏览历史中。例如,因此为了安全,你要确保使用POST请求方法来处理这类事情,明确一点,不要这样做,GET也是可能的,我们看到过如何做到这一点,但当然这最终会出现在你的历史记录中。
用于识别你的计算机与网站的数据为六十百分比,上述的为三十五百分比,仅有两百分之的你喜欢单独的饼干,布莱恩。呃,上述的两个选项是正确答案,我要提醒大家,这些问题最初都是由学生撰写的,答案选项也是。
学生选择的正确答案是上述两个选项。好吧,现在仍然在领先,但底部有些波动,这是CS50课程的最后一个问题。你现在的舒适度如何?答案都在了,让我们看看分布,似乎43人说你们在字典中比那些其他数据结构更舒服。
你们当中有24人选择了第二个选项,非常底部的19人,但重点是,你们现在确实被正式纳入了。更加舒适的环境,感谢你们加入我们CS50课程,我们迫不及待想看到你们的最终项目。
计算机科学在这里。计算机科学今天我们与Cs一起。这个星期很特别。我们来看看人工智能或AI。你可能还记得几周前,当我们首次介绍Python时,我们谈到了编写程序,当你说hello时程序会回应hello,当你说goodbye时程序会回应goodbye。但这些程序都是通过。
如果条件和elseifs和else,实际上这并不是真正的人工智能。如果你想和这样一个计算机程序进行整个对话,那将需要大量的if和elseif,以预测人类可能说的所有内容,因此我们可以做得更好。人类在人工智能领域已经做得更好。
我非常高兴地说,布赖恩在这里引导我们。现在CS50的布赖恩,非常感谢你。欢迎大家来到CS50。正如大卫所提到的,讨论的主题是人工智能,这一切都是关于让我们的计算机尝试变得智能,试图让它们以某种理性的方式行动。
这可能有许多不同的形式。人工智能的一个例子可能是游戏。你可能熟悉井字棋游戏,你有一个三乘三的网格,X和O轮流尝试排成三行。X先下在这个网格的中间格,然后轮到O。
下在上面。事实证明,此时在游戏中,X有一个非常有策略的举动,而一个非常擅长这个游戏的人或计算机,可能会尝试找出如何玩这个游戏,可能会做出一个聪明的举动,比如在右上角下棋。
始终处于微妙的境地。X有一种方式可以在水平上赢得比赛,还有一种方式可以在垂直上赢得比赛,因此必须阻止其中一种方式。也许他们选择阻止水平的方式。但无论如何,X会赢,只需在右下角下棋。因此,人类在玩这个游戏时可以推理对手可能会如何。
响应的方式,以及X会如何回应。计算机也可能试图在游戏中做同样的事情,比如一个简单的井字棋,或是更复杂的游戏。但人工智能不仅仅局限于游戏。你可能会看到类似于手写识别的例子,如今计算机在处理人类手写文本方面相当不错。
这个过程因人而异,并以相当高的准确率判断人类实际上写了什么字符。人工智能应用于垃圾邮件检测。也许在你的电子邮件收件箱中,垃圾邮件通常会被分类到一个单独的文件夹中,而你的收件箱中可能会涌入一大堆邮件。
不知何故,你的计算机能够相对准确地判断哪些邮件是好的,哪些邮件是垃圾邮件。计算机并不是在这方面完美无缺。有误报的情况,计算机会认为某封邮件可能是垃圾邮件,而实际上并不是。而且还有漏报的情况,垃圾邮件可能意外地出现在你的收件箱中。
计算机无法捕捉到这一点。但这种误报和漏报正是人工智能研究者努力减少的问题,以使这些系统越来越准确。你会在这种系统中看到类似的表现。如果你曾在像YouTube或Netflix这样的视频观看网站上看过许多视频、电视节目或电影。
Netflix或YouTube背后的软件能够给你推荐,建议你可能感兴趣的其他视频,基于你已经观看的内容。近年来,人工智能在更复杂的事情上也变得相当出色。例如生成数据。看看这两张人的图片,看看你是否注意到什么奇怪的地方。
看看这两个人中是否有哪个看起来很奇怪。你能分辨出这两张图片中哪一张不是一个真实的人吗?
一个计算机生成的人物,看起来像人类。但这实际上不是一个真实人物的照片。你可以仔细观察这两张图片。也许看看眼睛、头发、嘴巴和鼻子,看看你能否分辨出哪一个是人。结果发现这两张图片都不是现实中的人。它们都是计算机生成的。
而不是现实中的人。但计算机已经被训练生成看起来像真实人物的图像。它可能会让人误以为这是一个真实的人,但这完全是人工智能生成的信息。因此,今天我们将探讨所有这些概念,了解人工智能是如何运作的。最终,一个重要的收获是,人工智能不仅仅是一个算法或一个。
这实际上是一系列方法的集合,都是用于尝试解决一些让计算机智能化的问题。因此,让我们从我们可能想要进入的第一个领域开始,那就是决策制定。我们经常想训练计算机能够做出决策。
这个决策可能是在判断一封邮件是垃圾邮件还是非垃圾邮件,或决定是否向你推荐一个视频。或者它可能是在游戏中决定采取什么行动。所以我们来玩一个简单的游戏。也许你之前玩过这样的游戏,控制这个位于底部的球拍,并试图反弹这个球,以击中所有的。
上方的砖块。想象一下,如果你试图编程让计算机能够战略性地玩这个游戏。计算机观察到球朝那个方向移动,那么球就朝那个方向移动。你应该编程让计算机做什么?逻辑上讲,如果球向左移动,那么你应该编程让计算机也向左移动球拍。
尝试在球掉到地面之前捕捉到那个球。因此,你可以将这种逻辑编码成一个计算机程序,使用我们可能称之为决策树的决策。决策树只是一种表示计算机可能通过提问来做出决策的方式。根据这些问题的答案,我们可能会问另一个问题或做出某种决策。
所以在这个球拍游戏中,我们可以通过问这样一个问题来创建一个决策树:球在球拍的左侧吗?如果这个问题的答案是肯定的,那么我们应该采取的行动是将球拍向左移动,因为球正在向左移动。球拍也应该向左移动。如果问题的答案是否定的。
那么我们也许需要再问一个问题。我们可以问:“球在球拍的右侧吗?”
请继续将面板向左移动。如果球在球拍的右侧,请将球拍向右移动。如果这两者都不成立,那么就不要移动球拍。所以这个决策树的一个优点是它能够很好地与您在编程世界中熟悉的条件相对应。
所以让我们试着做一些更复杂的事情。我们来看看井字游戏,想象一下你正在尝试编程一个人工智能,以战略性地玩这个井字游戏。在你的决策树中,你可能会问什么问题?是的或不是的问题。但你想要提出问题,以创建一个能很好地玩这个游戏的人工智能。
能够战略性地玩这个游戏,或许我会请一位志愿者。如果有人想建议一个可能的问题,我可能会问我的人工智能,因为我正在教这个人工智能如何玩这个游戏。在这种情况下,我可以问什么问题?让我们看看,嗯,给定某种情绪,获胜的概率是多少?
你如何检测获胜的含义,比如你会在棋盘上寻找什么?
三个连续的。多余的圈。你在寻找三个连续的叉或圈的机会。你可能会问,检查一下是否能够在下一个回合连成三行。如果你已经有两个连在一起,并且有一个空位。这可能是我们想尝试的机会。
如果我们不阻止他们,他们就会赢得比赛。如果这个问题的答案是否定的。如果我不能在这一轮得到三个连成一排,而我的对手在下一轮也不能得到三个连成一排。现在就有点不那么明确。你或许可以尝试提出额外的问题。但这两个问题很明确,其余的可能就不那么明显。
但最终,我们不希望做这些,因为我们开始让我们的AI变得越来越智能。正如大卫之前提到的,我们不想为计算机编程每一种条件,告诉它在每种情况下该做什么。我们希望计算机能够聪明到自己分析所有可能性,并找出该采取的行动。
这些人试图玩某种竞争游戏,比如井字棋。但它也适用于其他游戏。正如我们在CS50中看到的,我们需要某种方式在计算机中表示这个游戏。最终,你会记得从CS50的开始,我们一直在尝试用数字表示一切,我们也可以用同样的方法。
对于每一种结果,我们可以说,X获胜的结果记为-1。平局记为0。没有人赢得比赛,现在每个玩家都有一个目标,某种可以用这些数字量化的目标。
最大化玩家,或者我们也可以称之为MaxPlayer,目标是最大化得分。对X来说,最佳得分为1,意味着X将赢得比赛。但如果X不能获胜,那么平局得分为0,这比输掉比赛要好。我们将称最小化玩家为MinPlayer。他们的目标是最小化得分。
-1是对O玩家来说最佳的结果。但如果-1无法实现,如果O不能获胜,那么平局仍然比X获胜要好。因为X获胜意味着最小化玩家O最终将输掉比赛,因此我们可以考虑每个局面。
井字棋的分数要么是1,要么是0,要么是-1。这场比赛已经结束。X已经赢得了比赛,因而我们会将其赋值为1,表示X胜利,-1表示O胜利,0表示平局。那么现在让我们考虑这个棋盘。这场比赛尚未结束。
如果两个玩家都在玩最佳的棋步,在这种情况下如果X玩得最好,那么X将在这个方格下棋以得到三个连成一排。因此这个棋盘的价值也为一,因为X的游戏表现。X将赢得价值为一的比赛。那么让我们看看另一个棋盘。
可能性有三种。如果X将会获胜,负一总是会获胜,零则表示平局。如果两个玩家都玩得最好,而现在是O的回合。你会给这个棋盘什么价值,为什么?
让我们看看Santiago。我认为这个棋盘的价值为零,因为两个玩家都在尽力而为。最终没有人会赢,因为O会挡住X,之后就不会有任何进展,确实。完全正确。X有可能获胜,因为X已经有两个连成一排。他们可以得到三个连成一排。但如果两个玩家都玩得最好。
然后O将在这里挡住,接着X将在左上角下棋。最后将会平局,这意味着这个棋盘的价值将是零。计算机可能会通过与我们一样的推理方式来计算这个值,考虑这两个可能的选项。如果我想知道这个棋盘的价值,现在是O的回合。
然后我会考虑这两个可能的选项。O有两个选择。可以在左上角下棋。或者可以在底部挡住X。计算机会考虑这两个可能的选择,并尝试弄清楚每个棋盘的价值。那么如果在左上角下棋会发生什么?
那么让我们考虑其他可能的选择。也可以通过在底部下棋来阻挡X,而在这种情况下,这将导致左上角的解释。这是唯一的其他可能选项。那个棋盘的值为零,这意味着这个棋盘的值也为零。现在,最小化的玩家将考虑这两个选项。
如果我在左上角下,那将是一个对我不利的值为一。如果我在底部下,那将是一个值为零。因此,这样更好。我们可以得出结论,桑蒂亚哥上面的这个棋盘的值也将是零。如果双方都下出最佳走法,最终结果将是平局。
这就是我们可能称之为游戏树的东西,你正在探索所有可能的分支,所有可能的游戏走向。我们距离游戏结束还有两步。但是你可以退一步,考虑一下这会是什么样子?
距离游戏结束还有三步,你会得到一个更大的树,因为现在你需要探索。可能性是你试图弄清楚任何特定棋盘的值。因此,这就是最小最大算法的工作方式。考虑所有可能的走法,然后递归。
考虑一下如果我下出最佳走法,我的对手会如何回应?
对于所有可能的走法,让我们像之前那样递归地为这个棋盘计算一个分数。跟随所有可能的走法,为这个棋盘得到一个分数,然后选择分数最高的走法。如果玩家是O,那么我们将对所有可能的走法做同样的事情,为那个棋盘计算一个分数。但随后我们将选择分数最低的走法。
所以X玩家选择最大化分数的走法。O玩家选择最小化分数的走法。使用这种方法,你可以创建一个能够完美玩井字游戏的AI。如果你正确编程,它将永远不会输掉比赛。
这不仅适用于井字游戏,还适用于其他游戏。但是你是否看到这种方法有什么问题?这种考虑我所有可能走法的方法,然后是我对手对那些走法的可能回应,最后是我对那些走法的所有可能回应。直到我们到达游戏的结束,有没有可能出现的问题?
井字棋有大约三十万种可能的游戏,看起来很多,但计算机处理起来相当迅速。大多数现代计算机通常能在几秒钟内完成这255,000种井字棋的对局。但如果想象一下国际象棋,这是一种相对复杂的游戏,那么有多少可能的国际象棋对局呢?如果有255。
井字棋有三十万种可能的对局,那么国际象棋有多少种可能的对局呢?
结果发现,仅前四步就有2880亿种可能的国际象棋对局。这对于任何计算机来说都是一项巨大的挑战。这仅仅是前四步,如果考虑整个国际象棋游戏的话。
为了找出任何棋盘的得分。正因为如此,像国际象棋这样的游戏对于人工智能来说比井字棋要难得多。但这并不是不可能的。目前,最好的计算机国际象棋选手远远优于最好的人类棋手。那么我们可以做些什么呢?我们可以做哪些改进?
或者我们可以对算法做出什么改变,使我们的AI仍然能够玩这样的游戏?即使有很多额外的可能走法,仍然有很多可能的答案。有人想提出一个想法或建议吗?我们如何应对这个问题?
有太多的走法需要考虑,计算机永远无法合理尝试任何方法。你有什么想法可以尝试的呢?也许沿途可以为一些低于某个阈值的走法分配一些点值?也许我们可以丢弃那些路径。我们希望有一种更智能的方式来避免探索所有路径。
丢弃一些路径或者不丢弃,或者在某个地方停止,以便我们不考虑所有10的29000次方的可能国际象棋对局,而是考虑更少的对局。探索更少的可能游戏和更少的可能层级,嗯,因此在最小最大算法上有很多变体。
最常见的算法之一是所谓的深度限制的最小最大算法。我们考虑一种评估函数,而不是一直跟随游戏直到最后。我们跟随游戏某个数量的走法,可能是15、16步,但并不是一直到游戏结束。
然后你只是问一个问题,比如,在游戏的这个节点,谁看起来更有可能获胜,即使我们不会完全计算。你可以通过统计每一方的棋子数量及其价值来做出一些判断,并可能会为不同的游戏想出其他策略。
但这正是我们需要开始的地方。更智能的做法是思考如何制定一个好的评估函数,能够处理复杂的游戏并估计谁可能会胜出。Minimax是一个可以用于玩游戏的算法例子。我在这里暂停,看看关于Minimax或游戏玩法的任何问题。
在我们转向完全不同类型的人工智能之前,有什么问题吗?聊天中有没有问题?我只是想问一下,这个算法是类似于我们思考游戏的方式。但有没有其他算法,不一定是逐步评估的?
还有其他算法和其他方法。我们给出的例子实际上只是一个穷举搜索的例子,搜索所有可能的移动,然后做出判断。我们稍后会看到一些更智能的算法,能够学习的算法。
我们将查看其他一些可能性,以及我们如何应用它们。这实际上是我们可能称之为搜索算法的一个例子。搜索算法是一种算法,我们在寻找某种解决方案。它可能是在寻找在游戏中该做的最佳移动。
或者你可能会想象一个搜索问题,或者抽象地说是找到你在迷宫中走的路径。你需要从迷宫中的一个点到另一个点,并且必须决定转哪个方向和如何走。这种应用可能类似于驾驶指引。如果你使用谷歌地图。
你输入你所在的位置和目的地,谷歌地图很快就能提供一个相当最佳的路线,告诉你该转哪个方向以及何时转弯。这是一个搜索问题的例子。谷歌地图需要以某种方式搜索所有可能的路线,以找出如何到达你要去的地方。
因此,我们可以想出一个算法来解决这个问题。想象一下,我们有一个迷宫,我们试图从A点到B点。但这里有一些墙,这些灰色的方块或墙是我们无法穿越的。我们仍然想找到一条从A点到B点的路径。
你可以尝试一些算法,如果你在解决这个迷宫,你会考虑如何决定做出哪个决定,去左边还是右边?
我们可能想到的最简单的算法之一就是深度优先搜索。深度优先搜索的导航方式很简单:AI会沿着一条路径前进。如果AI需要做出选择,它会到达一个分岔口,可以选择向左或向右。
它只是随机选择一个。它不知道哪个更好。因此它会选择一条路径并尝试。如果它遇到死胡同,无法继续前进,我们的AI就会退回来,尝试另一条路径。
我们从A点开始。我们想要到达B点。我们的AI可能的工作方式是,我们可能开始时只是一个一个地跟随方格。我们在这方面没有太多选择。我们还没有到达任何分叉点或路口。但在这一点上。
我们可以向左走,也可以向右走。就深度优先搜索(DepthFirstSearch)而言,它不知道该走哪条路。它不知道是向左还是向右。所以我们随机选择一条路径。我们可能选择向左走。我们会继续沿着这条路径,直到到达另一个分岔口。
我们可以向左或向右。深度优先搜索不知道该选择哪条。所以我们将随机选择。现在我们遇到死胡同,我们的算法知道这不是一条好的路径。因此,它会退回到最新的决策点,并做出不同的选择。它会退回到这里,表示我尝试过向左,但没有成功。
让我们尝试向右走,这条路不是一条好的路径。因此,不是走左边,让我们尝试向右走。我们将尝试这条路径。也许我们尝试向上走,但遇到死胡同。所以我们会再尝试向右走。我们会做出一些选择。我们会不断遇到死胡同并尝试新路径,直到最终到达目的地。
这就是深度优先搜索。只要有有限数量的方格,这个算法最终会找到到达目的地的路径。如果这样的路径存在。如果从A点到B点有一条路,那么这个算法最终会找到它,因为它尝试了一些东西。我们不断这样做,直到最终找到目的地。
但这个算法并不理想。显然有一些问题,也许还有改进的空间。所以也许我会问问你们。你们觉得这个方法有什么问题?
也许这个算法可能不是理想的原因,可能是我们可以改进的地方。目前来看,这个算法非常耗时,耗时的方式有很多。我们探索了很多实际上没有任何意义的路径。
我们探索了所有这些区域,但最终并没有帮助我们找到目标。而且,在最终找到的路径上,这也很耗时。就像想象使用谷歌地图,从一个地方导航到另一个地方。你可能希望找到一条高效的路线。
或许我们会选择向右走,并找到通往目的地的道路。深度优先搜索能够为我们找到从A到B的路径。但请记住,我们通常想要做的是做出最佳决策。这是一条从A到B的正确路径,但并不是最佳路径。
如果我们在寻找从A到B的最短路径,那将是从A到B的这条路径,因此我们希望找到某种方法来解决这个问题。我们希望有一个算法,能够找到最短路径,而不仅仅是找到任何路径。为此,我们可以使用一种称为广度优先搜索的算法,也称为B。
选择一条路径直到碰到死胡同,然后选择另一条。广度优先搜索会在遇到岔路口时,选择两条路径。我们先走左边一步,然后右边一步,再走左边一步,再走右边一步,有效地从中间向外搜索。
我们将从头开始,寻找所有可以通过从A点出发一步、两步、三步等到达的地方。因此,我们总是先查看附近的事物,然后再查看更远的事物。所以,这样的操作可能是,从A点出发向上走一步,然后进行搜索。
然后我们将查看这个方向的第二个方块,然后查看右侧路径上的第二个方块,再然后是第三个方块。我们将有效地重复这个过程,在所有不同的选项之间交替,直到最终找到从A到B的最佳路径。因为我们在寻找较短的路径之前,首先会考虑那些较长的路径。
正如Joy所指出的,这些算法往往不必要地探索许多路径。我们回到最初的迷宫,考虑广度优先搜索会做什么?
我们可以向左或向右,并记住深度。首先搜索选择一个方向并一直跟随直到死胡同。而广度优先搜索将选择两个方向。它将先向左再向右,然后再向左再向右,基本上在所有这些之间交替,直到我们到达另一个决策点,然后它会考虑所有这些。
它考虑了许多不同的方块。它查看所有方块,以便做出简单的决策,比如找到最短的路径,从一个点到另一个点。因此,我们可以尝试变得更加智能。理想情况下,我们希望在到达第一个决策点时选择向左或向右。大多数人,如果你给他们这个迷宫并告诉他们在这个决策点尝试解决。
到达迷宫的尽头。因此,到目前为止我们讨论的算法,深度优先搜索和广度优先搜索,是我们可以称之为无信息搜索的例子。它们并没有利用任何关于问题的专业知识。它们对这个迷宫并不了解。
他们基本上是在盲目猜测,希望最终能到达目标。但在一个I中,我们可以通过使用知情搜索来改进这一点,知情搜索。利用我们对问题的了解来改进搜索方式,使我们能更有效地搜索。
所谓启发式,是估计某个特定状态有多好的方法。在这个迷宫中,如果我想从A到B,启发式会让我回答这样的问题:如果我看到点C在这里,点D在那边。我更想处于哪个点,哪个点更适合找到到达目的地的路径。在C和D之间进行深入研究时,他们没有哪个更好的知识。就它们而言,每个方格只是一个方格。但如果你把它当作一个迷宫,大多数人会直观地告诉你。
如果我可以选择C或D,我可能会选择D,因为它看起来离目标更近。因此,我们可以使用的启发式通常称为曼哈顿距离。任何两个方格之间的曼哈顿距离实际上是说,忽略墙壁,忽略所有边界。只考虑在这种情况下向上和向右走了多少格。
但就曼哈顿距离而言,我会向右走四格,然后再向D走。D离目标比C更近。所以我宁愿选择D,而不是C。一旦我们有了两个选择之间的概念,哪个更好?这给了我们改进其他算法随机猜测的方式。
深度优先搜索在遇到分叉时,它可以选择向左或向右。它不知道选择哪个,所以随机选择了一个。现在我们有了这样的启发式,可以做出明智的选择。我们可以说我不知道向左还是向右是正确的解决方案。
根据曼哈顿距离,它的启发式值是什么,所以这个正方形离我们标记为一的目标是一个单位。离我们的目标是两个单位,我们标记为二。这是离目标三个单位的,忽略了任何墙壁和边界。
因为那些计算起来会很困难。我们希望找到高效的,这个正方形,即使为了到达目标,我们必须沿着这个曲折的路径绕过所有边界,仍然给它打了两分。我们现在想要高效的,它看起来是离目标两单位,所以这不是一个完美的启发式。启发式仅仅是一个估算。
或者我们可以选择向右,根据启发式,这个正方形距离目标13个单位,而这个距离目标11个单位。所以在这两者中,离目标11个单位的听起来好多了。因此,贪婪最佳优先搜索将选择向右。
我们将继续前进,直到到达下一个决策点,这里有两个选择:向上或向右。就启发式而言,这两个都是等价的,向上是七个单位,向右是六加一,总共七个单位。即使贪婪最佳优先搜索有时也需要随机选择。
如果两个正方形的启发式值相同,我们只能做出选择。也许我们会做出错误的选择并到达尽头。但我们可以尝试另一个。因此,76我们将继续前进,这里有另一个决策点。我们可以向下或向右,但右侧的启发式值较小。
在这里,我们又到达了一个决策点,启发式值是四,另一个是六。所以即使在这里,因为四的值较小,我们将向上走,尽管人类的自负使我们最终会遇到死胡同。计算机还不知道这一点,只能根据启发式判断认为最好的选择。
但最终它会遇到那堵墙,意识到无法前进,然后返回,沿着这条路径,我们最终会找到解决方案。因此,我们达到了与广度优先搜索相同的解决方案。但我们不需要考虑所有的正方形,可以忽略左侧的所有。
深度优先搜索,广度优先搜索,现在是启发式算法。贪心最佳搜索。关于这些算法有什么问题吗?让我们去看看。让我们看看索非亚在随机决策中。是否可以继续查看是否短些,比如向西?
你当然可以,比如尝试向前看几步,看看后面可能跟随的内容。这可能提供一些数字来改善情况,但即使这个算法也并不完美。当你只是查看这些启发式值并跟随这些启发式值时,有时启发式值会引导你朝一个良好的方向。
在某些情况下,因为启发式只是一个估计,启发式有时是错误的。如果你跟随贪心最佳搜索,13和12。你可以选择11或13,而贪心最佳搜索会向前看并说,这条路径看起来不错,并且这些值没有比12更大。
所以它会找到这条从A到B的路径。但即使这条路径实际上也不是最佳路径,最佳路径是A和B之间的最短路径,实际上是这里这条,看起来有点违反直觉,而我们没有注意到的原因是因为它涉及。
像是先向左走,然后绕道而行。根据这个启发式,我们通常不想往左走,因为我们知道我们的目标点B在我们想要到达的地方。所以,即使你在查看这些启发式值并向前展望,这个算法也可能不是完美的。因此,我们可能会尝试改进这个算法。
A星搜索试图建立在贪心最佳优先搜索的思想基础上。我们将使用启发式来尝试智能地做出选择,但我们会尝试解决我们刚刚看到的问题,即贪心最佳搜索并不总是最佳的,因为它只是考虑了。
它并不总是会做出最佳选择,因为我们可能最终选择了一条路径。A*搜索试图意识到,如果我们已经一路走到这里,现在在一个地方,我们可能距离目标还有11个格子。这没关系,距离目标更早地达到13个格子可能更好。
所以我们不能只考虑启发式值,我们还应该考虑我们已经走了多远。如果我已经走了很多格子,发现自己离目标很近。我宁愿走得少一些,但依然离目标很近。因此,A*搜索会尝试结合这两部分信息。
结合我们在这里看到的启发式信息,也结合我到目前为止走了多少步。因为这会影响最终的最优解,所以A*搜索将如何工作。它将是之前相同的思路,只是看启发式值。
我们将考虑启发式值并加上我们走了多少步。因此我们迈出了第一步,现在我们已经走了一步,离目标还有16个格子,总共17。然后我们迈出第二步,离目标还有15个格子,接着迈出第三步,离目标还有14个格子,我们继续走,直到达到决策点。
所以现在看起来我们还没有做得更好。我们还没有找到最优解,但注意最终会发生什么。如果我们沿着这条路径走得足够长,我们会到达一个点,在那里我们已经走了14步,估计离目标还有五步,12345,忽略墙壁,现在我们有一个。
我们可能在走了15步后距离目标还有六个格子。所以15加六,总共是21,而这个选项仍然对我们可用。我们可能距离起点有六个格子,但距离目标还有13个格子,六加13。这总共是19,而这个19比21小。所以这个19最终是啊。
而且恰好你可以证明,如果选择一个好的启发式函数,这个算法将是最佳的。它总是能找到从A点到B点的最短路径,而且它不会像贪婪最佳搜索那样陷入实际上不是的路径。因此,有许多这样的搜索算法旨在尝试找到智能。
找到某些问题的解决方案的方法,穿越某些地形。因此,我在这里暂停一下,以便回答关于搜索算法的任何问题。我们已经看过我们所称的经典搜索,穿越迷宫,寻找驾驶方向,以及我们可能称之为对抗性搜索的内容。
方格以及将要分配给它们的数字。我只是想知道你展示的三个例子是否。我们使用地图来实际搜索地图。所以这有点像直接映射到文本搜索或其他类型的搜索,通过不同种类的问题。
这可以用于许多不同的问题。在文本空间中,它们通常在自然语言处理的世界中采用不同的方法。但你可以在任何时候使用这些搜索算法,当你有一些通常称为代理的东西,它在做决策时必须做出某些。
以某种方式从数据中学习或从经验中学习,跟人类的方式很相似。我们通过观察环境来学习。我们从周围学习。我们从经历中学习,从这些经历中获得一些东西。因此,一种机器学习类型被称为强化学习,正是从中学习。
你所做的正面或负面奖励。计算机做得好的时候,它会得到奖励。计算机做得不好的时候,它会受到某种惩罚。计算机尝试从中学习。一个非常简单的游戏,我们可能希望我们的计算机去玩。计算机将尝试在这些方块中导航。
它将尝试达到目标,这与我们之前看到的非常相似。但这次计算机不知道障碍在哪里。让我们想象一下,路上有一些障碍,用红色高亮显示。如果我们的计算机代理这个黄色点碰到了这些障碍之一。
计算机在这个游戏中输了,但它不知道障碍在哪里。我通过视觉向你展示这些障碍,但计算机对此一无所知。计算机将如何尝试解决这个问题?你将如何尝试解决这个问题?
它开始时所能做的就是随机做出选择。随机猜测,比如选择往右走。但是现在计算机可以从经验中学习,只要它知道一旦碰到障碍,那是一个坏结果。计算机可以学习。我最好不要再那样做了。与其往右走,不如让我尝试另一种行动,再试一次。
也许这次我会往下走。这导致了一个障碍。因此,计算机从中学习到这是一个不好的尝试。那么我们尝试其他方法。也许我们试着往上走。这次的两个方向导致了一个不好的障碍。那么也许我们试着往右走。也许我们再往右走。这又导致了另一个障碍。
所以反复进行。它只是犯错误,我们让计算机犯这些错误。但每次它犯错,计算机都在从中学习。它在学习下一次通过同样的过程该做什么或不该做什么。因此,在未来,它可以通过足够的试错来找到路径。
因此,在未来我可以一次又一次地这样做。这就是强化学习的一个例子。但是,即使在这个例子中,你是否看到了这种方法的潜在问题?限制或缺点是什么?
但因为我们的AI在学习,每当它达到目标时,它就会学到这一点;当它未能达到目标时,它就会学到不要这样做。我们的AI没有那种能力。无法在未来找到更好的路径。因此我们可以在此基础上进行改进。这就是我们所说的在人工智能中探索与利用之间的权衡。
我们希望我们的AI能做到这两件事。我们希望AI利用已经拥有的知识。我们希望使用这些信息,但也希望它探索一下。我们希望它有时尝试一些以前未尝试过的新行动,因为,或许这会比我过去尝试过的东西更好。
我们希望在这两者之间找到一种良好的平衡。我们希望做出明智的选择,但偶尔也冒一次险。看看是否能找到更好的解决方案。因此,解决这些问题的一种策略被称为Epsilon贪婪法,试图为我们分配一个值epsilon。
意大利技术研究院正在研究一个类似的例子。但是在我们继续之前,我想看看聊天中布莱恩的问题。这和遗传算法有关吗?这种方法中的遗传算法是这种类型学习的另一种形式。我们将会在稍后讨论遗传算法。
我们很快就会到达那里。这绝对与此有关。几年前,意大利技术研究院尝试教一个机器人如何翻转煎饼,这可能是你曾经看过别人做的事情。但在实践中,很难准确地将如何做到这一点编码到机器人中。
精确地确定应该采取什么动作,以及如何移动机器人的肌肉才能翻转煎饼。因此,他们并没有试图将每一个细微的动作编程到计算机中。他们只是让机器人通过强化学习进行学习,每次煎饼翻转错误时,它就学习到未来不该怎么做,而每次成功翻转时也会学习。
它学会了未来该怎么做。因此,我现在就来展示一个例子。我们要看的是一个人工煎饼。最初,研究人员向机器人展示成功的样子。机器人需要知道什么是成功,什么是失败。因此人类进行了演示。
这是实际翻转煎饼的样子,展示了到底应该怎么动作,以及成功翻转煎饼的感觉。然后我们让机器人尝试一下。这是它的第一次尝试,不算成功。经过三次尝试后的情况。每次它做错的时候,它都会学习到未来不该怎么做。
这不是因为人类程序员告诉它该如何做,而是因为它从失败中学习,从经验中学习。一旦它知道了,就会精确地知道未来该怎么做。训练后,它可以不断重复这种行为。
人类是如何学习的,自然又是如何运作的。由于自然已经进化出了智能生物,我们为什么不试着在计算机中做同样的事情呢?
与其编程一个在执行任务时表现优异的算法,不如创造大量在执行任务时表现不佳的算法,因为这要容易得多。但我们会让它们进化,让它们尝试某个任务。它们完成后,表现不会很好。但我们会保留那些表现最好的,复制和变异它们,让它们再试一次。
然后这一过程代代相传,淘汰那些表现不佳的计算机程序,但复制和变异表现良好的程序。伪代码可能看起来像这样。我们将从随机生成初始候选者开始,每个候选者都是设计来尝试解决某个任务的程序。
对于每一个候选者,我们将计算其适应度,评估它在执行设计任务时的表现如何。
这些程序并没有被编程来驾驶,而是完全随机开始驾驶。你看到的每一个矩形都是一个虚拟自动驾驶汽车的示例。每个小十字线,每个小X代表我们传感器的可获取数据。所以汽车能够获取关于特定障碍物在任何给定方向上的距离数据。
这些汽车试图学习的是如何在某种环境中行驶而不发生碰撞。起初他们做得并不好。你会注意到他们几乎立即发生碰撞,但现在我们已经经过了六代或七代,他们开始做得稍微好一些,开始有了一点转弯的感觉,当他们撞上墙壁时。
即使在他们进入新的环境时,他们并不一定见过这些环境。他们开始做得稍微好一些,但仍然频繁发生碰撞。有时一代的表现甚至不如前一代,因为随机突变并不总是会产生良好的结果。
它们不一定会变得更好。有时突变实际上会导致表现稍微变差,因此它们可能会在代与代之间有所退步。希望你现在看到的情况是,这些车经过十代后总体上表现比之前好得多。每一代我们都在进步。
聊天中有关于煎饼的问题吗?机器人是如何知道上一次翻转煎饼时出错的?在汽车的情况下,汽车是如何知道自己出错的?无论是煎饼还是这些机器人,进行强化学习时,程序仍然需要告知AI。
成功是什么样子的?失败又是什么样子的?
在煎饼的例子中,我们训练了煎饼翻转器,使其能够知道在翻转煎饼时,成功的样子是什么?这样它能对成功的情况有所感觉。
也许还被告知如果煎饼掉落,那不是AI应该尝试的事情。我假设这些汽车被编程为在碰撞到某物时,能够检测到这一点。
所以它可能也有某种感觉,了解它能行驶多远,以便我们能够复制那些行驶最远的,而不是那些没有行驶的。让我们看看这辆车的表现。它离终点非常近。也许再给它一代,我们看看这一代的表现如何。
所以它在导航这些转弯。看起来有一大部分没有成功。但只要我们能得到一个成功的实例,我们就可以在未来从这个成功中学习。这是结尾。但是,看起来有一辆车终于能够到达终点。它成功地学会了这个任务。我必须在这个迷宫般的环境中进行导航。
我在这里暂停一下,供大家提问,然后谈谈强化学习,这些想法可能如何运作。是的,所以针对遗传算法,特别是汽车的情况。所有的汽车都是相互学习的,每当一辆车发生碰撞时。这并不是说汽车之间在相互学习,而是我们生成了新车。
基于之前成功的汽车。这个想法是,如果我们运行10辆车,看看它们走得多远,我们保留走得最远的五辆,淘汰其他五辆。那些没有走远的。但接着我们复制并重复那些表现良好的,从而在未来。希望这一新一代的汽车能够取得进展。
进一步说,代代相传。希望我们能够在这条路上走得更远,直到最终,正如你看到的,经过15代后,我们能够得到一些能够成功执行任务的汽车。现在让我们去问乔赛亚。汽车是否专门只从轨道学习?
当你改变轨道时,我们需要另一个吗?我们需要从零开始吗,或者是这样。所以如果轨道有所不同,希望不是。希望汽车在这种情况下所学习的,基于传感器数据,比如墙壁在任何方向上的距离,应该如何转向。
目标是这种方法能够泛化。希望真正的自动驾驶汽车不会以这种方式训练。但你会希望,当它们在样本环境中接受训练时,当你把它们放到不同的环境中时,它们能够泛化知识,应用类似的技术,以应对真实世界的情况。
你把一辆车放在一条它从未见过的道路上。希望它已经看到了足够多的类似示例的传感器数据,以便识别。能够基于此做出智能决策。希望目标是能够将这种知识泛化,而不仅仅是其他问题。还有其他问题吗?
让我们谈谈雅干教堂。那么这些算法能否达到瓶颈?就像在现实生活中的进化一样。有些分支会达到瓶颈。那么这里也可能发生吗?这确实有可能。算法可能最终收敛到一些看起来相当不错的东西。
似乎没有任何突变表现得更好。但事实证明,完全不同的算法可能会比这更好。这通常被称为局部极大值。在人工智能的背景下,存在一些更好的方法或更好的算法,但我们不一定能找到它。
还有其他策略来解决这个问题,但这确实是我们需要考虑的挑战。还有一个问题,关于如何计算适应度,在这两种情况下。是运行的某些电机,还是像距离这样的参数?在煎饼的情况下。
这可能类似于一个二元结果,比如煎饼是否落入锅中。我们的计算方式是评估该特定例子的适应度。在汽车的情况下,我们可能根据行驶的距离来计算适应度。最终行驶得最远的汽车就是我们的适应度定义。
这实际上是程序员需要参与的部分。程序员需要决定,什么是最适应的?一个坏例子是什么样子的?
一旦计算机知道成功和失败的样子,它就能够从中学习,以便在未来做得更好。这就是遗传算法,一个能够让计算机学习的例子,模仿人类学习的方式,从自然的工作方式中学习。
但是计算机科学家并没有止步于此。还有其他例子可以补充,这也是一个例子。使用强化学习和遗传算法的另一个例子可能是视频推荐,在这种情况下,你可以拥有一些观看历史,而像YouTube或Netflix这样的算法建议你观看视频的方式正是通过这种强化过程。
它将尝试向你推荐视频并从经验中学习。如果它向你推荐一个你喜欢的视频,并且你从头到尾观看了它,那么算法将会在未来学习,推荐更多类似的内容,就像汽车行驶得很远。因此,我们学习在未来多做这种事情。如果它向你推荐一个视频,而你没有点击。
你从未观看过它。那么在未来,它可能不会再向你推荐这个视频,并且它可能也会从这个经验中学习。因此,这是计算机科学从自然中学习的一个例子,学习人类的方式。另一个发生这种情况的地方是通过研究人脑,人脑由什么组成。
这些神经元彼此连接,并且它们传递信息。电信号在一个神经元和另一个神经元之间流动。这就是大脑能够进行这些非常复杂计算的方式。这就是我们可能称之为神经网络的东西,一组这些神经元。
就像你在人的大脑中可能找到的那样,只不过是以数字格式存在。这些人工神经元,这些单元可以以某种输入的方式彼此连接。这转化为一种输出格式,其中这个箭头代表某种计算,将左侧的值转变为。
右边的值和神经网络。它们通常不仅仅是一个输入单元和一个输出单元,而可能更复杂。你可能会有一个神经网络,有两个不同的输入,每个输入连接到一个输出,甚至还有像这样的更复杂的神经网络,其中有多个层的单元相互连接。
每个箭头执行某种计算。如果你听说过“深度学习”这个术语,这通常指的就是这个。这个深度神经网络的概念有很多层,每层都在执行计算,并最终使用一些线性代数。
它们能够准确地进行这些计算,将输入转化为某种输出。如果你给神经网络足够的数据,它可以从这些数据中学习。它能够确切地设定这些不同的箭头,以计算某项任务,将输入转化为输出。
这可能表现为手写识别。我们如何训练计算机来学习识别手写字?考虑到各种不同的手写体?一种方法是使用神经网络,建立这些神经元及其所有连接的网络。然后你将数据输入到这个神经网络中。
我提供给神经网络一大堆已经存在的手写数据,每个数据都有标签。因此计算机知道哪个图像对应哪个数字。你在这里看到的数据集是一个非常著名的手写数字数据集,称为MNIST数据集。基于这些数据,计算机可以开始训练神经网络,并逐步弄清楚确切的计算方法。
在神经网络的每一层中进行运算,以将输入转化为输出。比如翻译一个看起来像手写数字“八”的截图,我们都能辨认出这个数字,但可能很难描述计算机如何得出这个结论。但通过神经网络。
我们不会在这里讨论所有的数学,因为它变得有点复杂,但它被广泛应用。它可以用于电子邮件垃圾邮件检测,如果你给计算机一大堆数据和一大堆电子邮件,其中一些标记为垃圾邮件而另一些不是,它可以学习一个函数。
你可以确切地学习如何调整神经网络,以便能够对任何给定的电子邮件进行预测,无论它是否是垃圾邮件。使这些网络发挥作用的关键因素是拥有大量的数据。这就是为什么许多公司现在正在努力获取大量数据并利用这些数据来训练他们的机器。因为你拥有的数据越多,你就能更好地优化这些算法,因为你可以更好地调整它们以适应各种不同类型。
每次你打开电子邮件应用程序并将一封电子邮件标记为垃圾邮件时,你通常都在提供一些数据。那个电子邮件应用程序可能在学习那个标记?
这类电子邮件是垃圾邮件的一个例子。因此,它在未来学习更好地进行分类。每当一封电子邮件被标记为垃圾邮件时,你必须告诉计算机。计算机正在从中学习,这是计算机可以用来帮助其算法获取更多数据。
最后一个例子,我们可以看看像这样的图像实际上是如何生成的。计算机怎么可能得到这样的图像并生成它呢?
制作看起来非常像真实人的东西,尽管它实际上并不是真正的人?
它是通过使用完全相同的技术来完成的,使用神经网络学习如何将输入转化为输出,但在这种情况下,拥有大量的数据,包括许多真实人物的照片。因此,计算机可以开始对一个真实的人是什么样子有一个感觉。它可以以这种方式开始训练网络,而不是一次性构建整个的人。
一步一步地构建它。计算机生成这样一张照片是一项相当复杂的任务。做起来很困难。但你知道什么更简单?生成16个像素的图像。它看起来根本不像一个人,但计算机可以很容易地生成看似一堆随机的东西。但随后你会训练计算机为这个图像添加一点细节,以便能够学习。
如果这是一张真实的图像,你会如何添加更多细节,使其看起来更像一个人?这同样是通过访问大量数据来实现的,许多人的照片使得计算机能够从中学习。因此,计算机学习如何将这个图像转化为这个。
仍然看起来并不像一个人,但看起来更像一个人了。它的分辨率更高。你也许可以看出这里有一些头发,脸上有一些特征。然后算法学习。你如何处理这样的图像?
它们像成功的模型那样被重新生成。这些数据是如何产生的?
但我们并不总是立即能够看出它在成功的过程中做了什么。这是机器学习中一个活跃的研究领域,包括哈佛的一些教师,这涉及到机器学习的可解释性。像算法这样,变得非常,非常擅长向你推荐视频或生成一个人的图像。
但对一个人来说,很难看清那个机器学习模型是如何得出结论的。人们只是举起手来说,我们并不在乎算法是如何运作的,只要算法能够成功地做到这一点,并最终。产生良好的结果。我们只会选择那些表现最好的,使用这些。即使我们不一定完全理解这些算法是如何工作的。
但当然,在做这种事情时,很多时候是为了验证你确实是。那是这些东西的目的之一,以确保机器人注册网站。但是,它当然可能只是提供更多标记数据的例子,机器学习模型往往依赖于标记数据,比如手写数字。
你有一个标签,这个标签是数字二或者数字八。因此,计算机可以利用这些数字来推断如何学习如何将手写数字转换为个体。因此,拥有这种标签数据的访问权限,最终会变得非常重要。
但更多内容在下次讨论,好吧,到目前为止我们只见过字符串,但。事实证明在C语言和许多其他语言中,还有其他被称为。类型或数据类型的东西,也就是说你,可以拥有变量和数值。它们不仅仅是文本字符串,而是两个、三个、四个,或者像3这样的浮点值。
14159或其他此类值,你可以拥有布尔值,这仅为真,chars。它是单个字符,这意味着在像C这样的语言中,实际上有许多可用的数据类型。string只是其中之一,甚至比这个列表上的更多。
但这只是一些我们今天将看到的最常见的类型的列表。并且将在本周第一组问题集中使用,允许你告诉计算机不仅存储一个变量中的值,而是存储在变量中的值的类型。此外,我们在CS50库中有更多函数,我们已经看到了getstring。
但同样,我们创建了函数,超越,允许你通过getchar获取单个字符,允许你通过getint获取整数,允许你通过getfloat获取浮点值,这是一种描述带有小数点的真实数字的fancy方式。但事实证明,这些数据类型,例如int和float只有有限的。
位数,并回想一下上周我们玩弄了灯泡,我们玩弄了位和零和一。更一般地说,事实证明,double、float、int、long、string等等,均使用特定数量的位,结果是,使用32位,这很好,直到你想计数超过大约。
40亿,此时你无法再继续。我们今天稍后将看到,如果你只使用特定数量的位,你只能计数到这么高,因此存在其他数据类型,例如long。long是C语言中的另一种数字类型,仅使用64位,因此它提供了。更高的表现力,类似地,double就像一个浮点数。
浮点值是一个带有小数点的真实数字,后面有一些数字,后面的数字,然后一个浮点值会是这样,所以我们稍后将看到并使用一些这些数据类型。printf同样具有,我们看到的,但也使用不同的格式代码。其他数据类型也是如此,这些类型更为晦涩,但没关系,你会。
最终将它们ingrained用于常见的用途,cases,percentc将是占位符。打印单个字符percentc,可以说percentf将用于。浮点值,所以如果你想打印一个带有小数点的真实数字。你将使用percentf,如果你想打印一个整数使用,integer。
如果你想打印一个字符串,我们已经见过百分号s,如果你想打印一个长整型,即长整数,你将使用百分号li,还有其他一些,但这些可能是最常见的,它意味着c确实需要你这个程序员要非常精确,你不能只说打印这个,你必须告诉printf如何打印你传递的变量或值。
你传入的变量,然后最后发现,在c中有一堆运算符,当然有数学运算符,还有其他许多运算符,就像scratch有一整套运算符,暂且说c也支持加法、减法、乘法和除法。
我们现在可以看到的一些运算符,所以让我继续做这件事,让我回到我的IDE,在清理完一切后,重新开始,在终端窗口中没有任何内容,也没有打开的标签。让我继续写我的下一个,这些函数。
否则计算机将不知道这实际上是源代码,而不是机器代码。让我继续利用cs50库,包括cs50.h。让我包括stdio.h,这样我就可以使用像getint和printf这样的东西,然后再次出于今天的目的,我只是会写在mainintmanevoid中,然后。
大括号,再次为今天,只是相信这是必要的,但我们将在一两周内解释为什么我们一直写intmainvoid,但现在就像绿色旗帜被点击的拼图一样,让我继续从用户那里获取一个整数,假设我现在的目标不是写一个获取。
一串文本打印出hello,brian或hellodavid,让我继续写一个程序,可能会要求输入两个整数,两个数字,然后将它们相加,所以让我用代码制作最简单的计算器,使用一个叫做x的变量,就像数学家一样。我将赋值给它,类似于x冒号,我可以随便说任何我想要的。
是x问号,但我会保持简单,只说x冒号。用分号结束我的思路,因此在这里我用getint从用户那里获取一个数字或整数,引用的参数是输入,打印到getint的输出,等号是赋值。
操作符表示,复制右侧的返回值,即人类希望输入的整数到左侧,左侧表示给我一个变量,叫x。让我在其中存储整数,因此之前我们在左侧使用了字符串,现在的不同在于左侧是int,因为我想要一个数字,右侧是getint。
然后让我再做一次,获取另一个数字,获取int,我只是说y冒号,但是我可以问y是什么,问号或任何英文,但最后一行将是有趣的,现在我将打印出来。例如,这两个数字的和,告诉它要精确打印出什么,所以我不能。
还没真正输入一个数字,因为我输入了,所以我将放置一个占位符,我将放置百分比i,表示这里放一个数字,我只是还不知道是什么。然后,为了保持整洁,我将使用换行符,它只是说,给我一个新行,这也是一个美观的细节,移动光标。
在布莱恩之前,让我们去观众中找人,如果可以的话,如果这个程序的目的是很简单地将两个数字相加,即使你从未编程过,基于现有的运算符和到目前为止我们看到的一些语法,你的直觉会让你输入什么?
即使你从未这样做过,santiago,你该怎么做?嗯,我会说只需写x加y,是的,就这么简单直接,x加y是正确的直觉,我将最后加一个分号来结束我的想法,但确实计算机。
在这种情况下,c完全理解,,,我现在要保存文件,然后往下走,我不会去,输入,添加,因为那是我的文件名,隐含地,edition。c我想编译成一个名为edition的程序。希望这是我交叉手指的地方,我没有犯任何错误。
slashedition,注意我首先被提示输入x,我将继续做一个。我接下来被提示输入y,我会再做一个。瞧,santiago提议我确实在屏幕上看到,x加y或者值2,而我没有硬编码2,我使用百分比i代入,无论结果是什么。
x加y实际上现在注意到,getint函数的一些特性,假设你不是很配合,输入cat作为x,注意到getint只是,忽略了你并再次提示,提示我,再次,如果我输入1.23,它忽略我,提示我再次,因为它想要一个整数,在这种情况下是像1这样的数字。
四或零,或任何高于或低于的值,所以好吧,我现在会合作,给它数字一。y也是如此,它会忽略任何非整数输入,所以如果我给它一个数字,比如这次是2,我希望能得到,3的答案。好的,所以我们有一个基本的。计算器在c中,我们使用一些基本构建块,和以前一样,我们有这些头文件。
这只是让我访问,分别使用getint和printf,但是,假设现在我想要更高的计数,你知道吗,让我尝试一下,像这样的程序让我再运行一次。让我贪心一点,怎么样,四零零零。零零零零零零,所以大约是,四,确切地说,四十亿,呃,这就是我想要的数字。
好吧,那次成功了,我在这里暂停,发生了什么?也许现在再次我们,工作人员编写了getint,所以我们是,拒绝,猫和狗以及四十亿,甚至三十亿的原因,但在这种情况下不太清楚,为什么我们拒绝四十亿和三十亿,你认为基于一些定义。
今天远远不够,为什么会这样,纳撒尼尔,你好,嗯,我刚刚解除静音,呃,数字的大小有限制,因而在两个亿之后,完美,所以整数再次是,蚂蚁。蚂蚁总共只使用32位,而你只有通过输入才能知道这一点。
只有32位,这就引出了问题,如果你有32位,或者灯泡,你能计数到多高,事实证明,有32个灯泡或位,你大约可以计数到四十亿,你绝对可以计数到三十亿。然而,getint仍然拒绝它,但这是因为getint函数,包含的不仅仅是数字。
而且要注意的是,如果你想支持正数和负数,你可以表示四十亿左右,总共有可能的值,但是如果你想尽量向左走,尽量向右走在我描述的数字线上,你只能在正方向上计数到两个亿。
负20亿是在负方向,因为这仍然给你一个总共4亿。但远没有3亿或4亿那么高,我记得早些时候提到过,还有其他数据类型,不仅仅是整数和字符串,还有长整型,实际上是更长的整数,即64位,所以让我继续试试。
让我继续并将get改为getlong,把这个改为long,所以同样的程序,同样的计算器,但我现在使用的是不同的数据类型,这将使用更多的位数来存储值。让我再次运行加法,重新编译我的程序,哦,天哪,我搞砸了,让我们看看能否找出哪里出了错。
让我向上滚动,我不能强调这一点,有时候我在这里运气很好,显然只有一个错误,在错误信息中,并不会出现两行代码的错误。在你的代码中,编译器有时会感到困惑,如果它感到足够困惑,它会开始思考。
你实际代码中的一切都是错误,所以最重要的收获是,总是向上滚动到输出的顶部,首先解决第一个错误。这就是我在窗口中向上滚动的原因,以便立即查看我输入的内容下面的内容。做加法,这就是第一个错误,位于第10行。
看第10行,所以让我把代码向上滚动,它提到格式指定类型为int,但参数的类型是long,我们之前没见过这个错误,但我想我可以从中推断出,这并不是超级神秘,尽管那是百分比,我记得是用于整数,我想我需要一个不同的长整型格式代码。
整数将会是li,而这是我之前的小备忘单。所以让我继续再试一次,li,哎呀,拼写错误,现在让我做点斜杠加法,现在我会输入四个零,四十亿,真是个长整型,因为它有足够的位数。好的,关于类型int的问题,当我输入的时候。
两亿和两个都是整数,错误的答案,一些负数是因为位和字节确实是相同的答案,所以我没有演示这一点。但如果你为x输入两个2亿,为y输入两个2亿,然后试图将它们相加,这在数学上会给你4亿,但再说一次,亿。
如果我们还希望能够表示的,那就是你无法将结果放入允许的数据类型中,我们稍后会看到,实际上这有什么后果。你试图把一个太大的数字塞进有限的位数中,32位,但你可以通过切换来避免这个问题。
让我再试一个别的,稍微有点意思,让我去写一个稍微不同的程序,现在,我将把这个称为截断。这是个华丽的术语,但我们很快就会看到这意味着什么,我将在顶部引入cs50.h,并引入stdio.h,这样做当然没问题。
和之前一样,我会再提示用户输入一个整型,继续进行这个操作,我想这次进行除法,不仅仅是加法,这有点太简单了。让我做除法,所以z等于x除以y,我在这里暂停一下,问一下,这行代码还不正确。
因为回想一下,每次你在左边创建一个新变量时,我需要在该变量名称的左侧放置一些内容,以便c知道我想要什么类型的变量,迄今为止我们看到了字符串和长整型,那么你提议我们用什么呢?z的数据类型应该是什么?jack,你觉得呢?
嗯,它应该是浮点数吗?是的,所以是浮点数,所以value,这是程序员描述实数的方式。让我接下来做一个浮点数,嗯,我猜你的直觉是,如果你输入一个x的数字和一个y的数字,而结果是某种分数。
所以有小数点的东西,我们需要将其存储为浮点数,以便我们实际上可以,嗯,存储所有数字,做这个,%f,\n,因为我正在打印一个浮点数。打印出z的值,顺便说一句,为了好衡量,让我开始练习良好的风格,所以从用户那里获取一个数字,让我再给自己加一个注释。
从用户那里再获取一个数字,或者说,这看起来有点傻。我可以合并这些行,为什么不直接说,从你的代码中获取,比如x乘以y,但即使这样也有点过于挑剔,因为从代码中你可以大致看出来,所以到某个时候,我们可能甚至不需要对此进行注释。
所以我们就这样简化一下,接下来我们编译这个程序,make,来吧,maketruncation。好的,它编译成功,我喜欢我们在这里使用浮点数,这感觉正确。让我运行truncation,然后我去输入,比如说x输入4,y输入2,好的,我喜欢这个结果是2.000,所以数学是正确的,x是1,y是2,结果是0.0000。
除以三的结果显然是零,后面跟着零,后面有六个小数位,甚至四分之三给我的是1.00000,而不是1.33333。因为在这种情况下,它们不喜欢像小数这样的东西,所以像四分之三的问题,三只能整除四一次,想要实际小数就用双精度。
我实际上是在做x除以y,你首先要问自己,x的类型是什么。答案将返回一个整数,因此如果你做1除以2,数学上是0.5,确实可以,但它会截断所有小数点后的部分。然而,因为你无法将浮点数和之后的数字放入整数中,所以你会失去小数点后的所有数字。
因为你只能适配整数,所以我保存的这个没关系,太晚了。数学运算已经在右侧完成了,所以是的,我在一个浮点数中存储了一个整数,因此我可以将其打印为浮点数,但一切都太晚了,所有数据都被丢弃了。那么这有什么影响,或者我该如何修复呢?
我可以逐一更改所有这一切,比如说,如果问题是x和y是整数,那么我就把它们改成浮点数,像这样更改,这样就可以了。这会解决问题,但这是一个过于粗暴的解决方案。你可以更加聪明一点,可以说服浮点数。
通过一种称为强制转换的方式,所以我实际上可以在这里,使用一种新的语法,我可以说floaty。我甚至可以为了保险起见,但这并不是严格必要的,也对x这样做。你可以在C语言中通过在括号中放入你想要的新数据类型,强制转换或类型转换一种数据类型到另一种,如果这样转换在数学上是合理的。
计算机会为你完成其他操作,所以通过这种方式。我是在告诉计算机将x转换为a,float将y转换为一个float,然后进行数学运算。在之前,当我分别输入1和2作为x和y时,现在就像我输入了1.0和2.0,而1.0除以2.0,数学上是0.5,但因为x和y现在被转换了。
在转换为浮点数之后,答案将保持为浮点数0.5。这将存储在z中,并最终被打印出来。所以如果我重新运行截断,已经修复了这个问题,让我输入./truncation,输入1,输入2。我不必自己输入小数点0,计算机为我做了这一切。
好的,所以我们似乎现在对程序有了一些非常基本的低级控制。让我们现在重新添加上周从头开始的所有高级特性,这样我们就可以开始编写更有趣的程序。因此,变量糖也是这些特性之一。
回想一下上周,当我们想要创建一个名为counter的变量并将其设置为零时。我们可以这样定义它,在C语言中,从今天开始,我们要说的是,counter等于零,但我们还需要指定该变量的数据类型。并且我们需要用分号结束我们的语句,因此我们所说的设置counter。
像上周那样设置为零,现在这将在右侧简单地转换为这个。那么接下来会发生什么呢?如果我们想要将上周的计数器变量增加1,加1,我们很简单地使用了这个拼图块。本周我们需要更明确一点,并说类似于counter等于counter加1。
并用分号来结束这个语句,现在这可能看起来很奇怪,counter并不在逻辑上工作,但在这种情况下,这不是等号。在C语言中,与其他语言一样,我们会遇到等号,它是赋值运算符,从右侧赋值给左侧,结果将counter增加1,因此有效地增加其总数。现在这是一个非常常见的操作。
程序在本学期会看到你只想加一些东西,因为你想跟踪某种计数,因此发现有一些语法糖,这意味着有一种不同的语法方式来做这件事,它并没有给你在C语言中没有的任何新功能,只是稍微让输入更愉快或更快。
因此,这行代码在C语言中与这样说是相同的。一个分号意味着取左侧的变量并加1。它稍微简洁一点,并且让你的代码更具可读性,因为人类需要处理的东西更少。还有更多额外的语法糖存在。
你甚至不需要键入这个,你可以直接使用counter++。counter++是在C语言中为给变量加1的最简写法。好的,除了变量之外,上周我们还有什么工具呢?当然是条件的概念,条件就像是道路上的分叉。
这可以让你在Scratch中做这件事。例如,如果我们想要比较上周的两个变量x和y是否不相等,x小于y。如果是这样的话,x就小于y,我们如何将其翻译为C语言呢?那么语法将会非常简单,这里有一些新的内容,一些新的括号,一些新的花括号。
但在视觉上看起来有点相同,尽管以文本形式,我字面上说,如果是空格,那么在表达式中,回想一下上周x小于y是我的布尔表达式。然后注意我使用一个开放的花括号,留一个空白,就像我可能在上周做的那样,事实上让我们在这里放入等效的代码行。
使用printf打印出x小于y,换行符,所以我们之前已经完成了那个翻译。说就像printf,就像if,现在就像if,严格来说,特别是如果你之前编程过,你在条件内部只需一行代码时,并不需要这两个花括号。然而,从风格上讲,为了CS50和风格50的缘故,始终包含。
这些花括号,尽管它们在自己的行上,好的,如果我们可以这样做if...else,我们可以在分叉的一个方向走,也可以在分叉的另一个方向走。在C中,相应的代码看起来像这样,所以几乎和之前一样。我只是添加了else,然后是另一个花括号和一个关闭的花括号。
让我添加一些printf,你可以看到在C中这实际上是上周非常图形化的版本,但理念是一样的,你只需开始识别括号放在哪里,花括号放在哪里,分号和所有那种视觉元素。好了,让我们再做一个Scratch和比较,这就是一个。
一切都是逐行翻译,虽然我们可以将elsif放在同一行,而elseif也可以放在同一行,除了这里有点愚蠢的变通方法,从某种意义上说人类,我们已经使用等号进行赋值,我们现在用什么来表示相等呢,嗯,麻省理工学院忽略了这个问题,只是。
使用单个等号表示相等,计算机科学家在发明C语言及其后续语言时。当比较左边和右边两个值的相等性时,使用两个等号,仅仅因为一个。左边,两个等号是相等性比较,这两个值是否相等。但是你知道,这不一定设计得很好,从逻辑上讲这是正确的,我的Scratch。
代码和我的C代码是正确的,但有没有人能观察到,为什么。代码不一定设计得很好,我做的工作比我需要的多一点。我可以稍微简化一下这段代码,我可以输入稍微少一些字符。并且达到相同的效果,正确的决策并不完美设计,呃,非常好。
是的,你使用了elsif两次,实际上可以在最后用else而不加条件。很好的观察,我用elsif两次在逻辑上是正确的,但请考虑,如果x小于y,那是什么路?如果x大于y呢?唯一的其他可能是什么?
这并没有更清晰,也没有更短,但确实避免了问一个额外的问题。因此,现在可能问三个问题的情况变成了两个。坦率地说,如果你编写大量代码或者一次又一次地执行这个,那种差异可能会累积,确实能给我们带来能力。
使用这些条件,我们实际上来尝试将其转换为程序,让我提前进行。让我先复制一个我今天带来的文件,名为conditions.c,这是课程网站上提供的文件之一,让我打开这个文件,看看这是否算是对scratch代码的字面翻译。
我现在来展示我在这里做了什么。我打开了一个我提前写的文件,名为conditions.c,在文件的顶部我有我通常的两个包含,然后在这里我几乎有我们在幻灯片上看到的内容,加上两次getins的调用。
elseifx大于y,否则去做以下的scratch翻译。让我先制作条件,conditions.c是文件名。没有明显的错误,所以让我去运行./conditions,输入x将是1,y是2,确实x小于y,如果我去运行。
在顶部,然后我的intmainvoid,这是从之前复制粘贴过来的。现在让我这样做,让我去ahd,并且不从用户那里获取一个整数,甚至不是一个单词,让我们简单点,只问用户y或n表示是或否,让我去ahd并给自己一个字符,我叫它c,但我可以叫它任何东西,比如answer。
但如果我只有getchar函数,c看起来是合理的,让我问你同意吗?问号,然后让我去ahd,比较一下。如果c等于y,那么让我去ahd并打印出同意,\n,否则如果c等于n,表示不同意。现在不幸的是,我在这里犯了几个错误,其中至少一个可能比其他的更明显,可能引入了。
任何人都可以,嗯,如何处理Olivia,布尔值你使用了单个等号而不是双等号,因此都很好,所以我使用了单个等号。这是因为c数据类型,我一直在使用双等号。
字符串的引号,但事实证明,在C语言中,当你比较单个字符时,你必须使用单引号。所以我会在这里修改,将y和n周围的引号改为单引号。为什么?因为我现在正在处理字符的世界。字符是在你。
讨论字符时,你需要像这样逐字引用它们。变量名c不需要引用,但y和n需要引用。但我不需要改变文件中其他的引用,因为那些仍然是文本字符串,实际的短语或。
所以让我去ahd试着运行makeagree,它编译得很好。让我去ahd运行./agree,我是否同意?让我去ah**d输入y,同意,我喜欢这样,所以让我试试n,不同意,哦,我忘了一个\n,所以让我快速修复一下,只是为了保持一致性。让我重新编译我的程序,假装这一切从未发生过,但让我现在非常合理地这样做。
./agree我想要同意,是的,大写Y,呃,没发生什么,那n呢,大写N。没发生什么,但程序仍然正常运行,如果我使用小写字母也能工作。如果我在这里使用小写字母也能工作,那到底发生了什么呢?好吧,计算机只会逐字理解你,即使我们人类可能会觉得。
好吧,是大写还是小写,问两个问题如下:我们可以做一些类似的elseifc等于大写Y并用单引号括起来,你可以想象再次说同意,就像这样。但就像上周我开始复制和粘贴Scratch块一样,这可能不是很好的设计,类似地,这段代码的第11行。
14行几乎是相同的,我们来看看是否能结合这些想法。让我表达一下,如果c等于y或c等于大写Y。实际上你可以使用这个竖线运算符,它是逻辑或运算符,并且实际上可以一次问两个问题。结果证明,你可以使用与的概念来实现这个。
使用“&&”逻辑与,更多的将在另一次讨论。但两个竖线是相当于说“如果左侧这个或右侧这个”,现在如果我保存并重新编译程序,运行makeagree,然后执行./agree,你会看到我可以输入小写的y。
或者用大写Y,这样它就能工作。因此,如果我可以结合这些思路并让我的程序更好设计,注意到这一点,我一直在每次进入花括号时,严格缩进,每次有if条件或else时也如此。
风格和美学好吧,现在让我们考虑,不仅有能力表达条件,还有这些称为循环的东西。结果,在Scratch中我们有非常简单的“永远做以下事情”循环。C语言则有点笨重,C中没有“永远”关键字,但我们可以如下模拟这个概念。
这实际上是说“当...时”,在英语中具有正确语义,像是“当某事成立时就做这个”。但你必须更加明确,不能仅仅说“当”。在C中,“当”类似于一个条件,它不断地询问一个问题来决定是否继续,这与它自己的布尔表达式非常相似。
在C中,使用while时,你必须在“while”之后有括号,并且必须在括号内询问一个问题。这是一个小的边缘案例,因为如果你想做某事永远,谁在乎问题是什么,你只想要答案始终为“是”,或者在计算机术语中。
始终为真,最简单的方式来表达“真”。虽然这看起来有点奇怪,但在C中,这就是你故意引发一个永不停止的无限循环的方式,你甚至不需要问一个更复杂的问题,你可以简单地让某事发生。
“永远”是表达某事永恒的最经典方式。如果你想做有限次数的事情,我们也可以在C中做到。使用我们将称之为for循环或while循环的方法,我们逐一考虑这两者,最机械的手动方式我能想到的是。
就像我用手指头计数一样,最少在我的手上计数十次。那么我该如何在C中做有限次数的事情呢?我可以使用变量来做计数器,将其初始化为零,数据类型为int,因为我只需要像在手指上计数那样。
冗长的程序员在频繁计数时,通常会从零开始计数。他们往往会直接使用i,字符串,你并不总是想在代码中这样做,有时让你的变量名称更具描述性更好。但对于一个只会从0开始计数的简单变量,我们不妨简单点,称它为i,我得问。
有个问题,因为我不想让这个无限运行,我希望它运行50次。我可以问什么问题呢?好吧,为什么我不检查一下i是否小于50。就像在50根手指上计数,直到50,然后执行以下操作,我想做什么呢?我想不断打印“你好,世界”,你好,世界,你好,世界,迭代。
在这个循环的每个周期,我需要做一件事。从数学上来说,我需要再加一个手指,换句话说,我需要给i加一。所以让我去把i设为当前值加一,但我们又有一些语法糖,让它更简洁。
我可以用i加上等于一,甚至更简洁地用i加一,所以尽管这在实现上比在C中要烦人得多,但我们现在有了所有的构建模块。现在有了变量,重复某个有限次数的概念。但是还有另一种方法可以做到这一点,正如你可能已经发现的那样。
从零开始有不同的方法来实现同样的功能。我可以从一开始计数,一直计数到50。所以你的键盘上大概没有大于或等于符号,或者小于或等于符号。因此在C中,像其他语言一样,你只需使用两个字符,使用小于符号。
后面跟上等于号,这表示小于或等于。所以如果我从一开始计数,到50也是正确的。你可以这样做,但不要这样做,这不符合常规,程序员通常会在上周开始时总是把灯泡关闭,他们会从零开始计数,到250。
这隐含给你0到49,所以做这个,而不是那个,但这确实说明了你可以用许多不同的方法解决问题,还有另一种计数的方法。我们必须用i减一,而不是i加一。所以这又是三种解决同一个问题的方法,而且你会开始拥有正确的。
直觉和肌肉记忆,你还会开始看到讲座代码中常见的模式,你的助教代码,书籍和在线参考资料,往往是做事的方式。好吧,这里还有一种循环的方法,结果发现还有另一种循环结构,这种结构更隐晦,称为for循环,它允许你自动化或说得更准确些。
再简洁一点,对于printf,打印“你好,世界”将使我们更接近于打印50次“你好,世界”,但for语句就像while语句一样,后面必须有必要的括号,但这次你可以在括号中放更多内容,它不仅仅是一个布尔表达式,括号中的第一件事。
你可以将任何变量初始化为某个值,我可能会说counter等于零,或者更简洁地,inti等于零分号,但for循环的样子有点奇怪。你可以在一行上做多件事,for循环括号内的第二件事是你想检查的条件,for循环括号内的事情是。
更新或递增或递减,或者说,让我们就用i加等于1,甚至更简洁地,i加加,这可能是C语言和其他编程语言中最常见的做某事的方法,50次或有限次数。这看起来与我们迄今为止所见的有所不同,分号出现在奇怪的地方。
但现在只需意识到,这表示将i初始化为零,检查条件,如果i小于50,则打印“helloworld”,然后更新i,然后检查条件,然后。如果它小于,打印“helloworld”,所以变量的初始化只发生一次。其他一切都一次又一次地发生,直到你完成这个。
一共50次,好吧,因此有了这些构建块,我们的Scratch到C的翻译就差不多了,现在让我们开始构建一些更有趣的程序,并进行实例抽象,因此抽象回想一下是这个。问题解决原则,你可以简化否则更复杂的问题。
细节是抽象,这是一种在更复杂的细节之上的简化,或者可以说。所以例如,让我继续写一个名为meow的程序。类似于上周,但这次用C语言,为了让猫以文本形式“喵”,让我在顶部给自己添加standardio.h,在intmainvoid这里,再次我在一个名为meow.c的文件中。
我已经包含了standardio.h和intmainvoid,现在我要继续做类似这样的事情,printf“喵”换行三次。让我保存那个文件,生成meow,好吧,现在点斜杠meow,喵喵喵,全是文本,所以没有上周那样神秘,但它是正确的,但并不是很。
设计良好,因为我在重复自己,这是坏的本能,但现在我们有能力使用循环,所以让我实际上删除这个函数的部分,试着回想一下之前的例子,如果我想做某事三次,我可以使用几行代码。让我这样做:inti等于零,i小于三,i加加。
melo,重新编译,meow通过让我执行dotslash,meow,然后voila。现在这个程序可以说设计得更好了,但让我进一步说明。这一周的轨迹不仅是实现meow,更是以更好的设计来完成,而不必重复自己,因此使用循环。
今天不会解释这个关键字void的含义,我在第三到第六行中做的是创建我自己的自定义函数,C语言并没有提供一个名为meow的函数,CS50的库也没有提供一个名为meow的函数,但现在多亏了我,这个函数存在于生活中,只是打印出meow,但是什么呢?
现在很酷的是,就像上周的scratch一样,我现在可以调用一个名为meow的函数,而我的,相当于说它的功能,只是通过函数名。让我去ah**d编译这个,makemeow,到目前为止一切顺利,dotslashmeow,似乎工作得还不错,但我不喜欢我把meow实现在文件顶部这个事实。
这没什么大不了,函数位于文件底部,为什么只有教学助理想从上到下理解你的代码?这只是人类的惯例,将主程序,主函数放在文件的顶部。问题是当我这样做时,我会为自己创造一个问题,当我运行makemeow,现在出现了两个错误。
所以有几个错误需要解决,但首先,布莱恩,来自小组的一个问题,聊天中有一个问题,关于为什么在第五行这里,你的for循环末尾没有分号,而在第十一行你没有在函数名末尾加分号。
那么为什么有些行需要在末尾加分号,而其他行则不需要?这是个好问题,为什么有些行没有分号而其他行有。简短的回答,虽然不是为了让人感到轻率,老实说,只是因为语言的设计方式是,你通常应该在表达动词、动作或函数时结束你的思想。
肌肉记忆和识别这些东西去哪里以及不去哪里的心理模型。但是到目前为止,我们看到分号的唯一地方是在函数末尾,比如meow和printf,现在是括号,问题是一个和第一次实验等等。您通常希望回顾像这样的例子和幻灯片。
变得困惑,C现在很好。那么为什么呢?让我向上滚动一下。坦率地说,C及其编译器并不聪明,它们显式地,当前的问题是,当编译器从上到下、从左到右读取我的代码时,直到第行。
11,meow函数甚至存在的那一刻,坦率地说,编译器并不知道meow是什么,因为它还没有处理meow,而编译器不够聪明,或者不够用户友好,不能先读取所有内容,然后决定是否有问题,它只会从上到下读取一次。
它将在遇到问题时立即对您大喊,所以解决这个问题的方法很简单,就是将函数移动到文件的顶部,但这最终会变得烦人,因为那样您就得去寻找主函数,或者还有另一种方法,我们会解释。
这也会在适当的时候完成,但您也可以只复制自定义函数main的第一行。然后回答布莱恩的问题,并且要加上分号,所以这很奇怪。这通常被称为原型,这是一个提示,只有它算是一种聪明的方式来告诉编译器,将会存在一个名为meow的函数,但现在还不存在。
但要知道它会的,这只是一个常见问题的变通办法。好吧,所以让我继续进行一个更改。再进行一次更改,假设我真的想完成这个meow示例,就像我们在Scratch中那样,我们还允许meow接受一些数字。
就像我上周做的那样,结果证明更多内容将在接下来的几周中出现,11。简而言之,我的自定义meow函数今天没有返回值,它不会输出任何内容,实际上它只是有一个副作用,就是在屏幕上可视化打印,但它确实有一个输入。
你可以字面上做一些,比如你想要的类型的名称和你想要的变量名称,所以假设我想让meow作为输入一些数字,我们称之为n,我想在循环中使用这个数字。我可以这样做,forinti从0开始i小于printf。
我自定义的函数可以像括号所示那样接受输入。它没有输出,因此我将这里留空,但我们稍后会更详细地解释void,但现在我正在使用那个输入。这是在C中的新实现,我使用相同的构建块,使用一个for循环。
我像之前一样,但不是硬编码3或50,就像我之前做的那样。现在我实际上会继续,将那个变量插入。就像Scratch让我做的那样,假设我想做一些更花哨的事情,你知道的,让我继续这样做,假设我们想从用户那里获取输入。
这个函数会有一个值,因此这也在课程网站上。让我带你走过我已经写的代码,在我文件的顶部,我包含了一些现在熟悉的头文件,在这里我包含一个原型,作为将要被调用的函数的提示,简单地说就是获取正整数。
这是一个只获取正整数的函数,在我的主函数中。注意我将使用这个,我将在第10行获取一个名为i的变量,并从用户那里获取一个正整数,然后我将把它打印出来,但现在有趣的是。我有这个额外的抽象,cs50库并不包含一个名为positive.c的。
它确实有一个返回值,之前我使用void这个词来表示没有输入或没有输出,在这里我仍然使用它来表示。没有输入,它总是获取一个正整数,但我在这个自定义函数的左侧说int。确实有输出,输出将是什么呢,注意这里在第。
17我给自己一个变量,称为n,然后我今天有c的一个最终新特性,这个循环。这个循环不是while循环,而是dowhile循环,while循环首先在检查条件之前。因此注意,这里我将执行以下操作。使用这个提示正整数调用getint,然后存储返回值。
赋值给变量n,然后在这里注意,我说,当n小于1时,这种语法有点奇怪,但如果从数学上讲,我想让用户给我一个正整数,这实际上与我想让用户给我一个整数是同一回事。只需确保它不小于1,因为如果小于。
一个是零或负一或负二,这显然不是代码中的。这时唯一的新东西是,存在这个东西,dowhile,然后。检查一个条件,while循环首先检查条件,然后做一些事情,而这正是我想要的,我想做到这一点,从整数中获取一个正数。
然后当n小于1时,如果人类输入零或,想要重复。一直这样做,所以它在语法上是合理的。在n小于1时执行以下操作,最后唯一的新行是返回。n,这是一种程序实际上可以返回某个值的方式,它可以把你交还一个。
通过在屏幕上打印它,或者从猫的嘴里听到声音来返回值。它以某种意义返回,返回的是n,这是一个匹配该函数输出的整数。为什么这有用呢?好吧,让我们向上滚动,现在假设getpositive函数存在,现在注意,我们如何在main中使用它,我调用get。
正数整数,右侧类型为整数,我将返回值存储在左侧名为i的变量中,然后我打印i,刚刚开始。现在我已经实现了getpositive,它在某种程度上已经被抛到脑后。我知道可以做到这一点,我可以抽象出,只是一些奇怪的东西。
可能你对这个有直觉,或者如果没有,也许只是稍微有点。为什么这可能无法正常工作,abigail,为什么这不起作用,我们,嗯。我不确定,我想我们已经,赋值过了,它是一个整数,思路很好。在这种情况下不是正确答案,出于更微妙的原因,这不会起作用。
让我去提议一下。
解决一些更图形化的东西,你当然会记得《超级马里奥兄弟》是我们上周提到的第一个问题集,在这个游戏中有很多视觉效果。例如,这里有早期的天空和这些问号,如果你跳起来。
在它们下面给你金币,对此让我问,嗯,我怎么能在C语言中写一个程序,打印出问号呢?好吧,让我去做这个,让我去写一个叫做mario.c的程序,接着让我去包含标准库,standardio.h。在一个叫mario.c的文件里,给自己一个main函数,mainvoid,我要把这个保持简单。
printf1234反斜杠n分号,这远没有老式游戏那么酷或好看,但如果我运行makemario,然后执行dotslashmario,瞧,我得到了一个非常糟糕的近似,或者说真正的ASCII艺术,但我可以做得更好,回想一下,现在我们有能力使用循环,所以我可以说for。
inti等于0,i小于4,输出一个问号,然后输出一个换行符。只是为了在最后一刻移动光标,我不想为每个问号都这样做,因为那样最后的结果是相同的,但在某种意义上稍微好一点,因为现在更动态,现在使用循环而不是硬编码的值。
但让我现在聪明一点,做点别的。让我借用那个正整数示例的逻辑,做一些类似的事情,叫做n,表示一个数字。让我像之前那样,先从用户那里获取一个整数,并询问用户我想打印的砖块的宽度,所以它不总是四,也许是。
可变数量的砖块。然后让我继续,当n小于1时,与我之前的逻辑相同。你知道吗,一旦我有了一个值n,让我在这里加上一个注释,从用户那里获取正整数。也就是说,所有这些单行注释每隔几行就可以注释一下,如果有必要。
逻辑上这样做是有意义的,让我继续打印出那么多的int。i初始化为0,i小于n时,i加1,现在我可以打印出一个问号而不换行,然后在程序的最后,我可以打印出一个换行符,分号。让我继续。
让我给自己一个宽度50,等等,所以现在我们有了一个更加动态的程序。但你知道吗,让我们进一步增强一下。稍后在超级马里奥兄弟中,有很多这种地下世界,你可以看到这些砖块的网格,注意到这一点。
这看起来像是多行砖块,横向和纵向都有,所以有宽度和高度。我该如何打印出,可能是三乘三的砖块呢?让我实际上进入我的程序,去掉之前所有的问号内容,考虑一下如何打印出一个。
三乘三的网格。那么,我的代码可能是,三更多的哎呀,也许再加三,然后再三。但是当然,这样的复制粘贴从长远来看是行不通的,但没关系。让我做一下./mario,好吧,我有点像是一个网格。看起来像这个东西,虽然不完全一样,但至少是对的想法。
但这不一定是最好的方法。我真的想去,结果发现。使用C语言,我们实际上也可以这样表达,让我继续这样做。让我打印出以下内容,不只是一次又一次的井号,让我继续为int做这件事。
我设置i为0,i小于3,i加1,我还不知道我接下来要做什么。我要做三次,那么我想要三行,每行上我想要三条井号。所以你知道,你可以嵌套循环,让我设置intj为0,j小于3,j加1,我还不知道我将要做什么,但。
我确实知道我要做这件事三次,你也许可以看到这是什么方向,三件事,三块砖,只要在这个内部循环里,可以说这个嵌套循环中,我打印其中一条井号,然后换行。在这里,为了明确,即使它已经开启,我们也知道这就是你所使用的语法类型。
进行有限次数的操作,三次,这就是相同的语法,但我使用了不同的变量名,以便跟踪两个不同的值,基本上是行和列,然后每次我只打印一个砖块,但在我打印完一整行后,换行。因此让我试试这个,让我去ah**d并在我的代码中制作马里奥。
./mario,瞧,现在我使用嵌套循环像这样打印砖块,如果我想做一个10乘10,我所需要做的就是在一个地方更改它,或者如果我真的想变得花哨,我可以再次使用getint。我可以从用户那里获取宽度和高度,并完全做到这一点。
动态地,但如果我做10乘10,比如说,我至少可以看到一个更大的网格。所以如果你想知道像超级马里奥兄弟,或者坦率地说,现在在PC、控制台或手机上任何游戏是如何制作的,就是这种地图生成方式。也许在过去,它们是硬编码的,也许是使用代码生成的,你可以完全想象得到。
像这样,因此最终你的游戏甚至你的世界都是部分动态生成的。我们已经有了基本构件,但并没有真正讨论计算机能做的限制,而在最后的时刻,我们想为计算机不太擅长的事情设定一个舞台,实际上存在的问题,几乎是潜在的。
今天我们所做的一切,但我已经超出,这幅图是典型计算机内存或RAM(随机存取内存)的图片,它只是你手机、桌面或笔记本电脑中的一个。在程序运行时,它存储着程序。所以在Mac或PC上,如果你双击一个程序。
这最终存储在一个文件中,执行./mario并按回车。在这样的程序中,你正在使用cs50的IDE的RAM,但相同的想法也适用于云中的其他地方。然而,事实证明,如果你只有有限的内存,就像这样,你能做到的事情是有限的,你无法解决世界上所有的问题。
我所指的是什么意思呢?让我继续创建另一个程序,叫做imprecision.c。我们将看到我为什么这样命名,在include,standardio.h中,再次在主函数中只是为了给自己一些设置,然后让我继续非常合理地非常简单地询问用户一个叫x的浮点型变量。
我还需要包含定义,现在让我重新编译,现在它工作了。让我继续运行imprecision,十分之一,好吧,所以1/10,看来根据我非常简单的计算器,但我现在有点好奇,它看来printf的功能比我们看到的还要强大,实际上你可以打印出超过单个数字。
假设我想输出的不仅仅是六个有效数字,而是十个。语法有点奇怪,但不再是说percentf,点,然后是你想要看到的数字位数,最后是f,所以让我继续编译这个,makeimprecision。现在让我执行点斜杠imprecision,和十分之一,哈哈,这有点奇怪。
我不记得知道十分之一的末尾有一个1.5。好吧,让我们更深入一点,打印出50个小数点,真的深入挖掘一下这里发生了什么,让我重新编译我的代码,并重新运行点斜杠imprecision,进行1除以10,哦我的天,就像我。
我非常确定在小学时,当我们都学会1除以10时,老师告诉我们是1/10或0.1。他们从未提到它实际上是0.1000001490116,等等。那么发生了什么呢?就像我们今天看到的所有语法一样复杂。我的天,电脑连十分之一都无法正确计算,所以我们正遇到问题。
面对一个基本限制,如果计算机有那么多的RAM,那么多的硬件,那么多的位,那很合理,如果你只使用有限数量的位,例如32或64,是的,你可以数得相当高或相当精确,但你无法无限制地高或无限制地精确地数。
在某个时候,你必须开始通过使用32来逼近值,表示浮点数,世界上有无限数量的浮点实数。不幸的是,如果你有有限的位数,你必须开始削减一些角落,而这正是计算机正在做的。
它尽可能接近地表示十分之一,这就是你随后在大多数。问题中看到的,这可能不是大问题,但如果。你在进行数学、处理金融、货币价值或军事操作时,那可能真的是一个大问题,其中许多小数字,呃,科学上确实开始累加。
确实有许多例子发生,还有另一个问题,计算机不仅仅面临这种浮点精度。即使是整数也有它们的局限性,回想一下,整数当然可以在十进制或,二进制中表示,如果我们有三个灯泡。
或者三位比特,让我们考虑一下如何,使用二进制进行计数,零零零是我们上周开始的地方,零零一,零一零零一一一零零,现在。这从上周回想起来是我们知道的七,在十进制中,只需。进位而已,但如果我只有,位数,进位,就消失了,因此存在这个问题。
也是整数,有人早些时候提到过,当我们。尝试做20亿或当你。尝试做20亿加20亿时,它无法适应结果,这是因为整数。如果它们只有32位或在C中是长整型,如果它只有64位,那些是大数字,但并不是,无限大,我们人类已经,屡屡绊倒在这上面。
你可能记得听说过,甚至经历过Y2K问题。世界上许多东西崩溃了,或者担心会崩溃,做出了合理但不够长远的决策,仅用两个数字来存储年份,因此1995年。将被表示为95,96、97、98、99,然后,到了1999年到2000年的年末。
任何仍在使用两个数字的计算机程序或系统,当然会在第三位数字。可用时加一,它就消失了,整个世界将年份2000混淆,认为,作为前缀,如果你能相信,我们人类即将在2038年再次做到这一点,这离现在并不远。
在2038年1月19日,达到第40亿秒,所以除非。我们所有人都升级我们的Mac和PC,还有更糟糕的嵌入式系统和卫星,以及任何硬件。嵌入在我们现在使用的各种设备中,否则我们即将再次遇到这个问题。突然之间,这将像是1月1日的问题。
我们将更深入地探讨这个新语言,并回顾一下上周的内容,以便你更好地理解C语言的一些特性,以及你所采取的一些步骤来让你的代码工作。所以我们将逐层剥离上周的抽象,以便你更好地理解真正的。
了解计算机内部正在发生的事情,所以当然,上周我们以C语言中可能最经典的程序开始。你几乎可以用任何语言编写的最经典的程序是helloworld。但请记住,在我们实际上运行这个程序之前,我们必须进行转换。
它将源代码转换为计算机自身所理解的语言,上周我们定义为二进制的零和一,亦称为机器语言。因此,我们必须以某种方式从这个源代码转化为更像这样机器代码的形式,也就是计算机实际理解的零和一。
你可能还记得我们命令叫做hello,我们能不能创建一个叫hello的程序,而make则有点高级,它假设如果你想创建一个叫hello的程序,它会自动查找一个叫hello.c的文件。这一切都会自动为你完成,最终的结果当然是一个名为hello的额外文件。
所以你可以执行dotslashhello然后继续前进。但结果显示,make实际上是在为我们自动化一组更具体的步骤,我们现在将更仔细地看看。因此,屏幕上显示的正是我们上周写的代码,它简单地说helloworld,hello。
或者制作马里奥,或者制作现金,或者处理最近可能遇到的任何问题,你会看到一些神秘的输出信息。但即使一切正常,你也会看到这段白色文本,表明一切都很好。而上周我们基本上忽略了像dotslashhello这样的内容,但今天我们来实际。
更好地理解我们一直视而不见的内容,以便让你了解屏幕上发生的事情。所以再次,如果我在这里执行ls,我们不仅会看到hello.c,还会看到我通过make实际创建的可执行程序hello,但看看这个输出,有一些。
这里提到了一个叫做clang的东西,还有很多其他计算机术语,包含了一些前面带有破折号的神秘短语。结果显示,clang其实是在自动化执行一个命令,更具体地说,是一个叫clang的编译器。
将源代码转换为机器代码,我们实际上一直在使用clang。但请注意,clang需要更多的专业知识,你必须对发生的事情有更多的了解才能使用它。所以让我去掉名为hello的程序,我将使用time,通过输入y来确认。
我现在再次输入ls,你好。c是唯一剩下的文件,暂时让我去掉使用make的能力,现在我们直接使用clang,clang是另一个程序。它安装在cs50ide中,是一个非常流行的编译器,你也可以下载到自己的Mac和PC上,不过运行它稍微有点不同。
我将去输入clang,然后是我想要编译的文件的名称,hello.c就是这个文件。接下来我将按回车,现在似乎什么都没有发生,但实际上。当看起来没有发生坏事时,这通常意味着发生了一些好事,你的程序编译成功了,但奇怪的是,如果我现在输入ls。
你没有看到程序hello,你看到的是这个奇怪的文件名a.out,实际上这是一个历史遗留问题,很多年前人们编写代码时,每个程序被赋予的默认文件名是那个,这有点愚蠢,因为它并没有说明其功能。因此,像clang这样的程序可以在命令行中进行配置。
再次提到闪烁的提示符,在那里你可以输入命令,所以确实我将去删除这个文件,rm,空格,点,输出,然后确认输入y。现在我回到了最开始的状态,只有hello.c,接下来让我去做一些不同的事情,我将输入clang-ohello,然后是单词hello。
c,我在这里做的是提供我们正在进行的参数。因此这些命令像make和rm,有时可以直接通过输入单词并按回车来运行。但我们经常看到它们在某种意义上需要输入,你输入makehello,你输入rmhello。在这些情况下,第二个单词hello可以算作命令的输入。
否则现在称为命令行参数,它是命令的输入,所以在这里我们有更多的命令行参数,我们有单词clang,这是我们即将运行的编译器。输出的符号,请输出以下单词,hello.c。所以长话短说,这个命令现在虽然更冗长,但它是在说运行clang,输出一个名为hello.c的文件。
最终,clang是如何帮助我编译我的代码的,它在自动化所有这些过程。但请记住,这并不是我们上周运行或编写的唯一类型的程序,我们实际上是像这样写代码,然后用helloworld加强它,实际上涉及到使用cs50的getstring函数来提示用户输入名字。
所以让我继续做吧,让我去,把hello去掉,因为现在是旧版本,让我们去我的hello.c文件,包含cs50.h,现在给自己一个字符串,叫做name,但我们可以叫任何东西,调用函数getstring,问是什么。
你的名字?在最后加一个空格,只是为了创造一个间隙。然后在这里,不再打印helloworld,让我打印hello%s,这是一个占位符,回想一下,并输出这个人的名字,所以上周我们编译这个程序的方式就是makehello,现在没有区别,但这一周,make只是因为它有点自动化。
错误消息,让我们考虑这实际上在说什么,仍然在这里。但注意,像往常一样,我们会看到熟悉的,因此,未定义的引用到getstring,我尚未知道未定义的引用是什么,我不知道链接命令是什么,但我至少意识到getstring出现了一些问题。
而且这样做是有原因的,事实证明,当使用一个库,无论是cs50的库还是其他库,有时并不是在你自己代码的顶部有文件,而是计算机在哪里找到零和一的功能,比如getstring,所以像cs50.h的头文件。只是告诉编译器这个函数存在,但还有第二种机制。
到目前为止,一直为我们自动化,这告诉计算机在哪里找到实际的零和一的文件,因此,话虽如此,我需要实际添加另一个命令行参数到这个命令中,而不是做clang-ohellohello.c,我还将附加并承认地做-。
一种艺术术语,我们稍后将更详细地看到它的含义。但这个额外的最终命令行参数告诉clang,你已经知道像getstring这样的函数存在,-lcs50意味着在编译hello.c时,确保将cs50库中的所有机器代码也包含到你的程序中,简而言之,它是。
当你使用某些库时,你必须做某些事情,所以现在当我按下回车时,一切似乎都很正常。我输入ls,看到hello,哇,我可以执行./hello,输入我的名字大卫。哇,hello大卫,那么我们为什么不在上周做所有这些,坦白说。我们没有取得任何实质性的进展,所有我们做的只是揭示了底层的运作情况,但。
我声称坦白地说,输入所有这些冗长的命令行参数来编译你的代码会迅速变得乏味,因此计算机,具体来说,最终通过make发生的事情是,所有这些,输入的makehello上周及以后。好吧,命令,其中一些我们甚至没有提到,最开始,我识别hello.c。
这里我识别-lcs50,但请注意还有许多其他东西,不仅是-ohello,还有-lm,它指的是一个数学库,-lcrypt,它指的是一个加密库。简而言之,我们的工作人员已经预配置了make,确保当你编译。
无需担心所有后续步骤,你当然可以通过直接使用clang编译你的代码,或者你可以回到makehello。但我们手动运行makehello是有原因的,这通常会变得乏味,因此。确实我们在这里所做的是,编译我们的代码,而编译意味着。
从源代码到机器代码,但今天,我们揭示了在底层有更多的确在发生。确实,链接以及其他几个步骤也在进行,所以,事实证明,当你从源代码编译到机器代码时,实际上还有一些其他步骤最终涉及到,当我们。
当我们说编译时,我们实际上是指这四个步骤,我们不会过多探讨这些低级细节,但看看。当你从源代码开始,最终试图生成机器代码时,了解发生了什么,或许是有启发性的。
你编译你的代码,第一步是处理你自己的源代码,它看起来大概像这样。然后它从上到下,从左到右预处理你的代码,预处理你的代码本质上意味着它寻找任何以井号开头的行,所以#includecs50.h。包括
它注意到哦,这里有一行#include,让我去啊,复制那个文件的内容,cs50.h。进入你的代码,类似地,当我遇到#include
预处理器显然是自动执行的,替换那些行,替换为这些文件的实际内容。这些东西是什么呢,呃,它是为你使用的代码定义所有函数,否则计算机就不知道该怎么做,确切地说,它是在我的代码中定义所有函数,所以我们遇到了这个问题。
上周有个令人烦恼的错误,我调用了,我想是getpositiveint,记得在我文件的底部,编译器有点傻,它没有意识到它存在,因为它是在我文件的底部实现的。所以为了杰克的观点,通过在最上面放置这个函数的提及,如果你愿意的话。
这就像是在训练编译器,让它提前知道,我不知道它是怎么实现的,但我知道,getstring会存在,我不知道,printf会存在,所以这些头文件,实际上包含了所有的原型,也就是所有函数在库中存在的所有提示。
所以预处理器只是在省去我们复制粘贴所有这些原型的麻烦,如果你愿意的话,所有这些提示,我们自己完成。那么在那一步之后会发生什么呢?接下来会发生什么呢,可能在那些文件中还有其他内容,仅在这里是原型,所以现在编译实际上有了更准确的定义,我们今天要定义的C。
这里是另一种源代码,这可能是我们见过的最难懂的东西,这不是你需要理解的代码,但屏幕上显示的是所谓的汇编代码,长话短说,世界上有很多种不同类型的,呃,CPU。
被称为汇编代码,几十年前,人类编写了这些东西,人类编写了汇编代码。但如今我们有C,如今我们还有像Python这样的语言,更多的用户友好,尽管在过去的一周里你可能没有感觉到。汇编代码更接近计算机本身所理解的内容。
但还有一步,那就是组装步骤,再次强调,所有这一切发生在你简单地运行make时,而这个命令clang组装你的代码,意味着将这个汇编代码转化为零和一,所以你编写源代码,编译器将其组装成汇编代码,然后将其编译成汇编代码。
然后它将其组装成零和一,但实际上还有最后一步。因为你编写的代码已经转换成了零和一,但它仍然需要与cs50编写的零和一以及多年前C语言设计者编写的零和一进行链接。
在它们的情况下,printf函数也是如此,就像这样,这不仅包括原型。对于像getstring和printf这样的函数,最顶部的这些黄色行就是最终转换成的,呃,零和一,我们现在必须将其组合,从很久以前的,。
一大堆零和一,表示cs50.c,在cs50ide的某处还有另一个文件,表示stdio.c的零和一,所以这个最后的第四步,也就是链接,正好将我所有的零和一、所有的cs50的零和一、所有的printf的零和一全部连接成一个,庞大的整体。
我们称整个过程为编译,所以即使是四个步骤。程序员通常在说编译时,只是轻描淡写地提及细节。但确实存在多个步骤在幕后进行,而这就是,实习生clang为你做的,自动化这个过程。
从源代码到汇编代码再到机器代码,然后将其与任何可能使用的库链接在一起,所以不再把发生的事情视为理所当然。希望这能让你更清楚,当你编译自己的代码时,实际上发生了什么。让我在这里停一下,因为这实在太复杂了。
看看关于预处理、编译、汇编或链接的任何问题,别名编译。我们不会停留在这个低层次,我们现在会倾向于抽象出来。是的,这些步骤是存在的,但真正重要的是整个过程,顶部的步骤,信息是否包含在ide中,或者我们那些文件存在哪里。
我想知道的是这些信息来自哪里。是的,真的很好奇这些文件从何而来,所以当你使用cs50ide,或者坦率地说,如果你在自己的Mac或PC上,且已经预装了编译器,就像我们在cs50ide中一样。
你得到的是一大堆.h文件,可能还有一大堆.c文件或其编译版本,存放在系统的某个地方。所以是的,当你下载并安装编译器时,你获得了所有这些库,我们预装了一个额外的库,称为cs50s库。
h文件及其自己的机器代码也在,所以所有这些文件都在cs50ide中,或者如果你在本地工作,也在你的Mac或PC上,而编译器clang则知道如何找到它,因为安装自己编译器的一个步骤是确保它配置为知道。
发音错误,基本上,呃,所以每当我们在编译hello时,比如说,编译器是否也在编译cs50,还是cs50已经存在于某个机器代码中。是的,这也是一个非常好的问题,所以我有点避开了索非亚问题的这部分,因为从技术上讲,可能cs50.c并没有安装在系统中。
预处理、编译和汇编,第三方代码你只对自己的代码执行这些步骤,然后链接,嗯,所有事情都是提前完成的。是的,当我们像替换头文件时,是否只用被使用的原型,或者说所有原型技术上都在呢?
像替代品是的,所以我有点,我,等等,在那些文件里还有很多其他东西,你得到的是这些文件的全部内容,即使你需要的只有这部分。
原型,但这就是为什么我提到技术上可能会有那么多东西在里面,可能没有那么多。
只是一个标准的stdio.h文件,里面包含所有内容,可能有一些较小的文件神奇地。
包括在内,不过是的,确实有很多行代码在那些文件里。但没关系,一旦你的计算机,你的编译器只会使用它实际上关心的行,过去一周无疑在某些方面有点令人沮丧,因为你可能遇到了一些问题。
生命形式,而在代码的背景下,这一点更为真实,正如我们前两周提到的,精确度和正确性都很重要,有时很难同时达到这两个目标。所以现在让我们考虑一下,如何让你更有能力调试自己的代码,也就是发现你自己代码中的问题,而这个词实际上有一些词源。
这不一定是第一个错误,但在这里的图片,来自著名计算机科学家格蕾丝·霍普的研究笔记,她发现哈佛马克二号计算机有一些问题,这是一台非常著名的计算机,现在它实际上位于新工程科学中心,计算机出现了问题。
确实,当工程师们查看这个大型主机计算机时,里面实际上有一个错误,图片在这里,贴在格蕾丝·霍普的笔记本上,所以这不是个错误。但这是一个众所周知的实际计算机中的错误的例子。如今我们更倾向于称之为程序中的“错误”,我们上周确实给你提供了一些工具。
对于故障排除,help50可以帮助你更好地理解一些晦涩的错误信息,这只是因为工作人员编写了这个程序来分析你遇到的问题,我们试图将其翻译为更易于理解的语言。我们看到一个叫做style50的工具,它帮助你解决你的正确性问题。
或空格字符,这样对于人类来说更易读。然后检查50,当然是工作人员编写的,这样我们可以立即反馈你的代码是否正确,根据问题集或实验室规范,但还有一些其他工具你今天应该具备。
说实话,这是一种通用的调试工具,叫做c语言中的printf。因此printf当然就是这个函数,它将内容打印到屏幕上,但这本身就是一个强大的工具,通过它你可以追踪代码中的问题,即使在几周后我们离开c语言。
这是一个有问题的程序,我甚至将这个文件命名为buggy0.c,在这个文件的顶部,我将继续包含stdio.h,这个不需要cs50库。然后我将做intmainvoid,我们上周见过,并将在今天详细解释。然后我将给自己打印出,哦,我不知道,比如10个哈希在。
屏幕上我想打印出一个垂直列,像是超级马里奥兄弟中的一个截图,不是金字塔,只是一列哈希,总共10个,因此我将做一些类似于inti等于0,因为我觉得我在课上学到通常应该从0开始计数。
然后我将有我的条件在这个for循环中,我想做10次,所以我将其设为小于或等于10,然后我将继续。我的递增,可以简单地表示为i加1,然后在这个循环内部,我将继续打印出一个单一的哈希,后面跟着。
新的一行,我将保存程序,编译它,意思是你不必手动使用clang,以这种方式更简单,命令,buggyzero,make会为你处理调用clang的过程,我将继续运行,似乎编译成功,所以不需要帮助,事实上如果我运行。
我想,好吧,这真是个愚蠢的错误,可能对你们中的一些人来说显而易见,但对其他一些人来说可能更微妙。但你从哪里开始呢?假设我运行check50,check50会说不,你打印出了11个哈希而不是10个,但我觉得我的代码看起来是对的。
乍一看,我该如何调试或解决这个问题呢。再次强调,printf是你的朋友,如果你想要了解更多信息,临时打印更多信息到屏幕上,不是我们希望在最终版本中看到的,也不是你的tf想要看到的,但你作为程序员可以暂时看到。
我正在插入变量i的值,我现在要保存我的代码,然后用makebuggyzero重新编译它,我现在要重新运行它,让我去增加我的输出的大小,然后我将去运行dot,slashbuggyzero。输入好吧,现在我不仅可以看到我的输出,还有一些诊断输出,如果你愿意的话。
调试输出更是更加严谨地告诉我,i现在是零,i现在是一,i现在是二。点点点i现在是九,i是十,但我不喜欢的是,如果我从0开始,并打印一个哈希,然后我达到10,并打印另一个哈希,那么显然这就是我的问题,所以这可能并没有比之前更明显。
查看代码本身,但通过使用printf,你可以更清楚地看到。所以如果现在我看到,好吧,如果我从0开始,我必须上升到10。我可以更改我的代码,使其从1到10,但再次,程序员的惯例是从0到10,所以我觉得我现在很好,在这个,makebuggy0。
我现在要去删除这个作为临时输出的内容。但是,如果你对为什么你的代码编译但运行不正常没有完全理解,并且想更好地看到计算机在清晰地看到的内容,使用printf来告诉自己某个变量或变量的值是什么。
在你的代码中任何你想要看到更多细节的地方。好吧,让我暂停一下,看看有没有关于这个使用printf的技术来开始调试你的代码以及查看变量值的问题。没有,好吧,那让我建议一个更强大的工具。
一个称为gdb的标准工具,GNU调试器,这是一个标准工具,很多不同的计算机系统使用它,提供一种比单纯使用printf更复杂的调试代码的能力。所以让我们继续,回到这个程序的有错误版本,记得让我从0开始。
但坦率地说,我们的程序越大,变得越复杂。它们需要在屏幕上有更多输出,如果你输出了不该出现的内容,情况很快就会变得混乱。想想马里奥。马里奥的金字塔就是这种快速生成的金字塔,如果你把那个金字塔和其他东西混在一起。
逐步调试器让你运行程序,但速度慢得多,逐步进行,这样你可以看到发生了什么。所以我现在将运行debug50,点斜杠hello,不,抱歉,debug50点斜杠buggyzero,所以我先写debug50,一个空格,然后点斜杠和程序的名称,这个程序已经编译好了。
我想调试,所以我会继续按Enter,注意到哦,它很聪明,注意到我更改了代码,我刚才把它恢复到了有错误的版本,所以让我修复这个,让有错误的版本为零。好吧,现在没有错误。让我再运行debug50,如果你还没有注意到这一点。
我需要提前告诉计算机我想在哪一行断点,以逐步执行,所以我将去文件的侧面,如它所示的那样。第一个有趣的文件,第一个有趣的行就是这里的第六行,所以我在所谓的“水沟”里点击了左侧的第六行,自动在这里放置了一个红点,像个停车标志。
现在最后一次我将继续并运行调试器,注意,这个新的面板在右侧打开,起初看起来有点神秘,但让我们考虑一下屏幕上发生了什么变化。
开始逐行调试我的代码,所以让我继续并放大。让我继续并点击“逐步执行”,突出显示移动到下一行,但如果我再次放大,i的值没有改变。现在让我继续并再次执行,注意到黄色突出显示再次返回,这很合理。
因为我在一个循环中,所以它应该来回反复。但在循环中,每次回到循环的开头时,请记住你的递增是通过i++来实现的。所以现在请在右上角仔细观察。
我可以在这个图形界面的右侧直观地看到i的值。如果我开始点击得更快一点,注意到在循环执行时,i的值会不断更新。你知道吗,我打赌,即使我们从0开始,如果我多次这样做,我会看到最终值是10。
我没有获得任何新信息,但注意我获得了未受干扰的信息,我没有混乱且马虎地在屏幕上打印所有这些printf语句,我只是更有条理地观察我的变量状态,在右上方。
好的,让我在这里暂停一下,看看关于这个调试器有没有问题。再次提醒,你编译代码,运行debug50在你的代码上,但仅在设置一个所谓的断点之后,你提前决定在哪里暂停代码的执行。尽管在这里我几乎是在程序的开头做的,但对于较大的程序,能够在代码的中间暂停是非常方便的。
嗯,关于调试器的内容,嗯,stepover和stepinto、stepout之间的区别是什么,真是个好问题,让我稍后再回来讨论。
我们将做另一个例子,其中stepinto和stepout实际上很重要。但在此之前,有没有其他关于debug50的问题,在我们揭示stepinto和stepover为我们做了什么之前,好的,那么让我们来看一下彼得的,现在,退出调试器,老实说,我没有看到明显的退出方式。
此时调试器,几乎在你失去程序控制的任何时候。因为调试器正在运行,而你对它失去了兴趣,或者也许上周你写的程序有一个无限循环,按ctrlc将中断该程序。但现在我们快速写另一个。
这个程序这次有一个第二个函数,我们今天将看到调试器的另一个特性,我现在称之为buggy1.c。它将故意有缺陷,但我首先要继续,包含cs50.h这次。我还将包含standardio.h,我将写intmainvoid。
你可以通过将你编写的自定义函数的输出,所谓的返回值放在这一行的第一部分来指定函数的输出,然后你可以放int,在括号中放输入,你可以字面上写出“void”这个词,这是一个专业术语,意思就是这里什么都不放。我要继续进行。
现在实现getnegativeint,坦白说,我认为这会与上周非常相似,但我的记忆有点模糊,所以再次,它会故意有缺陷。首先我将设置n等于getint,然后我会明确地询问用户。
负整数后面跟着一个空格,然后我将继续这样做,直到n小于零。然后在最后一行我将返回,n。所以我再次声称这个函数会从用户那里获取一个负数,并且会不断重复,直到用户合作。然而,这里有一个错误,还有几个。
我唯一会声称复制粘贴是可接受并且鼓励的情况是,我将复制那个函数的第一行,正如JavaScript提出的,我将把它粘贴到文件的最上面,从而给自己一个提示,通常称之为原型。所以我甚至会标记它,以提醒自己它在那里是为什么。
这里我要去d并从用户那里获取负整数,写下。所以我现在在文件的最上面有这个原型,我认为这确实会消除这个错误。让我再做一次makebuggyone,现在我看到,点斜杠buggyone让我去d并输入一个负整数负一。
嗯,负二负三,我觉得这个函数应该会满意。这显然不是,所以我需要使用控制C,退出我的程序,因为否则它可能会永远运行,现在我要使用调试50。但调试50现在对彼得之前的问题变得非常有趣。
因为现在我有东西可以逐步调试,主函数,还有另一个函数叫做。getnegative,所以让我们看看,现在发生什么,让我去d并在像第10行的第一个有趣的代码行上设置一个断点,这有趣仅仅是因为,其他的都是模板代码,在这个时候你只需要这样做。
执行当然是主要的,因为我在第10行设置了一个断点,这显然是在main内部。就像第10行和第11行,坦率地说,它们看起来相当正确,对吧?在这一点上,很难搞砸第10行和第11行,除了语法方面,因为我得到了一个负的整数,我把它存储在i中。
然后我在这两行上打印出i的值,但如果相反,我对那个bug感到好奇。从逻辑上说,它肯定在里面,写道,注意这次而不是点击步过。让我去啊**d并点击进入,这也是彼得提到的按钮之一。当我点击步骤,兔子洞,调试50跳入了函数。
栈就像自助餐厅的托盘堆,最底层的第一个托盘就像main。自助餐厅的堆中第二个托盘现在是get负整数,而这很酷的是,注意到现在我可以看到我的局部变量n。这确实是我使用的变量,因此我不再看到i,而是看到n,因为我进入了get。
负整数函数,一次又一次,在输入一个数字后,让我输入负一。现在注意到右上角的n等于负一,我现在要继续点击步过,我想我会停在第22行。如果人类输入了一个负整数,比如负一,显然这是负数,让我们继续到第22行,步过。
它似乎确实不断回到do循环,整数。因此,我的逻辑应该是,如果n是负一,但我的循环仍然在运行,那么你逻辑上的结论应该是什么呢?如果n是负一,而这在定义上是负整数,这可能是你的诊断结论,如果调试器。
本质上,这向你透露了这个提示,n是负一,但循环仍在进行,奥马尔。嗯,条件可能是错误的,也许某种布尔逻辑有缺陷。显然,要么条件错误,要么我的布尔逻辑有问题,而布尔逻辑只是指真或假,所以在某处。
我说的是true而不是false,或者我说的是false而不是true。坦率地说,唯一必须在21行的地方,我一再强调。所以即使你还不太确定怎么修复它,仅仅通过推理你应该意识到,负1在变量中,但这还不够。
循环仍然在继续,我一定是搞砸了循环,确实让我现在指出,第21行确实是bug的源头。因此我们已经将其孤立出来,在23行中,我们至少找到了我知道的那一行。解决方案是什么,如何修复逻辑现在。
多亏了调试器的引导,我来到了这条路,我该如何修复,第二十一行呢?你会提议的修复是什么?是的,雅各布,你需要将它从“当n小于零”改为“当n大于零”,我想稍微澄清一下,我想在这里包括0,因为0不是负数,如果,我想要一个负的n,我认为我可能想说的是。
可以理解的是,我只是颠倒了逻辑,没什么大不了,我在考虑负数,而我用了小于,但,修复很简单,重点是调试器让你到了这一点。现在那些之前编程的人,可能在你面前看到了bug,可能有时候会,搞清楚bug是什么。
在23行的代码中,这一定是其中之一,但随着我们的程序变得,越来越复杂,我们开始编写更多行代码,调试50,以及调试器通常会是你的。朋友,我意识到这说起来容易,但实际上当你第一次使用调试器时,你会觉得啊,我只是,想用printf。
科学圈内的计算机科学家,他们的桌上,关键是有时在试图理解,代码中出了什么问题时,交流是有帮助的。我们会和我们的同事,或我们项目上的合作伙伴交谈。仅仅听到自己表达出代码应该做什么,往往能带来很大帮助。
典型的“灵光乍现”,我明白了,仅仅因为你听到自己说话。逻辑不通时,你本来想表达一些,实际的逻辑,和我们正在合作的同事或。伙伴,或朋友,我们并不。常有家人或朋友想听我们讲代码。
所有事情中,一个绝妙的代理,实际上可以是一个橡皮鸭。因此在更健康的时代,我们会,给你们每人一只橡皮鸭。我们在舞台上带来了一个大号的,供大家共享,如果你注意到在一些广角。镜头中,有一只鸭子一直在看着这一切。
每当我出错时,我实际上有一个人可以,和我对话,虽然在这种情况下是非语言的。但我们不能强调,在printf的基础上,以及。更复杂的调试50,与你的代码问题进行讨论。是非常有价值的事情,如果,你的朋友或家人愿意,写下来。
如果你正在解决某个bug,太好了,但如果没有,就和你房间里的。毛绒玩具谈谈,和一只真正的橡皮鸭谈谈,这真是一个美妙的。习惯,因为,听到自己表达出,认为合理的想法。好吧,那么,话说到这里,已经说了很多,让我们继续。
然后休息五分钟,让每个人都喘口气,当我们。回来时,我们将看看现在的一些,更强大的C语言特性。现在我们可以相信,我们可以解决,所有这些新工具的问题。好的,我们回来了,让我们来看看,计算机底层的,机制。
这些设备再复杂,它们似乎再强大,实际上它们在其。工作上是相对简单的。让我们通过最后一部分揭示尽可能多的,C语言支持不同的数据类型,回顾一下。我们发现,结果是,这些数据类型。被定义在典型计算机系统上,占据固定的量。
空间,这取决于计算机,是否是Mac或PC,旧或新。通常这些数据类型使用的空间,但在CS50IDE中。所有这些类型的大小如下,布尔值,真或假只需一个字节。现在,这实际上有点浪费,因为一个字节是八位。
哇,对于布尔值你只需要,一位,不能轻松在C语言中以单个位级进行操作。因此,我们通常,花费一个完整的字节来表示布尔值。字符也是一个字节,这听起来可能熟悉,因为上周我们谈到ascii时,提出了总数。
可能的字符可以表示,字符是256,因为八位和二的。八次方,所以一个,不管怎样,还有其他的数据。类型,浮点数是一个带有小数点的实数,使用四个字节。双精度数也是一个带小数点的实数,但它使用八个字节。
这给你提供了更高的精度,你可以有更多的有效数字,我们使用了一些。通常来说,四个字节的长整型是双倍的,这就允许你表示更大的数字,你们中的一些人可能已经在信用卡存储整个信用卡号码时做到了这一点。
内存或RAM(随机访问存储器),它可能会稍微小一点,也可能会稍微大一点,这取决于正在运行的内容,以及文件在打开时存储的位置,因此通常情况下,如果你保存安装程序或保存文件,这些文件会保存在通常称为硬盘或硬盘驱动器的地方。
固态硬盘或CD或其他某种物理介质,而电力的好处在于能够长期存储数据,而RAM是不同的,可以说是易失性的,但它比硬盘或固态硬盘快得多,甚至比它们快得多,因为它是纯电子的,确实没有任何活动部件。
它是纯电子的,如这里所示,因此通过RAM,你可以更快地打开文件和运行程序,因为当你双击一个程序运行时,或者打开一个文件以查看或编辑时,它会临时存储在RAM中。长话短说,如果你的笔记本电脑电池,或者你的笔记本电脑电池曾经死过。
它确实按定义在你计算机的内存中运行,但有趣的是,像这样简单的图片,每个黑色矩形实际上是一个芯片,在这些芯片中存储着所有的零和一,周围是零。因此,让我们专注于并放大其中一个芯片。
现在可以推断,我不知道这根RAM棒的大小,可能是1GB,一亿字节,可能是4GB,物理上更大,由这硬件决定,所以如果我们再进一步放大,让我假设,我不知道这里有多少字节,但如果有一些字节,无论是多还是少,可以推断出我们可以简单地。
将所有这些字节编号,我们可以把这个物理设备,这个内存视为一个从上到下、从左到右的网格,每一个我在这个物理设备上叠加的方格,可能代表一个独立的字节。再次说明,实际上可能有更多,或者更少,但确实有。
我们可以把每个字节想象成一个位置,就像这是第一口,第二口,第三口,等等。那么,一个字符占用一个字节是什么意思呢?这意味着如果你的计算机内存正在运行一个程序,可能是你写的或我写的,它在某个地方使用了一个字符变量。
存储在那个变量中的值可能会存储在这块RAM的左上角,可能就在那,也可能在别处,但这只是一个物理的方格。如果你存储的是像int这样占用四个字节的东西,老实说,它可能会占用顶部的所有四个方格,或者在别处,如果你使用的是长整型。
这将占用两倍的空间,因此在计算机内存中表示一个更大的数字将需要使用构成这八个字节的所有零和一。不过现在让我们远离物理硬件,抽象一下,如果你把内存看作一个网格,从技术上讲,它就是这样的。
这不是一个二维结构,我可以同样容易地从左到右绘制所有这些字节。我可能在屏幕上只能放下更少的字节,因此我们将物理隐喻进一步拓展,想象我们计算机的内存,这些字节每个都是八位,这些位在隐喻上只是聚焦于我们计算机的。
内存开始考虑,当你双击Mac或PC上的程序,或者在CS50IDE中执行./hello或./buggy0或buggy1时,所有变量的值都会填满你计算机内存中的这些字节。因此,让我们在这里考虑一个例子。
在CS50IDE中,我将创建一个名为scores.c的程序,在scores.c中,我将包含stdio.h,然后像往常一样编写我的intmain(void),然后在这里我将保持简单,给自己一个名为score的整型变量,值为72,和上周一样。
设定第二个分数为73,然后设定第三个分数为33。然后让我去打印这三个值的平均值,通过插入一个占位符来表示浮点值。如果你将三个整数相加,然后除以三,我可能会得到一个分数或一个带有百分比的实数。
而不是percenti,因为我不想截断某人的成绩。否则如果他们有像99.9,他们不会被四舍五入到100,他们会得到99,因为截断,正如我们上周讨论的。那么我现在怎么做平均值的数学呢?其实很简单,得分1加得分2,数学。
让我再次在底部做makescores,我们不会手动使用clang,没有必要。因为运行make要简单得多,但我在这里搞错了,参数类型不对,我不太明白,但是它引起了我对percentf的注意。并且我的数学看起来像这样,printf在这里会帮到我,因为我是。
错误出在printf行上,我认为调试50在这里不会真的帮到我,因为我已经知道错误在那一行代码中。这感觉像是一个机会,可以和对象对话,或者我们也许可以想想上周遇到的错误,阿尔潘,你觉得呢?我认为这是因为好的,它是那个文件。
我告诉你这些是因为,它接收到的所有值都是整型。但是你告诉它要受到影响,是的,你把它放入了平均分中。正是得分,哦,天哪,抱歉,所以计算机仍然把它当作浮点数。结果它不知道该怎么做,实际上得分一、得分二、得分三都是整数。
整数,因此这次编译器足够聪明,可以将整数结果强制转换为浮点值,但你并没有进行任何浮点运算。如果你愿意的话。所以你知道有几种方法来修复这个,上周回想一下,我们提出你可以使用强制类型转换,你可以显式地将一个或多个值转换为浮点数,所以我可以这样做,例如。
或者我可以把所有这些转换为浮点数,或者其中一个转换为浮点数,还有很多,最简单的修复方法。就是例如除以3.0,这样我可以避免一些从一种类型到另一种类型的麻烦,确保至少参与这个运算中有一个浮点值。现在让我重新编译得分。
这次编译正常,让我做一下点,斜杠得分,瞧,我的平均分不高,59.3333。好的,那么计算机内部到底发生了什么,与浮点数无关。上周,让我们考虑这三个,三个,它们实际上存储在哪里。再考虑一下那个网格,我将从左上角开始,方便起见。
但从技术上讲,我们会在后面看到,你的计算机内存就像是这样一大堆不同的地方,但今天我们会保持简洁。第一个变量得分一,我声称它将在这里的左上角,简单起见,但存储的重要性在于,它占用了这四个。
这些框中的每一个代表一个字节,整数回忆,cs50ide是四个字节,因此我使用了四个字节的空间来表示数字72。分数二中的数字73同样是三个,也会占用四个框,但这里的底层到底发生了什么呢?
这些正方形代表一个字节,每个字节是八个比特,而比特就是零或一,真正发生的事情是这样的,这种电子存储器以正确的方式存储电,存储并以十进制方式存储这个零和一的模式,也就是33。
在十进制中,但我们不必继续思考或沉迷于此。到目前为止我们讨论的一切,现在在这张图中汇聚在一起。因为计算机只是为我们存储这些模式,而我们正在分配空间,感谢我们的编程语言,通过这样的代码,但这段代码。
虽然我的平均值确实是59.33333,如果我的测试分数是72、73和33,但我觉得这里有更好设计的机会。因此,不仅仅是正确性,不仅仅是风格,回忆一下设计是代码质量的另一种衡量标准。这更主观,且更容易在合理的人之间引发辩论。
但我并不太喜欢我在这个命名方案中所做的,实际上如果我们看看代码,我的程序除了这三行外并没有更多。我担心这个程序设计得并不好。
这三行代码可能是什么,即使你不知道解决方案,尤其是如果你从未编程过,关于这三行代码的气味是什么?这就像某种不爱的感觉。
如果你愿意说分数一有什么不好的地方,我本来想说,如果你要把它们全部加起来。在代码中,你可以添加变量,绝对可以,如果我在计算平均值,我不需要保留这三个分数,我可以只保留总和,然后用总和除以数量,我喜欢这样,关于这段代码的设计,现在分数一、分数二。
三个分数,一个分数,两个分数,三个分数,是否还有机会进行改进。我感觉每当你开始看到这种重复时,哦,我想我不会将这三个分数硬编码在一起,好的,所以不硬编码这三个分数,那你会怎么做呢?我可能会接受一个输入,或者我会。
是的,我不会写出分数本身,是的,另一个好的程序。我编写了一个程序,仅计算某个学生的平均分,实际上得到了这三个测试分数。这里,此外,称我的变量为分数一、分数二、分数三也有点懒,究竟能到哪里呢?
之后如果我想在下学期参加第四次测试,现在我必须去拿四分。如果我得到了五分,那就开始有点让人想起。
这并不是最佳实践,因此让我提议清理一下。通过另一个话题,另一种特性,编程语言中称为数组的内容。如果你在Scratch中使用某种叫做列表的东西,它在精神上与Scratch的列表非常相似,但我们没有看到C语言中的数组。数组是存储在内存中的值序列。
连续的值序列,依次相连。回到回到回到这一点,因此从这个意义上说,如果我们使用我们一直在绘制的图片的隐喻。那么这在这里又如何呢?事实证明,如果你想存储一整堆值,但它们都在那数组中。
所以这个数组将会回溯,我可以在其中放入值,而我放入这些值的方式将看起来像这样,语法上我仍然使用数字,但现在我使用一种新的表示法,与我之前使用的类似,但现在它更为普遍化。现在如果我想更新该数组中的第一个分数。
我实际上是写变量的名称,分数[0],然后给它赋值。如果我想获取第二个分数,我就用分数[1]。如果我想要第三个分数,那就是分数[2],唯一有点奇怪且需要适应的是。
我们的数组是零索引的,因此在过去的例子中提到过。这是编程中的一种约定,当涉及到数组时,从零开始计数是必要的。数组是计算机内存中连续的值序列,它们必须从零开始,否则,如果你不从零开始计数,你实际上会浪费空间。
通过忽略一个值,所以现在如果我们在屏幕上重新命名这些内容。不是叫这三个矩形分数一、分数二、分数三,而是它们都叫分数。如果你想特别提到第一个,你就用这个花括号表示法,而第三个也是如此,但注意到。
二分法当,数组说,给我三个整数,你使用的就是[3]。其中[3]是总值的数量,当你索引数组时。你去特定的位置,使用数字,但现在那些是相对位置,位置零,位置一,位置二,这就是,数量总和,第一。
第二和第三,好的,所以,图示上没有变化,只有我们的,命名发生了变化。所以让我去**d,开始改善这个程序,听取建议。嗯,之前提到过如何,我们可以,sortof,去除它的味道,让我先来。先处理这些最简单的方法,首先去掉这三个。
单独的变量,而不是给我,一个名为scores的变量,然后,再来两个,就这样。全都消失,就这样全都消失,现在如果我想要。用这三个值初始化那个数组,我说scores[0]。然后在这里我说scores[1],再在这里我说scores[2]。
所以我增加了一行代码,但,第四行,我可以在这里分配,然后用另一行代码放入。或五、六、七、八,我不。需要开始复制和粘贴所有,约定,但我认为如果我们采纳一些。之前提供的建议,我们也可以通过循环清理这个。
或者类似的,让我们这样做,让我去**d,首先给我,cs50库。这样我可以使用getint,让我们接受第一个建议。让我们开始请求分数,使用get_int,我将这样做,三次。是的,我有点,懒惰,已经有点无聊了。
所以我要复制粘贴,这在一般情况下并不乐观,当。复制粘贴时我们可能可以,做得更好,但现在我想我需要再改动一点。这里在进行数学时,我想scores[0]+scores[1],这里的问题是。逻辑仍然是一样的,但我现在,是动态地接受三个整数。
这里仍然有一种味道,明确一点,我现在可以做些什么更好。如何清理这段代码,让它,设计得更好,这里还剩下什么,nina。嗯,这段代码是仅针对,三个分数,所以你可以在一开始,询问输入想要多少分,然后就像有scores[for]循环。
这个过程从零到,嗯,n减一或者说少于n。它应该像是,替代成一行代码。是的,真的很好,事实上我们,要反复提到,这样做是,次优的,可能是正确的,但可能设计不够好,因为我做了。
字面上resort到复制粘贴,这里有种模式我,像一个循环。所以让我这样做,实际上去掉这两行代码,去上面做些,像forinti=0;i<3;现在。i++让我打开这个for循环,代码,而不是scores[0]。
这就是数组变得真正强大的地方,数组,可以去特定位置。我想用什么作为我的变量,嗯我会认为我在这里,所以现在我把代码行从三行几乎相同的三份减少到一行,里面有一个循环,它将为两个做同样的事情。
我不需要到处硬编码这些三,也许我可以做一些像这样获取分数的事情,我可以从那里直接询问人类,变量。在多个地方使用总数,因此现在我需要在第14行稍微努力一下,因为现在我不能硬编码分数0、1和2,因为如果数字。
如果少于这个我需要做更多的加法,如果超过这个我需要做更少的加法。所以我想我们引入了一个错误,但我们可以修复它。不过让我先提议一下,不要让它变得动态,先引入另一个功能。我要在这里定义今天C语言的新特性。
在这里你没看到,这其实是一种约定,当你想引起对常量的注意时,使用全大写字母,所以我将把这个改为总数,我会在这里、这里以及这里使用这个值。但我担心妮娜和我在那一部分的分数上还有一点清理工作要做。
这是两个分数,我想把一系列变化的数值相加。所以你知道我有一个主意,让我去创建一个函数,来计算一个平均值。如果我想创建自己的计算平均值的函数,我希望它返回一个浮点值。
为了避免截断任何数学运算,我将这个函数命名为average,输入参数将是数组的长度和实际数组,而这就是目前最后一段有趣的语法,结果表明,当你想将数组作为输入传递给自定义函数时,你确实使用那些方括号。
再次提醒,你不指定大小,而这种做法的好处是你的函数可以支持一个空间、两个空间、三个空间,甚至一百个空间,以这种方式更具动态性。那么我如何在这里计算平均值呢?我认为,之前建议的方法是合理的,让我做intsum等于0,因为你如何计算一堆的平均值。
数字嘛,你把它们加在一起,然后除以总数。好吧,让我们看看我可能怎么做,让我从inti等于0开始,我被作为这个自定义函数传递数组的长度,从i迭代到长度,然后每次迭代后i加一。
想要计算总和,加上数组中第i位置的内容,可以说这是简写符号,上周提到的,这个总和等于总和加上数组中第i位置的内容,一旦我完成了所有这些,我认为我可以返回总和除以数组的长度。
total将会是,这个常量在最上面,哦抱歉,我搞错了,我应该把它大写,这就是在最上面的那个常量,再次强调,这只是所有这些分数的数组,同时在自定义函数中,在自定义函数的上下文中,注意函数的输入名称。
变量的名称不需要与传递到那个函数中的变量名称匹配,所以即使在主函数中它们被称为total和scores,在我的自定义函数average的上下文中,我可以将它们称为x和y,array。我不知道这个数组是什么,但它是一个整数数组,我不知道如何处理。
它的长度是多少,但那个值,答案将会是长度,但这里仍然有一个bug。这里仍然有一个bug,如果我们暂时忽略主函数,这个bug是微妙的。有没有人看到我过去两周在代码中犯的错误?这个错误有点微妙,但目标是计算一个。
程序现在是正确的,因为我,实际上会确保,重要性远低于这些真实世界的上下文,只是在计算一些分数的平均值。我不会意外截断任何我的值,所以在这个上下文中,函数平均值只是应用了一些变量,我有一个循环,并且正在进行一些操作。
浮点运算最终需要两个输入,一个是长度,另一个是数组本身,float,所以我的输出也会被很好地定义。但有趣的是,你可以把这些函数视为抽象。我不需要担心如何计算平均值,因为我现在有这个辅助函数。
我写了一个自定义函数,可以帮助我回答这个问题。在这里注意,这个平均函数的输出将作为printf的输入。还有我添加到这里的唯一其他特性就是数组,这使我们能够创建多个变量,任意数量的变量。
还有一个常量的概念,如果我发现自己一次又一次地使用相同的数字,这个常量可以帮助我保持代码整洁,注意这一点,如果明年可能有四次测试,轰,我就完成了。一个设计良好的程序不需要你逐行阅读并修改数字。在一个地方进行更改可以让我改进这个程序,使其明年支持四次测试。
而不是仅仅三个,但更好的是,嗯,考虑一下妮娜之前的建议。那就是可能使用getint,询问用户他们实际有多少次测试,这样也能很好地工作。让我在这里暂停一下,看看是否有关于数组或常量输出的问题,嗯,是的,我有一个关于使用的疑问。
浮动和原因,比如使用一个流量使得整个输出变得浮动。为什么会这样,是个很好的问题,这涉及到一个或多个浮点值。参与了一个数学公式,它会使用这种数据类型。这个数据类型更强大,如果你愿意,而不是冒着截断的风险,所以你只需要一个。
真的很好问题,括号的短答案是否定的,我所做的方式是你必须这样做,事实上,如果我突出显示我在这里做的事情。现在它目前显示为总数,如果我去掉版本,假设我说类似三和三,呃这里的三,你不能这样做,建议。
当你创建数组时,计算机需要知道数组的大小。例外情况是,当你从一个函数传递数组到另一个函数时。你不需要告诉那个自定义函数数组有多大,因为。再说一次,你不能提前知道你的函数,存在的目的是接收任何长度。
与该东西的大小相匹配,作为旁注,对于那些以前编程的人,尤其是在Java中,不同于Java和某些其他语言,数组的长度不是内置在数组本身中。如果你不将数组的长度传递给另一个函数,就无法确定数组的大小,这点不同。
哦,呃你好,我还是有点困惑。什么时候括号里是void,我们什么时候定义结尾,因为,像我记得我们做获取负数或获取正数输入时,我对这一点并不是完全信服。
注意当我在第10行调用它时,我说getanegativein,打开括号关闭括号,括号内没有输入。因此,这个关键字void,之前我们看到过几次,上周和这周,只是在C语言中一个明确的关键字,表示这里不要放任何东西。
也就是说,我在这里做这样的事情是错误的,比如传入一个数字或传入一个提示,或者任何在那些括号内的东西。这个函数getnegative中输入void意味着它根本不接受任何输入,这没问题,忘记负数这个名字吧。
函数的说明一切都在这里,没有必要对get的行为进行参数化或自定义。你只想获取一个负整数,刚刚写的average这个函数确实有用。你不能仅仅说,给我平均值,平均什么呢?它需要输入,以便回答你的问题,和数字的长度,这样你就可以。
数组和内存实际上相交,创造出一些非常熟悉的东西,假设我们。我们进一步简化,不再使用整数,暂时只用一个字符,编写一个程序,只创建一个像马里奥游戏中的单砖,让我去啊**d并创建一个程序。
叫做brick.c,在brick.c中,我只会包含标准io.h。intmainvoid,稍后会详细说明这个void,charc获取“#”。然后在这里,让我去啊**d,简单地打印一个占位符%c,换行。然后输出c,这个程序相当简单,它的唯一目的是。
打印一个单独的哈希,就像你在马里奥金字塔中可能会有的那样,高度为1,非常简单,让我去创建brick,似乎编译没问题,让我用./brick运行它,瞧,我们得到了一个,瞬间,究竟发生了什么,这背后实际上发生了什么。
嗯,我有点好奇,我记得上周我们可以将值从一种转换为另一种。如果我稍微好奇一点,没有打印出c,也就是这个哈希字符,作为%c占位符。如果我稍微疯狂一点,说%i。我想我可能可以强制这个char的十进制等价,我可以看到它的实际ASCII。
上周的相同图表,我在这里寻找哈希符号,它的ASCII码是35。结果在C语言中,如果这是一个字符,计算机很清楚,我知道我可以将其转换为,你可以替代性地。隐式转换一种数据类型为另一种,仅仅通过上下文,所以printf和C。
还有其他表达式,如果我做这样的事情让我回到我的代码。让我先不要实践我刚才所说的,给自己三个变量,这次是c1c2和c3,让我故意存储这三个变量。hi,全部大写,后面跟着一个感叹号,上周你处理的是。
单个字符,你正在处理多个字符,上周被称为字符串。使用双引号,但这就是我为什么用单引号,因为我们现在只在玩单个字符。现在让我继续打印这些,百分比c,百分比c,输出c1c2c3。所以这可能是打印完整单词的最愚蠢方式,存储。
每个单独的字符在它自己的变量中,但这样也没关系,我只是把这些第一个原始。百分比c,作为我的占位符,我打印这些字符,所以让我现在执行砖。编译没问题,如果我点斜杠,你知道我真的应该重命名。这个文件,但我们会在一个。
现在如果我打开这个文件,果然里面有高。c,我已经修正了我的重命名错误。好吧,如果我现在执行高,输入点斜杠高。哇,我看到了高,但这显然是实现一个字符串的愚蠢方式,但我们还是深入探讨一下,让我打印出来。
内存,当我现在存储这三个字符时,我只是把它们存储在三个不同的盒子里,所以c1c2c3,当你从整体上看,它有点像一个完整的单词,尽管当然只是这些。单个字符,但底层当然是。
高,用双引号表示,好吧,让我们相应地更改这个程序。让我去做我们上周会做的,字符串我将其称为s,只是为了S代表字符串,全部大写,我可以简化下一行,让我使用%s作为字符串s的占位符,接下来让我去重新编译,这使得。
标准,我没有,但这是因为字符串,结果显示在技术上并不存在。结果是,这个字符串一词,实际上在C中并不存在,这是一点白色谎言,或是cs50的库的一个特性。如果你愿意,我们下周将去掉这个训练轮,但现在让我们揭示什么。
字符串实际上是因为字符串是每种编程语言都有的艺术术语,即使它在技术上并没有一个叫做字符串的数据类型。我们通过cs50的库将这个类型添加到C中,但现在如果我创建一个高。注意我的代码可以正常编译,如果我输入./hi,然后按回车,哇,我仍然看到高,这就是。
我在上周也见过,如果我们在计算机的内存中描述这个,因为hi是三个字母,这就有点像说,给我三个盒子。让我称这个字符串为s,所以这感觉像是一个合理的艺术表现,如果它存储一个像hi这样的三字母单词,但每当我们有这样的字符序列时。
我觉得我们现在看到了一个合适的编程语言的能力。我们稍早前引入了字符串的概念。所以,也许可以有人重新定义今天的命名法中的字符串,比如什么是字符串。这里有一个例子,high占用了三个盒子,但我们是如何实现的。
字符串的本质,呃,它是一个字符的数组和整数。好吧,如果整数用于基本的单字符,那么这就是完美的。如果我们现在有能力表达,做得很好,塔克。如果我们现在有能力表示,事物的序列,例如分数,那么可以推理出我们可以。
取另一个基本的数据类型,例如char,如果我们想要英语单词。那么我们不妨将字符串视为字符的数组,即字符的数组。实际上,这正是字符串的本质,所以这里的东西,high!从技术上讲是一个叫做s的数组,而这是s的第一个元素,这就是s。
括号一,这就是s括号二,这只是一个名为s的数组。我们上周没有使用“数组”这个词,因为它没有“实例”那么熟悉。但字符串显然只是一个数组,如果它是数组,那么我们可以访问,如果我们想的话。从今天开始,结果是有一些特殊的东西。
关于字符串的实现,回想一下我们涉及分数的例子,我们知道那个数组的长度是因为我有第二个变量叫做总数。也就是说,在我们的分数示例中,并没有同时保存数组。虽然直到现在,每当你和我使用printf函数时。
我们已经传递给printf函数一个像s的字符串,我们只提供了printf字符串本身,或者从逻辑上讲,我们仅仅提供了printf某种方式。printf神奇地找出字符串的长度,毕竟,当printf打印s的值时,它打印的是hi,感叹号,仅此而已。
如果你打印四个字符、五个或二十个,那么很明显,在你的计算机内存中还有其他东西,如果你有其他变量或其他程序在运行,然而printf似乎足够聪明,知道给定一个数组,数组的长度。因为很简单,它只打印那个单一的单词,那么,如何呢?
在内存中,如果一个字符串仅仅是字符的序列,事实证明,只有一个hi感叹号。从技术上讲,一个字符串在内部实现使用四个字节,它使用四个字节,并使用第四个字节来初始化我们所称的反斜杠零,这是一种奇怪的描述方式,但这仅仅表示一个特殊的。
字符通常被称为null,表示字符串的结束。也就是说,当你仍然创建一个字符串,用双引号引起来,比如hi,感叹号。是的,这个字符串的长度是三,但你实际上浪费或使用了四个字节,为什么呢?因为这是给计算机的提示,字符串。
也许开始的时候,仅仅逐个打印字符是不够的。在printf中,从左到右需要有类似的停止标志在字符串的末尾,表示这就是这个字符串的结束。那么,回到十进制,727333,那奇妙的反斜杠零仅仅是表示。
字符形式是零,更具体地说,平方,所以为了存储一个字符串,计算机需要额外的字节。所有的零位可以写成字面上的值零,因此这个东西通常被称为null只是一个特殊字符,我们将在片刻后,如果我回到我的ASCII表。
所以说,字符串的强大之处在哪里,一旦我们有了,这种能力。让我继续,回到刚才的代码。并让我继续,增强一下,我对发生的事情很,感兴趣。你知道,我可以在这里做的事情,我敢打赌。
如果我想打印出所有这些,字符的s,我可以再好奇一点,打印出百分c,数组,按照今天的语法,我技术上可以做,s括号0。s括号1s括号2,然后如果我保存这个,重新编译我的,代码makehi。好的,dotslashhigh我仍然看到高,但你知道,让我再稍微。
更好奇了,让我用百分号i,实际上可以看到那些ascii代码,高。dotslashhigh,那里有7273,33,现在让我再好奇一下,打印出第四个值。像这样,s括号3,也就是第四个位置,记住。所以如果我现在做makehi。和dotslashhi,瞧,现在你看到零,这暗示了实际上一个。
让我总结一下,数字37是一个,百分号,这有点奇怪,百分。现在我有点在,计算机的内存里乱翻,某种意义上我不应该,查看的地方。事实上,如果我真的好奇,看看不是位置4,如何呢,位置40。像是深入那幅图,makehigh,看看位置,400,重新编译我的代码,makehidotslashhi。
数组,所以让我们继续考虑,如何在一个程序中存储多个字符串。我们称它们为,s和t,分别。另一个程序员的惯例,如果你需要两个字符串,叫第一个为s,然后第二个为t,也许我存储高然后低。好吧,去吧,让我们再挖掘一下,高,像以前那样将存储在这里。
整个事情都指向s,它占用了四个字节,因为最后一个是那个特殊的空字符,它是结束字符串bye的停顿标志。感叹号五个字节,因为我需要第五个字节来表示另一个空字符,而这个字符故意回绕,尽管这只是一个艺术表现。
现实中不一定有网格,感叹号\0现在表示t。所以这是说如果我有一个程序像这样,开始在计算机的内存中探测,只是使用方括号符号b、y或e,只是通过查看稍微超出字符串s。所以,尽管我们的程序多么复杂,底层发生的一切。
理论上,只需将东西放入内存中的这些位置。因此,现在我们有了这种能力,或者说对计算机内部发生的事情的心理模型,我们可以考虑一些特性,你可能想要在你编写的程序中使用,所以让我继续在这里快速写一个。
在CS50的IDE中,我将把这个文件命名为one_string.c,然后我将快速在顶部包含像往常一样的cs50.h,并且我将继续包含stdio.h。然后我将给自己一个intmain(void),然后在这里我将得到一个字符串。所以strings等于getstring,让我问人类输入一些内容,不管是什么。
在这里让我继续打印出这个字符串,对于inti,从0开始,i小于……哎,我还不知道字符串的长度,所以我就放一个问号在这里,这不是有效的代码,但我们会回来处理这个。i++,然后在循环内部,我将继续打印出每个字符。
一次一个地使用我的新数组符号,最后在行的末尾。只是为了确保光标在它自己的行上,所以这是一个完整的程序。现在从本周开始,这个程序将把字符串视为数组。因此我在第10行的语法使用了我新的花哨的方括号符号。
但我还没有回答的唯一问题是这个字符串,我怎么知道何时停止呢?事实证明,迄今为止,当我们使用for循环时,我们通常做的事情就是从零开始数到某个数字,这个条件其实是任何布尔表达式,我只需要一个“是”或“否”或者“真”或“假”的答案。所以你知道,我可以继续循环。
在s中的字符,位置i,s不等于,所以不等于是程序员发音感叹号的方式,因为稍微快一点,bangequals表示不等于,所以这是你在数学中如何用斜杠表示等号,代码中是。呃,感叹号等于号,然后注意这种奇怪的字符。
但是它用单引号表示,因为它定义上是一个字符。出于我们以后会讨论的原因,反斜杠零是你表达它的方式,就像反斜杠n是一种奇怪的换行符,所有零。因此,这是一种不同的for循环,我仍然从零开始。
字符串结束,我只知道它们在我看到反斜杠零时结束。因此,当我现在下去做makestring时,它编译正常,点斜杠字符串让我输入像helloagain的内容,让我再试一次,买在全部大写时。输出是by,所以这在某种程度上是个无用的程序,因为它只是。
打印出我输入的相同内容,但我有条件地使用这个布尔表达式来决定是否继续打印字符。现在,感谢这个,对我来说,事实证明有一个叫做strlen的函数,所以我可以简单地说,算出字符串的长度,函数叫strlen。
字符串长度存在于一个文件中,显然是字符串h,字符串h,所以现在让我下去做makestring编译正常,点斜杠字符串,函数strlen确实存在于通过头文件字符串h的库中。已经存在,其他人写的,但他们是怎么写的,可能他们。
我写了我做的第一个版本,让我在这里问一个微妙的问题,这个程序是。它遍历整个字符串的长度,并打印出每个字符。有没有人观察到这个函数中的设计决策有问题,这一点很微妙。但我对我的for循环特别不喜欢,我会将其隔离到第九行。
我在第九行没有做到最优设计,有一个更好设计的机会,有什么想法吗?关于我可能做得更好的任何想法,呃,是的,乔纳森,是的,基本上创建另一个变量来存储流长度,并记住它,嗯,如果你想为流长度使用不同的值,或者如果它可能波动或改变,你想要只拥有一个不同的。
变量作为某种占位符值,好的,可能可以,但我在这种情况下声称,因为人类已经输入了这个词,一旦输入这个词,它不会改变,但我认为你走在正确的方向上。因为在这个布尔表达式中,我小于s的字符串长度。
这意味着有一些代码,可能是有人写的,类似于我几分钟前写的,你不断地问字符串的究竟是什么。而且从我们的图像回忆,你找出字符串长度的方法是从字符串的开头开始,你不断检查我是否在反斜杠零。
找到那个反斜杠零。所以我不喜欢这行代码的原因是,为什么你要一次又一次地询问s的字符串长度,在这种情况下它不会改变。因此,乔纳森的观点是正确的,如果我们不断要求用户提供更多输入,但在这种情况下,我们只问了人一次,所以你知道吗,听从乔纳森的建议去做。
一次又一次地,同样的问题需要花费相当多的工作来找到反斜杠零,一次又一次。现在我们这里也可以做一些清理。事实证明,如果你想将另一个变量初始化为一个值,对于for循环还有另一个微妙的特性,你实际上可以一次性做到这一点。
在分号之前你可以做逗号,n等于斯特林的s,然后你可以像我在这里一样使用n。所以这并没有好太多,但稍微干净一些,因为现在我把两行代码合并成了一行。它们都必须是相同的数据类型,但这在这里是可以的,因为i和n都是。因此,之前的低效是愚蠢的,我。
不断重复同样的问题,但现在我只问了一次。记住在一个叫n的变量中保存它,并仅将i与那个不实际改变的整数进行比较。好吧,我知道这也是很多内容,让我们去做一个三分钟的任务,然后再回来开始应用。
好的,所以我们回来了,这些无疑是一些低级细节,而我们最终要去的地方是应用这些构建块,接下来的一个问题集将是密码学的问题。如果你试图加密信息,比如消息,那些。
消息可能用英语或ASCII编写,你可能想转换一些内容,以便如果你的消息被某个第三方截获,他们无法真正解码或弄清楚你发送的内容。因此,我感觉我们几乎要接近了,我们能开始转换。
从一个单词到另一个单词或者混淆我们的文本,但我们需要几个更多的构建块。所以回忆一下我们停留在这里的画面,计算机的内存中有两个单词,高和bi,两个都有感叹号,但也都有这些反斜杠零,这些是你我并没有显式放入的。
这些在你使用双引号和getstring函数时就会自动发生,所以一旦我们将它们放入内存,你可以分别把它们想象成s和t,但字符串s或t只是一种数组,所以你也可以通过今天的新方括号表示法来引用所有这些单独的字符或字符。
s1s2s3,然后tbracket0tbracket1234,以及计算机内存中其他的内容,但你知道你甚至可以这样做,假设我们想要一个单词数组,所以之前我们有一个分数数组,一个整数数组。但现在假设我们在某个其他程序的上下文中有一个数组。
你完全可以这样做,没有什么阻止你拥有一个单词数组,语法是相同的,注意如果我想要一个字符串数组,bracket2。这意味着嘿计算机,给我一个大小为2的数组,每个成员都是那个数组。和之前的分数一样,wordsbracket0得到的是“高”。
wordsbracket1得到的是“拜”,也就是说,通过这段代码,我们能否创建一个类似于之前的图片,但我现在不再称这些字符串为s和t,而是称它们为两个不同位置的单词0和1。因此我们可以像这样重新绘制那幅图,现在bracket0,这个由单词引用。
括号一个,但再说一次,什么是字符串?字符串是一个数组,而这里我们有一个字符串数组。数组,所以我们有一个单词数组,但单词只是一个字符串,而字符串又是字符数组。因此我在板子上真正有的是一个数组的数组。所以这里,这将是今天最后一个奇怪的语法,你实际上可以有多个方括号。
括号背对背,所以如果你的变量叫做words,而那是一个数组。如果你想获取数组中的第一个单词,你可以用words[0]。一旦你到达那个单词hi,并且你想获取那个单词中的第一个字符。你可以同样用bracket[0],所以第一个括号指的是什么。
你想在数组中放入哪个单词,第二个括号指的是i当前在的字符。是words[0][1],零括号二和空字符在words[0][3],与此同时b在words[1][0]、[1][1]、[1][2]、[1][3],所以这几乎有点像一个坐标系统,如果你。
这是二维的,仅仅是说,如果我们想将字符串数组视为单独的字符,我们可以,我们现在也有这种表达能力。在代码中,那么我现在可以做些什么,既然我可以在这个层次上操纵事物。让我做一个程序,我认为这将非常适用,结合我们的。
输入单词转换为大写,让我们看看,并且包含cs50.h。让我继续并且包含stdio.h,我还要包含string.h,这将给我们像字符串长度这样的函数。然后让我做intmain(void),然后让我继续在这里从用户那里获取一个字符串,就像之前那样,我只是会问用户。
一个字符串,我希望他们给我,不管字符串在字面上应该是什么,在之前,之后。我将字符串中的所有内容都大写,现在让我继续,forinti=0;i n=stringlengthofs;for(i=0;i 正如我们用任何算法所做的,具体来说,我想问当前字母是否是小写。让我以某种方式将其转换为大写,否则让我直接输出,不做更改。那么我如何用上周和这周的构建块来表达这一点呢?让我说类似这样的话:如果位置i处的字符。 大于或等于小写字母a,并且s中的第i个字符小于或等于小写字母z,我想做什么呢?让我继续打印出,什么,这里还没决定。但是让我回到else,继续打印出那个字符,保持不变的s括号i,减去占位符的问号。 我几乎已经全都做完了,第10行将i初始化为0,它将计数一直到n,其中n是字符串的长度,并且它将不断递增i,所以很快就会成为肌肉记忆。第12行来自上周,这周我们有新的方括号符号来获取字符串s中的第i个字符。 或者小于或等于,我们上周至少看到了其中一个。这仅仅意味着大于或等于,小于或等于,我提到了上周的&符号,它是逻辑与运算符,意味着你可以检查一个条件和另一个条件。只有当这两个条件都为真时,整个条件才为真。 当前字符在小写字母a和大写字母及小写字母z之间,是完全可以隐式地将a和z视为数字,因为它们确实是。因为如果我们回到我们最喜欢的ASCII表,访问asciichart.com,你会再次看到这一点。 像这样,我想表达,如果这个值大于或等于97,并且小于或等于122,但这像是糟糕的设计,我永远不会记得小写字母z是122,像是没有人会知道,这让代码不那么明显,所以我决定以一种更友好的方式写出来。 像这样,但注意这个问号,我该如何填充这个空白。好吧,让我回到ASCII表,这个问题很微妙,但这很酷,人类显然是在前瞻性思考,注意小写字母a是97,大写字母A是65。小写字母b是98,大写字母B是,注意这两个数字,67到90,65到97,66到98。 看起来,从67到99,无论我们比较什么字母,小写字母和大写字母之间总是相差32,这一点是一致的,我们可以对所有26个英语字母进行这种处理。所以如果它们总是相差32,你知道我可以做什么。如果我想取一个小写字母,14,大小写,这并不是最干净的做法,因为我在某个时刻。 至少在数学上,我认为这会奏效,因为97将变成65,98将变成66,这就迫使那些字符变成更低的数字。我仍然在使用percentc将其强制转换为字符,所以如果我没有搞错任何语法,让我将其改为大写,好的,点斜杠大写,让我继续输入,例如。 我的名字全部小写,瞧,变成大写了,现在有点丑,我忘记了\n,所以让我继续,快速添加一个。只是为了修复光标,让我用makeuppercase重新编译代码。让我用dotslash重新运行程序,并输入我的名字大卫。 让我再用布莱恩做一次,注意它是逐个字符地大写的。仅使用今天的构建模块,这是正确的,样式也很好,因为一切都很好缩进,非常可读,尽管乍一看可能有点神秘。 但是我认为我可以做得更好,我可以通过使用另一个库来做到这一点。这里是C语言和编程的一般强大之处,使用流行语言的整个意义在于,许多其他人在你之前已经解决了问题,而你不需要再次解决它们。我相信在过去的50年里。 可能有人为我写了一个函数来大写字母,我不必自己去做,实际上有另一个库,我打算通过它的头文件ctype.h包含。这个库有一些函数,具体来说,让我去掉所有这些代码,调用一个叫做的函数。 islower和pastislower,如你所猜,它的目的是基本上返回一个布尔值,true或false,如果那个字符是小写的话。那么,让我继续打印出一个占位符,后面跟着那个字母的大小写。现在在我之前必须做那烦人的数学计算,减去32并弄清楚,toupper的。 括号和方括号,现在我可以只打印出那个字符,保持不变,就像之前一样,方括号。但现在请注意,我的程序。老实说,它确实短了一点,简单了一点,因为代码少了,希望如果写islower和twoupper的人做得好。 我知道这是正确的,我只是在借助他们的成果,坦白说,我的代码更可读,因为我理解islower的意思,而那个疯狂的&语法和所有额外的代码,确实让人很难理解。因此现在如果我继续编译这个,makeuppercase,好的,似乎工作得很好,现在。 我打算继续做dotslash,大写并输入我的名字,然后再小写。大卫似乎可以,布莱恩似乎也可以,我可以整天这么做。这似乎仍然有效,但你知道,明确地说,如果写twoupper的人聪明,我敢打赌我可以盲目传入任何字符给twoupper,它只是。 goingtouppercase,如果可以转换为大写,否则将保持不变。所以你知道,让我去掉所有这些东西,并为C输出一个占位符,然后twoupper的方括号,果然,如果你阅读这个函数的文档,它将处理小写和大写的情况。 真正清楚的是,我的代码到底变得有多紧凑,有多干净,以及有多简短。而且它的可读性也更强,从这个函数的命名来看,它的确命名得很好。“toupper”就是它的确切名称,但这里有一个重要的细节。“twoupper”期望输入一个字符,你不能传入整个单词。 接下来几周的例子,但如果我去查找所谓的C语言手册页面,我们有自己基于网页的版本,我们会在课程实验和问题集中链接给你。你可以看到C语言中至少常用函数的所有可用列表,在CS50中。 等等,所以我们会推荐这些资源给你,以便你不需要上课。但你可以利用这些其他函数和工具包,我们在Python、SQL和其他语言中也会这样做,这些都是我们所称的功能。在我们考虑加密和混淆信息之前。 问题集二,所以我之前提到的命令行参数,像一个你可以在程序名后输入的单词,以在命令行提供输入,所以makehello。hello是程序hello的命令行参数,而rma.out则是程序rm的命令行参数,当我想要的时候。 为了去掉它,我们已经在实际操作中看到了命令行参数。但到目前为止,我们实际上还没有编写任何允许你从所谓的命令行接收单词或其他输入的程序。到现在为止,你和我在程序中获得的所有输入都来自getstring、getint等,我们从未能查看单词。 人类在运行你的程序时可能非常好地在提示符下输入的,但这一切即将改变。让我去创建一个名为argv.c的程序,我将要包括标准的stdio.h,然后我将给自己一个intmain(void),然后我将非常简单地返回。 并改变void,正如我们自己的自定义函数可以接受输入,我们已经看到getnegativeint,main可能接受输入。到现在为止,我们一直在说void,我们告诉你上周说void,告诉你说void,C语言确实允许你将其他输入放入main,你可以说nopemaindoes。 不接受任何命令行参数,但如果它接受,你可以字面上说intargc和stringargv带方括号,所以这有点神秘,技术上你不必准确地这样输入,但人类的惯例会要求你至少现在这样做。这表示主函数main。 将表示用户在提示符处输入的单词数量。程序名称argv是argumentvector的缩写,vector是一个花哨的说法,指的是列表。它是一个变量,将存储人类输入的名称,所以我们可以这样使用。例如,假设我想让用户在命令行输入他们自己的名字。 prompt我不想使用getstring,我不想稍后再提示人类输入他们的名字。我希望他们能够运行我的程序,并一次性给我他们的名字,就像make,rm和我们见过的其他程序一样。所以我将这样做。如果argc等于2,所以如果传递给我的程序的参数数量是2。 类型,stringunknown类型string好吧,我搞错了,如果我使用string。请记住,现在我需要开始使用cs50库,我们会看到更多的原因,在接下来的几周里,当我们去掉那些辅助工具时。但现在我要再做一次,makeargv,来了,现在它有效。 参数也就是,在你的程序名字之后提示的单词,你改变我们一直在做的事情,从void变成这变成argc,字符串argv,带有方*括号,计算机会自动为你做的事情,是会存储人类输入的单词总数,而不仅仅是。 参数技术上来说,所有的单词,包括你自己程序的名字。然后它将填充这个字符串数组,也就是,argv,包含人类在提示符下输入的所有单词,所以不仅仅是像布莱恩或,大卫这样的参数,还有。你的程序名称,所以如果人类,输入了两个单词,那么就打印出来。 无论rv一个的值是什么,我故意不做零,如果我做了零,根据口头,这个程序,我不想看到这个helloargv,所以程序自己的名字是。自动存储给你,如果你想要第一个,真正有用的信息,你实际上会在重新编译代码后,这里,方式,我们能看到一个argv,我们实际上,也能。 假设我想打印出所有的,单个字符。在某人的输入中,你知道吗,我敢打赌我,甚至可以这样做,让我去啊**d,做这个,而不是。仅仅打印出hello,让我做rv的长度,哎argv一个,然后在这里,我将做i小于n,i加一。 数组,因此我可以在这里使用这种语法,通过访问argv一来获取单词,如大卫或布莱恩等,然后进一步索引,使用更多的方*括号来获取d,a,v,i,d等等。为了更加清晰,我在这里放一个新行字符,这样我们可以明确看到是什么。 继续让我去啊**d,现在就删除这个helloworld,因为我。并不想看到任何hello,我只想看到人类输入的单词,制造argv,哎,我做错了什么,哦,我用了sterling,实际上不应该,因为我没有,包含。stringdoth在顶部,好吧,现在如果我重新编译这段代码并,重新编译我们的v,太好了,点。 返回一个int,尽管它不是一个普通的函数,它不是一个获取正数的函数,它不是获取负数的,出于某种原因,主函数总是保持不变。这是我们上周承诺的最后一部分,最终会说明这可能意味着什么,这个有点棘手,布赖恩,我们有谁,怎么样。 是的,通常函数最终返回零,这意味着函数停止。而且这个零就像是从主函数中弹出的整数。如果你之前编程过,几率是,我猜你曾经见过这个。我们人类在像使用Mac和PC的现实世界中。 你实际上在奇怪的地方看到了数字整数。坦白说,几乎每次你的计算机卡住或看到错误信息时,你都能看到它。很可能你在错误信息中看到的是英文或其他某种语言,但你常常会看到一个数字代码,例如如果你在使用Zoom。 退出代码,这是一种微妙的东西,退出状态,这是一种微妙之处,随着我们的程序的深入,它会很有用。这里包括standardio.h,我将给自己一个更长的主函数版本,因此intargcstringargv,带方括号。在这里,我要说如果argc不等于,人类并没有按照我的要求去做。 我将对他们在某个行参数中大喊大叫,所以任何类型的错误消息屏幕。我只是会告诉他们这个消息,但我将非常微妙地返回数字一,我将返回一个错误代码,人类不一定会看到这个代码,但如果我们有一个图形用户程序。 这将是他们在错误窗口中看到的数字,就像Zoom一样,某种东西出了问题。类似地,如果你曾经访问过一个网页,坦白说,当你发现网页不存在时,你会看到整数404,这在技术上并不是这个确切的表现形式,但它代表程序员使用数字来表示错误,因此你可能见过这个。 如这里所示,我将继续默认说“你好%s”,就像之前一样,但我不会输入任何无聊的内容,而是检查人类是否在命令行给了我两个单词。如果没有,我将打印缺少,退出代码,否则如果一切正常,我会。 我们将继续并明确返回0,这又是另一个数字。但我们可以访问它,并且坦率地说,为了课程目的,检查50可以访问这些,伟大的笔记也都很好,但一个将意味着继续并进行退出,这在我们结束时是适当的。 继续执行dotslash,退出缺少命令行参数,即显示的内容是什么。如果我继续说“退出大卫”,我会看到“你好,大卫”,或者退出“布莱恩”,我会看到“退出布莱恩”。这不是你需要经常使用的技巧,但你可以想要。 美元符号问号,这是一个非常晦涩的方式来表示我的退出状态。如果你按回车,你会看到一个;相比之下,如果我运行“退出大卫”,我实际上看到“你好,大卫”。 如果我运行退出并看到这个错误信息,我可以很奇怪地说“echo”。所以这不是你我会非常频繁使用的技巧,但这是一个程序的能力,而这是你现在可以访问的C语言能力。因此,在编写程序时,我们在实验室和作业中经常会要求你从主函数返回。 错误代码可以是0、1、2、3或4,基于你在程序中检测到并适当地响应的问题。所以这是以标准方式处理错误的非常有效的方法,这样你就知道自己在主动检测错误。那么我们可能会遇到什么样的错误呢? 处理这周我们可能解决的问题,今天完全是关于拆解字符串是什么,上周只是文本的序列,今天是一个数组C,用于访问那些字符。我们今天还可以访问更多库和头文件的文档。 这样我们就可以在不自己写那么多代码的情况下解决问题。我们可以以这些库的形式使用其他人的代码。因此,这周我们要解决的一个问题是可读性,像是你在阅读一本书、论文或任何东西时,是什么让它看起来像是一个第三类。 评估阅读水平,比如12年级的阅读水平或大学阅读水平。我们可能都有一种直观的感觉,对吧?比如如果字体大且单词短,可能是给年轻孩子的;如果是非常复杂的单词和我们不知道的内容。 也许这意味着大学,稍微更公式化,但不一定是唯一的方法,我们会给你几个著名的句子:德尔斯利先生和夫人住在四号普莱维特大道,我们很自豪地说,他们是完全正常的,谢谢。然后还有其他内容,这段文本有什么让《哈利·波特》成为年级。 7的阅读水平可能与词汇有关,但也可能与句子的长度、标点符号的数量,以及你可能计算的字符总数有关。你可以想象只基于文本的外观和美感来量化它,那么在计算机科学中这又如何呢? 语言学中的作者归属是预测未知作者文档作者的任务,这个任务通常通过分析风格特征来完成。特别是,这是布赖恩的毕业论文,所以这并不是七年级的阅读水平,而是实际评级在年级。 16,因此O'Brien也相当复杂,你或许可以从句子的复杂程度、长度和所用词汇中得到一些启示。我们或许可以评估文本的可读性,即使你没有字典来确定哪些是。 实际上,大或小的词汇,以及密码学,那是非常常见和重要的,现在你我都需要使用密码学。并不一定是使用我们自己想出的算法,而是使用像WhatsApp、Signal、Telegram和Messenger等软件。 支持在你和第三方之间进行加密,比如你的朋友或家人。或者至少与之互动的网站,利用密码学来隐藏信息。如果这些信息是文本,那么坦率地说,在CS50的第三周,我们已经拥有了不仅仅是基础的必要构建块。 表示文本,但我们今天看到,即使只是将文本大写,也在操纵它。那么,从上周开始,加密意味着什么?你有一些输入,你想要输出,明文。你想从自己发送给其他人的消息,密文是你想要的输出。所以在这之间会有我们称之为密码的东西,或输出。 第三方无法理解的信息,且希望这个密码和算法是一个可逆的过程,这样当你弄清楚那个人想要发送给你的内容时,你也会有一个秘密密钥。如果你回想一下小学,也许你在课堂上和某人调情,并在一张纸上给他们写了便条。 如果你在纸上只是写了“我爱你”,然后通过所有朋友或老师传递给最终收件人,希望你没这样做。也许你做了一些像是应用算法,将所有字母加一,这样看。 在这方面,他们可能没有足够的耐心去搞清楚这些胡言乱语。但如果你的朋友知道你将a变为b,b变为c,通过给每个字母加一,他们就可以逆转这个过程,从而解密。因此,密钥可能字面上就是数字一,消息字面上可能就是我爱你。 但密文或输出会是什么呢?让我们考虑一下。我爱你是一个字符串,截至今天,是一个字符数组。那么这有什么用呢?让我们具体考虑这个短语,就像它是一个数组一样。它是一个字符数组,我们知道上周,字符实际上就是整数,十进制整数。 多亏了ascii,进而是unicode,因此结果是我们已经知道i是73,图表,l是*****。很容易看出,你可能需要检查你的笔记和我的示例代码等等,但在c语言中相对容易地将我爱你转换为相应的整数,实际上可以通过将字符转为整数来实现,我可以非常轻松地使用c语言中的加法运算符。 开始为这些字符每个加一,从而对我的消息进行加密。但我可以将这些数字发送给我的朋友,也不妨让它更加用户友好。因此,现在看起来,对于我爱你,使用密钥为一时,实际上只是意味着将a变为b,而不是变为c。 只是将其移动一个位置,这是我爱你的加密消息的密文,因此整个过程变得简单,输入是文本,输出最终是这个。如果某个老师或朋友截取了这条消息,他们可能不知道发生了什么,实际上这就是密码学的本质。 保护我们的电子邮件、短信、财务信息和健康信息比那个特定算法更复杂,但它归结为相同的过程,通过一些输出,所谓的密文,有时甚至是机械形式。在过去,你实际上可以得到这些小的圆形设备。 字母表的一侧是字母,另一侧也是字母。a可能对应b,b可能对应c,所以你甚至可以有一种物理形式的密码学,就像在美国圣诞节期间电视上不断播放的电影一样,你可能会认出,如果你看过的话。 圣诞故事,就是这样。我们将利用最后的几分钟,来看看这个密码学的真实世界体现,你无疑可能在电视上看到过。众所周知,在周日,拉尔夫·帕克被任命为小孤儿院秘密圈子的成员,享有所有的权利。 签名,皮埃尔·安德烈,荣誉与利益,来吧,继续,我不需要那些关于走私者的所有花哨,听明天晚上的黑色信息结局,为你们秘密圈子的成员,圈子。可以解码任何秘密信息,记住,安妮在依靠你们,把你的针设置为太这里。 325,这是安妮亲自发来的信息,记住不要告诉任何人,[音乐]。90秒后,我在房子里唯一一个九岁男孩可以独自坐着的房间。可以私下解码,啊哈,b,我接着读,第一词是b,现在变得更容易了。哦,确保,确保,小孤儿院想说什么,确保观看。