[Powered by Google Translate] [第4节 - 更舒适] [罗布·鲍登 - 哈佛大学] [这是CS50。 - CS50.TV] 我们明天有一个测验,如果你们不知道。 它基本上是你能看到在课堂上或在课堂上所看到的一切。 这包括指针,即使他们是一个非常新的主题。 你至少应该深入了解他们的高水平。 凡是过去在课堂上,你应该了解的测验。 所以,如果你有问题了,你可以问他们。 但是,这将是一个非常学生主导的会议,你们问的问题, 所以我希望人们有很多疑问。 有没有人有问题吗? 是。 >> [学生]你能对指针呢? 我就去了指针。 所有的变量一定住在内存中, 但通常你不担心你刚才说X + 2和y + 3 编译器将找出的东西都在那里生活。 当你正在处理的指针,现在你显式地使用这些内存地址。 因此,一个单一的变量将永远只住在任何给定的时间在一个单一的地址。 如果我们想声明一个指针,类型是什么的样子? 我要声明一个指针p。的类型是什么样子呢? [学生] * P。 >>呀。所以int * p。 我怎样才能使它指向为x? >> [学生]连字号。 鲍登]符号是真正的被称为运营商的地址。 所以当我说&X变量x的内存地址。 所以现在我有指针p,在我的代码中的任何地方,我可以使用* P 我也可以用x,这将是完全一样的东西。 (* P)。这是什么做的吗?这是什么明星是什么意思? [学生]是指在该点的值。 >>呀。 因此,如果我们看它,它可以是非常有用的,画出来的图 这是一个小方块的记忆,而这恰好为x值4, 然后我们有一个小盒子,内存为P, ,因此P点为x,所以我们画一个箭头从p到x。 所以,当我们说我们说* P框,即p。 星是按照箭头,然后做你想做的那个盒子在那里。 因此,我可以说,* P = 7;将去框,x和变化,7。 或者,我可以说Z = * P * 2,这是令人困惑,因为它的明星,明星。 一星级提领P,其他明星乘以2。 请注意,我可以有一样好更换用x * P。 您可以使用它们以同样的方式。 再后来我可以有p指向一个完全新的东西。 我只能说,P = &z; 所以,现在P没有再久点为x,它指向到z。 任何时候,我做* P一样做Ž。 因此,有用的事情是,一旦我们开始进入功能。 声明一个指针,它指向的东西,这是一种无用的 然后你只提领 时,你也可以使用原来的变量开始。 但是,当你进入功能 - 让我们说,我们有一些功能,诠释富, 需要一个指针,* p = 6; 就像我们之前看到的掉期,你不能做一个有效的交换和一个单独的函数 只是路过,因为一切都在C总是按值传递的整数。 即使当你通过指针是按值传递的。 碰巧的是,这些值是内存地址。 所以当我说富(P);我的指针传递到函数foo 然后foo是做* P = 6; 所以里面的函数,* p是等价于x, 但我不能使用x里面的这个函数该功能,因为它不是范围之内。 因此,* p = 6是唯一的出路,我可以访问另一个函数的局部变量。 或者,指针是唯一的出路,我可以访问另一个函数的局部变量。 [学生]:比方说,你想返回一个指针。你究竟是如何做到这一点? [鲍登]返回一个指针的东西,如int y = 3的回报&Y? >> [学生]是啊。 鲍登]好吧。你不应该这样做。这是不好的。 我想我看到了在这些演讲的幻灯片,你开始看到这个图的内存 在这里,你有内存地址0 在这里你有内存地址4演出或2到32。 那么,你已经得到了一些东西,有些东西,然后你有你的堆栈 你有你的堆,你刚开始学习,长大了。 [学生]是不是在堆上面的堆栈? 是啊。堆在上面,不是吗? >> [学生]好了,他把0之上。 [学生]:哦,他把0之上。 >> [学生]:哦,好吧。 免责声明:Anywhere与CS50你要这样看。 >> [学生]好吧。 只是,当你第一次看到栈, 就像当你想到一个堆栈,你认为彼此顶部堆放的东西。 因此,我们倾向于翻转左右,所以堆栈的增长就像一个堆栈通常会 代替堆栈垂下。 >> [学生]不要堆技术上长大过,虽然? 这取决于你的意思是长大了。 堆和栈的增长方向相反。 栈是在不断增长,在这个意义上,它的成长过程 朝着更高的内存地址和堆增长 在它的增长向低内存地址。 因此,顶端是0和底部是高的存储器地址。 他们两人都不断增长,只是在相反的方向。 [学生]:我刚才只是说,因为你说你把堆栈的底部 因为它的堆栈看起来更直观,因为在堆的顶部开始, 堆的本身上,所以that's - >>呀。 你也觉得长大了,大的堆空间,但堆栈如此。 因此,堆栈是一个样的,我们要展现长大的。 但是无论你看,否则会显示地址0顶部 和最高的内存地址的底部,所以这是你的内存通常的看法。 你有问题吗? [学生]:你能告诉我们更多的堆? 是啊。我会在第二。 首先,要追溯到为什么返回&Y是一件坏事, 在栈上,你有一堆的栈帧代表所有的功能 已被调用。 因此,忽略以前的事情,你的堆栈的顶部总是要的主要功能 因为这是第一个函数的调用。 然后当你调用另一个函数的堆栈是怎么回事增长下降。 所以,如果我调用一些函数,foo和它得到它自己的堆栈帧, 它可以调用一些函数,酒吧,它得到它自己的堆栈帧。 和酒吧可以是递归的,它可以调用本身, 等第二次调用酒吧是会得到它自己的堆栈帧。 因此,这些堆栈帧的局部变量 和所有的功能参数 - 此功能是在本地范围内的任何事情,这些堆栈帧。 因此,这意味着当我说像酒吧是一个函数, 我只是要声明一个整数,然后返回一个指针,该整数。 Ÿ生活? [学生] Y住在酒吧。 >> [鲍登]是啊。 在这个小广场的内存的某个地方,是一个较小的广场,有Y。 当我返回&Y,我返回一个指针,这个小的内存块。 但后来当函数返回时,它的堆栈帧被弹出堆栈。 这就是为什么它被称为堆栈。 这是堆栈的数据结构,如果你知道那是什么。 甚至像一个托盘堆叠的例子, 主要是要去的底部,然后你调用的第一个函数是要去最重要的是, ,你可以回到主,直到返回的所有功能,被称为 已放置在它上面。 [学生]所以,如果你做返回该值是Y,如有更改,恕不另行通知。 是的,it's - >> [学生]会被覆盖。 >>呀。 这是完全 - 如果你尝试 - 这也将是一个int *吧,因为它返回一个指针, 因此它的返回类型是int *。 如果您尝试使用这个函数的返回值,这是不确定的行为 因为指针所指向坏的内存。 >> [学生]好吧。 那么,如果,例如,你宣布INT * Y = malloc的(如sizeof(int))? 这是更好的。是。 [学生]当我们谈到如何将事情拖到我们的回收站 他们实际上没有被删除,我们只是失去它们的指针。 因此,在这种情况下,我们实际上是删除值是它在内存中仍然存在? 对于在大多数情况下,它会仍然存在。 但是,让我们说,我们碰巧调用一些其他的功能,巴兹。 巴兹在这里会得到它自己的堆栈帧。 这将覆盖所有的这些东西, 然后如果你稍后尝试和使用的指针,你有, 它不会是相同的值。 它已经改变了,只是因为你调用该函数巴兹。 [学生],但我们没有,我们仍然可以得到3? 鲍登在所有的可能性,你会的。 但你不能依靠这一点。 Ç只是说未定义的行为。 [学生]:哦,确实如此。好吧。 所以,当你想返回一个指针,这是在使用中的malloc来。 我其实只是返回的malloc(3 *表示sizeof(int))。 我们将在malloc的在第二,但对malloc的想法是所有的局部变量 始终走在堆栈中。 任何malloced的堆,它会永远,始终是在堆上 直到你明确地释放它。 因此,这意味着,当你的malloc东西,在函数返回后,它要生存下去。 [学生]程序停止运行后,它会生存吗? >>序号 好了,所以它会在那里,直到该程序是所有的方式运行。 “是的。 我们可以去当程序停止运行时,会发生什么。 您可能需要提醒我,但是这​​是一个完全独立的东西。 [学生]的malloc创建一个指针? >>呀。 的malloc的 - >> [学生]我认为的malloc指定的内存块的指针可以使用。 [鲍登我想,图。 >> [学生]因此,虽然此功能吗? [学生]是啊,malloc的指定,您可以使用一个内存块, 然后返回该内存块的地址。 鲍登]是啊。所以,当你的malloc,你抓住一些的内存块 这是目前在堆中。 如果堆太小,则堆只是要发展壮大,它生长在这个方向。 因此,让我们说,堆太小。 然后,它的增长一点点,返回一个指向块,只是增长。 当你免费的东西,你有更多的空间在堆中, 于是在以后调用malloc的,可以重复使用,您以前释放的内存。 malloc和free重要的事情是,它可以完全控制 这些内存块的整个生命周期。 全局变量是永远活着。 在其范围内的局部变量还活着。 只要你走过去花括号,局部变量都死了。 Malloced记忆是活着的时候,你希望它是活着的 然后被释放,当你告诉它被释放。 其实这些都是只有3种类型的内存,真的。 自动内存管理,这是堆栈。 事情发生自动为您。 当你说诠释的x,分配的内存诠释x。 当x超出范围时,内存回收的x。 有动态内存管理,这是malloc的, 这是当你有控制。 您可以动态决定内存时应该和不应该被分配。 然后是静态的,它只是意味着它永远常存, 这是全局变量。 他们只是一直在内存中。 有问题吗? [学生]:你可以定义一个块用花括号 但不必有一个if语句或while语句或类似的东西吗? 您可以在一个函数中定义一个块,但有花括号。 [学生]:所以,你不能只是像一个随机的一对花括号中的代码 有局部变量吗? >>是的,你可以。 里面的int酒吧,我们可以有{Y = 3;}。 这应该是在这里。 但是,这完全定义的诠释y的范围。 之后,第二个大括号,Y不能使用了。 你几乎从来没有这样做,虽然。 程序结束时会发生什么, 有一个误解/的一半谎言,我们只是使事情变得更容易给人以种。 我们告诉你,当你分配内存 你分配一些内存块,该变量。 但是你没有真正直接接触RAM永远在你的程序中。 如果你想起来了,我怎么德鲁 - 实际上,如果你通过在GDB中,你会看到同样的事情。 不管有多少次你运行你的程序或你正在运行什么程序, 栈总是要开始 - 你总是会看到的变数时,地址oxbffff的东西。 它通常是在该地区的某个地方。 但如何才能在2个节目可能有相同的内存的指针呢? [学生]有一些任意指定的地方应该是在RAM oxbfff 其实是可以在不同的地方,当该功能被称为。 是啊。这个词是虚拟内存。 我们的想法是,每一个过程,每一个正在运行的程序在您的计算机上 有自己的 - 让我们假设32位 - 完全独立的地址空间。 这是一个地址空间。 它有自己完全独立的4 GB的使用。 所以如果你同时运行2个节目,这个节目本身,看到4千兆字节 这个程序看到4 GB的本身, 这是不可能对这一计划取消引用指针 从这项计划中的内存。 虚拟内存是什么是一个进程的地址空间的映射 实际的事情上RAM。 所以,就看您的操作系统知道, 嘿,这家伙解引用指针oxbfff,这实际上意味着 他希望1000字节RAM, 而如果这个程序指针引用oxbfff,他真正想要的RAM字节10000。 他们可以随意相距甚远。 在一个单一的进程的地址空间,即使是真实的东西。 所以像看到所有4个千兆本身,但让我们说 - [学生]:是否每一个过程 - 比方说,你有一台电脑,只有4 GB的RAM。 每一个过程看到整个4 GB的吗? “是的。 但是,4千兆字节,它认为是骗人的。 这只是它认为这一切的记忆,因为它不知道任何其他过程中存在。 它只会使用尽可能多的内存,因为它实际上需要。 操作系统不会给RAM这一过程 如果它不使用任何内存在这整个地区。 它不会给该地区的记忆体。 但这样的想法是 - 我想 - 我不能想到一个比喻。 类比是很难的。 的虚拟内存的问题之一,它解决的事情之一 是的进程应该是彼此完全不知道。 所以你可以写任何程序,任何指针解除引用, 喜欢只写一个程序,说*(ox1234), 而这解引用的内存地址为1234。 但它的操作系统,然后翻译一下1234手段。 所以,如果1234年恰好是一个有效的内存地址,这个过程中, 就像是在堆栈上或什么的,然后将返回值的内存地址 据的过程都知道。 但是,如果1234是不是一个有效的地址,如发生土地 在一些小的内存,这里说的是超越的堆栈和堆超越 和你还没有真正使用,那么这时候你得到的东西一样的segfaults 因为你接触的记忆,你不应该接触。 这也是如此 - 一个32位系统,32位意味着你有32位以确定一个内存地址。 这就是为什么指针为8个字节,因为32位是8个字节 - 或4个字节。 指针是4个字节。 所以,当你看到一个指针一样oxbfffff上,即是 - 在任何给定的程序中,你可以构建任意的指针, 任何地方,从OX0的牛8 f's的 - FFFFFFFF。 [学生]:你不是说他们是4个字节? >>呀。 [学生]然后每个字节 - >> [鲍登]十六进制。 十六进制 - 5,6,7,8。所以指针,你要总是在十六进制。 这只是我们如何分类的指针。 每2个数字的十六进制数是1个字节。 因此,有4个字节是8个十六进制数字。 因此,每一个指针在32位系统将是4个字节, 这意味着,在你的过程,你可以构造任意4个字节 和一个指针,它 这意味着,只要它是知道的,它可以处理一个完整的2到32个字节的存储器。 虽然它并没有真正访问, 即使你的电脑只有512兆,它认为它有那么多的记忆。 和操作系统是足够聪明的,它只会分配你真正需要的。 它不只是去了,哦,一个新的进程:4个音乐会。 是啊。 >> [学生]什么牛是什么意思?你为什么不写呢? 这仅仅是十六进制的符号。 当你看到一个数字开始牛的,连续的东西是十六进制的。 [学生]您解释有关程序结束时会发生什么。 “是的。 程序结束时会发生什么事是操作系统 只是,它已经为这些地址,这就是它的映射删除。 操作系统可以现在只是给另一个程序使用该内存。 [学生]好的。 所以,当你分配在堆或堆栈或全局变量或任何东西, 他们都只是消失尽快程序结束 因为操作系统现在是免费的,任何其他进程的内存。 [学生]:即使有可能还是写的值吗? >>呀。 值很可能仍然存在。 这只是这将是很难得到他们。 它更难以得到他们比是在一个已删除的文件 因为被删除的文件类型的坐在那里,很长一段时间,和硬盘驱动器是大了很多。 因此,它要覆盖的不同部分的内存 在它发生之前,该文件用于在覆盖的内存块。 但主记忆体,RAM,循环快了很多, 所以要非常迅速地被覆盖。 或其他任何问题吗? [学生]我有一个不同的题目的问题。 “好了。 有没有人有问题吗? 好吧。不同的主题。 >> [学生]好吧。 我所经历的实践考验, 在其中的一个,它是谈论的sizeof 和它返回的值或不同类型的变量。 “是的。 它说,int和long都返回4,所以他们都是4个字节长。 是一个int和long之间有什么区别呢,还是同样的事情吗? 是的,是有差别的。 C标准 - 我可能会陷入困境。 就像C标准C是什么,官方文件C. 这是它说什么。 因此,在C标准只是说,一个字符将永远,永远是1字节。 之后的一切 - 简短的永远只是定义为大于或等于一个字符。 这可能是严格大于,但不积极。 一个int刚刚定义为大于或等于一个短。 长定义为大于或等于int。 和长的长是大于或等于长。 因此,C标准定义是唯一的相对顺序的一切。 实际的内存占用量普遍实施, 但它在这一点上相当不错的。 >> [学生]好吧。 因此,短裤几乎总是为2个字节。 诠释几乎总是要为4个字节。 长期多头几乎总是为8个字节。 和渴望,这取决于您使用的是32位或64位系统。 如此长的是将对应的系统的类型。 如果您使用的是32位系统,如家电,这将是4个字节。 如果您使用的是64位的,想了很多最新的计算机,这将是8个字节。 整数是几乎总是在这一点上的4个字节。 长期多头几乎都是8个字节。 在过去,整数用于仅是2个字节。 但是请注意,这完全满足所有这些关系大于等于。 ,只要是完全可以是相同的大小为一个整数, 它也可以是相同的大小作为一个长长。 它只是恰巧是在99.999%的系统,它是将等于 int或一个很长很长。它仅仅依赖于32位或64位。 >> [学生]好吧。 在彩车上,是如何指定的小数点位? 作为二进制一样吗? >>呀。 你不需要知道,CS50。 你甚至不知道,在61。 你不好好学习,真的在任何课程。 这只是一个表象。 我忘了确切的位分配。 浮点的想法是,你分配一个特定的比特数来表示 - 基本上,一切都在科学记数法。 所以,你分配一个特定的数位代表的数量,如1.2345。 我不能代表一个数字位数多于5。 然后,你也分配一个特定的数位,它往往能像 你只能去到了一定的数量,那样你可以有最大的指数, 和你只能去到一定指数, 喜欢,你可以有最小的指数。 我不记得确切的方式位被分配到所有这些值, 但在一定的比特数的致力于为1.2345, 另一个一定的比特数,致力于指数, 它的唯一可能代表一个具有一定规模的指数。 [学生]和双?那是像一个额外的长期持股量呢? >>呀。 这是同样的事情,但现在你用8个字节,而不是4个字节为float。 现在,您就可以使用9位或10位, 而这将是能上去到第300,而不是100。 >> [学生]好吧。 花车也是4个字节。 “是的。 好了,再次,它可能取决于整体上普遍实施, 但花车为4个字节,双打有8个。 双打被称为双,因为它们的两倍大小的花车。 [学生]好的。以及是否有两双吗? >>有没有。 我想 - >> [学生]喜欢长期多头? >>呀。我不认为如此。是。 [学生]在去年的测试的主要功能有问题 是程序的一部分。 答案是,它并没有你的程序的一部分。 在什么情况下?这是我所看到的。 [波顿似乎 - >> [学生]什么情况? 你有问题吗? >> [学生]是的,我可以肯定拉起来。 它没有,从技术上说,但基本上将是。 [学生]我看到了一个不同的年份的。 这是True或False:一个有效的 - “”哦,。c文件吗? [学生]所有的文件必须有 - [都讲一次 - 不知所云] 好吧。所以这是分开的。 。c文件只需要包含的功能。 你可以编译成机器代码,二进制文件,不管, 没有它的可执行文件。 一个有效的可执行文件必须有一个main函数。 你可以写100个函数在1个文件,但没有主 ,然后编译为二进制, 那么你就写另一个文件,只有主,但这些函数调用了一堆 在这个二进制文件在这里。 因此,如果你正在做的可执行文件,这就是链接器 是它结合了这2成一个可执行的二进制文件。 因此,一个c文件并不需要在所有的主要功能。 而在大的代码库,你会看到成千上万的文件和1个主文件。 还有问题吗? [学生]是另一个问题。 它说是一个编译器。 True或False? 得到的答案是假的,我明白了为什么它不是像铿锵。 但我们所说的是什么,如果不是吗? Make是基本上只是 - 我可以清楚地看到它调用它。 但它只是运行命令。 制作。 我可以拉起来。是啊。 哦,是的。同时也要做到这一点。 这说的make工具的目的是自动确定 一个大程序都需要重新编译件 发出的命令重新编译它们。 你可以让文件,绝对是巨大的。 让看文件的时间戳记,就像我们之前说的, 你可以编译单个文件,并且它不是,直到你得到的链接器 他们拼凑成一个可执行文件。 所以,如果你有10个不同的文件,你犯了一个变化1, 那么是什么牌子要做的只是重新编译,1个文件 然后重新链接起来。 但比它更笨。 它是由你来完全确定,这就是它应该做的。 默认情况下,有能力认识到这一点时间戳的东西, 但你可以写一个make文件做任何事情。 你可以写一个文件,这样,当你键入它只是CD到另一个目录。 我感到沮丧,因为我粘性里面的一切,我的产品 然后我看到的PDF从Mac。 所以我去搜索,我可以干什么去了,连接到服务器, 连接到服务器,我为我的设备,然后我打开PDF 被编译使用LaTeX。 但我感到沮丧,因为每一次我需要更新的PDF, 我不得不将它复制到一个特定的目录,它可以访问 它越来越烦了。 所以我写了一个make文件,你必须定义它使事情。 这是你如何使PDF LaTeX的。 就像任何其他的make文件 - 我猜你没见过的牌子文件, 但我们有一个全球性的品牌在家电文件,该文件只是说, 如果你要编译一个C文件,使用铿锵。 所以在这里,我让我说,在我的make文件 这个文件,你会想与PDF LaTeX的编译。 所以它的PDF胶乳做编译。 提出的,是不能编译。它只是运行这些命令中指定的顺序我。 所以它运行的PDF LaTeX的,它会将我希望它被复制到的目录, 它的CD的目录和做其他的事情, 但它是所有认识的文件改变时, ,如果它的变化,然后它会运行它应该运行的命令 当文件更改。 >> [学生]好吧。 我不知道在哪里的全球MAKE文件是为我检查出来的。 其他问题吗?任何东西,从过去的测验?任何指针的东西吗? 有一些微妙的指针一样的东西 - 我不会能够找到一个竞猜的问题上 - 但就像这样的事情。 请确保你明白,当我说* X * Y - 这不正是任何东西,我猜。 但是,像* X * Y,那是2个变量是在栈上。 当我说X = malloc的(如sizeof(int)),X仍然是一个变量在栈上, malloc的一些块以上的堆,我们在x点的堆。 因此,在栈上的堆的东西。 无论何时,只要你的malloc什么,你不可避免地存储它里面一个指针。 因此,该指针是在栈上,malloced块在堆中。 很多人感到困惑,并说* X = malloc的,x是在堆上。 号X点是在堆上。 x本身是在栈上,无论出于何种原因,除非你有X是一个全局变量, 在这种情况下,它正好是在另一个区域中的内存。 因此,跟踪,这些盒子和箭头图是很常见的测验。 或者,如果它不是0测验,测验1。 你应该知道所有的这些,在编译的步骤 因为你必须回答这些问题。是。 [学生]:我们能不能​​去这些步骤 - >>当然。 我们之前的步骤和编译预处理, 编译,汇编和链接。 预处理。那是干什么的? 这是最简单的一步 - 嗯,不喜欢 - 这并不意味着它应该是显而易见的,但是它是最容易的一步。 你们能实现它自己。是啊。 [学生]你有你的,包括像这样的副本,然后还定义。 它看起来的东西如#include和#define, 它只是复制和粘贴那些真正的意思。 因此,当你说#cs50.h的,预处理的复制和粘贴cs50.h 到该线。 当你说#定义x为4,预处理整个程序 4 x的所有实例替换。 因此,预处理器需要一个有效的C文件,并输出一个有效的C文件 事情已经复制和粘贴。 所以,现在编译。那是干什么的? [学生]从C到二进制。 鲍登]不走,一路为二进制。 [学生]为机器代码呢? >>这是不是机器代码。 [学生]大会? >>大会。 在大会之前,去所有的方式为C代码, 和大多数语言做这样的事情。 选择任何高级语言,如果你要编译它, 它的编译步骤。 首先,它要编译Python到C,那么它会编译C大会, 然后,大会会得到转换为二进制。 所以编译是把它从C到大会。 这个词编译通常是指把它从一个更高的水平 到一个较低的电平的编程语言。 因此,这是唯一的编译步骤,你开始与一个高层次的语言 并最终在一个低层次的语言,这就是为什么被称为编译步骤。 [学生]在编写过程中,让我们说你已经完成了#cs50.h. 编译器重新编译cs50.h,类似的功能,在那里, 翻译成汇编代码为好, 或将它复制并粘贴大会前的东西吗? cs50.h将几乎永远不会结束在大会。 函数原型和事情的东西一样,只是你要小心。 它保证了编译器可以检查的事情,比如你调用函数 用正确的返回类型和正确的参数之类的东西。 所以cs50.h将预处理到文件中,然后当它的编译 它基本上是扔掉后,确保一切都被正确调用。 但定义的函数在CS50库,这是分开cs50.h, 那些不会被单独编译。 这实际上会在连接步骤,所以我们会在第二。 但首先,什么是装配? [学生]:大会为二进制? >>呀。 组装。 我们不把它编译,因为大会是一个很值得纯二进制翻译。 大会二进制的逻辑是非常小的。 这就像在一个表中查找,哦,我们有这个指令; 对应于二进制01110。 因此,组装一般输出。o文件的文件。 o文件是我们之前说的, 怎样文件不需要有一个主要的功能。 可将任何文件编译成。o文件,只要它是一个有效的C文件。 它可以被编译成。Ø。 现在,连接实际上是带来了一堆的。o文件,并给他们带来一个可执行的。 因此,联做的是你能想到的的CS50库的。o文件。 这是一个已编译的二进制文件。 因此,当你编译你的文件,你的hello.c的,这调用getString, ,hello.c中被编译成hello.o hello.o是二进制。 它使用GetString的,所以它需要去到cs50.o的, 连接器smooshes在一起,并将其复制到这个文件中的GetString 和出来一个可执行文件,它需要的所有功能。 因此,cs50.o是不实际的O文件,但它是足够接近,有没有根本的区别。 因此,联刚带来了一大堆的文件 分别包含了所有的功能,我需要使用 并创建将实际运行的可执行文件。 因此,这也是我们之前说什么 在那里你可以有1000个。c文件,在编译所有。o文件, 这可能会需要一段时间,那么你改变1。c文件。 你只需要重新编译。c文件,然后重新链接一切, 连接一切重新走到一起。 [学生]当我们连接我们写lcs50吗? 是啊,所以lcs50。这标志信号连接器,你应该链接该库中。 有问题吗? 我们走了过来二进制以外的第一堂课,5秒吗? 我不认为如此。 你应该知道,我们已经讨论了所有的大的OS, ,你应该是可以的,如果我给你一个函数, 你可以说这是大O,大约。好,大O是粗糙的。 所以如果你看到嵌套的for循环遍历相同数量的东西, 如INT I,I > [学生] n的平方。 >>它往往为n的平方。 如果您有三重嵌套的,它往往是n的三次方。 这样的事情,你应该能够立即指出。 你需要知道插入排序,冒泡排序和归并排序和所有的人。 这是容易理解为什么他们是那些N的平方和n日志n和所有的 因为我觉得有一个测验的一年,我们基本上给你 冒泡排序算法的实现和说,“什么是这个函数的运行时间?” 所以,如果你认出来冒泡排序,那么你可以立即回答n的平方。 但是,如果你只是来看看,你不甚至需要认识到它的冒泡排序; 你可以说这是这样做的,这。这是n的平方。 [学生]有任何强硬的例子,你可以拿出, 像类似的想法搞清楚? 我不认为我们会给你任何强硬的例子。 冒泡排序的是艰难的,因为我们会去, 甚至认为,只要你明白,你遍历数组 为每个数组中的元素,这将是东西的N平方。 有一般的问题,比如在这里,我们有 - 哦。 就在几天前,道格声称,“我已经发明了一种算法,可以排序的数组 “n个数O(log n)的时间!” 那么,我们如何知道这是不可能的吗? [听不见的学生反应] >>呀。 最起码,你要摸每个数组中的元素, 所以这是不可能的,对数组进行排序 - 如果一切是在未排序的顺序,那么你将要接触的数组中的一切, 所以这是不可能做到这一点比O的n。 [学生]您向我们展示了这个例子能够做到这一点,在O n个 如果你使用了大量的内存。 >>呀。 而that's - 我忘了什么that's - 计数排序? 嗯。这是一个整数的排序算法。 我一直在寻找这个特殊的名字,我不记得上周。 是啊。这些类型的排序,可以完成的事情,大O的n。 但也有局限性,你只能使用整数到了一定的数量。 另外,如果你想的东西that's排序 - 如果您的阵列为012,-12,151,400万, 然后,单元素要完全毁掉整个分拣。 有问题吗? [学生]如果你有一个递归函数,它只是使递归调用 在一个return语句,这是尾递归, 这不使用更多的内存在运行时 或至少​​使用可比较的记忆体作为一个迭代的解决方案吗? 波顿:是的。 它可能会有点慢,但不是真的。 尾递归是相当不错的。 再看看堆栈帧,让我们说,我们有主 我们有int酒吧(X)的东西。 这不是一个完美的递归函数,但回报酒吧(X - 1)。 所以,很显然,这是有缺陷的。您需要基础案例之类的东西。 但这里的想法是,这是尾递归, 这意味着,当主调用酒吧,它会得到它的堆栈帧。 在这个堆栈帧,将是一个小的内存块 对应其参数x。 因此,让我们说主要发生在调用酒吧(100); 因此,x是要开始为100。 如果编译器认识到这是一个尾递归函数, 然后在酒吧禁止递归调用, 而不是制造新的栈帧,这是堆栈中开始增长在很大程度上, 最终它会运行到堆,然后你得到的segfaults 因为内存开始碰撞。 因此,而不是它自己的堆栈帧,它可以实现, 哎,我从来没有真正回来到这个堆栈帧, 所以不是我就替换此参数与99,然后开始酒吧。 ,然后它会再次将达到回转杆(X - 1), ,而不是一个新的堆栈帧,将刚刚替换其当前参数98 然后跳回到最开始的吧。 这些操作,更换,堆栈上的值,跳回到开始, 是非常有效的。 所以这不仅是一个单独的函数相同的内存使用情况,这是迭代 因为你只能使用1堆栈帧,但你没有苦难的缺点 调用函数。 调用函数可能会有点昂贵,因为它需要做的这一切设置 和拆卸,这一切的东西。 因此,这尾递归还是不错的。 [学生]:为什么不创建新的步骤? 因为它意识到它并不需要。 调用酒吧刚刚返回的递归调用。 因此,它并不需要做任何的返回值。 它只是将立即返回。 因此,它只是要更换自己的参数,并重新开始。 还有,如果你没有尾递归版本, 然后你会得到所有这些酒吧,这个酒吧返回 它有这一个,然后返回它的值,酒吧立即返回 ,并返回它的值这一个,那么它只是将立即返回 这一个,并返回它的值。 因此,要保存弹出所有这些东西的堆栈 因为返回值仅仅是将要通过所有的方式回到反正。 那么为什么不直接取代我们的观点与更新的参数,并重新开始吗? 如果该函数不是尾递归,如果你做这样的事情 - [学生]如果酒吧(X + 1)。 >>呀。 所以,如果你把它的条件,那么你正在做的事情的返回值。 或者即使你只是做回2条(X - 1)。 所以,现在巴( - 1)需要返回,以便它来计算该值的2倍, 所以现在它需要有自己的独立的堆栈帧, 现在,无论你怎么努力,你将需要 - 这是不是尾递归的。 [学生]我会尽量把一个递归的目标是一个尾递归 - 鲍登在一个理想的世界,但在CS50你没有。 一般情况下,为了得到尾递归,成立一个额外的参数 栏将采取整数X到Y y对应到极致的事情,你要返回。 那么你将要返回酒吧(X - 1),2 * Y。 所以,这只是一个高层次的,你怎么变换的事情是尾递归。 但是,额外的参数 - 然后在结束时你达到你的基本情况,你刚刚返回y 因为你已经积累的全部时间,你想要的返回值。 你这种已经这样做迭代,但使用递归调用。 有问题吗? [学生]也许指针的算术运算,如在使用字符串。 “当然可以。 指针的算术运算。 使用字符串时,很容易因为字符串是char明星, 字符是永远的,始终是一个单字节, 指针的算术运算相当于定期算术当你在处理字符串。 远的不说的char * s =“你好”。 因此,我们有一个内存块中。 它需要6个字节,因为你总是需要空终止符。 和char * s将要指向这个数组的开始。 因此,S点。 现在,这基本上是任何数组是如何工作的, 无论它是否是一个由malloc或无论是在堆栈上的回报。 任何数组基本上是一个指针阵列的开始, 那么任何数组操作,任何索引,只是进入该数组一定的偏移。 所以当我说类似[3]这是怎么回事s和计数3个字符中。 因此s [3],我们有0,1,2,3,因此s [3]将参考本升。 [学生]我们可以达到相同的值,执行s + 3,然后括号明星的吗? 是。 这是相当于*(+ 3); ,这是永远总是相等的,无论你做什么。 你永远需要使用括号的语法。 您可以随时使用*(+ 3)语法。 人们往往喜欢括号语法。 [学生]因此,所有的数组实际上是指针。 有轻微的区别,当我说×[4] >> [学生]这是否创建内存吗? 鲍登,是要创建4个的整数堆栈上的,所以16个字节的整体。 它会在栈上创建16个字节。 x未存储在任何位置。 这仅仅是一个符号参照开始的事情。 因为你声明的数组里面的这个函数, 编译器会做的仅仅是变量x的所有实例都替换 与它发生在选择把16个字节。 它不能这样做,因为用char * s是一个实际的指针。 它是免费的,再点其他的事情。 x是一个常数。你不能拥有它指向不同的数组。 >> [学生]好吧。 但这样的想法,这个索引是一样的,不管它是否是一个传统的阵列 或者如果它是一个指针的东西,或者如果它是一个到malloced数组的指针。 而事实上,它是相等的,这是同样的事情。 它实际上只是翻译的括号里面是什么,什么是左括号内, 把它们加在一起,并取消引用。 因此,这是一样有效(+)或S [3]。 [学生]你能有2维数组的指针指向? 它的难度。传统上,没有。 一个二维数组是一维数组使用一些方便的语法 因为当我说诠释x [3] [3],这真的只是1阵列的9个值。 因此,当I指数,编译器知道我的意思。 如果我说,X [1] [2],它知道我要到第二行,所以​​将跳过第3, 那么想的第二件事,所以它会得到这一个。 但它仍然只是一个一维数组。 所以,如果我想指定一个指向该数组的指针, 我想说的* p = X; x的类型是 - 粗略的说这是x的类型,因为它只是一个符号,它不是一个实际的变量, 但它仅仅是一个int *。 x是刚刚开始的指针。 >> [学生]好吧。 因此,我将无法访问[1] [2]。 我认为是有特殊的语法来声明一个指针, 什么可笑的,如int(* p [ - 这是绝对荒谬的。我什至不知道。 但有语法声明函数指针像括号和活动。 它甚至有可能不会让你这样做。 我回头看的东西,将真相告诉我。 我会寻找它以后,如果有一个语法点。但你永远不会看到它。 即使语法是过时的,如果你使用它,人们会感到困惑。 多维数组是非常罕见的,因为它是。 你几乎 - 好吧,如果你在做矩阵的事情,它不会是罕见的, 但在C中,你很少会被使用多维数组。 是啊。 >> [学生]:比方说,你有一个很长的阵列。 因此,在虚拟内存中,它似乎是所有连续, 彼此旁边的类似的元素, 但在物理内存中,它有可能为被分裂了吗? “是的。 虚拟内存的工作原理是,它只是分离 - 的分配单元是页面,这往往是4千字节, 所以说,当一个进程,哎,我想使用此内存, 操作系统会分配它的那个小4千字节的内存块。 即使你只使用一个单字节的内存块在整个不大, 操作系统是要给它完整的4千字节。 因此,这是什么意思是,我可以 - 让我们说这是我的筹码。 该协议栈可以被分开。我的堆栈可能是兆字节,兆字节。 我的筹码将是巨大的。 但栈本身被分裂成单独的页面, 如果我们看一下在这里,让我们说,这是我们的RAM, 如果我有2 GB的RAM,这是我的第零个字节的RAM地址为0,如实际, 这是2 GB一路下跌。 因此,该页面可能对应到这个块在这里。 在这里,该页面可能与此块。 这可能对应于这一个在这里。 因此,操作系统是免费分配物理内存 任何单独的页面随意。 这意味着,如果这条边界发生跨越阵列, 发生一个数组左和右以此顺序一个页面 那么该数组将被分割在物理内存中。 然后当你退出程序,当进程结束时, 这些映射将被删除,然后它是免费的,使用这些小的块,其他的事情。 还有问题吗? [学生]指针的算术运算。 >>哦,是的。 字符串比较容易,但看着像int类型, 使之恢复为int x [4]; 这是否是一个数组无论是到malloced 4个整数的数组指针, 它会以同样的方式来对待。 [学生]因此,数组的堆? [鲍登]数组是不是在堆上。 >> [学生]哦。 [鲍登]这类型的磁盘阵列的倾向是在堆栈上 除非你宣布它在, - 忽略全局变量。不要使用全局变量。 里面的功能,我说×[4]; 这将在栈上创建一个整数4块,这个数组。 但是这个malloc(4 *表示sizeof(int));要去的堆。 但在这一点上我可以在几乎相同的方式,使用x和p 其他比我说的例外,前行约,您可以重新分配p。 从技术上讲,它们的尺寸有所不同,但是这是完全不相干的。 你从来没有真正使用它们的大小。 我可以说的p P [3] = 2;或×[3] = 2; 您可以使用完全相同的方式。 因此,指针的算术运算 - 是的。 [学生]你没有做P *如果你有括号? 括号内是一个隐式的间接引用。 “好了。 其实,你在说什么的你可以得到多维数组 的指针,你可以做的是一样的东西,比方说,** PP = malloc的(如sizeof(*)* 5); 我就写了这一切第一。 我不想要的那一个。 好吧。 我在这里做的是 - 这应该是PP [I]。 因此,PP是一种指向指针的指针。 ,你mallocing PP指向到一个数组中的5 INT星。 因此,在内存中,你必须在栈上页 要5块,这些都是自己的指针指向一个数组。 然后,当我的malloc在这里,我的malloc的那些个人,每个指针 应该指向一个单独的4个字节的堆块。 因此,这点到4个字节。 而这一次不同的4个字节。 所有的人都指向自己的4个字节。 这给了我做多维的事情的一种方式。 我能说的PP [3] [4],但现在这是不一样的东西,多维数组 因为多维数组它翻译[3] [4]到一个单一的偏移量x数组。 这解引用,P,访问的第三个指标,然后解引用 访问 - 将是无效的 - 第二个索引。 而当我们的int x [3] [4]前一个多维数组 和双支架时,它真的只有一个解引用, 你一个单一的指针和一个偏移量, 这是真正的2D引用。 你跟着2个独立的指针。 因此,这在技术上也是可以让你有多维数组 其中每个单独的阵列是不同的尺寸。 因此,我认为锯齿状的多维数组是它叫什么 因为真正的第一件事可能指向的东西,有10个元素, 第二件事情可能指向的东西,有100个元素。 [学生]:是否有任何限制数的指针,你可以有 指向其他指针? >>序号 您可以有int ***** p。 返回指针的算术运算 - >> [学生]哦。 >>呀。 [学生]如果我有int ** p,然后我做了一个间接引用和我说,P *是等于这个值, 它只会做1级提领吗? “是的。 所以,如果我要访问的最后一个指针指向的东西 - 然后,你*** P。 “好了。 因此,这是p点到1块到另一个块,点,点到另一个块。 然后,如果你做* P =别的东西,那么你正在改变这 到现在指向一个不同的块。 “好了。 [鲍登],如果这些malloced,那么你现在已经泄漏的内存 除非你碰巧有这些不同的参考 因为你不能得到那些的,你只是扔掉了。 指针的算术运算。 ×[4];要分配4个整数的数组 其中x是要指向的数组的开始。 所以,当我说的东西,比如x [1],我想它的意思去的第二个整数的数组, 这将是这一个。 但实际上,这是4个字节到数组中,因为这个整数占用4个字节。 因此,偏移量为1的真正含义偏移量为1 倍的大小的任何类型的数组。 这是一个整数数组,所以它知道做1次为int的大小,当它想抵消。 其他的语法。请记住,这是相当于*(X + 1); 当我说指针+ 1,返回的地址指针存储 加1倍的大小的指针的类型。 因此,如果x = ox100,那么x + 1 = ox104。 你可以滥用这一点,说什么像char * C =(CHAR *)X; 现在c将是相同的地址作为x。 c是将是等于ox100, 但C + 1将是等于ox101的 因为指针的算术运算取决于您要添加的类型的指针。 所以C + 1,它着眼于C,它是一个字符指针,所以它要增加1倍大小的char, 总是为1,所以你得到101, 而如果我这样做,X + 1 x,它也仍然是100将是104。 [学生]:你可以使用C + +,以推进指针加1呢? 是的,你可以。 你不能这样做,因为x与x只是一个符号,它是一个常量,你不能改变x。 但C恰好是一个指针,所以C + +是完全有效的,这将增加1。 如果c是一个int *,然后是C + + 104。 + +指针的算术运算正如C +会做指针算术。 其实,这是怎么了很多事情,比如合并排序 - 创建副本的事情,而不是相反,你可以通过 - 就像如果我想通过这半年的数组 - 让我们删除一些这方面的。 比方说,我想通过这一边的数组的功能。 ,我会通过该功能是什么? 如果我通过X,我通过这个地址。 不过,我想通过这个特定的地址。所以,我应该通过什么吗? [学生]指针+ 2? [鲍登]所以x + 2。是。 这将是这个地址。 您还可以经常看到它为x [2],然后在该地址。 所以,你需要采取的地址,因为它的支架是一个隐式的间接引用。 X [2]指的是在此框中的值,然后你要的那个盒子的地址, 所以你说&X [2]。 所以,如何在合并排序的东西,你想通过一半的东西的清单 你真的只是通过X [2],而现在的递归调用, 从那里开始我的新数组。 最后一分钟的问题。 [学生]如果我们不把一个符号或一个 - 那是什么叫什么名字? >>明星吗? [学生]星。 “从技术上讲,反引用运算符,但 - >> [学生]:取消引用。 如果我们不把一个明星或一个符号,会发生什么,如果我只是说y = x和x是一个指针? y的类型是什么? >> [学生]我只想说,它的指针。 所以,如果你只是说y = x的,现在的X和Y点了同样的事情。 >> [学生]点了同样的事情。 如果X是一个int的指针吗? >>,它会抱怨,因为你不能分配的指针。 [学生]好的。 请记住,指针,即使我们把它们画为箭头, 真的,他们店 - * X - 实际上所有的x存储类似ox100, 我们正好代表指向存储在100块。 所以当我说诠释* Y = X,我只是复制ox100到Y, 我们只是要表示为y,也指向ox100。 如果我说我=(int)的X,然后我将存储任何的价值ox100是 在它里面,但现在它会被解释为一个整数,而不是一个指针。 但是,你需要的演员,否则,它会抱怨。 [学生]:所以你的意思是投 - 它是将要铸造的y值的x或铸造诠释的? [鲍登什么? [学生]好的。在这些括号将是一个x或AY? 鲍登]。 x和y是等效的。 >> [学生]好吧。 因为他们是两个指针。 >>呀。 [学生]因此,它会存储100的十六进制整数形式呢? >> [鲍登]是啊。 但不是无论它指向的值。 鲍登]是啊。 >> [学生]因此,只要整数形式的地址。好吧。 [鲍登]如果你想一些奇怪的原因, 你可以只处理指针和不会处理整数 只是,如int * x = 0处。 然后,你会得到真正的困惑,指针的算术运算后开始发生。 因此,它们存储的数字是毫无意义的。 它只是你怎么收场解释它们。 所以我复制ox100从int *为int, 我是自由分配 - 你们可能会被骂不投 - 我可以自由分配(*)ox1234到这个任意整数*类似。 所以ox123是一样有效的内存地址&Y。 &Y发生,这几乎是ox123返回的东西。 [学生]:这是一个非常酷的方式,从十六进制到十进制的形式, 一样,如果你有一个指针,将其转换成一个int类型的吗? 鲍登]你真的可以使用类似printf打印。 比方说,我有int y = 100。 因此我们知道printf(“%d个\ n - 你应该已经知道 - 打印为一个整数,%X。 我们只需要打印十六进制的。 因此,一个指针存储为十六进制, 和没有存储为十进制,整数。 一切都以二进制形式存储。 只是我们往往显示为十六进制的指针 因为我们认为在这4个字节的块的东西, 和内存地址往往是熟悉的。 我们是一样的,如果它开始与BF,那么它正好是在栈上。 因此,这只是我们的解释为十六进制的指针。 好吧。最后还有什么问题吗? 我会在这里了一下后,如果你还有什么。 这是该结束。 [学生]耶! [掌声] [CS50.TV]