[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，J，J > [学生] 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]