[Powered by Google Translate] [第5条:不太舒服] [内特 - 哈迪森,哈佛大学] 这是CS50。[CS50.TV] 所以欢迎回来,伙计们。 第5节。 在这一点上,已完成测验,并看到了你所做的, 希望你觉得真的很不错,因为我的分数在本节留下了非常深刻的印象。 对于我们的在线观众,我们有几个问题 有关问题上的最后两个问题 - 或测验,而。 因此,我们要对这些真的很快,让大家看到发生了什么事 以及如何通过实际的解决方案,而不是仅仅查看解决方案本身。 我们要在过去的几个问题真的很快,32和33。 只是,再次,使在线的观众可以看到这一点。 如果你把你的问题是32,第13页, 13 16 32,问题是所有有关掉期。 这是所有关于交换两个整数。 这是问题,一对夫妇在演讲的时候,我们已经走了。 在这里,我们要你做的是一个快速的记忆痕迹。 要填写的值的变量,因为它们是在栈上 的代码通过这个交换功能。 特别是我们正在寻找的 - 我打算把这款iPad - 特别是,我们看到的是,这条线在这里6。 而它的编号为6的连续性与前面的问题。 我们想要做的是显示或标签的内存状态 因为它是在的时候,当我们执行这条线6号, 这实际上是一个回报,我们这里的交换功能。 如果我们向下滚动在这里,我们看到的一切都在内存中的地址提供给我们的。 这是非常关键的,我们会回来的,在短短的时刻。 然后在这里的底部,我们有一个小的内存图,我们要参考。 其实我已经做到了这一点,在我的iPad。 所以,我要交替之间来回iPad和这个代码仅作参考。 让我们开始。首先,让我们把注意力集中在这里的第一对夫妇行的主要。 首先,我们要初始化x为1和y 2。 因此,我们有两个整型变量,它们都是被放置在栈上。 我们打​​算把他们的A 1和A 2。 所以,如果我翻转过来,希望我的iPad,让我们来看看 - Apple TV的镜像,和我们走吧。好吧。 所以,如果我翻转过来,以我的iPad, 我想初始化x为1和y 2。 我们做到这一点很简单,写一个包装盒中标记为x 1 2包装盒中标记为Y。相当简单的。 所以,现在让我们回到笔记本电脑,看看接下来会发生什么。 因此,这下一行是在事情变得棘手。 我们传递的地址的x和y的地址作为参数a和b的交换函数。 x和y的地址,该地址是我们无法计算的东西, 没有这些子弹点就在这里。 幸运的是,前两个要点告诉我们的答案是什么。 x的地址是10,在存储器中,存储器中的y的地址是14。 因此,那些得到的值通过在A和B的向上顶在我们的交换功能。 所以,再一次,切换回我们的图,我可以写在10 和14 b中。 现在,这一点是我们进行交换。 所以再次翻转的笔记本电脑, 我们可以看到,交换的工作方式是我第一次解引用一个,并将结果存储在tmp。 因此,解引用运算符说,“嘿,治疗的变量a的地址的内容。 无论是存储在该地址,并加载它。“ 你加载的变量将被存储到我们的TMP变量。 翻转到iPad。 如果我们去解决10,我们知道地址10是输出变x 告诉我们,因为我们的子弹点x在内存中的地址是10。 因此,我们可以去那里,得到了它的价值,这是1,因为我们看到我们的iPad, 并加载到tmp目录。 再次,这是不是最后的内容。 我们要走过和结束时,我们会得到我们的最终状态的程序。 但是现在,我们的值为1,存储在tmp。 在这里有一个快速的问题。 [亚历山大]是解引用运算符 - 这只是前面的变量的明星权吗? “是的。因此,解引用运算符,当我们打开再次回到我们的笔记本电脑, 这是明星就在眼前。 从这个意义上说,它是 - 你对比乘法运算符 这需要两件事情:解引用运算符是一元运算符。 刚刚申请到一个值,而不是一个二元操作符, 适用于两种不同的价值观。 所以,这就是发生在这条线。 我们装的值为1,并把它保存到我们的临时整型变量。 下一行,我们的内容存储的B - 或者说,我们,b是指向到的地方,一个是指向存储的内容。 如果我们分析这个由右至左,我们将取消引用b, 我们要解决14,我们要抢的整数,它是有, 然后我们要去的地址10, 我们要抛的结果,我们解引用的b到该空间。 翻转回我们的iPad,在这里我们可以使这一点更具体的, 它可能会帮助,如果我在这里写号上的所有地址。 因此,我们知道,我们在y地址为14,x是地址10。 当我们开始在B,我们解引用b,我们要抓住的价值2。 我们要抓住这个值,因为是住在地址14的值。 我们打​​算把它的变量,居住地址10, 这是正确的,相应的变量x。 所以,我们可以做一点点的覆盖这里 我们摆脱我们的1,而我们写了一个2。 因此,所有的好和良好的世界,即使我们已经覆盖x现在。 我们已经存储在我们的TMP变量x的旧值。 因此,我们可以完成交换的下一行。 翻转回我们的笔记本电脑。 现在,剩下的就是我们的临时整型变量的内容 并将它们存储到地址b被保持住在变量。 因此,我们要有效地解引用b的变量 这是在它的地址是b持有, 我们将tmp是保存它的东西的价值。 翻转到iPad一次。 我在这里可以删除此值,2, 相反,我们将复制到它的。 然后,下一行执行的,当然 - 如果我们翻回的笔记本电脑 - 这是6点, 这是我们希望我们的图完全充满。 所以,翻转到iPad,只是让你可以看到已完成的图, 你可以看到,我们在10,14在b,1 tmp中,2 x中,在y和1。 是否有任何问题吗? 这是否更有意义,已经走过吗? 就不是那么有意义吗?但愿不会。好吧。 指针是一个非常棘手的问题。 与我们合作的球员之一有一个很常见的说法: “要了解指针,你必须先了解指针。” 我认为这是非常真实的。它确实需要一段时间才能使用它。 抽签的图片,像这样的抽签的内存图是非常有帮助的, 后例如,在你走过的例子后,例如, 它会开始做一些更有意义一些更有意义和更有意义一点。 终于,有一天,你会拥有这一切完全掌握。 任何问题之前,我们进入下一个问题吗?好的。 因此,翻转的笔记本电脑。 我们的下一个问题是问题33号文件I / O。 这是一个有点放大。 问题33 - 是吗? [丹尼尔]我刚做了一个简单的问题。此星或星号, 这就是所谓的提领时,你使用一个星号之​​前。 它是什么时调用之前使用的符号吗? >>“符号前是运营商的地址。 因此,让我们向上卷动。 哎呀。我在变焦模式,所以我真的不能滚动。 如果我们看一下这段代码,在这里真的很快, 再次,同样的事情发生。 如果我们看一下这段代码,在这里,在这条线,我们拨打电话交换, “&”只是说“的地址在变量x的生活。” 当你的编译器编译你的代码, 它实际物理标记在内存中所有的变量住的地方。 所以编译器就可以做一次,它的编译一切, 都知道,“哦,我把x地址10地址14,我要把Y”。 然后,它可以为你填写这些值。 这样你就可以 - 它可以通过本和通&Y以及。 这些人得到的地址,但他们也,当你通过它们的交换功能, 这此int *类型的信息,在这里,告诉编译器, “好了,我们将要解释这个地址作为一个整型变量的地址。” 一个int作为一个地址,它是从一个字符变量的地址不同 因为一个int占用了一个32位的机器上,占用4个字节的空间, 而一个字符只占用1个字节的空间。 因此,它是重要的是要知道什么是 - 生活中,什么类型的值 住在传入的地址,进行了 或地址,你的工作。 这样一来,你知道多少个字节的信息的实际加载的RAM。 然后,是的,这种反引用运算符,就像你问, 在一个特定的地址去访问信息。 所以说,这是一个变量,在这里,治疗的内容作为地址, 去到该地址,并拉出,到寄存器加载到处理器中,负载 实际值或住在该地址的内容。 还有什么问题吗?这些都是很好的问​​题。 这是一个很多新的术语。 这也是一种时髦,看到&和*在不同的地方。 好的。 所以回到问题33,文件I / O。 这是一个发生的几件事情,我认为这些问题。 一,这是一个相当新的课题。 据介绍很快测验前, 然后,我认为这是种像这些字中的数学问题之一 ,他们给你很多的信息,但实际上你不使用一吨的。 这个问题的第一部分描述的是一个CSV文件是什么。 现在,一个CSV文件,根据描述,是一个以逗号分隔值文件。 因此,这些在所有有趣的是,和你使用过他们的原因, ,因为,你们有多少人曾经使用Excel之类的东西吗? 图你最有可能,或将使用在一些点在你的生活中。 你会使用Excel之类的东西。 为了得到数据的Excel电子表格或做任何形式的处理, 如果你想编写一个C程序或Python程序,Java程序, 处理的数据储存在那里, 把它弄出来的最常见的方式之一,是一个CSV文件中。 ,你可以打开Excel中,当你去到“另存为”对话, 你可以得到一个实际的CSV文件。 很方便的知道如何处理这些事情。 它的工作方式是,它是类似的 - 我的意思是,它本质上是模仿电子表格, 在那里,我们在这里看到,最左边的一块, 我们所有的姓氏。 因此,我们有马兰,那么哈迪森,然后鲍登,MacWilliam,然后生。 所有的姓氏。然后一个逗号分隔的姓氏第一个名字。 大卫奈特,抢劫,张宇,和Zamyla。 我总是混淆:罗比和汤姆。 然后,终于,第三列是电子邮件地址。 一旦你了解了,其余的程序是非常简单的实现。 我们所做的,以模仿相同的结构在我们的C程序 是我们使用了一个结构。 我们将开始播放这一点以及。 我们看到了他们的第一个问题集3,当我们正在处理的字典点点。 但是,这名员工结构存储一个姓氏,一个名字,和电子邮件。 就像我们的CSV文件存储。 因此,这只是从一种格式转换到另一个。 我们必须转换,在这种情况下,工作人员成一条线的结构的, 一个以逗号分隔的行,就这样。 这是否有意义吗?你们都采取了有奖问答, 所以我想你已经至少有一些时间来思考这个问题。 在租用功能,这道题我们 - 我们将在这一点位变焦 - 在人员结构,人员结构,名称为s, 并附加其内容到我们的staff.csv文件。 事实证明,这是非常简单的使用。 我们将发挥着这些功能,今天多一点点。 但是,在这种情况下,fprintf函数是真正的关键。 因此,fprintf,我们可以打印,就像你们一直在使用printf的整个任期。 您可以输出到一个文件中的行。 因此,而不是只是通常的printf调用,你给它一个格式字符串 然后你替换所有的变量与下面的参数, 与fprintf,你的第一个参数,而不是你要写入的文件。 如果我们要看看这个设备,例如,人fprintf, 我们可以看到的printf和fprintf的区别。 我将在这里一点点放大。 因此,用printf,我们给它一个格式字符串,然后在随后的参数 更换或替换成我们的格式化字符串中的所有的变量。 而与fprintf,第一个参数是事实上,这称为流文件*。 在这里,我们的租赁, 我们已经为我们打开了我们的文件*流。 那这是什么第一线;它打开了staff.csv的文件, 它以附加的方式打开它,并为我们做了所有剩下的是 编写人员结构的文件。 而且,让我们来看看,我想使用iPad? 我会使用iPad。我们有无效的 - 让我们把这个放在桌子上,这样我就可以写一个好一点的 - 无效租用,它需要一个参数,称为s的人员结构。 得到了我们的大括号,我们有我们的文件*文件, 我们有我们的FOPEN线给我们, ,我就写点,因为它已经在百科。 然后在我们的下一行,我们要拨打电话为fprintf 和我们将要通过的文件中,我们要打印的, 然后我们的格式化字符串,其中 - 我会告诉你们告诉我它是什么样子。 你怎么样,Stella吗?你知道的格式字符串的第一部分看起来像什么? 斯特拉]我不知道。 >>随意问吉米。 你知道吗,吉米? [麦]这是最后一个吗?我不知道。我不能完全肯定。 “好了。怎么样,有没有人得到这个正确的成绩? 所有权利。 原来,这里所有我们需要做的是,我们希望我们的人员结构各部分的 作为一个字符串到我们的文件被打印出来。 我们只是使用字符串替换字符,三个不同的时间,因为我们有个姓 其次是逗号,然后一个名字后跟一个逗号, 然后最后的电子邮件地址,其次是 - 这是不 适合我的屏幕上 - 但它后面的一个换行符。 所以我要去写它只是出现了下滑。 然后按照我们的格式化字符串, 我们只需要换人,我们使用点表示法访问 我们所看到的问题集3。 我们可以使用s.last,s.first,s.email 在这三个值到我们的格式化字符串来代替。 所以,这是怎么去的?有意义吗? 是吗?不是吗?有可能吗?好吧。 最后一点,我们做印刷后,我们已经打开的文件后,我们已经: 每当我们打开一个文件,我们总是要记住关闭。 否则,我们将最终的内存泄漏, 使用文件描述符。 因此,要关闭它,我们使用哪些功能?丹尼尔? [丹尼尔] FCLOSE? >> FCLOSE,准确。 因此,这个问题的最后一部分是正确地关闭文件,使用fclose函数, 它只是看起来像。 太疯狂了。 酷。 所以这就是问题的测验33。 I / O来了,我们肯定将有更多的文件。 我们会做多一点点在今天的演讲,或在今天的部分, 因为这是怎么回事形成这个即将到来的pset中的大部分。 让我们继续在这一点上测验。是吗? [夏洛特]为什么FCLOSE(文件),而不是FCLOSE(staff.csv)? “啊。因为事实证明 - 这样的问题,这是一个伟大的, 正因为如此,当我们写FCLOSE,我们,写FCLOSE(文件)明星变量 而不是在文件名的,staff.csv?是正确的吗?是啊。 因此,让我们一起来看看。如果我切换回我的笔记本电脑, 让我们来看看fclose函数。 因此,fclose函数关闭一个流,它需要的指针,我们要关闭的流, 而不是实际文件名,我们要关闭。 这是因为在幕后,当你拨打电话的FOPEN, 当你打开一个文件时,你实际上是在分配内存来存储信息的文件。 所以,你必须有信息的文件的文件指针, 如它是开放的,它的大小,在那里你目前在该文件中, 这样就可以在文件中读取和写入到特定的地方调用。 您最终成交的指针,而不是关闭的文件名。 是吗? [丹尼尔]因此,为了使用租用,你会说 - 它是如何获得用户输入? fprintf像在这个意义上,它会等待用户输入的GetString 要求用户输入 - 等待你输入这三样东西? 或者你需要使用的东西,落实租? >>呀。因此,我们不 - 的问题是,如何得到用户输入 为了实现租吗?我们这里是出租的呼叫者, 在这种人员结构,所有的数据存储结构已经通过。 因此,fprintf是只是数据直接写入到文件。 有没有等待用户输入。 用户是否已经输入适当的把它在这名员工结构。 的事情,当然,将打破,如果这些指针为空的, 因此,我们在这里向后滚动,我们期待在我们的结构。 我们有字符串,字符串的第一个字符串的邮件。 现在我们知道,所有的那些真正的,引擎盖下,是char *变量。 这可能会或可能不会被指向空。 他们可能指向堆内存, 也许内存堆栈。 我们真的不知道,但如果其中任一指针是空的,或无效的, 一定会崩溃,我们的租赁功能。 这是什么,这是一种超越的考试范围。 我们不担心这一点。 大。好吧。因此移动从测验。 让我们来关闭这个家伙,我们要看看在pset中4。 所以,如果你们看在pset规范,一旦你可以访问它的,cs50.net/quizzes, 我们要经过几个部分问题,今天。 我向下滚动 - 部分问题开始的第三页上的pset的规范。 第一部分要求你去观看短重定向和管道。 这是一个很酷的短,显示出一些新的,很酷的命令行技巧,您可以使用。 然后,我们有几个问题要问你。 这第一个问题,关于流,输出写入默认情况下, 一种感动刚才只是一点点。 我们刚才谈到的这fprintf需要在一个文件中*流作为它的参数。 FCLOSE需要在一个文件中*流为好, 和返回值的FOPEN给你一个文件*流以及。 究其原因,我们还没有看到那些之前我们已经处理了printf 是因为输出有一个默认的流。 而它的默认流写 你会发现在短期内。 所以,一定要看看它。 在今天的部分,我们要谈一点关于GDB ,因为你是比较熟悉的,它更多的练习,你得到它, 你能够更好地将追捕在自己的代码错误。 这加快了极大的调试过程。 因此,通过使用printf,每次你这样做,你必须重新编译你的代码, 你必须再次运行它,有时你必须左右移动printf调用, 注释掉的代码,它只是需要一段时间。 我们的目标是尝试说服你,你基本上可以用GDB, 在任何时候在你的代码中的printf什么,你永远不会有重新编译它。 你从来没有开始,并不断猜测的printf下。 要做的第一件事是复制这条线和部分代码的网站。 我说,“wget的http://cdn.cs50.net”这行代码复制。 我要复制它。 我要到我的设备,缩放,所以你可以看到我在做什么, 将其粘贴在那里,当我按下Enter键,这个wget命令从字面上是一个网络获得。 这是怎么回事拉下这个文件在互联网上传播, 它的将它保存到当前目录中。 现在,如果我列出当前目录中,你可以看到,我已经了这section5.zip的文件右键在那里。 那家伙处理的方式是将其解压缩, 您可以在命令行中,就是这样。 Section5.zip。 将它解压缩,创建文件夹对我来说, 膨胀中的所有内容,让他们在那里。 所以,现在我可以进入我的第5节目录使用cd命令。 清除屏幕采用了明确。因此,清除屏幕。 现在我有一个干净漂亮的终端处理。 现在,如果我列出我看到在这个目录中的所有文件, 你看到我已经得到四个文件:buggy1,buggy2,buggy3,并buggy4,。 我也得到了相应的文件。 我们不会看的。c文件。 相反,我们要使用它们时,我们打开了GDB。 我们已经把他们周围,使我们有机会获得实际的源代码,当我们使用GDB, 这部分的组成部分,但我们的目标是修改周围的GDB 看看我们如何能够用它来找出什么地方出了错这四个错误的程序。 所以我们仅仅是在房间里真的很快, 我要问别人的bug的程序运行一个, ,然后我们会去通过GDB作为一个群体,我们将看看我们能做些什么来解决这些计划, 或者至少确定发生了什么事情错在他们每个人。 让我们从这里开始与丹尼尔。你会运行buggy1?让我们看看会发生什么。 丹尼尔说,有一个应用程序错误。 >>呀。没错。 所以,如果我运行buggy1,我得到了赛格故障。 在这一点上,我可以去开拓buggy1.c, 试图找出什么地方出了错, 但该段故障错误最厌恶的事情之一 的是,它不会告诉你哪一行的程序的东西实际是错误的,并打破。 你有看的代码 找出猜测检查或printf来看看会发生什么错误。 关于GDB的最酷的事情之一是,它是真的,真的很容易 要弄清楚的线在你的程序崩溃。 这是完全值得的,使用它,即使只是。 启动GDB,我输入GDB,然后我给它的,我想运行的可执行文件的路径。 在这里,我打字:GDB ./buggy1。 按下回车键。给了我这一切版权信息, 在这里你会看到这条线,说:“从/ home /读取符号 jharvard/section5/buggy1。“ 如果一切顺利的话,你会看到它看起来像这样打印出的信息。 它会读取符号,它会说:“我从你的可执行文件,读取符号” ,然后将这个“做”的消息在这里。 如果你看到一些其他的变化,你看到它找不到符号 或类似的东西,这是什么意思,你只需要编译你的可执行文件。 当我们使用GDB编译程序,我们必须使用特殊的-g标志, ,这就是默认情况下,如果你编译你的程序,通过键入make 或车或恢复,任何人。 但是,如果你正在编译手动铛,那么你就必须去,其中包括,使用-g标志。 在这一点上,现在,我们有我们的GDB提示, 这是很简单的运行程序。 我们可以键入运行,我们就可以输入r。 大多数GDB命令都可以缩写。 通常只是一个或几个字母,这是相当不错的。 因此,萨阿德,如果你输入r,并按下Enter键,会发生什么呢? 萨阿德我SIGSEGV,分割故障,那么这一切的官样文章。 >>呀。 就像我们在屏幕上看到,现在,像萨阿德说, 当我们键入run或r并按下回车键,我们仍然得到同样的赛格故障。 因此,使用GDB不解决我们的问题。 但它为我们提供了一些官样文章,而事实证明,这种官样文章 实际上告诉了我们它的发生。 要解析这一点,第一位的功能是,在这一切是怎么回事了。 有__ strcmp_sse4_2,它告诉我们发生了什么事在这个文件中的 被称为sysdeps/i386,这一切,再次,种混乱 - 而行254。 这是一种很难解析。通常,当你看到这样的东西, 这意味着它在一个系统库段断层。 因此,做的strcmp。你们已经看到的strcmp。 不是太疯狂了,但是这是否意味着STRCMP破损或STRCMP,有一个问题吗? 你怎么想,亚历山大? [亚历山大]是 - 是254的行吗? - 不是二进制的,但是这不是他们的天花板, 然后有每个函数的另一种语言。 254在该函数中,或者 - ? >>这是第254行。它看起来像。文件,所以它的汇编代码可能。 但是,我想更紧迫的事情,因为我们已经得到了赛格故障, 它看起来像它来自strcmp函数, ,那么,这是否意味着strcmp的坏了? 它不应该有希望。因此,仅仅因为你有一个分割故障 系统的功能之一,通常这意味着,你只是还没有被称为是正确的。 的事情弄清楚什么是怎么回事 当你看到这样的疯狂的东西,每当你看到一个段故障, 特别是如果你有一个程序,使用多只主, 使用的回溯。 我写BT,而不是全面的回溯字缩写回溯。 但是,夏洛特,当你键入BT并按下回车键,会发生什么呢? 夏洛特它让我看到两行,0行和第1行。 >>呀。所以第0行和第1行。 这些是实际的栈帧目前在播放的时候,你的程序崩溃。 从最上面的帧,帧0开始,并持续到最底层的,这是第1帧。 我们的最高框架是strcmp的框架,。 你可以认为这是类似的问题,我们只是在做测验的指针, 我们交换堆栈帧上的主栈帧的顶部, 和我们的互换使用,主要使用的变量的顶部上的变量。 在这里我们的崩溃发生在我们的strcmp函数,这被称为我们的主要功能, 回溯,不仅给我们的功能在里面的东西没有, 但它也告诉我们,这里的一切,被称为。 所以,如果我多一点点滚动的权利, 我们可以看到,是的,我们在这个的strcmp,sse4.s文件的254行。 但有人呼吁在buggy1.c,6号线。 因此,这意味着我们可以做的 - 是我们可以去看看,看看发生了什么事 在6号线buggy1.c。 同样,也有几种方法可以做到这一点。一个是退出GDB 或者你的代码在另一个窗口中打开和交叉引用。 ,其本身而言,是非常方便的,因为现在,如果你在办公时间 和你已经有了一个段故障和TF想知道一切都被打破, 你可以说,“哦,第6行,我不知道这是怎么回事, 但6号线是什么导致我的计划打破。“ 做到这一点的另一种方法是,你可以使用此命令列表中GDB。 您也可以将其缩写为升。 因此,如果我们打升,我们在这里吗? 我们得到了一大堆怪异的东西。 这是实际的汇编代码 是在strcmp_sse4_2。 这看起来有点时髦的, 的原因,我们正在这是,因为现在, GDB我们在第0帧。 所以,任何时候我们看变量,任何时候我们来看看源代码, 我们正在寻找源代码的堆栈帧,涉及到我们目前所在 因此,为了获得什么有意义的事情,我们必须 转移到更有意义的栈帧。 在这种情况下,主要的堆栈帧将使一些更有意义, 因为这实际上是我们写的代码。 strcmp的代码。 的方式,可以在帧之间移动,在这种情况下,因为我们有两个, 我们有0和1, 你这样做的up和down命令。 如果我把一帧, 现在,我在主堆栈帧。 我可以向下移动到的地方,我回去, 再上去,走了,再上去。 如果你这样做你的程序在GDB,你得到一个崩溃时,你会得到回溯, 你看到它的一些文件,你不知道发生了什么事情。 您尝试列表,代码并不很熟悉你, 一起来看看在您的框架,并找出你在哪里。 你可能在错误的堆栈帧。 或者至少,你是不是你真的可以调试堆栈帧中。 现在,我们在适当的堆栈帧,我们在主, 现在我们可以使用list命令可以计算出该行是什么。 你也可以看到它,它打印我们就在这里。 但是,我们可以打列出所有相同,列表,为我们提供了这个漂亮的打印输出 实际的源代码,在这里是怎么回事。 特别是,我们可以看一下在第6行。 我们可以看到,在这里发生了什么事情。 它看起来像我们正在做的字符串比较 字符串“CS50石头”和argv [1]。 他到这里来是崩溃。 所以,大小姐,你有什么想法什么可能会在这里? [大小姐]我不知道为什么它的崩溃。 >>你不知道为什么它的崩溃? 吉米,有什么想法吗? [麦]我不能完全肯定,但我们最后一次使用的字符串比较, 或strcmp的,我们有三种不同的情况下。 我们没有一个==,我​​不认为,正确的,第一行。 相反,它被分离成三个,一个是== 0, 1 <0,我觉得,一个是> 0。 因此,也许这样的事情? >>呀。所以有这个问题 我们做的比较正确? Stella吗?有什么想法? 斯特拉]我不知道。 >>不知道。丹尼尔?思考?好吧。 事实证明,所发生的事情在这里是当我们运行程序时, 我们得到了赛格故障,当你第一次运行的程序,丹尼尔, 你给它任何命令行参数? [丹尼尔]第>>号在这种情况下,是什么时,argv [1]? >>没有任何价值。 “没错。 那么,有没有适当的字符串值。 但有一定的价值。被存储在那里的​​价值是什么? >> A垃圾的价值呢? >>它要么是一个垃圾值,或者,在这种情况下, argv数组结束始终与null的终止。 所以实际上存储在那里是空的。 另一种方法解决这个问题,而不是想着它通过, 是要设法把它打印出来。 这是我说的,使用GDB是伟大的, 因为你可以打印出所有的变量,所有你想要的值 使用这种方便,花花公子p命令。 所以,如果我键入p,然后我输入一个变量的值或变量的名称, 说是argc,我看到argc是1。 如果我想打印出来的argv [0],我可以这样做,就这样。 就像我们所看到的,ARGV [0]是你的程序的名称, 总是的可执行文件的名称。 这里你可以看到它有完整的路径名。 我还可以打印出的argv [1],看看会发生什么。 在这里,我们得到了这种神秘的值。 我们拿到的这款0x0的。 记得在学期开始时,我们谈到十六进制数字吗? 或者说,在年底的pset 0的小问题如何表示十六进制50? 我们编写的十六进制数在CS,只是为了不混淆自己 带小数点的数字,是我们始终以0x前缀。 因此,这0x前缀始终只是解释为十六进制数, 而不是字符串,而不是作为一个十进制数,而不是作为一个二进制数。 由于5-0是一个有效的十六进制数。 这是一个十进制数,50。 因此,这是我们如何消除歧义。 因此,0x0的装置,十六进制的0,这也是十进制0,二进制0。 只是,它的值为0。 事实证明,这是空的,其实是在内存中。 NULL就是0。 在这里,存储元件的argv [1]为null。 因此,我们试图比较“CS50石头”字符串为空字符串。 因此,提领空,试图访问空的东西, 通常会造成某种分割故障或其他不好的事情发生。 而事实证明,STRCMP不请检查 您是否已经通过了一个值,该值是空的。 相反,它只是前进,试图做的事情, ,如果段故障,段故障,这是你的问题。你必须去解决它。 真的很快,我们会如何解决这个问题?夏洛特? [夏洛特,您可以检查使用的话。 因此,如果argv [1]为null == 0,则返回1,什么不知所云。 >>呀。所以这是一个伟大的方式来做到这一点,我们可以查看, 我们的价值传递到strcmp的,ARGV [1],它是空? 如果是null,那么我们可以这样说好了,中止。 更常见的方式做,这是使用的argc的值。 在这里你可以看到在开始的主, 我们省略了,第一,我们通常做测试时,我们使用命令行参数, 这是我们ARGC值测试是否是我们所期望的。 在这种情况下,我们预计至少需要两个参数, 的程序名加一个其它。 因为我们使用的第二个参数在这里。 因此,有一些测试的结果之前,在我们的strcmp呼叫 argv是,测试是否至少2个,也做了同样的事情。 我们可以看到,如果再次运行程序。 您可以随时重新启动你的程序在GDB中,这是非常好的。 您可以运行,而当你传递参数给你的程序, 通过他们,当你调用运行,而不是当你启动GDB。 这样,你可以每次都用不同的参数调用程序。 所以运行,或者,我可以输入r,,让我们看看会发生什么,如果我们输入“你好”。 它总是会问你,如果你想从一开始就再次启动。 通常情况下,你想从一开始就再次启动它。 而在这一点上,它会重新启动一次,它打印出 该程序,我们正在运行,buggy1的说法您好, 并打印出这个标准了,它说,“你得到一个D,”悲伤的脸。 但是,我们不段错误。它说,过程正常退出。 所以,看起来还不错。 没有更多的赛格故障,我们做了过去, 所以看起来这的确是段故障错误,我们得到。 不幸的是,它告诉我们,我们需要一个D. 我们可以回去看一下代码,看看在那里发生了什么事情 要弄清楚什么是 - 为什么它告诉我们,我们得到了D. 让我们来看看,这里被这个printf说,你有D. 如果我们输入列表,你继续输入列表,它不断迭代,通过你们的节目, 因此,它会告诉你你的程序的前几行。 然后,它会告诉你在接下来的几行字,下一个块和下一块。 它会继续尝试下去。 现在,我们会得到“线16号超出范围。” 因为它只有15行。 如果你到了这一点,你想知道,我该怎么办?“你可以用help命令。 使用帮助“,然后给它一个命令的名称。 你看到GDB为我们提供了这样的东西。 它说,“不带参数,列出了10个多行后或以前上市。 列表 - 列出了前十行 - “ 所以,让我们尝试使用列表中减去。 这列出的前10行,你可以玩弄名单一点点。 你可以做的列表,列表 - ,你甚至可以给列出一个数字,如清单8, ,它就会列出了10条线,8号线附近。 你可以看到在这里发生了什么事,是你已经有了一个简单的if else。 如果您键入在CS50岩石,它会打印出“你会得到一个A.” 否则,它会打印出“你得到了D.” 游民镇。好的。是吗? [丹尼尔]因此,当我试图这样做CS50不带引号的岩石, 它说:“你得到了D.” 我需要引号来得到它的工作,这是为什么? >>呀。事实证明,当 - 这是另一个有趣的小花絮 - 当您运行该程序,如果我们运行和输入CS50岩石, 就像丹尼尔说他这样做,按Enter键, 它说,我们得到了D. 而问题是,这是为什么? 而事实证明,我们的终端和的GDB解析这些作为两个单独的参数。 因为当有一个空间,这暗示 第一个参数结束,即将开始的下一个参数。 结合的方式一分为二,或遗憾的是,成为一个参数, 使用引号。 所以现在,如果我们把它放在引号并再次运行它,我们可以得到一个A。 因此,只要回顾一下,没有引号,的CS50和岩石被解析为两个独立的参数。 随着行情中,它作为一个参数完全解析。 我们可以看到这一个断点。 到目前为止,我们已经运行我们的程序,它的运行 直到它赛格故障或点击错误 或直到它已退出所有已经完全正常。 这并不一定是最有帮助的事情,因为有时 你有一个错误在你的程序中,但它不是一个分割故障导致。 它不会导致程序停止或类似的东西。 GDB的方式来获得你的程序在一个特定的点暂停 是设置一个断点。 你可以做到这一点,一个函数名上设置一个断点 或者您可以设置一个特定的代码行上设置一个断点。 我想设置断点的函数名,因为 - 简单易记, 如果你真的一点点去改变你的源代码, 那么你的断点将留在你的代码在同一个地方。 然而,如果你使用的是行号,行号更改 因为你添加或删除了一些代码,然后断点都完全搞砸了。 其中最常见的事情,我做的是设置一个断点上的主要功能。 我经常会启动GDB,我会键入b主,按下回车键,将设置一个断点 的主要功能,只是说,“暂停程序尽快开始运行,” 这样一来,当我运行我的程序,比方说,CS50岩石的两个参数 并按下回车键,得到的主要功能,它就停在第一线, 前strcmp函数求值。 由于我停顿了一下,现在我可以开始摆弄周围,看是怎么回事 所有的不同的变量传递到我的程序。 在这里,我可以打印出argc和看看会发生什么。 请参阅argc是3,因为它有3个不同的值。 它得名的程序,它得到的第一个参数,第二个参数。 我们可以打印在ARGV [0],ARGV [1]和argv [2]。 所以,现在你也可以看到为什么这STRCMP调用是要失败的, 因为你看到,它并分裂成两个独立的参数的CS50和岩石。 在这一点上,一旦你已经打了断点,你可以通过你的程序继续加强 一行行,而不是重新启动程序。 所以,如果你不想再次启动程序,只是继续从这里开始, 您可以使用continue命令,并继续将运行该程序的结束。 就像它在这里做的。 不过,如果我重新启动程序,CS50岩石,它打我的断点再次, 而这一次,如果我不想去,一路过关斩将,其余的程序, 我可以用下一个命令,这也是我缩写与n。 而这将逐步的程序行线。 所以,你可以看的东西执行,作为变量的变化,事情得到更新。 这是相当不错的。 其他很酷的事情,而不是一遍又一遍,再重复同样的命令, 所以在这里,如果你只需要敲击回车 - 你看,我没有在任何输入 - 如果我只是打回车键,将重复前面的命令, 或以前的GDB命令,我刚刚放进去。 我可以保持按下回车键,它会通过我的代码行的行不断加强。 我会鼓励你们去看看其他bug的程序。 我们没有时间去通过所有这些今天在第。 源代码是存在的,所以你可以看到这是怎么回事 如果你真的卡幕后, 但最起码,练习启动GDB, 运行该程序,直到它打破了你, 回溯,搞清楚什么样的功能崩溃, 哪一行,打印出一些变量的值, 只是让你的感觉,因为这将真正帮助你前进。 在这一点上,我们要退出的GDB,你退出或者只是q。 如果你的程序的运行仍然在中间,并没有退出, 它总是会问你:“你确定你真的要退出吗?” 您可以打是肯定的。 现在,我们要看看我们的下一个问题,这是猫的程序。 如果你看重定向和管道短,你会看到汤米使用这个程序 基本上所有输出的文件打印到屏幕上。 所以,如果我遇到猫,这实际上是一个内置的程序到设备, ,如果你有Mac电脑,你可以做到这一点在您的Mac上,如果你打开​​终端。 而我们 - 猫,比方说,cp.c,并按下回车键。 这是什么做的,如果我们向上滚动一点点,看到我们跑线, 我们跑了cat命令,它实际上只是打印出来的内容cp.c我们的屏幕上。 我们可以再次运行它,你可以把多个文件一起。 所以,你可以做猫cp.c,那么我们也可以连接起来的cat.c的文件, 这是我们写的程序, 它会同时打印文件备份到我们的屏幕上。 因此,如果我们向上滚动一点点,我们看到,当我们跑了这猫cp.c,cat.c, 首先它打印出来cp文件,然后在下面的,它打印出cat.c的文件就在这里。 我们将用此来只是让我们的脚湿。 玩简单的打印到终端,请参阅如何工作的。 如果你们打开用gedit cat.c,按Enter键, 你可以看到我们写的程序。 我们这个漂亮的锅炉板,所以我们不必花时间打字了这一切。 我们还检查传入的参数 我们打​​印出不错的用法消息。 这是诸如此类的事情,再次,就像我们一直在谈论, 这几乎就像肌肉记忆。 只记得继续做同样的东西 总是打印出某种有用的消息 让人们知道如何运行你的程序。 随着猫,它是非常简单的,我们只是要通过不同的参数 被传递到我们的计划,我们要打印 其内容在一个时间的一个屏幕。 为了打印文件输出到屏幕上,我们要做的东西非常相似 我们所做的,在测验结束时。 在测验结束,聘用程序,我们要打开一个文件, 然后我们有打印到它。 在这种情况下,我们要打开一个文件,我们要读取它,而不是。 然后我们要打印的,而不是到一个文件中,我们要打印到屏幕上。 所以打印到屏幕上,你用printf之前全部完成。 所以这是不是太疯狂了。 但是,读取一个文件是一种奇怪的。 我们将通过那么一点点的时间。 如果你们回去,最后一个问题,您的测验,问题33, 我们要在这里做的第一行,打开文件,非常类似于我们在那里做了什么。 因此,斯特拉,是什么样子,当我们打开一个文件时,该行吗? [特拉]资本FILE *文件 - “”好了。 >> - 是平等的FOPEN。 “是啊。 在这种情况下是什么?这是在注释中。 >>这是在评论吗?的argv [i]的和r? “没错。正确的。斯特拉是完全正确的。 这是行看起来像什么。 我们将得到一个文件流变量,它存储在一个FILE *,所以全部大写字母, 将文件FILE *,这个变量的名称。 无论我们喜欢,我们可以把它称为。我们可以把它first_file,或file_i的,无论我们想要的。 在命令行上这个节目,然后通过的文件名。 因此,它存储在argv [我],然后我们要打开这个文件在读取模式。 现在,我们已经打开了文件,有什么事情,我们总是要记住做 每当我们打开一个文件?关闭它。 所以,大小姐,我们该如何关闭一个文件? [大小姐] FCLOSE(文件)>> FCLOSE(文件)。没错。 大。好吧。如果我们看一下在这里做评论, 它说,“开放的argv [i]和它的内容打印到stdout。” 标准输出是一个奇怪的名字。 stdout是我们的一种说法 我们要打印到终端,我们希望将其打印到标准输出流。 事实上,我们可以摆脱这个评论就在这里。 我要复制并粘贴它,因为这是我们所做的。 在这一点上,现在我们要读取的文件的点点滴滴。 我们已经讨论了一对夫妇的读取文件的方式。 哪些是你最喜欢这么远吗? 哪种方式,你见过,你还记得,读文件? [丹尼尔·弗里德? >>弗里德?因此,弗里德是其中之一。吉米,你知不知道什么人吗? [麦]第>>好。不。夏洛特?亚历山大?还有其他人吗?好吧。 因此,其他的是fgetc,是我们将使用一个不少。 还有fscanf,你们看到这里的模式吗? 他们都以f。任何一个文件。 有FREAD,fgetc函数,fscanf。这些所有的读出功能。 为写我们FWRITE,我们的fputc,而不是fgetc函数。 我们也fprintf,像我们看到的测验。 由于这是一个问题,涉及从文件中读取, 我们将使用这三个功能中的一个。 我们不打算使用这些功能。 这些功能都在标准I / O库。 所以,如果你看一下这个程序的顶部, 你可以看到,我们已经包含标准I / O库的头文件。 如果我们想弄清楚,我们要使用哪一个, 我们可以随时打开的手册页。 因此,我们可以输入man的stdio 并阅读所有的的stdio的输入和输出功能C. 我们已经可以看到啊,看看。提fgetc,它的一提的fputc。 所以,你可以向下一点点,看,说,fgetc函数 看它的man page。 你可以看到,它伴随着一大堆的其他功能: fgetc函数,FGETS,getc函数,getchar函数,获取,ungetc的函数,其输入的字符和字符串。 因此,这是我们如何从标准输入文件读取的字符和字符串, 这本质上是从用户。 这是如何做到这一点,在实际C. 因此,这是不使用的GetString和getchar功能 我们使用的从CS50库。 我们要做的这个问题的几种方法 所以,你可以看到两种不同的方法做这件事。 fread函数,丹尼尔提到的和fgetc函数都不错的办法做到这一点。 我认为fgetc函数是一个容易一些,因为它只有你可以看到, 一个参数,FILE *,我们试图读取的字符, 它的返回值是一个int。 这是一个有点混乱,对吗? 因为我们得到一个字符,那么为什么没有这个返回一个字符? 为什么这可能不会返回一个字符,你们有什么想法? [的大小姐答案,不知所云] >>呀。所以大小姐是完全正确的。 如果它是ASCII,那么这个整数被映射到一个实际的字符。 可能是一个ASCII字符,这是正确的。 这到底发生了什么。 我们使用的是一个int,只是因为它有更多的比特。 这是更大的不是一个字符的字符只有8位,32位机,1个字节。 和一个int的所有4个字节的空间价值。 事实证明,fgetc函数的工作方式, 如果我们向下滚动本手册页一点点,在我们的简介 向下滚动的方式。原来,他们用这种特殊的值EOF。 这是一个特殊的常量,fgetc函数的返回值 只要你打的文件,或者如果你得到一个错误。 事实证明,做这些比较EOF正确的, 你想有额外的大量的信息,你必须在一个int 而不是使用一个char变量。 即使fgetc函数有效地从一个文件中的字符, 你要记住,它返回的东西是你的类型为int。 这就是说,它很容易使用。 它给我们一个字符,所以我们要做的是保持要求的文件, “给我的下一个字符的下一个字符给我,给我的下一个字符。” 直到我们得到的文件结束。 这会拉一个字符的时间从我们的文件, 然后我们可以做任何我们喜欢它。 我们可以将其存储,我们可以将其添加为一个字符串,我们可以把它打印出来。 任何。 放大退了出去,并要回到我们的cat.c的程序, 如果我们要使用fgetc函数, 我们会如何对待这下一行代码? 我们将使用 - 弗里德会做一些事情略有不同。 而这个时候,我们只是要使用fgetc函数得到一个字符的时间。 为了处理一个完整的文件,我们可能会做什么? 在一个文件中有多少个字符? 有很多。所以,你可能希望得到一个 然后得到另一个获得,并得到另一个。 你认为我们可能要在这里使用的是什么样的算法? 什么类型的 - ? [亚历山大]一个for循环? “没错。 某些类型的循环。 一个for循环实际上是很大的,在这种情况下。 像你说的,这听起来像你想有一个循环,在整个文件, 获得一个字符的时间。 任何建议,可能看起来像什么? [亚历山大,不知所云] 好了,只是英语告诉我你想要做的是什么? [亚历山大,不知所云] 因此,在这种情况下,这听起来像我们只是试图在整个文件中环。 [亚历山大]所以,我为int的大小吗? >>的大小 - ? 我猜大小的文件,对不对?的大小 - 我们就会把它写这样的。 尺寸文件的时间,我+ +。 因此,原来的方式,你使用fgetc,这是新的, 是,有没有简单的方法得到的文件大小 这个“大小”式的结构,你已经看到过。 当我们在使用,fgetc功能,我们要介绍的某种 新的,时髦的语法for循环,而不是只使用基本的计数器 去逐个字符,我们要拉一个字符的时候, 一次一个字符的方式,以及我们所知道的,我们是在结束 是不是当我们已经数了一定数目的字符, 但是,当我们拉出来的字符是特殊的文件结束符。 因此,我们可以做到这一点 - 我把这种路,我们要初始化 我们的第一次调用出来的文件的第一个字符。 所以这部分就在这里,这是怎么回事字符的文件 并把它存储到变量ch。 我们将继续这样做,直到我们得到的文件, 我们做测试不等于EOF字符,特殊字符。 然后,而不是做CH + +,这将只是增加价值, 因此,如果我们看到一个出来的文件,一个大写的A,说, CH + +会给我们B,然后我们会得到C,然后按下d。 这显然​​不是我们想要的。我们想在这里 在这最后一点是,我们希望得到的下一个字符的文件。 所以,我们怎么可能得到的下一个字符的文件呢? 我们怎样才能从文件的第一个字符? [学生]:fgetfile? >> fgetc函数,对不起,你是完全正确的。 我拼错了就在那里。所以,是的。 在这里,而不是做CH + +, 我们只是调用fgetc函数(文件) 和存储相同ch变量的结果在我们的。 [学生提问,不知所云] >>这是这些FILE *人是特殊的。 他们的工作方式是 - 当你第一次打开 - 当你第一次作出这样的fopen的调用, 有效地作为开头的文件指针FILE *。 然后每次调用fgetc,移动一个字符通过该文件。 所以每当你打电话,你的文件增加一个字符指针。 ,并时,fgetc再次,​​你要搬到另一个字符 另一个字符和其他字符和其他字符。 [学生提问,难以理解] >> that's的 - 是啊。 这是一种这种神奇的引擎盖下。 你不断递增通过。 在这一点上,你能够真正与字符。 因此,我们怎么可能打印出来的画面,现在呢? 我们可以使用相同的printf的事情,我们之前使用。 我们一直在使用一学期。 我们可以调用printf, 我们可以通过在性格就是这样。 做到这一点的另一种方式是使用printf不必做此格式字符串,而不是 我们也可以使用其他的功能之一。 我们可以使用的fputc,可打印字符在屏幕上, 除非我们看的fputc - 让我放大了一点点。 我们看到了什么是好的,是需要我们读出的字符使用fgetc函数, 但后来我们给它一个流打印到。 我们也可以用putchar函数,就可以直接输出到标准输出。 因此,有一大堆不同的选择,我们可以使用的打印。 他们都在标准I / O库。 每当你想打印 - 输出,默认情况下,将打印的特殊标准输出流, 这是标准输出。 因此,我们就可以把它作为一种本魔法值,在这里的stdout。 哎呀。将分号外部。 这是很多新的,时髦的信息在这里。 很多,这是很地道的,在这个意义上,这是代码 是这样写的,只是因为它是干净的,易于阅读。 有很多不同的方式来做到这一点,您可以使用许多不同的功能, 但我们往往只是遵循相同的模式,一遍又一遍。 所以,不要惊讶,如果你看到这样的代码来了一次又一次。 好的。在这一点上,我们需要打破的一天。 感谢您的到来。感谢收看如果你在线。我们会看到你下周。 [CS50.TV]