1 00:00:11,280 --> 00:00:13,650 好的 2 00:00:13,650 --> 00:00:14,390 欢迎回来 3 00:00:14,390 --> 00:00:19,260 这是CS50第二周 到目前为止 我们用了函数 4 00:00:19,260 --> 00:00:20,830 但是大部分时间 我们把这当作理所当然 5 00:00:20,830 --> 00:00:23,430 我们用了printf 它的副作用是 6 00:00:23,430 --> 00:00:24,110 在屏幕上输出东西 7 00:00:24,110 --> 00:00:25,790 我们用了GetInt 还有get float 8 00:00:25,790 --> 00:00:29,230 但如果你想要自己创造属于自己的函数 9 00:00:29,230 --> 00:00:31,740 你们当中一些人 虽然练习1没有要求 10 00:00:31,740 --> 00:00:33,140 但已经这样做了 11 00:00:33,140 --> 00:00:37,150 让我们回到 问用户要名字 12 00:00:37,150 --> 00:00:40,660 然后在屏幕上输出这个例子 13 00:00:40,660 --> 00:00:44,000 但是尝试提取出我们目前代码中的一些共性 14 00:00:44,000 --> 00:00:45,120 我的意思是 15 00:00:45,120 --> 00:00:47,315 我会创建一个新程序 16 00:00:47,315 --> 00:00:49,320 我叫它 hello.c 17 00:00:49,320 --> 00:00:53,730 在顶上 我会include stdio.h 18 00:00:53,730 --> 00:00:57,040 为了先发制人 我也要把CS50库包括进去 19 00:00:57,040 --> 00:00:59,080 这样编译器就不会骂我了 20 00:00:59,080 --> 00:01:02,400 然后就让我声明 int main (void) 21 00:01:02,400 --> 00:01:09,020 然后在这里 在这里我想开始把一些功能外包给一些函数 22 00:01:09,020 --> 00:01:12,090 我会来写这些函数 23 00:01:12,090 --> 00:01:13,820 虽然它们现在还不存在 24 00:01:13,820 --> 00:01:19,210 比如说 假设我想要写一个函数 25 00:01:19,210 --> 00:01:23,830 它能让我输出hello, 然后某个用户的名字 26 00:01:23,830 --> 00:01:29,010 我们不想再用printf hello, %s了 27 00:01:29,010 --> 00:01:33,380 如果有个函数叫print name 而不是 printf 不是很好么 28 00:01:33,380 --> 00:01:36,600 所以换句话说 我希望能写出一个 29 00:01:36,600 --> 00:01:37,710 能做类似事情的程序 30 00:01:37,710 --> 00:01:42,070 首先 我要说 printf 你的名字 这就提示用户 31 00:01:42,070 --> 00:01:46,150 把他或她的名字给我 然后我会用大家已经熟悉的string s 32 00:01:46,150 --> 00:01:47,290 来声明一个字串 33 00:01:47,290 --> 00:01:50,420 给我一个类型为字串的变量 命名为s 34 00:01:50,420 --> 00:01:52,120 使用GetString的结果存储在其中 35 00:01:52,120 --> 00:01:56,060 但是在过去的几周内 我都是用很麻烦的方式做的 hello, %s/n 36 00:01:58,630 --> 00:02:02,570 换句话说 我们已经看过这个例子很多次了 37 00:02:02,570 --> 00:02:05,280 这是个很细节的例子 因为它只占了一行代码 38 00:02:05,280 --> 00:02:06,860 所以重复输入这些也没什么问题 39 00:02:06,860 --> 00:02:09,990 但是如果这一行代码已经成为负担 40 00:02:09,990 --> 00:02:12,900 或者几周后 它就不再是一行 而是10行代码 41 00:02:12,900 --> 00:02:15,190 你觉得每次要复制粘贴 42 00:02:15,190 --> 00:02:17,180 或者重新打它一遍太麻烦 43 00:02:17,180 --> 00:02:22,100 如果能要不再输入printf hello, %s 什么的 44 00:02:22,100 --> 00:02:26,500 而是用一个叫print name的函数 45 00:02:26,500 --> 00:02:27,560 接受一个参数 46 00:02:27,560 --> 00:02:29,120 换句话说 它接受input 47 00:02:29,120 --> 00:02:30,620 然后分号 48 00:02:30,620 --> 00:02:33,240 这样一个函数 如果它存在的话 不是很好么 49 00:02:33,240 --> 00:02:36,690 这样我就不用担心printf是什么 %s是什么 50 00:02:36,690 --> 00:02:39,400 以及其他那些复杂又不那么有趣的东西了 51 00:02:39,400 --> 00:02:40,570 它们很有用 52 00:02:40,570 --> 00:02:44,700 但是print name 不幸的是 40多年前没被人创造出来 53 00:02:44,700 --> 00:02:45,980 没人想到要写它 54 00:02:45,980 --> 00:02:48,300 这时 有一门编程语言的好处就体现出来了 55 00:02:48,300 --> 00:02:52,930 就像在scratch里你可以自定义图块一样 在C语言和其他大多数语言中 56 00:02:52,930 --> 00:02:57,260 你可以自己定义你的功能 和自定义你的函数 57 00:02:57,260 --> 00:03:01,710 虽然 我们自动就能获得main 58 00:03:01,710 --> 00:03:02,730 我们还是能自己声明函数 59 00:03:02,730 --> 00:03:05,670 我要在这上面腾出点位置 然后我要来声明我的函数 60 00:03:05,670 --> 00:03:08,210 虽然一开始 它看上去会有点怪 61 00:03:08,210 --> 00:03:09,400 但我们后面会再回到讲它 62 00:03:09,400 --> 00:03:12,310 我要输入 void 这表示 63 00:03:12,310 --> 00:03:16,040 这个函数会做某事 它有个副作用 但它不像 get int或get string 64 00:03:16,040 --> 00:03:18,810 不会返回给我任何东西 65 00:03:18,810 --> 00:03:22,450 我要叫这个函数print name 然后我要说 66 00:03:22,450 --> 00:03:26,470 这个函数会接受一个字串 67 00:03:26,470 --> 00:03:27,600 我要叫它string name 68 00:03:27,600 --> 00:03:32,100 我想起什么名都可以 但我想要我的代码可以自文档化 69 00:03:32,100 --> 00:03:34,770 这也就是说 如果你们中任何一个人打开了这个文件开始读它 70 00:03:34,770 --> 00:03:39,020 你可以从input的名字上猜到它的功能 71 00:03:39,020 --> 00:03:42,270 在这底下 我会输入一对大括号 72 00:03:42,270 --> 00:03:47,140 请注意 我在第4到第7行 还是遵照了我 73 00:03:47,140 --> 00:03:51,622 这一周多来遵照的规律 74 00:03:51,622 --> 00:03:53,400 组成了main 75 00:03:53,400 --> 00:03:56,160 换句话说 print name是另外一个函数 76 00:03:56,160 --> 00:03:58,990 现在 编译器不知道要自动使用它 77 00:03:58,990 --> 00:04:02,670 因为我才刚把它发明出来 但是编译器知道要自动使用main 78 00:04:02,670 --> 00:04:08,710 然后在第13行 我在召出我自己的函数 79 00:04:08,710 --> 00:04:12,805 因为我在上面第四行 在main之前就声明了我的函数 80 00:04:12,805 --> 00:04:16,579 这会告诉编译器 81 00:04:16,579 --> 00:04:18,140 "print name"是什么意思以及它会做什么 82 00:04:18,140 --> 00:04:22,700 所以就好像是在Scratch里放了一块新的自定义图块一样 83 00:04:22,700 --> 00:04:27,240 在这里 我可以把我们经常见到的一堆代码放上去 84 00:04:27,240 --> 00:04:32,300 printf %s hello, %s/n" 85 00:04:36,720 --> 00:04:37,590 我要在这里放什么呢? 86 00:04:37,590 --> 00:04:39,200 S? 87 00:04:39,200 --> 00:04:41,420 在这个情况下 我想放name 88 00:04:41,420 --> 00:04:43,440 注意 这里和先前有点不同 89 00:04:43,440 --> 00:04:47,680 因为我在声明自己的函数 90 00:04:47,680 --> 00:04:50,880 我选择叫它print name 而我在括号中表示 91 00:04:50,880 --> 00:04:55,035 这个函数有一个参数 它的类型是字串 92 00:04:55,035 --> 00:05:00,010 所以它是一个字或词组或者类似的东西--而我把参数名称叫name 93 00:05:00,010 --> 00:05:04,770 这也就是说 唯一的变量 就是name 94 00:05:04,770 --> 00:05:07,780 S只在哪两个大括号中存在? 95 00:05:07,780 --> 00:05:12,990 第10行到第14行 就像我们在周一 96 00:05:12,990 --> 00:05:17,650 不能使用S一样 不过我可以把S放到print name里 97 00:05:17,650 --> 00:05:21,030 Print name正好给了它一个假名 一个化名 一个昵称 98 00:05:21,030 --> 00:05:24,400 就叫name 然后现在在这一行使用它 99 00:05:24,400 --> 00:05:26,840 现在让我保存 缩小窗口 100 00:05:26,840 --> 00:05:31,250 然后让我编译hello 101 00:05:31,250 --> 00:05:32,400 看上去很好 102 00:05:32,400 --> 00:05:36,110 没有出现错误 ./hello 回车 103 00:05:36,110 --> 00:05:37,020 我的名字是什么? 104 00:05:37,020 --> 00:05:38,060 David 105 00:05:38,060 --> 00:05:39,270 然后 hello David 106 00:05:39,270 --> 00:05:41,820 并没有太让人激动 不过请想一想 107 00:05:41,820 --> 00:05:44,310 你现在像我们在Scratch中一样 108 00:05:44,310 --> 00:05:45,420 有足够的原料来自己创造函数了 109 00:05:45,420 --> 00:05:46,770 但是事情并没有这么简单 110 00:05:46,770 --> 00:05:50,620 假设我没有想太多 111 00:05:50,620 --> 00:05:54,250 就把这个函数写在这了 112 00:05:54,250 --> 00:05:55,420 感觉很合理对吧 113 00:05:55,420 --> 00:05:58,440 在Scratch中 对你把脚本放哪并没有要求 114 00:05:58,440 --> 00:06:00,670 你可以一个放这 一个放这 一个放这 115 00:06:00,670 --> 00:06:03,310 如果不整齐放的话看上去会比较乱 116 00:06:03,310 --> 00:06:05,910 但是脚本在屏幕上在哪并不重要 117 00:06:05,910 --> 00:06:09,660 不幸的是 C语言 不像Java或Python这些语言 118 00:06:09,660 --> 00:06:13,600 顺序很重要 119 00:06:13,600 --> 00:06:15,830 因为注意下面会发生什么 120 00:06:15,830 --> 00:06:19,010 默认运行的函数 当然是main 121 00:06:19,010 --> 00:06:22,290 main会在第8行使用print name 但不幸的是 122 00:06:22,290 --> 00:06:26,660 编译器到第11行才会知道有print name这个东西存在 123 00:06:26,660 --> 00:06:28,520 但这太迟了 124 00:06:28,520 --> 00:06:30,660 所以让我们make hello 125 00:06:30,660 --> 00:06:32,950 不好 两个错误生成了 126 00:06:32,950 --> 00:06:36,050 让我滚动回最上面 每一次 我们都应该这样处理 127 00:06:36,050 --> 00:06:39,560 注意 它对我喊说 128 00:06:39,560 --> 00:06:40,540 "print name 函数隐式声明" 129 00:06:40,540 --> 00:06:43,860 我们以前看到过这个信息 "print name 函数隐式声明" 130 00:06:43,860 --> 00:06:48,080 我们在什么时候遇到过这个错误? 131 00:06:48,080 --> 00:06:49,180 当我没有包括库到程序里时 132 00:06:49,180 --> 00:06:53,470 如果我忘了包括cs50.h的话 我想用get string或get int的话就不行 133 00:06:53,470 --> 00:06:56,880 但这在里 print name这个函数并不在哪个库里不是么 134 00:06:56,880 --> 00:07:00,230 它是真的在这个文件里 所以问题到底在哪? 135 00:07:00,230 --> 00:07:04,660 不幸的是 在C语言中 如果你想让一个叫print name的函数存在的话 136 00:07:04,660 --> 00:07:08,640 它一定要你在你代码的最顶端 137 00:07:08,640 --> 00:07:11,940 这样后面的函数就可以用它了 138 00:07:11,940 --> 00:07:15,070 但说实话 很快这就会开始误导人 139 00:07:15,070 --> 00:07:18,160 我个人喜欢把main放在最前面 因为这样 140 00:07:18,160 --> 00:07:19,890 一看之下 就知道这个程序是要做什么的了 141 00:07:19,890 --> 00:07:23,290 另外 你还可能遇到极端情况 比如 142 00:07:23,290 --> 00:07:27,530 x会使用y但y也要使用x 143 00:07:27,530 --> 00:07:28,540 你就没法决定要把哪个放在上面 144 00:07:28,540 --> 00:07:31,230 但是在C中 其实我们可以简单地解决这个问题 145 00:07:31,230 --> 00:07:34,010 让我在上面腾一点空间 146 00:07:34,010 --> 00:07:38,170 然后虽然看上去有点累赘 但我要在这里教编译器 147 00:07:38,170 --> 00:07:42,320 有一个叫print name的函数存在 它接受字串 148 00:07:42,320 --> 00:07:46,330 然后我要叫它name; 149 00:07:46,330 --> 00:07:50,220 那么现在在第4行 我们以前没见过这个 150 00:07:50,220 --> 00:07:53,940 我们声明了函数print name 但它只是保证说 151 00:07:53,940 --> 00:07:56,620 这个函数我们后面会定义它 甚至会执行它 152 00:07:56,620 --> 00:08:00,180 我可以暂时不管这个了 153 00:08:00,180 --> 00:08:04,090 因为这是这个函数的定义 也是执行这个函数 154 00:08:04,090 --> 00:08:05,130 要做的最后一步 155 00:08:05,130 --> 00:08:08,450 这是有点傻有点烦人 但是C语言就是这样的 156 00:08:08,450 --> 00:08:12,050 这是因为它是按字面意义理解你的话 就像电脑那样 157 00:08:12,050 --> 00:08:16,020 只会做你叫它做的事 所以顺序很重要 158 00:08:16,020 --> 00:08:18,940 所以请记住这一点 然后请注意一些我们重复看见的规律 159 00:08:18,940 --> 00:08:21,850 你们大概会 如果你们还没有遇见的话 160 00:08:21,850 --> 00:08:24,700 看见像这个一样 第一眼看上去很难懂的信息 161 00:08:24,700 --> 00:08:29,000 但如果你开始找像"隐式声明"这样的关键词 162 00:08:29,000 --> 00:08:32,380 这里提到的是函数 而且有时你会发现一个绿色的^号 163 00:08:32,380 --> 00:08:35,010 它会告诉你问题出在哪里 164 00:08:35,010 --> 00:08:40,980 你就可以从那开始解决你的问题了 165 00:08:40,980 --> 00:08:45,860 对于这样写你们自己的函数 有没有什么问题? 166 00:08:45,860 --> 00:08:47,540 让我们来做点更激动人心的事 167 00:08:47,540 --> 00:08:51,760 这一次我们不要仅仅用这个副作用是输出东西的函数 168 00:08:51,760 --> 00:08:55,340 让我来保存一个新文件 我把它叫做positive.c 169 00:08:55,340 --> 00:08:57,600 虽然这个和上次的会有所不同 170 00:08:57,600 --> 00:09:01,910 这一次 我想要再执行一次上节课的positive.C 171 00:09:01,910 --> 00:09:04,430 也就是反复问用户要一个正整数的程序 172 00:09:04,430 --> 00:09:07,280 不过上一次我用了get int 173 00:09:07,280 --> 00:09:10,780 如果有一个函数 叫get positive int 174 00:09:10,780 --> 00:09:13,610 让我可以把这部分功能外包出去 不是更好么 175 00:09:13,610 --> 00:09:16,480 这里的差别在于 我们会执行 get positive int 176 00:09:16,480 --> 00:09:20,330 但不像print name 只有副作用 它不会返回给我 177 00:09:20,330 --> 00:09:21,710 一个数字或者一个字串 178 00:09:21,710 --> 00:09:25,510 get positive int 当然 我希望 它会给我一个正整数 179 00:09:25,510 --> 00:09:26,170 所以让我们行动起来 180 00:09:26,170 --> 00:09:30,840 Include cs50.h, Include standard io.h. 181 00:09:30,840 --> 00:09:33,520 Int main void. 182 00:09:33,520 --> 00:09:42,160 这里 我要输入 int n = 183 00:09:42,160 --> 00:09:44,270 get positive int 184 00:09:44,270 --> 00:09:49,080 get int已经存在了 因为我们的团队写了它 185 00:09:49,080 --> 00:09:53,950 我在这里要假设get positive int存在 186 00:09:53,950 --> 00:09:57,730 然后我会输入printf thanks for the %i/n",n 187 00:10:02,940 --> 00:10:07,770 如果我现在编译这个程序 我在屏幕下方的的终端窗口中 188 00:10:07,770 --> 00:10:09,075 会发生什么? 189 00:10:11,580 --> 00:10:13,900 我大概会得到和先前相同的错误 190 00:10:13,900 --> 00:10:14,570 那就让我们试一下 191 00:10:14,570 --> 00:10:16,450 make positive 192 00:10:16,450 --> 00:10:19,900 我们又一次看到了 get positive int函数隐式声明 193 00:10:19,900 --> 00:10:21,970 我们可以用几种方法解决这个问题 194 00:10:21,970 --> 00:10:27,310 我会用简单的办法 在这上面 195 00:10:27,310 --> 00:10:28,120 声明 get positive int这个函数 196 00:10:28,120 --> 00:10:29,720 我需要所谓的 签名 197 00:10:29,720 --> 00:10:32,410 签名就是程序 198 00:10:32,410 --> 00:10:34,090 的这个第一行代码 199 00:10:34,090 --> 00:10:37,420 所以get positive int会返回什么东西呢? 200 00:10:37,420 --> 00:10:37,970 一个整数 201 00:10:37,970 --> 00:10:41,540 我是说 在情况下 它会把一个正整数这样东西返回给你 202 00:10:41,540 --> 00:10:42,160 但这种情况并不存在 203 00:10:42,160 --> 00:10:45,280 我们在数据类型中没有见过正整数 204 00:10:45,280 --> 00:10:47,170 所以我们必须接受我们可以使用的数据类型十分有限 205 00:10:47,170 --> 00:10:50,360 但我们可以返回一个整数 然后相信它会是正的 206 00:10:50,360 --> 00:10:52,690 就叫它get positive int 207 00:10:52,690 --> 00:10:55,122 它的参数呢? 208 00:10:55,122 --> 00:10:56,440 它会接受input么 209 00:10:56,440 --> 00:10:58,280 它需要input么 210 00:10:58,280 --> 00:11:00,900 所以它不需要提前知道什么 211 00:11:00,900 --> 00:11:03,220 get string不用 get int也不用 212 00:11:03,220 --> 00:11:06,430 printf需要 - 它需要有人把input 放到其中去 - 然后 213 00:11:06,430 --> 00:11:09,020 print name也需要input 但是 get positive int不需要 214 00:11:09,020 --> 00:11:11,530 所以我会清楚地告诉编译器 void 215 00:11:11,530 --> 00:11:13,470 void表示没有其他任何东西 216 00:11:13,470 --> 00:11:17,990 所以void表示在这些括号间 不需要放任何东西 分号 217 00:11:17,990 --> 00:11:20,840 现在 到我的文件的底部 218 00:11:20,840 --> 00:11:23,640 我只是习惯地把main放在顶端 这样训练一下挺好 219 00:11:23,640 --> 00:11:26,220 因为这样一来 无论是你还是其他人 在任何时候打开你的文件 220 00:11:26,220 --> 00:11:27,400 这些功能都会在 221 00:11:27,400 --> 00:11:29,660 你可以从头开始研究 222 00:11:29,660 --> 00:11:34,190 现在我要复制一下这个 get positive int void 223 00:11:34,190 --> 00:11:35,430 但我在这里不会加一个分号 224 00:11:35,430 --> 00:11:38,280 我要输入左大括号 225 00:11:38,280 --> 00:11:39,700 然后我要借用一些周一的想法 226 00:11:39,700 --> 00:11:44,450 你们还记得 我们曾做过 如果某事为真 227 00:11:44,450 --> 00:11:45,830 就执行某事 228 00:11:45,830 --> 00:11:46,630 我是怎么做到的呢? 229 00:11:46,630 --> 00:11:51,540 我先说 给我一个正整数 230 00:11:51,540 --> 00:11:52,430 这是一个提示 231 00:11:52,430 --> 00:11:53,540 我可以说任何的话 232 00:11:53,540 --> 00:11:54,960 然后我用了什么? 233 00:11:54,960 --> 00:11:59,530 Int n = get int 234 00:11:59,530 --> 00:12:00,550 这里没有参数 235 00:12:00,550 --> 00:12:04,680 当你用一个函数时 你不用输入void 236 00:12:04,680 --> 00:12:08,570 你只有在声明函数时才要这样做 237 00:12:08,570 --> 00:12:09,780 告诉编译器它会看到什么 238 00:12:09,780 --> 00:12:11,650 所以你不用自己输入void 239 00:12:11,650 --> 00:12:12,940 现在我的条件是什么? 240 00:12:12,940 --> 00:12:19,670 是 n不等于正数 但这只是伪代码 241 00:12:19,670 --> 00:12:22,530 我要怎样才能表达得更简洁呢? 242 00:12:22,530 --> 00:12:24,090 小于或等于0 243 00:12:24,090 --> 00:12:26,250 请注意 你可以同时表示小于与等于 244 00:12:26,250 --> 00:12:28,100 虽然这是两个不同的符号 你可以 245 00:12:28,100 --> 00:12:29,350 用键盘这样表达 246 00:12:29,350 --> 00:12:33,950 但这里还是有个bug 我上次也犯了这个错误 247 00:12:33,950 --> 00:12:36,950 我需要声明-- 248 00:12:36,950 --> 00:12:37,460 没错 249 00:12:37,460 --> 00:12:39,640 我需要在循环外声明n 250 00:12:39,640 --> 00:12:44,180 所以我要把n放到这上面来 然后我不想 251 00:12:44,180 --> 00:12:46,480 在这里重新声明n 除非我有了一个新的变量 252 00:12:46,480 --> 00:12:48,860 我只是想在这把值赋给它 253 00:12:48,860 --> 00:12:54,320 现在我的工作还没有结束 254 00:12:54,320 --> 00:12:57,290 但让我假装我都做完了 255 00:12:57,290 --> 00:13:01,220 make positive 现在出现了一个新错误 256 00:13:01,220 --> 00:13:04,550 控制达到非void函数底端 257 00:13:04,550 --> 00:13:07,760 这是一条新的错误信息 但如果你能把这些词分开看 258 00:13:07,760 --> 00:13:09,620 它指向错误的来源 259 00:13:09,620 --> 00:13:11,240 控制 260 00:13:11,240 --> 00:13:14,250 控制指的是程序中操作的顺序 261 00:13:14,250 --> 00:13:16,510 电脑在控制下 但是产生了错误 262 00:13:16,510 --> 00:13:18,510 它到达了非void函数的底端 263 00:13:18,510 --> 00:13:21,760 这里它是在说什么函数呢? 264 00:13:21,760 --> 00:13:24,790 哪一个函数是非void的? 265 00:13:24,790 --> 00:13:27,400 是get positive int 这有点绕人 266 00:13:27,400 --> 00:13:29,010 它从某种意义上来说是void的 267 00:13:29,010 --> 00:13:33,070 它的参数可以是void 的 但是它的output 268 00:13:33,070 --> 00:13:34,540 类型会是n 269 00:13:34,540 --> 00:13:37,260 所以左边的字是所谓的返回类型 270 00:13:37,260 --> 00:13:40,320 里面的字是函数会接受的 271 00:13:40,320 --> 00:13:41,970 0或是更多的参数 272 00:13:41,970 --> 00:13:44,060 所以我需要做什么呢? 273 00:13:44,060 --> 00:13:47,650 现在在我的代码里 第21行 也就是光标所在处 274 00:13:47,650 --> 00:13:51,430 我在变量n中储存了一个正整数 275 00:13:51,430 --> 00:13:55,200 我要怎么把它给main呢? 276 00:13:55,200 --> 00:13:55,960 很直观地 277 00:13:55,960 --> 00:13:59,320 Return n; 278 00:13:59,320 --> 00:14:04,090 所以 就像Colton把一张写着答案的纸 放在黑盒里 279 00:14:04,090 --> 00:14:07,020 将它给返回给了我 280 00:14:07,020 --> 00:14:10,100 在代码中 你想要达到这个效果 只要写 return n就行 281 00:14:10,100 --> 00:14:12,140 这就像是Colton把东西递回给我一样 282 00:14:12,140 --> 00:14:15,870 这样一来 get positive int会把一个正整数 283 00:14:15,870 --> 00:14:19,220 返回给谁呢? 284 00:14:19,220 --> 00:14:21,380 这个值最后会去哪呢? 285 00:14:21,380 --> 00:14:29,080 会到这个变量n里 然后我们继续第9行 286 00:14:29,080 --> 00:14:31,920 所以换句话说 就操作顺序来说 这个程序运行了 287 00:14:31,920 --> 00:14:34,430 然后编译器意识到 你需要库哟 288 00:14:34,430 --> 00:14:36,310 那让我从里面抓点东西出来 289 00:14:36,310 --> 00:14:37,750 哦 你想要standard IO库? 290 00:14:37,750 --> 00:14:39,660 那我就从里面抓点东西出来 291 00:14:39,660 --> 00:14:44,510 当读到第4行时 编译器会对自己说什么? 292 00:14:44,510 --> 00:14:47,980 你保证你会执行叫get positive的函数的 293 00:14:47,980 --> 00:14:50,820 但我们后面会来看这个 在这些行附近 294 00:14:50,820 --> 00:14:53,450 Int main void 表示我程序的主要部分在这 295 00:14:53,450 --> 00:14:54,990 第7行只是个大括号 296 00:14:54,990 --> 00:14:59,540 第8行是在说 在左边 给我一个32位的整数 把它叫做n 297 00:14:59,540 --> 00:15:02,160 在右手边 它说 获取一个正整数 298 00:15:02,160 --> 00:15:07,120 让我们停一下 因为我的鼠标不再往下滚了 299 00:15:07,120 --> 00:15:11,720 我的鼠标现在又开始向下了 因为get positive int开始运行了 300 00:15:11,720 --> 00:15:13,100 整数n被声明了 301 00:15:13,100 --> 00:15:14,040 做以下的事 302 00:15:14,040 --> 00:15:16,090 printf 给我一个正整数 303 00:15:16,090 --> 00:15:19,740 从用户手中获取一个整数 把它存储在n 里 然后也许一次次重复这件事 304 00:15:19,740 --> 00:15:23,010 这个循环表示 这个代码也许会一直这样上下来回地执行 305 00:15:23,010 --> 00:15:25,810 但当用户终于配合 给了我一个正整数 306 00:15:25,810 --> 00:15:31,750 我到了第21行 在这里 数字会被返回 307 00:15:31,750 --> 00:15:34,280 那现在我应该高亮什么? 308 00:15:34,280 --> 00:15:35,070 9 309 00:15:35,070 --> 00:15:39,010 控制回到了第9行 310 00:15:39,010 --> 00:15:40,650 也就是说 第9行现在有了控制权 311 00:15:40,650 --> 00:15:43,250 这也是一直以来 程序里面发生的事情 312 00:15:43,250 --> 00:15:46,480 但当我们用像printf或get string这种其他人写的代码时 313 00:15:46,480 --> 00:15:50,600 控制权被交给了别人的代码行 314 00:15:50,600 --> 00:15:51,290 一行一行地进行 315 00:15:51,290 --> 00:15:53,770 我们只是看不见这个过程 316 00:15:53,770 --> 00:15:57,620 也想像不出来 因为它发生在这个在硬盘的其他文件中 317 00:15:57,620 --> 00:16:00,000 在哪我们不知道 318 00:16:00,000 --> 00:16:02,100 现在让我们来编译然后运行它 319 00:16:02,100 --> 00:16:03,890 Make positive 320 00:16:03,890 --> 00:16:05,260 编译了 这是进步 321 00:16:05,260 --> 00:16:06,650 ./positive 322 00:16:06,650 --> 00:16:08,020 给我一个正整数 323 00:16:08,020 --> 00:16:08,800 让我们不配合一下 324 00:16:08,800 --> 00:16:10,430 -1 325 00:16:10,430 --> 00:16:11,360 0 326 00:16:11,360 --> 00:16:13,370 让我们给它50 327 00:16:13,370 --> 00:16:18,100 谢谢给我50 现在 控制权又回来了 328 00:16:18,100 --> 00:16:21,750 对这个有什么问题么? 329 00:16:21,750 --> 00:16:23,180 什么? 330 00:16:23,180 --> 00:16:25,630 [学生提问] 331 00:16:25,630 --> 00:16:26,130 请再说一遍 332 00:16:26,130 --> 00:16:27,860 好问题 333 00:16:27,860 --> 00:16:31,100 你们可能会注意到这里有个平行结构 334 00:16:31,100 --> 00:16:35,420 在第12行 我说 get positive int返回了一个整数 335 00:16:35,420 --> 00:16:39,660 但依据同样的逻辑 在第6行 那main就返回了一个整数 336 00:16:39,660 --> 00:16:44,040 但我们在程序中没有提到过什么 337 00:16:44,040 --> 00:16:46,470 我们从没有提过这个关键词 返回 338 00:16:46,470 --> 00:16:49,970 所以在C语言中 至少在我们现在用的这个1999年的版本中 339 00:16:49,970 --> 00:16:55,750 这个是自动完成的 340 00:16:55,750 --> 00:16:59,300 任何时候 当你执行一个程序 执行main这个函数时 341 00:16:59,300 --> 00:17:04,230 如果你没有作其他规定 这个函数就会默认把0返回给你 342 00:17:04,230 --> 00:17:05,849 0只是约定俗成的数字 343 00:17:05,849 --> 00:17:09,430 返回0就表示 一切正常 344 00:17:09,430 --> 00:17:13,040 这样有其他400万种出错的可能 345 00:17:13,040 --> 00:17:17,530 如果返回了1 这可能表示 346 00:17:17,530 --> 00:17:18,310 有哪里出错了 347 00:17:18,310 --> 00:17:20,589 也可以返回2 这表示另一个地方出错了 348 00:17:20,589 --> 00:17:23,440 也可以返回400万 这表示又有另一个地方出错了 349 00:17:23,440 --> 00:17:27,170 现在如果你们想一下自己的PC或MAC 你们可能会记得 350 00:17:27,170 --> 00:17:29,610 从你用的某个软件里看到过难懂的错误信息 351 00:17:29,610 --> 00:17:32,650 有时它上面会有人类能读懂的解释 352 00:17:32,650 --> 00:17:35,265 但时常 上面只有一个代码或是一个数字 353 00:17:35,265 --> 00:17:37,800 如果你们不记得遇过这样的情况 那以后可以注意一下 354 00:17:37,800 --> 00:17:40,790 这就是那些代码表示的东西 355 00:17:40,790 --> 00:17:44,200 它们在Microsoft Word和其他程序中都有 356 00:17:44,200 --> 00:17:48,850 这样如果你发送错误报告的话 你就可以说 我遇到了错误45号 357 00:17:48,850 --> 00:17:51,750 然后公司的程序员就可以在错误代码表上查一下 358 00:17:51,750 --> 00:17:54,940 然后说 因为是有这个bug 359 00:17:54,940 --> 00:17:56,240 所以用户得到了这个错误信息 360 00:17:56,240 --> 00:17:59,490 但说实话 至少对于我们头几个程序来说 这个解释起来有点冗长 361 00:17:59,490 --> 00:18:02,130 所以我们就 362 00:18:02,130 --> 00:18:02,970 一直省略了这部分 363 00:18:02,970 --> 00:18:07,450 但是一直以来 编译器自动给每一个main函数 364 00:18:07,450 --> 00:18:11,600 都加了这一行 这只是出于惯例 365 00:18:11,600 --> 00:18:13,172 给你节省时间而已 366 00:18:13,172 --> 00:18:14,620 [学生发言] 367 00:18:14,620 --> 00:18:16,250 你不需要在main中包含它 368 00:18:16,250 --> 00:18:16,700 不用的 369 00:18:16,700 --> 00:18:20,260 如果你要执行一个这样的函数时 你确实要包括它 370 00:18:20,260 --> 00:18:22,850 否则的话这函数就不会运行成功了 371 00:18:22,850 --> 00:18:24,480 但是在main中 不需要这样 372 00:18:24,480 --> 00:18:28,450 在一两周内 我们想要表示错误时 373 00:18:28,450 --> 00:18:29,690 我们就会习惯这个了 374 00:18:29,690 --> 00:18:32,550 这个问题很好 375 00:18:32,550 --> 00:18:36,880 快速插一句 这周五 376 00:18:36,880 --> 00:18:39,980 我们不会有午餐活动 但是团队成员和部分学生会进行晚餐活动 377 00:18:39,980 --> 00:18:42,940 如果你想参加 请去cs50.net/rsvp报名 378 00:18:42,940 --> 00:18:45,030 本周五晚6点 379 00:18:45,030 --> 00:18:47,990 位置有限 但我们差不多每周都会举行这个活动 380 00:18:47,990 --> 00:18:51,420 所以这周位置没有的话也没关系 381 00:18:51,420 --> 00:18:56,160 上周一我们留下的悬念是 我们实际可以给字串编索引 382 00:18:56,160 --> 00:19:00,520 这就是说 我们可以取第一个字符 383 00:19:00,520 --> 00:19:03,770 第二个 第三个 这样一直下去 384 00:19:03,770 --> 00:19:07,860 因为一个字串 比如hello 是个5个字母组成的字 385 00:19:07,860 --> 00:19:09,670 放在一个个盒子里一样 386 00:19:09,670 --> 00:19:13,370 你可以从这些盒子里 387 00:19:13,370 --> 00:19:15,230 找到我们在周一介绍的什么句法? 388 00:19:15,230 --> 00:19:16,760 就是你键盘上的中括号 389 00:19:16,760 --> 00:19:18,980 这就表示 去0号位置 390 00:19:18,980 --> 00:19:22,840 我们是从0开始数的 所以[0]表示h 391 00:19:22,840 --> 00:19:25,170 [1]表示有e 这样 392 00:19:25,170 --> 00:19:28,490 所以一直以来 我们在用字串 然后输入"hello" 393 00:19:28,490 --> 00:19:31,250 "world"等等的时候 394 00:19:31,250 --> 00:19:32,820 它其实是被存储在这样的盒子里 395 00:19:32,820 --> 00:19:33,370 猜一猜 396 00:19:33,370 --> 00:19:37,470 这样的盒子在你电脑里实际是什么? 397 00:19:37,470 --> 00:19:38,250 [学生回答] 398 00:19:38,250 --> 00:19:39,150 什么? 399 00:19:39,150 --> 00:19:39,580 字符 400 00:19:39,580 --> 00:19:44,760 所以一个字符 在字串里的字符 401 00:19:44,760 --> 00:19:46,800 占8位 或是一个字节 402 00:19:46,800 --> 00:19:49,550 这样你至少关于电脑的内存是什么 403 00:19:49,550 --> 00:19:50,500 有个模糊的概念 404 00:19:50,500 --> 00:19:52,110 计算机至少有两种内存 405 00:19:52,110 --> 00:19:54,810 一种是硬盘上 你用来永久保存东西的内存 406 00:19:54,810 --> 00:19:57,400 一般这个容量很大 你可以存电影 音乐之类的 407 00:19:57,400 --> 00:20:04,010 另一种内存叫 RAM R-A-M 缓存 408 00:20:04,010 --> 00:20:07,510 这种内存是当你电脑 409 00:20:07,510 --> 00:20:11,520 在运行然后如果停电 或者电池没电时 410 00:20:11,520 --> 00:20:15,300 存在RAM里的东西就会消失 411 00:20:15,300 --> 00:20:16,060 因为它不是永久的 412 00:20:16,060 --> 00:20:19,120 一般来说 你会有一兆 两兆 可能会有更大的RAM 413 00:20:19,120 --> 00:20:23,490 RAM的好处是 它比硬盘快得多 414 00:20:23,490 --> 00:20:27,390 现在它甚至比固态硬盘还要快 但是一般来说它很贵 415 00:20:27,390 --> 00:20:28,480 所以容量比较小 416 00:20:28,480 --> 00:20:32,400 今天我们讲得其实是RAM 这种内存 417 00:20:32,400 --> 00:20:35,270 只有当你电脑有电的时候才存在 418 00:20:35,270 --> 00:20:40,530 当我们输入H-E-L-L-O 回车 H就被储存到 419 00:20:40,530 --> 00:20:44,550 RAM中的一个字节中去 E去了另一个字节 420 00:20:44,550 --> 00:20:45,800 这个字的其余部分也是这样 421 00:20:45,800 --> 00:20:49,010 还记得我们上一次可以这样做 422 00:20:49,010 --> 00:20:53,940 让我打开string.c这个文件 423 00:20:53,940 --> 00:20:56,860 还记得它长得是这样 424 00:20:56,860 --> 00:20:59,860 让我向上滚动 把它变成上次的样子 425 00:20:59,860 --> 00:21:02,654 string length of s 426 00:21:02,654 --> 00:21:04,560 请看这个程序 427 00:21:04,560 --> 00:21:08,530 我们包括了CS50库 这样我们就可以用get string了 428 00:21:08,530 --> 00:21:11,400 我们包括了standard io.h 这样我们就可以用printf了 429 00:21:11,400 --> 00:21:13,580 为什么我们要包括string.h呢? 430 00:21:13,580 --> 00:21:16,980 这是周一新讲的 431 00:21:16,980 --> 00:21:18,230 我们想要知道字串长度 432 00:21:18,230 --> 00:21:19,090 Str leng 433 00:21:19,090 --> 00:21:21,470 很多年前 人们决定 我们要简洁些 434 00:21:21,470 --> 00:21:24,290 所以我们把它叫作str leng而不是string length 435 00:21:24,290 --> 00:21:28,540 这就是我们包括了string.h后可以用的东西 436 00:21:28,540 --> 00:21:29,390 这个讲过了 437 00:21:29,390 --> 00:21:30,320 这个讲过了 438 00:21:30,320 --> 00:21:31,450 这个讲过了 439 00:21:31,450 --> 00:21:32,370 这个有点新东西 440 00:21:32,370 --> 00:21:35,420 在第22行--我会再回来讲这个--但现在 441 00:21:35,420 --> 00:21:37,880 除非你读了文档 或者你已经学过C语言 442 00:21:37,880 --> 00:21:39,010 否则你不会知道这是什么 443 00:21:39,010 --> 00:21:41,510 get string有时会出问题 444 00:21:41,510 --> 00:21:45,130 如果用户真的不合作 或者他/她就是不输入任何东西 445 00:21:45,130 --> 00:21:49,450 或者输入太长的东西 446 00:21:49,450 --> 00:21:53,760 让电脑内存受不了的话 理论上来说 447 00:21:53,760 --> 00:21:56,270 get string可能会返回一个不是字符串的东西 448 00:21:56,270 --> 00:22:01,930 它可能会返回一个特殊的值叫NULL 全大写 449 00:22:01,930 --> 00:22:03,390 NULL这个是所谓的标记 450 00:22:03,390 --> 00:22:08,010 它是个特殊的值 表示有不好的事情发生了 451 00:22:08,010 --> 00:22:10,520 它表示字串的缺失 452 00:22:10,520 --> 00:22:16,190 所以简单来说 我在检查NULL 453 00:22:16,190 --> 00:22:20,230 对于str leng和其他C语言中的函数 如果它们是在等待一个字串 454 00:22:20,230 --> 00:22:23,630 但你却给了他们字串的缺失 即NULL 455 00:22:23,630 --> 00:22:25,000 电脑或是那个程序就会崩溃 456 00:22:25,000 --> 00:22:25,610 它会停止工作 457 00:22:25,610 --> 00:22:27,250 它会显示错误信息 458 00:22:27,250 --> 00:22:28,690 不好的事会发生 459 00:22:28,690 --> 00:22:31,130 虽然这个现在定义得还比较模糊 460 00:22:31,130 --> 00:22:33,730 --但在一两周内你们会了解得更清楚--在第22行 461 00:22:33,730 --> 00:22:38,790 这里的只是个例子 为了自身安全进行错误排查 462 00:22:38,790 --> 00:22:42,040 这样就算有那么百万分之一的机会有错误产生 我的程序也不会崩溃 463 00:22:42,040 --> 00:22:45,960 所以如果s不等于某种错误的东西的话 我就有了这个for循环 464 00:22:45,960 --> 00:22:47,710 那在这里我们就有种新句法 465 00:22:47,710 --> 00:22:51,580 我这有个for循环 是从0一直到s的长度的 466 00:22:51,580 --> 00:22:56,140 然后这里 我在印出要s[i] 但虽然s是字串 467 00:22:56,140 --> 00:23:00,770 为什么我突然开始用%c而不是%s了呢? 468 00:23:00,770 --> 00:23:02,110 它是个字符 对不对 469 00:23:02,110 --> 00:23:06,560 S是个字串 但s加上中括号 s[i]当i是0 470 00:23:06,560 --> 00:23:10,380 或者是1或者是2的时候 它是字串中的一个字符 471 00:23:10,380 --> 00:23:14,970 所以printf需要知道 它将得到的是个字符 472 00:23:14,970 --> 00:23:18,096 还记得这个程序实际上做的是什么么? 473 00:23:18,096 --> 00:23:19,848 把它在一列中印出来 474 00:23:19,848 --> 00:23:21,120 没错 475 00:23:21,120 --> 00:23:24,990 它会把我输入的字在一列中印出来 每个字符一行 476 00:23:24,990 --> 00:23:26,190 让我们再看一遍 477 00:23:26,190 --> 00:23:27,810 make string 478 00:23:27,810 --> 00:23:30,200 编译好了 ./string 479 00:23:30,200 --> 00:23:35,560 让我输入 H-E-L-L-O 回车 我确实得到了我想要的 每行一个字符 480 00:23:35,560 --> 00:23:37,280 那么让我优化一下 481 00:23:37,280 --> 00:23:40,240 请想一想 特别是以前编过程的人 482 00:23:40,240 --> 00:23:43,340 第24行 效率好像不太高 483 00:23:43,340 --> 00:23:46,160 也就是说 这还不是最好的设计 484 00:23:46,160 --> 00:23:50,200 至少当你记得str leng是什么后 这确实很直接 485 00:23:50,200 --> 00:23:52,640 但是这可能会做一些傻事 486 00:23:52,640 --> 00:23:54,863 可能会是什么呢? 487 00:23:54,863 --> 00:23:56,280 [学生回答] 488 00:23:56,280 --> 00:23:56,800 没错 489 00:23:56,800 --> 00:24:00,340 它每次都要检查s的长度 490 00:24:00,340 --> 00:24:02,980 虽然我们知道H-E-L-L-O 永远都是5个字符这么长 491 00:24:02,980 --> 00:24:05,490 每一次经过这个循环的时候 这个数字5是不变的 492 00:24:05,490 --> 00:24:08,750 我可能在增加i的值 但是每一次循环时 493 00:24:08,750 --> 00:24:09,690 s的长度是多少? 494 00:24:09,690 --> 00:24:15,810 是5 是5 是5 但我还是 495 00:24:15,810 --> 00:24:18,320 一次又一次地问这个问题 496 00:24:18,320 --> 00:24:20,750 说实话 电脑的速度是如此快 497 00:24:20,750 --> 00:24:23,780 没人会发现这有什么差别 但是如果编译器不帮你修正的话 498 00:24:23,780 --> 00:24:28,330 它一般也是不会帮你的 这种设计缺陷会开始叠加 499 00:24:28,330 --> 00:24:30,630 至少在这种机器里 500 00:24:30,630 --> 00:24:31,540 所以我会这样做 501 00:24:31,540 --> 00:24:34,580 我要在我的第一个变量i后面加一个逗号 502 00:24:34,580 --> 00:24:37,310 我要再加一个变量n 503 00:24:37,310 --> 00:24:41,330 数字我们习惯用n 然后我会把字串s的长度 504 00:24:41,330 --> 00:24:42,530 赋值给它 505 00:24:42,530 --> 00:24:46,060 然后我要把我的条件改成什么? 506 00:24:46,060 --> 00:24:51,960 我要把我的条件改成 当i小于n时 507 00:24:51,960 --> 00:24:55,700 现在 我要检查s的长度几次? 508 00:24:55,700 --> 00:25:00,110 一次 但是一次次比较i与n的大小没关系 509 00:25:00,110 --> 00:25:03,170 因为它们是不变的 510 00:25:03,170 --> 00:25:06,020 就现在而言 只要知道当你用一个函数时 511 00:25:06,020 --> 00:25:09,930 是要有一定开销的 不是说让你从此不用函数了 512 00:25:09,930 --> 00:25:12,750 但是当有这样一行代码时--过不久这一行代码就会变得更有趣 513 00:25:12,750 --> 00:25:15,490 --如果有空 就想一想 514 00:25:15,490 --> 00:25:18,320 如果我输入了这行代码 它会被执行多少次? 515 00:25:18,320 --> 00:25:20,950 你会慢慢发现 这个会改变 516 00:25:20,950 --> 00:25:21,660 你程序的表现 517 00:25:21,660 --> 00:25:24,110 事实上 几年前我们做的一个练习 518 00:25:24,110 --> 00:25:27,600 就是关于实行一个拼写检查器 你们可能记得第0周我们提过这个 519 00:25:27,600 --> 00:25:31,380 但是这个拼写检查器要支持一个 520 00:25:31,380 --> 00:25:32,860 有超过15万词的词典 521 00:25:32,860 --> 00:25:37,100 你们需要写代码将这些词装进RAM里 522 00:25:37,100 --> 00:25:40,700 装进我们刚才在屏幕上看到的 一个个盒子里 523 00:25:40,700 --> 00:25:43,740 然后你必须马上回答一个问题 这个词有没有拼错? 524 00:25:43,740 --> 00:25:44,280 答得越快越好 525 00:25:44,280 --> 00:25:45,420 这个词有没有拼错? 526 00:25:45,420 --> 00:25:46,770 这个词有没有拼错? 527 00:25:46,770 --> 00:25:49,525 我们过去几年的做法是 虽然这是完全非强制性的 528 00:25:49,525 --> 00:25:53,500 把它办成一个比赛 529 00:25:53,500 --> 00:25:59,470 让那些用较少RAM 较短时间 较少CPU周期的学生 530 00:25:59,470 --> 00:26:02,640 进行一个排行榜的前几名 531 00:26:02,640 --> 00:26:04,770 然后我们会把它放在课程主页上 532 00:26:04,770 --> 00:26:08,100 你们完全可以选择要不要参加 但这是关于程序设计的 533 00:26:08,100 --> 00:26:11,250 当我们打好基础后 就会有越来越多 534 00:26:11,250 --> 00:26:14,010 在上面添砖加瓦 进行设计的机会 535 00:26:14,010 --> 00:26:16,780 让我回到这个图 536 00:26:16,780 --> 00:26:17,610 然后再多讲一些 537 00:26:17,610 --> 00:26:21,400 这确实是个字串 我们也已经利用了几个库 538 00:26:21,400 --> 00:26:25,150 standard io.h 它其中有 539 00:26:25,150 --> 00:26:26,110 printf 540 00:26:26,110 --> 00:26:27,860 printf 还有其他的 541 00:26:27,860 --> 00:26:31,540 cs50.h 它其中有get int, get string还有其他的 string.h 542 00:26:31,540 --> 00:26:32,570 它其中有str leng 543 00:26:32,570 --> 00:26:34,800 但我们发现还有一个 544 00:26:34,800 --> 00:26:38,540 说句实话 有很多很多给库的声明函数的头文件 545 00:26:38,540 --> 00:26:43,320 但这里的ctype.h会对我们很有帮助 546 00:26:43,320 --> 00:26:46,900 因为我要来 547 00:26:46,900 --> 00:26:48,120 实行另一个程序 548 00:26:48,120 --> 00:26:52,420 让我打开我先前写的的 549 00:26:52,420 --> 00:26:55,750 叫capitalize.c的文件 然后看看它是怎么工作的 550 00:26:55,750 --> 00:27:00,340 注意我在这个版本里用了三个大家熟悉的文件 551 00:27:00,340 --> 00:27:04,110 注意在第18行 我得到了一行文字 552 00:27:04,110 --> 00:27:07,660 在第21行 我在声明以下的代码 553 00:27:07,660 --> 00:27:12,170 会把s变大写 无论用户输入什么字 我是怎么做到这个的呢? 554 00:27:12,170 --> 00:27:13,300 我在-- 555 00:27:13,300 --> 00:27:14,750 --根据上一次的教训-- 556 00:27:14,750 --> 00:27:18,370 我在声明i还有n 然后在重复字串中的字符 557 00:27:18,370 --> 00:27:22,720 然后24到27行中的这堆代码 558 00:27:22,720 --> 00:27:24,550 用一般人的话来说 在做什么? 559 00:27:27,766 --> 00:27:29,730 针对小写字母 560 00:27:29,730 --> 00:27:30,430 没错 561 00:27:30,430 --> 00:27:35,920 如果s[i]--如果s的第i个字符 562 00:27:35,920 --> 00:27:40,220 也就是字串中的一个字元--大于等于小写a 563 00:27:40,220 --> 00:27:42,670 --还记得两个&表示 和 -- 564 00:27:42,670 --> 00:27:46,810 同时这个字符 s[i] 也小于等于小写z 565 00:27:46,810 --> 00:27:50,600 这就表示它是个小写a或b或c或……或者小写z 566 00:27:50,600 --> 00:27:51,340 也就是说它是小写的 567 00:27:51,340 --> 00:27:52,900 这样的话我要做什么呢? 568 00:27:52,900 --> 00:27:55,010 我可以想出很难懂的方式 569 00:27:55,010 --> 00:27:56,160 但是我们还是来分解一下吧 570 00:27:56,160 --> 00:28:00,210 我要用printf 印出%c因为我想要 571 00:28:00,210 --> 00:28:01,580 把这个字符印在屏幕上 572 00:28:01,580 --> 00:28:06,650 然后我要拿s[i]也就是s的第i个字符 573 00:28:06,650 --> 00:28:12,330 为什么我在这玩了个小花样 用小写a减大写A? 574 00:28:12,330 --> 00:28:16,352 这会给我什么呢? 575 00:28:16,352 --> 00:28:18,600 [学生回答] 576 00:28:18,600 --> 00:28:19,390 没错 577 00:28:19,390 --> 00:28:20,860 我不记得-- 578 00:28:20,860 --> 00:28:24,390 大写A是65 我不记得小写a是什么了 579 00:28:24,390 --> 00:28:25,540 但没关系 580 00:28:25,540 --> 00:28:26,580 电脑知道的 581 00:28:26,580 --> 00:28:30,380 所以我们说 小写a减大写A 582 00:28:30,380 --> 00:28:33,530 这样让一个字元减另一个字元是有点奇怪 但字元背后其实是什么? 583 00:28:33,530 --> 00:28:34,520 它们是数字 584 00:28:34,520 --> 00:28:36,980 所以不管这些数字是什么 让电脑 585 00:28:36,980 --> 00:28:38,240 而不是我 记住它们就行了 586 00:28:38,240 --> 00:28:41,710 所以小写a减大写A会给我一个差值 587 00:28:41,710 --> 00:28:45,370 它是32 对于小写b减大写B也一样 588 00:28:45,370 --> 00:28:45,930 对其他的也一样 589 00:28:45,930 --> 00:28:47,710 幸好这个值是恒定的 590 00:28:47,710 --> 00:28:51,930 所以我在说 用小写字母 减掉这个差值 591 00:28:51,930 --> 00:28:55,340 这样就会把s[i] 592 00:28:55,340 --> 00:28:59,400 从小写变成大写了 而我不用去想 593 00:28:59,400 --> 00:29:03,040 也不用记住 当那8个志愿者上台的时候 594 00:29:03,040 --> 00:29:04,800 他们表示的那些数字是什么 595 00:29:04,800 --> 00:29:08,800 那么现在 其他情况 如果它不是像24行中说的那样 596 00:29:08,800 --> 00:29:10,400 是小写字母的话 就把它印出来 597 00:29:10,400 --> 00:29:12,590 我只想动那些 598 00:29:12,590 --> 00:29:14,410 本来就是小写的字母 599 00:29:14,410 --> 00:29:15,150 所以让我们看一下这个 600 00:29:15,150 --> 00:29:17,400 make capitalize 601 00:29:17,400 --> 00:29:18,470 编译好了 好的 602 00:29:18,470 --> 00:29:19,730 ./capitalize. 603 00:29:19,730 --> 00:29:23,530 让我用小写输入 H-E-L-L-O 回车 604 00:29:23,530 --> 00:29:26,370 注意到 它们被变成了大写 605 00:29:26,370 --> 00:29:27,940 让我换个词试试 606 00:29:27,940 --> 00:29:32,720 不如用D-A-V-I-D 第一个D大写 就像一般名字的写法那样? 607 00:29:32,720 --> 00:29:33,560 回车 608 00:29:33,560 --> 00:29:34,870 注意 它还是正确的 609 00:29:34,870 --> 00:29:40,250 它把第一个D 通过else 结构 没动过就印出来 610 00:29:40,250 --> 00:29:42,170 这里 要记住几件事 611 00:29:42,170 --> 00:29:45,060 一 如果你想一并检查两个条件 612 00:29:45,060 --> 00:29:46,500 你可以用 和 把它们连接 613 00:29:46,500 --> 00:29:49,900 你可以这样比较字符 把它们当作数字 614 00:29:49,900 --> 00:29:53,050 但说实话这实在太难懂了 615 00:29:53,050 --> 00:29:56,510 如果不给我多一点时间仔细想 我是不可能知道要怎么自己从头 616 00:29:56,510 --> 00:29:57,140 想到要怎么做的 617 00:29:57,140 --> 00:30:00,590 如果有人给我写好了个函数 叫 is lower(是小写) 618 00:30:00,590 --> 00:30:05,390 用来判断一个字符是不是小写 不是很好么 619 00:30:05,390 --> 00:30:09,350 谢天谢地 那个写ctype.h的人正是做了这样一件事 620 00:30:09,350 --> 00:30:15,540 让我回到上面加上ctype 621 00:30:15,540 --> 00:30:18,820 然后回到底下重写这几行 622 00:30:18,820 --> 00:30:27,510 如果它叫 is lower 我声明 s[i] 623 00:30:27,510 --> 00:30:29,400 然后我要把这两行一起删掉 624 00:30:29,400 --> 00:30:32,570 我希望有别人写了叫is lower的函数 625 00:30:32,570 --> 00:30:36,250 然后我发现有人确实写了 而且在ctype.h里声明了 626 00:30:36,250 --> 00:30:39,480 现在27行我不用改变什么 31行也不用 627 00:30:39,480 --> 00:30:41,890 但是注意我让我的代码变紧凑了许多 628 00:30:41,890 --> 00:30:42,690 现在看上去更干净了 629 00:30:42,690 --> 00:30:47,250 现在看起来也更容易了 因为这个函数 630 00:30:47,250 --> 00:30:50,080 名字取得很好 从名字就能知道它会做什么 631 00:30:50,080 --> 00:30:51,520 现在让我保存 632 00:30:51,520 --> 00:30:52,930 我要缩小一下界面 633 00:30:52,930 --> 00:30:56,650 就像在Scratch中你们可以用布尔表达式一样 布尔表达式有真与假两个值 634 00:30:56,650 --> 00:31:01,530 这就是is lower会返回给你的东西 635 00:31:01,530 --> 00:31:02,960 让我重新编译一下 636 00:31:02,960 --> 00:31:04,500 重新跑一下 637 00:31:04,500 --> 00:31:07,350 现在再试一次 H-E-L-L-O 回车 638 00:31:07,350 --> 00:31:07,970 不错 639 00:31:07,970 --> 00:31:10,150 再试一次 确保它不会出错 640 00:31:10,150 --> 00:31:11,670 这也变大写了 641 00:31:11,670 --> 00:31:14,190 但这还不够好 因为另外我得花很多时间去研究 642 00:31:14,190 --> 00:31:19,090 比如 写论文时去研究的东西 643 00:31:19,090 --> 00:31:19,920 就是这一行 644 00:31:19,920 --> 00:31:23,450 如果有个函数叫 to upper (变大写) 不是很好? 645 00:31:23,450 --> 00:31:26,930 我们发现在ctype.h里就有 646 00:31:26,930 --> 00:31:30,150 让我输入-- 647 00:31:30,150 --> 00:31:31,340 让我回到这一行 648 00:31:31,340 --> 00:31:36,430 我现在要在这里 对s的第i个字符 把%c替换成 649 00:31:36,430 --> 00:31:42,110 使用to upper这个函数得到的结果 650 00:31:42,110 --> 00:31:45,430 现在 它看上去比较平衡了 651 00:31:45,430 --> 00:31:48,870 我需要注意我用了多少组括号 652 00:31:48,870 --> 00:31:50,050 这样 现在它看上去更干净了 653 00:31:50,050 --> 00:31:53,460 现在这个程序设计上越来越好了 654 00:31:53,460 --> 00:31:56,450 因为它越来越易读 并且保持了其正确性 655 00:31:56,450 --> 00:31:57,600 Make capitalize 656 00:31:57,600 --> 00:31:58,930 ./capitalize 657 00:31:58,930 --> 00:32:03,220 H-E-L-L-O 让我们再跑一次 D-A-V-I-D 好 658 00:32:03,220 --> 00:32:04,250 所以我们还是比较满意的 659 00:32:04,250 --> 00:32:06,030 但是再看到 to upper 660 00:32:06,030 --> 00:32:09,720 我认为对to upper我们还可以做一些调整 661 00:32:09,720 --> 00:32:12,820 这能使它变得更好 让代码更紧凑 662 00:32:12,820 --> 00:32:15,150 让我们在设计上能得到满分 663 00:32:15,150 --> 00:32:16,510 去掉什么会比较好呢? 664 00:32:16,510 --> 00:32:20,770 看到 这一块代码有那么长 但做的却是很简单的事 665 00:32:20,770 --> 00:32:23,850 顺便提一句 你们在上周末的超级小班课上可能看到了 666 00:32:23,850 --> 00:32:27,570 如果你只有一行代码的话 你并不一定需要一对大括号 667 00:32:27,570 --> 00:32:32,180 虽然 我们建议保留它们 668 00:32:32,180 --> 00:32:36,190 因为会让条件更清楚 就像Scratch 中的U型块一样 669 00:32:36,190 --> 00:32:40,170 如果to upper拿到input时 input不是大写 670 00:32:40,170 --> 00:32:44,730 就把它变成大写 那如果是另一种情况 671 00:32:44,730 --> 00:32:47,210 input已经是大写了 怎样会比较好? 672 00:32:47,210 --> 00:32:49,620 就不要动它 673 00:32:49,620 --> 00:32:50,660 也许它就是这样做的 674 00:32:50,660 --> 00:32:52,990 我可以试一下 然后希望它是这样处理的 675 00:32:52,990 --> 00:32:54,450 但让我再给大家介绍一个东西 676 00:32:54,450 --> 00:32:57,440 我不想再在底下用这个自带的终端窗口 677 00:32:57,440 --> 00:33:01,130 还记得这个正方形图标可以让你放大终端窗口 678 00:33:01,130 --> 00:33:02,260 至全屏吗? 679 00:33:02,260 --> 00:33:05,820 它们的名字有点怪 680 00:33:05,820 --> 00:33:10,970 但这些叫做手册页 man是manual 的缩写 681 00:33:10,970 --> 00:33:14,515 我可以通过输入man来得到它们-- 682 00:33:14,515 --> 00:33:15,570 我应该输入什么? 683 00:33:15,570 --> 00:33:17,830 man to upper 684 00:33:17,830 --> 00:33:21,090 注意 如果电脑里有这样一个函数 685 00:33:21,090 --> 00:33:23,970 在这种情况下 也就是操作系统linux 686 00:33:23,970 --> 00:33:27,920 会给我一组很难懂的output 687 00:33:27,920 --> 00:33:31,720 慢慢你们会发现 它们的格式是固定的 所以你就会开始习惯它了 688 00:33:31,720 --> 00:33:35,130 注意在to upper最上方 689 00:33:35,130 --> 00:33:35,680 和to lower里 是一样的 690 00:33:35,680 --> 00:33:38,740 那个写了这个的人是在省时间 把它全放在了一页上 691 00:33:38,740 --> 00:33:40,720 而这些东西的目的就是 692 00:33:40,720 --> 00:33:42,780 把一个字母换成大写或小写 693 00:33:42,780 --> 00:33:46,290 注意 在提要中 手册页就教我 694 00:33:46,290 --> 00:33:48,130 要使用这个东西的话 需要包括哪些文件 695 00:33:48,130 --> 00:33:51,320 它给了我这些函数的签名 两个都给了 696 00:33:51,320 --> 00:33:53,510 虽然现在我们只关心其中的一个 697 00:33:53,510 --> 00:33:54,730 这里是一个描述 698 00:33:54,730 --> 00:33:58,800 如果可能的话to upper把字母c变成大写 699 00:33:58,800 --> 00:34:02,280 这并不是很有帮助 但是再让我看到返回值 700 00:34:02,280 --> 00:34:03,520 也就是它返回给我的东西 701 00:34:03,520 --> 00:34:08,600 这个值要么是转换后的大写字母 要么是c 702 00:34:08,600 --> 00:34:09,870 如果转换不成功的话 703 00:34:09,870 --> 00:34:11,202 c是什么? 704 00:34:11,202 --> 00:34:12,560 原始的字符 705 00:34:12,560 --> 00:34:15,370 我们知道它是原始字符 是因为回到上面看提纲 706 00:34:15,370 --> 00:34:19,179 那个写出这个函数的人决定 707 00:34:19,179 --> 00:34:22,909 要把to upper和to lower的input叫作c 708 00:34:22,909 --> 00:34:24,909 他们可以给它取任何名字 709 00:34:24,909 --> 00:34:26,270 但他们决定简单地叫它c 710 00:34:26,270 --> 00:34:27,880 所以我去咨询了手册页 711 00:34:27,880 --> 00:34:31,870 这一句让我确定 如果它不是一个小写字母 712 00:34:31,870 --> 00:34:34,969 它就会把c还给我 这很棒 因为 713 00:34:34,969 --> 00:34:36,199 我就不用写我的else条件了 714 00:34:36,199 --> 00:34:39,679 让我回到gedit 然后我要做这个 715 00:34:39,679 --> 00:34:41,960 我要复制我的printf语句 716 00:34:41,960 --> 00:34:45,969 我要到for循环里然后印出它来 717 00:34:45,969 --> 00:34:48,760 然后把整个if结构都去掉 718 00:34:48,760 --> 00:34:51,860 这不是个坏主意 这也符合我们一直以来所教你们的 719 00:34:51,860 --> 00:34:54,100 但是这并不必要 720 00:34:54,100 --> 00:34:57,070 当你意识到库中存在一些别人写好的函数 721 00:34:57,070 --> 00:35:01,340 或是你写在文件其他地方的函数 722 00:35:01,340 --> 00:35:02,690 你就可以用它 这会让代码紧凑 723 00:35:02,690 --> 00:35:06,080 我说的好的样式 就比如这个人把函数叫做 724 00:35:06,080 --> 00:35:11,490 to upper或者前面的 is lower 这就很有用 725 00:35:11,490 --> 00:35:12,900 因为它们描述性很强 726 00:35:12,900 --> 00:35:16,120 你不会想要叫你的函数x, y或z 727 00:35:16,120 --> 00:35:19,620 因为它们携带的意义要少很多 728 00:35:19,620 --> 00:35:25,160 对刚才进行的调整有什么问题吗? 729 00:35:25,160 --> 00:35:28,010 我们学到的很重要的一点 是对你们的练习来说 730 00:35:28,010 --> 00:35:30,960 练习1 不太确定 但对于练习2及以后的练习来说 731 00:35:30,960 --> 00:35:34,380 虽然代码是正确的 但并不表示它们是完美的 732 00:35:34,380 --> 00:35:36,155 或是设计得好的 733 00:35:36,155 --> 00:35:38,420 这是另一件我们需要开始考虑的事 734 00:35:38,420 --> 00:35:41,730 这是你电脑内存中的一个字串 735 00:35:41,730 --> 00:35:46,180 但如果你在RAM中有一堆字符 比如H-E-L-L-O 736 00:35:46,180 --> 00:35:51,330 假设你的程序用了get string很多次 737 00:35:51,330 --> 00:35:54,200 你用了get string一次 然后你又用了一次 738 00:35:54,200 --> 00:35:55,880 那会发生什么? 739 00:35:55,880 --> 00:35:59,170 换句话说 如果你有一行代码 不管在什么情形下 740 00:35:59,170 --> 00:36:02,120 比如string s得到 -- 741 00:36:02,120 --> 00:36:02,960 让我们这样 742 00:36:02,960 --> 00:36:05,270 string name = get string 743 00:36:05,270 --> 00:36:08,590 假设这行代码是用来问用户名字的 744 00:36:08,590 --> 00:36:14,580 下一行是用来问用户学校的 745 00:36:14,580 --> 00:36:15,920 而下一行又是问别的的 746 00:36:15,920 --> 00:36:18,150 假设我们一直在问用户 747 00:36:18,150 --> 00:36:19,750 要一个又一个字串 748 00:36:19,750 --> 00:36:22,390 它们会同时被储存在内存里 749 00:36:22,390 --> 00:36:24,280 一个不会伤到另一个 750 00:36:24,280 --> 00:36:26,420 学校不会把其他的盖掉 751 00:36:26,420 --> 00:36:28,520 但它们都会存在内存中么 752 00:36:28,520 --> 00:36:32,030 如果我们开始在屏幕上画东西 753 00:36:32,030 --> 00:36:35,800 我们可以把它当黑板来用 如果这个黑色三角表示 754 00:36:35,800 --> 00:36:39,800 我电脑的内存 那我就开始把它们分成小方块 755 00:36:39,800 --> 00:36:42,120 每一个代表一字节的内存 756 00:36:42,120 --> 00:36:46,560 不过说实话 如果你现在有1G RAM的话 757 00:36:46,560 --> 00:36:49,540 你电脑里就有一百万字节内存 也就是一百万个这样的方块 758 00:36:49,540 --> 00:36:52,110 所以这个比例并不准确 759 00:36:52,110 --> 00:36:58,250 但是我们可以继续画这些比例不对的方块 760 00:36:58,250 --> 00:37:01,260 它们在一起 表示我电脑的内存 761 00:37:01,260 --> 00:37:03,136 现在我们就画点点点好了 762 00:37:03,136 --> 00:37:06,260 换句话说 当我现在用get string提示用户 763 00:37:06,260 --> 00:37:07,350 给我一个字串时 会发生什么? 764 00:37:07,350 --> 00:37:14,270 如果用户输入hello 最后会变成H-E-L-L-O 765 00:37:14,270 --> 00:37:15,720 但假设用户输入了-- 766 00:37:15,720 --> 00:37:17,250 事实上 我不该说hello 767 00:37:17,250 --> 00:37:18,330 因为我们是在问他们名字 768 00:37:18,330 --> 00:37:20,580 所以如果我可以的话 让我重新做一次 769 00:37:20,580 --> 00:37:26,130 如果我输入D-A-V-I-D作为我的名字 770 00:37:26,130 --> 00:37:29,220 但注意 第二行代码还是get string用来要学校 771 00:37:29,220 --> 00:37:32,090 那用户输入的下一个字会去哪呢? 772 00:37:32,090 --> 00:37:38,290 也许它会进H-A-R-V-A-R-D中 773 00:37:38,290 --> 00:37:41,560 即使我把它分两行来画 它们一起 774 00:37:41,560 --> 00:37:42,710 只是你电脑RAM里的一堆字节 775 00:37:42,710 --> 00:37:46,560 现在有了个问题 因为如果我在如此合理 776 00:37:46,560 --> 00:37:49,910 但又如此天真地使用RAM 你会无法区别什么东西呢? 777 00:37:52,640 --> 00:37:54,680 哪是开头哪是结尾 对不对? 778 00:37:54,680 --> 00:37:55,860 它们好像都混到一起了 779 00:37:55,860 --> 00:37:57,920 我们发现电脑不会这样 780 00:37:57,920 --> 00:38:04,720 让我倒退一下 781 00:38:04,720 --> 00:38:09,570 Harvard并没有紧接在用户名字后面 用户实际上得到了 782 00:38:09,570 --> 00:38:12,000 一个特别的字符 是电脑自己 783 00:38:12,000 --> 00:38:13,885 为他或她加上的 784 00:38:13,885 --> 00:38:19,470 \0 也就是nul character(空字符) 它叫做nul 785 00:38:19,470 --> 00:38:22,190 不是N-U-L-L 但是写作 /0 786 00:38:22,190 --> 00:38:27,130 它只是所有位都是0的标记 出现在 787 00:38:27,130 --> 00:38:28,290 用户输入的第一个词与第二个词之间 788 00:38:28,290 --> 00:38:33,020 所以Harvard现在实际上是这一组字符 789 00:38:33,020 --> 00:38:36,110 后面还有一个/0 790 00:38:36,110 --> 00:38:41,690 换句话说 有了这些标记 8位连续的0 791 00:38:41,690 --> 00:38:45,220 你就可以区别字符了 792 00:38:45,220 --> 00:38:49,720 所以一直以来 "hello"实际上是"hello"加上\0 793 00:38:49,720 --> 00:38:53,580 同时 在电脑里 RAM 794 00:38:53,580 --> 00:38:56,400 可能要更大一些 795 00:38:56,400 --> 00:38:57,810 让我来做另一件事 796 00:38:57,810 --> 00:39:01,800 我们在画的这些方块实际上是 没错 797 00:39:01,800 --> 00:39:06,140 字串 但更广泛地说 它们是数组(array) 798 00:39:06,140 --> 00:39:10,590 数组是一块连续的内存 799 00:39:10,590 --> 00:39:15,130 你一般用中括号表示数组 800 00:39:15,130 --> 00:39:18,210 我们后面会经常看到它 801 00:39:18,210 --> 00:39:21,160 但让我打开 就叫它 ages 802 00:39:21,160 --> 00:39:23,920 注意 我们可以怎样玩一些一样的花样 803 00:39:23,920 --> 00:39:25,750 这里又多了一点句法 804 00:39:25,750 --> 00:39:29,270 程序里的第17行 --实际上 让我先跑一下程序 805 00:39:29,270 --> 00:39:30,770 这样我们就知道它会做什么了 806 00:39:30,770 --> 00:39:33,530 让我用make ages来编译这个程序 807 00:39:33,530 --> 00:39:34,950 ./ages. 808 00:39:34,950 --> 00:39:36,480 这房间里有多少人? 809 00:39:36,480 --> 00:39:38,020 就说3个好了 810 00:39:38,020 --> 00:39:39,575 第一个人的年龄? 811 00:39:39,575 --> 00:39:42,710 18, 19和20 812 00:39:42,710 --> 00:39:46,770 现在 这有点荒谬 但是我刚写了一个程序 813 00:39:46,770 --> 00:39:47,740 让这三个人年龄增加 814 00:39:47,740 --> 00:39:50,390 这里显然我们有机会有些有趣的算法 815 00:39:50,390 --> 00:39:51,560 还好 计算是对的 816 00:39:51,560 --> 00:39:54,720 18变成19 19变成20 这样 817 00:39:54,720 --> 00:39:58,510 但这里我想说明的 818 00:39:58,510 --> 00:40:00,190 是我们是怎样储存这些人的年龄的 819 00:40:00,190 --> 00:40:02,370 让我们放大一下 看看这里到底发生了什么 820 00:40:02,370 --> 00:40:06,240 首先 前面这几行大家应该比较熟悉了 821 00:40:06,240 --> 00:40:08,770 我是在提示用户给我屋子里面人的数量 822 00:40:08,770 --> 00:40:11,490 然后我用了get int及do while循环去重复这一过程 823 00:40:11,490 --> 00:40:15,780 我们前面看过这样的结构 不过第27行是新的 824 00:40:15,780 --> 00:40:18,160 并且会变得越来越有用 825 00:40:18,160 --> 00:40:21,620 注意 27行不同之处是我看上去在声明一个 826 00:40:21,620 --> 00:40:23,960 叫ages的整数 但等一下 827 00:40:23,960 --> 00:40:27,140 它不只是int ages 828 00:40:27,140 --> 00:40:30,130 还有这些中括号 n被包在里面 829 00:40:30,130 --> 00:40:35,150 在这里 [n]不是在printf语句里 830 00:40:35,150 --> 00:40:44,370 而是单独在第27行里 这一行是在说 给我n个整数 831 00:40:44,370 --> 00:40:46,080 每一个的数据类型都是整数 832 00:40:46,080 --> 00:40:49,870 所以这是可以想像成是个桶 里面有三个整数一个接一个 833 00:40:49,870 --> 00:40:52,770 所以我就有了3个变量 834 00:40:52,770 --> 00:40:54,890 另一种情况 会是这样的 835 00:40:54,890 --> 00:40:57,400 如果我想要第一个学生的年龄 我可能会这样 836 00:40:57,400 --> 00:40:59,520 如果我想要第二个学生的年龄 我可能会这样 837 00:40:59,520 --> 00:41:01,860 如果我想要第三个学生的年龄 我可能会这样 838 00:41:01,860 --> 00:41:04,320 如果我们要屋里所有人的年龄-- 839 00:41:04,320 --> 00:41:07,670 我是说 这会是大量的复制粘贴工作 840 00:41:07,670 --> 00:41:10,870 再加上如果我编译了这个程序后 一个学生从这门里走进来 841 00:41:10,870 --> 00:41:14,200 那我的变量数目就不对了 842 00:41:14,200 --> 00:41:17,450 所以数组的好处就是 如果你觉得 843 00:41:17,450 --> 00:41:20,190 自己在不断复制粘贴 有很大可能 你采取的不是最好的方法 844 00:41:20,190 --> 00:41:22,240 数组是动态的 845 00:41:22,240 --> 00:41:24,610 我并不知道屋子里会有多少人 846 00:41:24,610 --> 00:41:28,670 但是我知道会有n个 在某个时候 我会弄清n是多少的 847 00:41:28,670 --> 00:41:35,500 这一行代码现在表示 给我像这样一块内存 848 00:41:35,500 --> 00:41:40,380 里面盒子的个数完全取决于 849 00:41:40,380 --> 00:41:42,010 用户输入的n这个数字 850 00:41:42,010 --> 00:41:44,850 现在 这个程序剩余的部分 851 00:41:44,850 --> 00:41:46,860 和我们刚才用字符作例子的 就差不多一样了 852 00:41:46,860 --> 00:41:49,970 注意 从第30行开始 我有一个for循环 853 00:41:49,970 --> 00:41:54,920 所以当我得到了数组后 我会重复给y赋值 从0一直到n 854 00:41:54,920 --> 00:41:58,890 我有这个指导性的printf信息 说 855 00:41:58,890 --> 00:42:03,690 给我#%i这个人的年龄 也就是第一个人 第二个 第三个 856 00:42:03,690 --> 00:42:04,730 为什么我要这么做? 857 00:42:04,730 --> 00:42:08,870 因为 一般人喜欢从1开始数 而计算机科学家 858 00:42:08,870 --> 00:42:09,620 喜欢从0开始数 859 00:42:09,620 --> 00:42:11,700 计算机科学家不会用这种程序的 860 00:42:11,700 --> 00:42:13,990 所以我们就像正常人一样 从1开始数 861 00:42:13,990 --> 00:42:17,630 在第33行 注意这个有点不同的句法 862 00:42:17,630 --> 00:42:23,710 数据类型为数组的变量中的第i个年龄 会得到一个整数 863 00:42:23,710 --> 00:42:25,770 最后 这下面的就只是算法了 864 00:42:25,770 --> 00:42:29,200 我决定在另外一个循环里声明过了一段时间 865 00:42:29,200 --> 00:42:31,400 那现在在这个循环里 这些行会被执行 866 00:42:31,400 --> 00:42:35,810 一年之后 i这个人就i岁了 但注意 867 00:42:35,810 --> 00:42:36,500 这并不是变量i 868 00:42:36,500 --> 00:42:38,390 这是%i 表示一个整数 869 00:42:38,390 --> 00:42:43,210 注意 作为第一个占位符 我放了i加1 870 00:42:43,210 --> 00:42:44,250 这样我们就能像正常人一样数数了 871 00:42:44,250 --> 00:42:49,190 然后 他们的年龄 i岁 我拿了ages[i] 872 00:42:49,190 --> 00:42:52,980 --为什么我要加1呢? 873 00:42:52,980 --> 00:42:53,760 因为他们年龄增长了 874 00:42:53,760 --> 00:42:55,030 因为我选了这个程序 875 00:42:55,030 --> 00:42:56,810 他们年龄增长了1岁 876 00:42:56,810 --> 00:42:59,770 我可以随便输入任何数字 877 00:42:59,770 --> 00:43:02,430 所以这些都有什么关系呢? 878 00:43:02,430 --> 00:43:07,610 让我回到这里来 879 00:43:07,610 --> 00:43:10,830 然后向你们描绘一下下面会发生什么事 880 00:43:10,830 --> 00:43:15,720 我们在练习2中 就会开始 881 00:43:15,720 --> 00:43:17,070 进入密码的世界 882 00:43:17,070 --> 00:43:22,500 所以这是一个字符串 多个字元组成的序列 883 00:43:22,500 --> 00:43:23,750 这表示什么呢? 884 00:43:28,530 --> 00:43:30,600 这个不在幻灯片的网上版本中 885 00:43:30,600 --> 00:43:35,880 所以我声明 这个等于这个 这是个广告 886 00:43:35,880 --> 00:43:39,950 已经有点历史了 如果你们还记得的话 887 00:43:39,950 --> 00:43:42,740 所以这是加密或者密码学的一个例子 888 00:43:42,740 --> 00:43:46,150 如果你真的想要安全地送出 889 00:43:46,150 --> 00:43:49,310 或是与人分享信息 就像是这一条信息 890 00:43:49,310 --> 00:43:50,500 你可以打乱字母 891 00:43:50,500 --> 00:43:53,170 但一般来说 这些字不是随意打乱的 892 00:43:53,170 --> 00:43:56,365 通过某种方式 它们被转换了 --不好意思 893 00:43:56,365 --> 00:43:59,040 这是下一次的一个小剧透 894 00:43:59,040 --> 00:44:04,390 所以你可以把O对到B 895 00:44:04,390 --> 00:44:05,420 注意 大小写上它们也对应 896 00:44:05,420 --> 00:44:07,960 显然 r变成了e 897 00:44:07,960 --> 00:44:14,000 显然 F-H-E-R变成了S-U-R-E 所以我们发现 是有对应关系的 898 00:44:14,000 --> 00:44:18,720 这里这个对应挺傻的 如果有人已经发现的话? 899 00:44:18,720 --> 00:44:21,440 这个叫做 Rot 13也就是旋转13 900 00:44:21,440 --> 00:44:24,760 它是加密中最笨的一种 因为它就是 901 00:44:24,760 --> 00:44:29,160 把每个数字加上了13 说它笨的原因是 902 00:44:29,160 --> 00:44:31,890 如果你手上有时间 外加一支笔的话 903 00:44:31,890 --> 00:44:35,260 或者你就用脑子想一想 你可以试过所有的可能 904 00:44:35,260 --> 00:44:38,470 1,2,3.……25 然后把整个字母表旋转一次 905 00:44:38,470 --> 00:44:40,860 总终 你是能破解这个信息的 906 00:44:40,860 --> 00:44:43,700 所以如果你在上学时 这样传递信息给你最好的朋友的话 907 00:44:43,700 --> 00:44:46,830 你的老师可能只是读了一遍 908 00:44:46,830 --> 00:44:50,320 就得出了答案 909 00:44:50,320 --> 00:44:52,550 你大概知道是为什么了 910 00:44:52,550 --> 00:44:54,970 在现实世界中 密码要比这复杂地多 911 00:44:54,970 --> 00:45:00,120 这是一台有用户名和密码的电脑中 912 00:45:00,120 --> 00:45:03,630 文本中的一个片段 你的密码大概就长这样 913 00:45:03,630 --> 00:45:07,260 它被存在你的硬盘里 但是加了密 914 00:45:07,260 --> 00:45:11,050 它不是把字母旋转 A变成B B变成C 915 00:45:11,050 --> 00:45:15,620 这要复杂地多 它用了我们说的 916 00:45:15,620 --> 00:45:16,690 密钥加密 917 00:45:16,690 --> 00:45:20,210 这幅图用几个图标告诉了我们这些 918 00:45:20,210 --> 00:45:22,250 在左边 我们有我们称为称文本的东西 919 00:45:22,250 --> 00:45:25,420 在密码的世界中 纯文本是最原始的信息 920 00:45:25,420 --> 00:45:29,050 用英语会法语或者任何一种语言写成 921 00:45:29,050 --> 00:45:32,405 如果你想加密它 我们会把它转成图 通过挂锁 922 00:45:32,405 --> 00:45:35,580 这样别人写的某种算法 某数函数或者某种程序 923 00:45:35,580 --> 00:45:39,880 就会把字母打乱 而不是简单地 924 00:45:39,880 --> 00:45:40,980 往每个字母上加上13 925 00:45:40,980 --> 00:45:43,780 经历了这个过程 你得到的就是密文 926 00:45:43,780 --> 00:45:44,850 听上去挺有感觉的 927 00:45:44,850 --> 00:45:47,630 它的意思是 纯文本加密后的版本 928 00:45:47,630 --> 00:45:52,570 只有当你有同样的秘密 13或是减13时 929 00:45:52,570 --> 00:45:54,970 你能才破解这样的信息 930 00:45:54,970 --> 00:45:57,770 在练习2中 在黑客版本里 931 00:45:57,770 --> 00:46:01,860 你就需要写代码来破解这些密码 932 00:46:01,860 --> 00:46:05,170 尝试找出它们是什么 以及它们是怎样被加密的 933 00:46:05,170 --> 00:46:06,460 不过 我们会给大家提供一些指导 934 00:46:06,460 --> 00:46:09,320 在普通版的练习中 我们会介绍一些密码 935 00:46:09,320 --> 00:46:12,400 加密机制 一种叫Caesar 一种叫Vigenere 936 00:46:12,400 --> 00:46:16,100 它们也是旋转密码 也就是A变成某个东西 B变成另外的什么 937 00:46:16,100 --> 00:46:18,820 但是你需要用程序 因为有用到密钥 938 00:46:18,820 --> 00:46:22,840 密钥一般是一个数字或者关键词 939 00:46:22,840 --> 00:46:26,420 只有信息的发送者和接收者才懂得 940 00:46:26,420 --> 00:46:28,660 在现实世界中 也有类似的东西 941 00:46:28,660 --> 00:46:32,910 比如说这个 说是孤儿安妮的解密环 942 00:46:32,910 --> 00:46:35,180 你可以用这些可旋转的密码 943 00:46:35,180 --> 00:46:37,930 让A变成了某个字母 B变成了某个字母 944 00:46:37,930 --> 00:46:40,840 它外面有个齿轮 里面也有一个 如果你旋转它们 945 00:46:40,840 --> 00:46:44,170 你就能把字母对应上了 946 00:46:44,170 --> 00:46:45,430 然后得到一个密码 947 00:46:45,430 --> 00:46:48,110 今天的悬念是 948 00:46:48,110 --> 00:46:52,170 如果你在12月24日打开电视 949 00:46:52,170 --> 00:46:55,390 你可以24小时一直看这电影看到吐 950 00:46:55,390 --> 00:47:06,030 但是今天 我只会给你们看2分钟 出于教学目的 951 00:47:06,030 --> 00:47:13,493 给你们看一个叫Ralphie的小孩的圣诞故事 952 00:47:13,493 --> 00:47:14,400 [视频播放] 953 00:47:14,400 --> 00:47:17,420 请广而告之 Ralph Parker今日正式成为了 954 00:47:17,420 --> 00:47:20,650 小孤独安妮秘社的成员 955 00:47:20,650 --> 00:47:23,460 享受一切荣誉与福利 956 00:47:23,460 --> 00:47:25,990 签名 小孤儿安妮 957 00:47:25,990 --> 00:47:30,100 签名的另一方 Pierre Andre 用墨水签的呢 958 00:47:30,100 --> 00:47:34,270 9岁 就有荣誉与福利了 959 00:47:34,270 --> 00:47:39,440 [广播] 960 00:47:39,440 --> 00:47:40,770 好啦 让我们开始吧 961 00:47:40,770 --> 00:47:44,965 我才不用听那些走私犯还是海盗的乱吼乱叫呢 962 00:47:44,965 --> 00:47:48,270 明晚请收听 黑海盗船的 963 00:47:48,270 --> 00:47:49,650 最终冒险 964 00:47:49,650 --> 00:47:53,320 现在 是安妮给秘密社团成员 965 00:47:53,320 --> 00:47:55,720 的秘密信息 966 00:47:55,720 --> 00:47:56,580 记住 孩子们 967 00:47:56,580 --> 00:48:01,720 只有安妮秘社的成员才能解出安妮的秘讯 968 00:48:01,720 --> 00:48:05,872 记住 安妮指望着你们呢 969 00:48:05,872 --> 00:48:08,670 把指针转到B2 970 00:48:08,670 --> 00:48:11,000 消息在这 971 00:48:11,000 --> 00:48:12,335 12,11,2 972 00:48:12,335 --> 00:48:14,670 我在我第一次秘密会议上 973 00:48:14,670 --> 00:48:19,720 25,14,11,18,16 974 00:48:19,720 --> 00:48:21,650 Pierre今晚声音很洪亮 975 00:48:21,650 --> 00:48:24,830 我听得出来 今晚的讯息非常重要 976 00:48:24,830 --> 00:48:26,400 3,25 977 00:48:26,400 --> 00:48:28,540 这是来自安妮的讯息 978 00:48:28,540 --> 00:48:30,086 记住 不能告诉任何人 979 00:48:34,370 --> 00:48:38,710 90秒后 我进入了房子里唯一一个 980 00:48:38,710 --> 00:48:42,668 能让一个9岁男孩单独破解密码的房间里 981 00:48:42,668 --> 00:48:47,628 啊 B 我看向下一个字母 982 00:48:47,628 --> 00:48:53,060 是E 第一个词是"be" S 现在很容易了 983 00:48:53,060 --> 00:48:54,980 U 25 984 00:48:54,980 --> 00:48:55,940 这是R 985 00:48:55,940 --> 00:48:56,900 加油 Ralphie 986 00:48:56,900 --> 00:48:57,860 我得走了 987 00:48:57,860 --> 00:48:59,780 我马上就下来 妈 988 00:48:59,780 --> 00:49:01,030 天 989 00:49:04,300 --> 00:49:08,220 T O Be sure to (一定要) 990 00:49:08,220 --> 00:49:09,500 一定要做什么呢? 991 00:49:09,500 --> 00:49:11,660 小孤儿安妮到底想说什么 992 00:49:11,660 --> 00:49:12,844 一定要做什么呢? 993 00:49:12,844 --> 00:49:14,732 Ralphie Randy得走了 994 00:49:14,732 --> 00:49:16,148 你能不能出来下 995 00:49:16,148 --> 00:49:17,092 好的 妈 996 00:49:17,092 --> 00:49:18,510 我马上来 997 00:49:18,510 --> 00:49:20,270 我已经快解出来了 998 00:49:20,270 --> 00:49:21,823 实在是太紧张了 999 00:49:21,823 --> 00:49:23,045 到底是什么了 1000 00:49:23,045 --> 00:49:26,510 星球的命运就悬在此间了 1001 00:49:26,510 --> 00:49:28,985 Ralphie Randy要走啦 1002 00:49:28,985 --> 00:49:32,680 我马上就来好么 1003 00:49:32,680 --> 00:49:33,956 快了 1004 00:49:33,956 --> 00:49:35,140 我的手指动得飞快 1005 00:49:35,140 --> 00:49:36,880 我的头脑像捕兽夹一样 1006 00:49:36,880 --> 00:49:38,010 每个毛孔都在震动 1007 00:49:38,010 --> 00:49:39,878 就快要想清楚了 1008 00:49:39,878 --> 00:49:43,210 是的 是的 是的 1009 00:49:43,210 --> 00:49:49,030 记得喝你的阿华田 1010 00:49:49,030 --> 00:49:50,280 阿华田 1011 00:49:53,980 --> 00:49:55,230 这是条破广告? 1012 00:49:58,572 --> 00:50:00,694 靠 1013 00:50:00,694 --> 00:50:01,900 [视频播放结束] 1014 00:50:01,900 --> 00:50:04,260 这是CS50 以上就是练习2 1015 00:50:04,260 --> 00:50:06,305 下周再见 1016 00:50:06,305 --> 00:50:08,800 在下一节CS50课上 1017 00:50:08,800 --> 00:50:11,060 到目前为止我们还没有说到的话题是 1018 00:50:11,060 --> 00:50:12,220 是函数指针 1019 00:50:12,220 --> 00:50:14,540 函数指针是一个公共函数的地址 1020 00:50:14,540 --> 00:50:17,000 就像是-- 1021 00:50:17,000 --> 00:50:18,250 靠--