1 00:00:00,000 --> 00:00:10,920 [音乐播放] 2 00:00:10,920 --> 00:00:14,680 好的 欢迎回到CS50 3 00:00:14,680 --> 00:00:16,500 这是第二周的开始 4 00:00:16,500 --> 00:00:18,940 帮一个朋友传话 5 00:00:18,940 --> 00:00:22,620 如果你现在 或是以后 6 00:00:22,620 --> 00:00:25,670 想要教中学生 7 00:00:25,670 --> 00:00:27,680 计算机的话 请点击此链接 8 00:00:27,680 --> 00:00:32,360 他们现在很需要老师 9 00:00:32,360 --> 00:00:34,700 特别是如果你们已经对计算机有所了解之后 10 00:00:34,700 --> 00:00:38,060 上一次 我们介绍了C语言中的几种数据类型 11 00:00:38,060 --> 00:00:40,590 你们也许已经开始在练习1中 12 00:00:40,590 --> 00:00:41,940 进行实践了 13 00:00:41,940 --> 00:00:43,230 我们还讲到了字元 14 00:00:43,230 --> 00:00:49,100 用比较专业的说法来说的话 就你们现在的了解 字元是什么 15 00:00:49,100 --> 00:00:51,050 它是一个字符 但是让我们更精确些 16 00:00:51,050 --> 00:00:53,735 我们说字符 或者一个字元时 是什么意思? 17 00:00:56,700 --> 00:00:59,500 一个非数值型的字符 18 00:00:59,500 --> 00:01:00,670 也不一定 19 00:01:00,670 --> 00:01:04,580 事实上 数字 甚至是标点符号 字母 20 00:01:04,580 --> 00:01:06,980 也是用字元这种数据类型表示的 21 00:01:06,980 --> 00:01:09,440 所以它不一定是按字母顺序排列的 22 00:01:09,440 --> 00:01:11,100 什么? 23 00:01:11,100 --> 00:01:12,275 它是个ASCII字符 24 00:01:12,275 --> 00:01:15,510 如果我们回到第0周 当时我们请志愿者上台来扮演字节 25 00:01:15,510 --> 00:01:19,150 一些人举手 一些人不举手 每人代表一比特 26 00:01:19,150 --> 00:01:22,450 但是他们8个人一起 就代表了一个字节 27 00:01:22,450 --> 00:01:26,030 我们在那堂课上还介绍了ASCII的概念 28 00:01:26,030 --> 00:01:28,170 也就是数字和字母间的对应 29 00:01:28,170 --> 00:01:32,010 ASCII使用8位 30 00:01:32,010 --> 00:01:33,660 来表示一个字符 31 00:01:33,660 --> 00:01:36,890 相应的 如果8位中的每一位可以有两种值 32 00:01:36,890 --> 00:01:38,010 0或1 33 00:01:38,010 --> 00:01:40,280 也就是说 这个人 34 00:01:40,280 --> 00:01:41,230 可以是0或者是1 35 00:01:41,230 --> 00:01:44,070 这个人有2种可能 这个人有2种可能 这个人有2种可能 36 00:01:44,070 --> 00:01:47,450 那么一共就有2乘2乘2乘2乘2乘2 37 00:01:47,450 --> 00:01:49,700 也就是2的8次方种可能 38 00:01:49,700 --> 00:01:54,320 所以用8位 一共可以 39 00:01:54,320 --> 00:01:55,750 表示256种字符 40 00:01:55,750 --> 00:01:59,210 你们当中会亚洲语言的人会知道 41 00:01:59,210 --> 00:02:02,620 在这世界中 可不只有A B C D这些字符 42 00:02:02,620 --> 00:02:06,130 的确 ASCII 对于世界中很多语言来说 都不够用 43 00:02:06,130 --> 00:02:07,760 不过这个我们以后再讲 44 00:02:07,760 --> 00:02:11,240 现在 只要知道在C语言中 如果你想表示一个字母 45 00:02:11,240 --> 00:02:15,780 一个标点 或者任何本质上是字符的东西 用字元就行了 46 00:02:15,780 --> 00:02:18,240 它有一个字节或者是8位 47 00:02:18,240 --> 00:02:19,690 那么int呢? 48 00:02:19,690 --> 00:02:20,780 int是一个整数 49 00:02:20,780 --> 00:02:23,175 如果你们还记得的话,一个整数一般有多少位? 50 00:02:25,930 --> 00:02:27,512 有谁记得么? 51 00:02:27,512 --> 00:02:29,600 一般是32位 52 00:02:29,600 --> 00:02:32,120 实际上 这要取决于你在用什么电脑 53 00:02:32,120 --> 00:02:35,770 但是在大多数电脑里 是32位 54 00:02:35,770 --> 00:02:37,140 或者是4字节 因为8乘以4等于32 55 00:02:37,140 --> 00:02:39,790 int是用来存储数字的 56 00:02:39,790 --> 00:02:41,610 不论是负数 正数 还是0 57 00:02:41,610 --> 00:02:45,250 如果你们32位 而且你只想关注正数的话 58 00:02:45,250 --> 00:02:48,960 有谁知道电脑可以表示出 59 00:02:48,960 --> 00:02:51,820 多少种大于0的整数? 60 00:02:51,820 --> 00:02:56,130 2的32次方 也就差不多是400万 61 00:02:56,130 --> 00:02:59,720 2的次方将是我们在计算机科学中不断见到的主题 62 00:02:59,720 --> 00:03:03,930 我们会发现 与它们打交道还比较容易 63 00:03:03,930 --> 00:03:05,790 虽然在脑中计算它们可能有点困难 64 00:03:05,790 --> 00:03:07,000 所以我们就说 差不多是400万 65 00:03:07,000 --> 00:03:08,620 超长整数 66 00:03:08,620 --> 00:03:09,770 你们可以猜到 67 00:03:09,770 --> 00:03:10,480 比整数要长 68 00:03:10,480 --> 00:03:12,440 那有多少位呢? 69 00:03:12,440 --> 00:03:14,250 64位 或者是8字节 70 00:03:14,250 --> 00:03:17,480 这就意味着 你们可以表示更大的数字 71 00:03:17,480 --> 00:03:19,160 不管是更大的正数还是更大的负数 72 00:03:19,160 --> 00:03:20,060 浮点数呢? 73 00:03:20,060 --> 00:03:22,260 浮点数有32位 74 00:03:22,260 --> 00:03:25,180 它是一个实数 带有小数点 75 00:03:25,180 --> 00:03:30,100 但如果你在小数点后想要放更多的数字 76 00:03:30,100 --> 00:03:33,720 或是你想表示更大的分数?? 77 00:03:33,720 --> 00:03:36,260 你可以用一个双精度浮点数 它有64位 78 00:03:36,260 --> 00:03:38,240 但这里我们会有个有趣的发现 79 00:03:38,240 --> 00:03:42,890 如果整数只要32位 超长整数也有64位的限制 80 00:03:42,890 --> 00:03:46,180 那么自然产生这样一个问题 如果你想要数到 81 00:03:46,180 --> 00:03:48,790 大于400万的整数呢? 82 00:03:48,790 --> 00:03:50,330 那你就用一个超长整数 83 00:03:50,330 --> 00:03:54,200 但如果你想要数到 超2的64次方的数字呢? 84 00:03:54,200 --> 00:03:55,810 那会是一个超级大的数 85 00:03:55,810 --> 00:03:59,250 但也许最后 你会想要关注这样值 86 00:03:59,250 --> 00:04:03,070 特别是如果你们要开始用数据库 并且开始收集 87 00:04:03,070 --> 00:04:06,190 大量的数据 并且将特定的数字赋给这些数据之后 88 00:04:06,190 --> 00:04:07,430 所以我们就面临着一个问题 89 00:04:07,430 --> 00:04:10,700 同样 浮点数值 -- 浮点数或双精度浮点数-- 90 00:04:10,700 --> 00:04:14,290 如果你只有有限个数的位 你一共能够 91 00:04:14,290 --> 00:04:16,980 用它们表示多少种数字呢? 92 00:04:16,980 --> 00:04:19,540 有了小数点之后 答案会不太明显 93 00:04:19,540 --> 00:04:20,899 但是肯定是有限个数的数字 94 00:04:20,899 --> 00:04:24,390 如果你有有限个数的位 有限个数的人 95 00:04:24,390 --> 00:04:27,350 有限个数的的灯泡 那你肯定只能表示有限个数 96 00:04:27,350 --> 00:04:28,510 的浮点数 97 00:04:28,510 --> 00:04:33,170 但是在这世界上一共有多少个数字呢? 98 00:04:33,170 --> 00:04:33,680 无限个 99 00:04:33,680 --> 00:04:37,280 所以因为在电脑里 我们没有无限大的空间 100 00:04:37,280 --> 00:04:39,970 或是RAM 这就构成了问题 101 00:04:39,970 --> 00:04:41,780 所以我们会面对一些挑战性很大的事 102 00:04:41,780 --> 00:04:43,900 那就让我们来试着讲一下 103 00:04:43,900 --> 00:04:46,240 让我打开gedit 104 00:04:46,240 --> 00:04:50,360 我会保存一个叫floats0.c的文件 105 00:04:50,360 --> 00:04:54,630 这样它就和网上的例子名字一样了 106 00:04:54,630 --> 00:04:58,080 我会将它定义为-- 107 00:04:58,080 --> 00:05:01,540 我要输入 int main void 就像我们经常做的那样 108 00:05:01,540 --> 00:05:07,190 在这个程序里 我要声明一个浮点数 109 00:05:07,190 --> 00:05:09,700 也就是一个32位的变量 叫f 这是我们任意取的 110 00:05:09,700 --> 00:05:13,910 然后我要把 就 十分之一 0.1好了 存在里面 111 00:05:13,910 --> 00:05:16,590 我要表它表示为 1除以10 112 00:05:16,590 --> 00:05:17,790 这在C语言中是完全合理的 113 00:05:17,790 --> 00:05:20,460 在第二行里 我只想输出这个值 114 00:05:20,460 --> 00:05:22,950 还记得吗 我们可以用我们已经很熟悉的printf 115 00:05:22,950 --> 00:05:25,420 我们不想用%i代表一个整数 116 00:05:25,420 --> 00:05:28,360 我们要用%f代表一个浮点数 117 00:05:28,360 --> 00:05:33,080 然后我要输入\n",f; 118 00:05:33,080 --> 00:05:34,400 这就是我的程序了 119 00:05:34,400 --> 00:05:35,820 已经出现了一个bug 120 00:05:35,820 --> 00:05:38,640 我至少犯了一个错 有谁想要解释一下 121 00:05:38,640 --> 00:05:40,220 是什么错么? 122 00:05:40,220 --> 00:05:42,470 嗯? 123 00:05:42,470 --> 00:05:42,800 是的 124 00:05:42,800 --> 00:05:47,860 我忘记在最上面输入 #include了 造成的结果是 125 00:05:47,860 --> 00:05:50,490 如果我试图编译它的话 编译器就会说 126 00:05:50,490 --> 00:05:52,770 未定义符号或者类似的东西 127 00:05:52,770 --> 00:05:55,360 它会看不懂printf这类的东西 128 00:05:55,360 --> 00:05:59,380 所以我要 #include 然后保存文件 129 00:05:59,380 --> 00:06:00,400 这下好多了 130 00:06:00,400 --> 00:06:02,690 但我今天要指出一个新细节 131 00:06:02,690 --> 00:06:08,620 除了要明确像%f, %i, %s这样的占位符以外 132 00:06:08,620 --> 00:06:12,320 你还可以影响占位符的行为 133 00:06:12,320 --> 00:06:15,540 比如 拿浮点数值来说 134 00:06:15,540 --> 00:06:22,200 如果我只想显示小数点后一位的话 我可以输入0.1f 135 00:06:22,200 --> 00:06:26,830 换句话说 我把f和%号中加了一个0.1 136 00:06:26,830 --> 00:06:30,200 这就告诉printf 在小数点后面 137 00:06:30,200 --> 00:06:30,930 有可能有一大堆数 138 00:06:30,930 --> 00:06:32,870 但是我只想要看到一位 139 00:06:32,870 --> 00:06:36,280 那么我现在就来保存这个文件 然后到我的终端窗口 140 00:06:36,280 --> 00:06:41,870 然后我会输入 make float 0 回车 141 00:06:41,870 --> 00:06:44,930 我看到了句难懂的句子 142 00:06:44,930 --> 00:06:46,900 在这周后下一周 我们分解它之后 你们会开始理解它 143 00:06:46,900 --> 00:06:50,480 不过现在就让我运行float 0 144 00:06:50,480 --> 00:06:52,020 不好 145 00:06:52,020 --> 00:06:54,880 出于某种原因 这又有了一个bug 146 00:06:54,880 --> 00:07:02,490 我挺确定十分之一 或者是1除以10 不是0.0 147 00:07:02,490 --> 00:07:04,590 也许我只是没有看到足够的数位 148 00:07:04,590 --> 00:07:08,580 为什么不让我说.2来看小数点后两位 而不是一位呢 149 00:07:08,580 --> 00:07:11,810 让我回到我的终端窗口 然后敲击几下向上键 150 00:07:11,810 --> 00:07:12,840 来看一下我的历史 151 00:07:12,840 --> 00:07:15,910 重新make float 0 然后再向上 152 00:07:15,910 --> 00:07:17,730 现在 回车 153 00:07:17,730 --> 00:07:20,000 现在我比较确定这是错的 154 00:07:20,000 --> 00:07:23,030 我也可以改成3或者是4 但我大概会一直看到0 155 00:07:23,030 --> 00:07:24,880 那么bug在哪里呢? 156 00:07:24,880 --> 00:07:27,910 1除以10应该是0.1 157 00:07:27,910 --> 00:07:30,310 有人想要猜一下 根本上的问题在哪里么? 158 00:07:30,310 --> 00:07:32,400 什么? 159 00:07:32,400 --> 00:07:33,420 他们都是整数 160 00:07:33,420 --> 00:07:33,920 所以呢? 161 00:07:33,920 --> 00:07:37,820 所以1除以10 这是我在算术里做的事 162 00:07:37,820 --> 00:07:41,185 然后我得到了0.1 163 00:07:41,185 --> 00:07:41,660 没错 164 00:07:41,660 --> 00:07:43,240 就是这个问题 165 00:07:43,240 --> 00:07:46,700 当你在电脑里 用一个整数除以另一个整数的时候 166 00:07:46,700 --> 00:07:50,430 电脑就会默认你想得到一个整数 167 00:07:50,430 --> 00:07:54,620 问题在于 0.1并不是一个整数 168 00:07:54,620 --> 00:07:55,680 它是一个实数 169 00:07:55,680 --> 00:07:59,610 所以电脑默认的举措 就是 170 00:07:59,610 --> 00:08:01,070 把小数点后的东西都扔掉 171 00:08:01,070 --> 00:08:03,380 它不会四舍五入什么的 172 00:08:03,380 --> 00:08:06,480 它只会把小数点后的所有东西都扔掉 173 00:08:06,480 --> 00:08:07,430 现在这就解释得通了 174 00:08:07,430 --> 00:08:09,740 因为我们只看到0 175 00:08:09,740 --> 00:08:10,250 但等一下 176 00:08:10,250 --> 00:08:11,840 我看到的不是整数0 177 00:08:11,840 --> 00:08:14,910 我看到的是0.00 178 00:08:14,910 --> 00:08:16,340 这个要怎么解释呢? 179 00:08:16,340 --> 00:08:22,850 如果1除以10是0 但我看到了0.00 180 00:08:22,850 --> 00:08:24,250 它在哪里被转换成了实数呢? 181 00:08:24,250 --> 00:08:25,500 没错 182 00:08:29,850 --> 00:08:30,630 没错 183 00:08:30,630 --> 00:08:35,600 在上面第五行这里 当我存储0.1的时候 184 00:08:35,600 --> 00:08:39,549 它被截成了0 在浮点数里面来说 这就相当于 185 00:08:39,549 --> 00:08:42,100 把它存成了一个浮点数 而不是一个整数 186 00:08:42,100 --> 00:08:46,540 另外 我还用了printf来显示 187 00:08:46,540 --> 00:08:49,740 小数点后两位的数 虽然小数点后可能根本没有东西 188 00:08:49,740 --> 00:08:51,020 这感觉不太妙 是不是 189 00:08:51,020 --> 00:08:53,640 显然 你没法用电脑计算 190 00:08:53,640 --> 00:08:55,600 至少在这个精度要求下 191 00:08:55,600 --> 00:08:56,930 但是肯定是有解决办法的 192 00:08:56,930 --> 00:09:00,410 最简单的解决方法是什么呢 193 00:09:00,410 --> 00:09:01,130 最直观的想法? 194 00:09:01,130 --> 00:09:02,380 什么? 195 00:09:04,700 --> 00:09:06,574 把整数变成-- 196 00:09:06,574 --> 00:09:06,976 没错 197 00:09:06,976 --> 00:09:10,420 即使我还不太确定这里发生了什么 198 00:09:10,420 --> 00:09:13,440 如果根本问题是因为它们是整数的话 199 00:09:13,440 --> 00:09:18,230 我为什么不把它改成10.0 把它改成1.0 重新保存 200 00:09:18,230 --> 00:09:20,990 让我回到底部重新编译 201 00:09:20,990 --> 00:09:23,030 然后重新运行 202 00:09:23,030 --> 00:09:23,420 好了-- 203 00:09:23,420 --> 00:09:27,690 现在 我的十分之一显示成0.10了 204 00:09:27,690 --> 00:09:28,420 好 205 00:09:28,420 --> 00:09:29,220 这不错 206 00:09:29,220 --> 00:09:31,730 让我再提出另一种解决办法 207 00:09:31,730 --> 00:09:35,580 让我们倒带一下 回到刚才 208 00:09:35,580 --> 00:09:36,680 说十分之一的时候 209 00:09:36,680 --> 00:09:40,800 让我把这个文件重新保存成另一个名字 210 00:09:40,800 --> 00:09:41,750 这样就有存档了 211 00:09:41,750 --> 00:09:43,450 所以这是版本一 212 00:09:43,450 --> 00:09:45,520 然后让我再做一个版本 213 00:09:45,520 --> 00:09:48,540 我们把它叫做版本2.0? 214 00:09:48,540 --> 00:09:51,280 而我要--你们知道么 215 00:09:51,280 --> 00:09:54,400 在这情况下 加上.0实际上行得通 216 00:09:54,400 --> 00:09:56,060 但是假设1是个变量 217 00:09:56,060 --> 00:09:57,680 假设10是个变量 218 00:09:57,680 --> 00:10:00,680 换句话说 假设我不能把.0硬编码 219 00:10:00,680 --> 00:10:02,340 在这个算术表达式最后 220 00:10:02,340 --> 00:10:05,820 实际上 我可以在括号中做一个叫做casting(转换)的事 221 00:10:05,820 --> 00:10:11,920 我可以把整数10转换成一个浮点数 然后我也可以 222 00:10:11,920 --> 00:10:12,800 把整数1转换成浮点数 223 00:10:12,800 --> 00:10:17,190 然后算术上 就会变成1.0除以10.0了 224 00:10:17,190 --> 00:10:19,250 其结果会放在f里 就像以前一样 225 00:10:19,250 --> 00:10:26,130 所以如果我把它重新编译成make floats 2然后运行 floats 2 226 00:10:26,130 --> 00:10:27,020 我会得到一样的结果 227 00:10:27,020 --> 00:10:29,640 所以这是一个我设计出来的例子 228 00:10:29,640 --> 00:10:31,400 用了转换来解决问题 229 00:10:31,400 --> 00:10:34,410 但一般来说 转换的力量非常强大 230 00:10:34,410 --> 00:10:38,180 特别是在一周之后的练习2中 当你想要把一种数据类型 231 00:10:38,180 --> 00:10:41,800 转换成另一种的时候 说到底 它们都是用相同的方式表示的 232 00:10:41,800 --> 00:10:44,970 说到底 我们目前为止说的所有东西 233 00:10:44,970 --> 00:10:46,710 都是整数 只不过不明显而已 234 00:10:46,710 --> 00:10:48,950 如果这听上去太低端 那么 235 00:10:48,950 --> 00:10:49,750 他们其实都是数字 236 00:10:49,750 --> 00:10:52,850 就算是字符 请回忆一下第0周的内容 237 00:10:52,850 --> 00:10:53,990 归根到底也是数字 238 00:10:53,990 --> 00:10:57,240 也就是说 我们可以将两种类型的数字相互转换 239 00:10:57,240 --> 00:10:58,060 如果他们只是一位一位的数字的话 240 00:10:58,060 --> 00:11:01,020 同样 我们也可以将数字和字母之间 241 00:11:01,020 --> 00:11:02,580 进行相互转换 242 00:11:02,580 --> 00:11:07,170 而转换(casting)是编程中的一项机制 243 00:11:07,170 --> 00:11:10,970 让你将一种数据类型变成另一种 244 00:11:10,970 --> 00:11:14,570 不幸的是 它并没有我想像的那么简单 245 00:11:14,570 --> 00:11:19,220 我要回到floats1 那个简单的版本 246 00:11:19,220 --> 00:11:22,830 也就是我们把.0加在两个数后面的版本 247 00:11:22,830 --> 00:11:25,260 为了帮大家回忆一下 让我重新编译一下 248 00:11:25,260 --> 00:11:27,670 make floats 2-- 249 00:11:27,670 --> 00:11:30,300 对不起 是 make floats 1 250 00:11:30,300 --> 00:11:32,050 现在 运行floats 1 251 00:11:32,050 --> 00:11:34,810 在最底下 注意 我确实得到了0.1 252 00:11:34,810 --> 00:11:36,165 好的 问题解决了 253 00:11:36,165 --> 00:11:37,280 但其实还没有 254 00:11:37,280 --> 00:11:40,000 我现在有点好奇 所以我要回到 255 00:11:40,000 --> 00:11:41,620 我的printf语句 然后说 256 00:11:41,620 --> 00:11:44,090 我想要确定 这真的是十分之一 257 00:11:44,090 --> 00:11:47,890 然后我想要看到 小数点后5位 258 00:11:47,890 --> 00:11:48,570 这并不是问题 259 00:11:48,570 --> 00:11:52,020 我把2变成5 我用make重新编译 260 00:11:52,020 --> 00:11:53,770 我重新运行floats 1 261 00:11:53,770 --> 00:11:54,990 看上去很好 262 00:11:54,990 --> 00:11:58,570 这个检查已经足够让我安心了 但我想要更有冒险精神些 263 00:11:58,570 --> 00:12:00,330 我要把0.5变成0.10 264 00:12:00,330 --> 00:12:03,440 我想要看到小数点后10位 265 00:12:03,440 --> 00:12:09,060 让我来重新编译然后重新运行floats 1 266 00:12:09,060 --> 00:12:13,060 我实际上有点后悔要做这个测试 267 00:12:13,060 --> 00:12:14,320 因为好像我的数学算得不那么准了 268 00:12:14,320 --> 00:12:15,630 但等一下 也许这只是意外 269 00:12:15,630 --> 00:12:17,810 也许电脑只是临时抽风 270 00:12:17,810 --> 00:12:21,810 让我把它改成小数点后20位 271 00:12:21,810 --> 00:12:22,540 然后告诉自己我懂得怎么计算 272 00:12:22,540 --> 00:12:23,460 我知道怎么编程 273 00:12:23,460 --> 00:12:26,960 make floats 1 重新编译 不妙 274 00:12:26,960 --> 00:12:31,110 这实在是差得有点远 275 00:12:31,110 --> 00:12:32,490 所以到底发生了什么? 276 00:12:32,490 --> 00:12:36,050 直觉上来说 根据我们对于数据类型大小的猜测 277 00:12:36,050 --> 00:12:38,040 这背后可能发生了什么问题? 278 00:12:38,040 --> 00:12:39,290 什么? 279 00:12:43,000 --> 00:12:43,590 没错 280 00:12:43,590 --> 00:12:46,480 如果你想要精度达到如此之高 281 00:12:46,480 --> 00:12:48,770 小数点后20位 这精度真的非常高 282 00:12:48,770 --> 00:12:51,990 除非你有任意位的数 否则你不可能 283 00:12:51,990 --> 00:12:52,930 表示一个任意数 284 00:12:52,930 --> 00:12:54,190 但是我们没有 285 00:12:54,190 --> 00:12:57,200 浮点数 我们只有32位 286 00:12:57,200 --> 00:13:02,260 所以这32位 --就像我们的志愿者在台上举手或放下那样 287 00:13:02,260 --> 00:13:05,780 --的变化是有限的 我们只能用它 288 00:13:05,780 --> 00:13:08,640 来表示一定数量的实数 289 00:13:08,640 --> 00:13:10,500 所以最终 电脑 290 00:13:10,500 --> 00:13:11,730 会开始偷工减料 291 00:13:11,730 --> 00:13:15,500 电脑可以在我们面前把这些细节藏起来一阵 292 00:13:15,500 --> 00:13:18,880 但是如果我们开始深入调查这些数字 293 00:13:18,880 --> 00:13:23,220 我们会发现 294 00:13:23,220 --> 00:13:26,480 它是在把十分之一这个概念近似化了 295 00:13:26,480 --> 00:13:29,860 所以我们发现 不幸的是 我们只能用 296 00:13:29,860 --> 00:13:35,060 电脑精确表示一定数目的数字 297 00:13:35,060 --> 00:13:38,030 至少对于一定数量的位 或者是一定大小的RAM来说 298 00:13:38,030 --> 00:13:41,210 不幸的是 这在现实世界中可能会带来一定后果 299 00:13:41,210 --> 00:13:45,980 如果人们不了解这个 或者想当然以为 300 00:13:45,980 --> 00:13:48,310 他们让电脑做什么 电脑就会做什么 301 00:13:48,310 --> 00:13:51,430 而不懂这些根本的表达细节 302 00:13:51,430 --> 00:13:55,290 确实 不像C语言 在一些语言中 这些细节用户是看不到的 303 00:13:55,290 --> 00:13:56,500 那么一些不好的事可能会发生 304 00:13:56,500 --> 00:13:58,650 我想 我们可以退后一步 305 00:13:58,650 --> 00:14:00,420 这个视频大约8分钟 306 00:14:00,420 --> 00:14:04,200 它是几年前出的 可以给我们一些启发 307 00:14:04,200 --> 00:14:09,290 如果你在这个现实的世界里 不注意这些细节的话 308 00:14:09,290 --> 00:14:10,080 可能会发生什么 309 00:14:10,080 --> 00:14:12,965 我们能不能把灯调暗一下 310 00:14:12,965 --> 00:14:14,360 我们现在回到Modern Marvels中的 311 00:14:14,360 --> 00:14:17,160 的工程学灾难 312 00:14:17,160 --> 00:14:18,680 电脑-- 313 00:14:18,680 --> 00:14:21,340 我们已经学会接受它们带来的 314 00:14:21,340 --> 00:14:23,170 恼人的问题了 315 00:14:23,170 --> 00:14:27,570 Bugs 病毒 和软件的小毛病 316 00:14:27,570 --> 00:14:28,960 都是方便要付出的代价 317 00:14:28,960 --> 00:14:32,040 但是对于高科技 高速度的军事及太空程应用来说 318 00:14:32,040 --> 00:14:38,650 这些小问题 有可能变成大灾难 319 00:14:38,650 --> 00:14:44,480 在1996年6月4日 科学家们正准备发设没有载人的Ariance 5火箭 320 00:14:44,480 --> 00:14:48,700 它携带了科学卫星 以探测 321 00:14:48,700 --> 00:14:53,250 地球磁场怎样与太阳风相互作用 322 00:14:53,250 --> 00:14:57,540 这个火箭是为欧洲空间局造的 323 00:14:57,540 --> 00:14:59,906 在法属圭亚那岸边发设 324 00:14:59,906 --> 00:15:03,660 发设后大约37秒 325 00:15:03,660 --> 00:15:04,910 他们发现了不对劲的地方 326 00:15:04,910 --> 00:15:08,130 喷嘴处旋转得很不正常 327 00:15:08,130 --> 00:15:12,380 发设后40秒 很明显 火箭出了问题 328 00:15:12,380 --> 00:15:14,400 那时 他们决定要销毁它 329 00:15:14,400 --> 00:15:18,520 安全管理官员很有勇气 按下了按钮 330 00:15:18,520 --> 00:15:23,900 在火箭可能对公众安全造成威胁前 将它炸毁 331 00:15:23,900 --> 00:15:27,810 这是Ariance 5号火箭的处女行 332 00:15:27,810 --> 00:15:32,020 它的毁灭是因为火箭内部软件问题 333 00:15:32,020 --> 00:15:33,980 问题是 一个数字需要64位 334 00:15:33,980 --> 00:15:36,390 来表达 335 00:15:36,390 --> 00:15:39,420 然后他们想要把它转换为一个16位的数字 336 00:15:39,420 --> 00:15:43,130 他们以为那个数字不会太大 337 00:15:43,130 --> 00:15:46,810 因为这个64位的数字中很多位都是0 338 00:15:46,810 --> 00:15:48,270 他们错了 339 00:15:48,270 --> 00:15:51,380 一个软件程序不能接受 340 00:15:51,380 --> 00:15:55,350 另一个程序产生的数字 是问题的根源所在 341 00:15:55,350 --> 00:15:59,970 软件开发是新科技当中相当耗费钱财的一部分 342 00:15:59,970 --> 00:16:03,980 Ariane 4号火箭很成功 343 00:16:03,980 --> 00:16:07,480 所以其中的很多软件也被Ariane5使用了 344 00:16:07,480 --> 00:16:11,980 基本的问题是 Ariane 5更快速 345 00:16:11,980 --> 00:16:13,720 加速更快 346 00:16:13,720 --> 00:16:17,250 而软件没有考虑到这一点 347 00:16:17,250 --> 00:16:20,770 火箭遭到销毁在经济上造成了极大损失 348 00:16:20,770 --> 00:16:24,200 而这只是因为一个小小的软件错误 349 00:16:24,200 --> 00:16:27,820 但这并不是第一次 数据转换问题造成 350 00:16:27,820 --> 00:16:30,620 火箭科技中的问题了 351 00:16:30,620 --> 00:16:34,480 1991年 海湾战争刚开始 352 00:16:34,480 --> 00:16:38,610 爱国者号导弹也经历了类似的数字转换问题 353 00:16:38,610 --> 00:16:44,910 造成了28名美国士兵死亡 超过100人受伤 354 00:16:44,910 --> 00:16:48,600 爱国者 本是应该在飞毛腿导弹袭击时 保护人们的 355 00:16:48,600 --> 00:16:51,630 但却发射失败了 356 00:16:51,630 --> 00:16:55,110 当伊拉克入侵科威特时 美国在1991年初实行了沙漠风暴战略打击 357 00:16:55,110 --> 00:17:00,570 爱国者导弹的炮塔被运去保护沙特阿拉伯 358 00:17:00,570 --> 00:17:04,760 和以色列不被伊拉克的飞毛腿导弹袭击 359 00:17:04,760 --> 00:17:09,720 爱国者导弹是美国的中程地对空导弹 360 00:17:09,720 --> 00:17:11,569 由Raytheon公司研制 361 00:17:11,569 --> 00:17:16,410 爱国者导弹拦截器的长度 362 00:17:16,410 --> 00:17:17,710 大概是6米 363 00:17:17,710 --> 00:17:20,800 重约907公斤 364 00:17:20,800 --> 00:17:22,940 携带的弹头 365 00:17:22,940 --> 00:17:24,905 大约是68公斤重 366 00:17:24,905 --> 00:17:31,030 这个弹头具有高爆性 367 00:17:31,030 --> 00:17:33,270 周围还有弹片 368 00:17:33,270 --> 00:17:37,490 弹头的弹壳设计类似霰弹 369 00:17:37,490 --> 00:17:40,720 导弹每四个放在一个集装箱内 370 00:17:40,720 --> 00:17:43,050 由半拖挂车运输 371 00:17:43,050 --> 00:17:47,490 爱国者导弹防御系统的历史 372 00:17:47,490 --> 00:17:50,710 至少有20年 373 00:17:50,710 --> 00:17:54,350 最初 它的功能是空中防御 374 00:17:54,350 --> 00:17:56,190 用来击落敌机 375 00:17:56,190 --> 00:18:02,490 在第一次海湾战争中 随着战事 军方想要 376 00:18:02,490 --> 00:18:05,535 用它来拦截飞毛腿 而不是飞机 377 00:18:05,535 --> 00:18:09,310 伊拉克空军不是什么大问题 378 00:18:09,310 --> 00:18:12,450 让军方头疼的 是飞毛腿导弹 379 00:18:12,450 --> 00:18:15,950 所以他们想要将爱国者升级 380 00:18:15,950 --> 00:18:18,750 拦截速度5倍于音速的敌方导弹 381 00:18:18,750 --> 00:18:20,890 本身就很有挑战性了 382 00:18:20,890 --> 00:18:25,590 当爱国者仓促服役时 军方并没有发现 383 00:18:25,590 --> 00:18:31,710 伊拉克方面改进了飞毛腿 让拦截它几乎成为不可能 384 00:18:31,710 --> 00:18:35,240 发射出来的飞毛腿导弹 385 00:18:35,240 --> 00:18:36,570 并不稳定 386 00:18:36,570 --> 00:18:37,532 它们会摇晃 387 00:18:37,532 --> 00:18:43,220 其摇晃的原因是伊拉克人为了使本来射程为300公里的导弹 388 00:18:43,220 --> 00:18:47,530 射程达到600公里 把弹头重量减轻了 389 00:18:47,530 --> 00:18:49,290 他们把弹头重量减轻了 390 00:18:49,290 --> 00:18:53,110 所以当爱国者试图拦截飞毛腿时 391 00:18:53,110 --> 00:18:56,470 大多数时候 绝大多数时候 392 00:18:56,470 --> 00:18:58,730 它会从飞毛腿旁边擦过 393 00:18:58,730 --> 00:19:01,760 当爱国者的系统操作员发现爱国者未能击中目标后 394 00:19:01,760 --> 00:19:06,690 他们会触发爱国者弹头 这样如果它落到地面上 395 00:19:06,690 --> 00:19:10,300 也不会造成伤亡 396 00:19:10,300 --> 00:19:14,540 这也是大多数人在在空中看到的大火球时 397 00:19:14,540 --> 00:19:20,350 他们误以为是成功拦截了飞毛腿 398 00:19:20,350 --> 00:19:23,320 虽然在夜间爱国者好像能成功摧毁飞毛腿 399 00:19:23,320 --> 00:19:27,530 但其在宰赫兰的表现 400 00:19:27,530 --> 00:19:29,140 是无法抹去的 401 00:19:29,140 --> 00:19:34,180 在那里 爱国者的雷达系统没能探测到飞毛腿的存在 402 00:19:34,180 --> 00:19:36,380 由于软件问题 没有发射 403 00:19:39,890 --> 00:19:42,700 是以色列人先发现 系统运行时间运长 404 00:19:42,700 --> 00:19:48,020 系统中电脑中的时钟造成的时间差距 405 00:19:48,020 --> 00:19:50,470 就越多 406 00:19:50,470 --> 00:19:54,640 在宰赫兰惨剧发生约两周前 407 00:19:54,640 --> 00:19:58,440 以色列人报告国防部 系统正在损失时间 408 00:19:58,440 --> 00:20:01,280 在运行约8小时后 他们发现系统 409 00:20:01,280 --> 00:20:03,530 已经不再准确 410 00:20:03,530 --> 00:20:07,710 国防部回应 让所有的爱国者导弹炮塔 411 00:20:07,710 --> 00:20:10,500 都不要让系统运行太久 412 00:20:10,500 --> 00:20:12,430 他们并没有说太久是多久 413 00:20:12,430 --> 00:20:13,330 8小时? 414 00:20:13,330 --> 00:20:13,810 10小时? 415 00:20:13,810 --> 00:20:14,990 1000小时?? 416 00:20:14,990 --> 00:20:17,150 没有人知道 417 00:20:17,150 --> 00:20:20,220 在宰赫兰的爱国者导弹炮塔 418 00:20:20,220 --> 00:20:24,660 以及它内部出错的时钟在25日晚上时 419 00:20:24,660 --> 00:20:27,470 已经运行了超100小时 420 00:20:27,470 --> 00:20:31,770 它的时间精度大概是十分之一秒 421 00:20:31,770 --> 00:20:34,480 十分之一是个很有趣的数字 422 00:20:34,480 --> 00:20:39,940 因为它不能用二进制精确表示 也就是说 它不能在 423 00:20:39,940 --> 00:20:42,500 任何电脑当中被表示 424 00:20:42,500 --> 00:20:46,920 这有点难以至信 但是举个例子 425 00:20:46,920 --> 00:20:49,000 我们用三分之一这个数好了 426 00:20:49,000 --> 00:20:53,150 三分之一不能用小数精确表示 427 00:20:53,150 --> 00:20:57,500 因为三分之一是0.333无限下去 428 00:20:57,500 --> 00:21:02,270 用小数是无法精确表示它的 429 00:21:02,270 --> 00:21:05,370 这和爱国者的问题是同一类型 430 00:21:05,370 --> 00:21:09,880 系统运行时间越长 时间的偏差就越严重 431 00:21:09,880 --> 00:21:13,840 运行100小时后 时间差 432 00:21:13,840 --> 00:21:16,140 大约为三分之一秒 433 00:21:16,140 --> 00:21:20,800 但是对于锁定速度5倍于音速的导弹来说 434 00:21:20,800 --> 00:21:24,410 追踪距离上的错误会超过600米 435 00:21:24,410 --> 00:21:27,670 这对当时在宰赫兰的士兵来说 是个致命的错误 436 00:21:27,670 --> 00:21:33,450 当时 飞毛腿发射被预警卫星 437 00:21:33,450 --> 00:21:34,280 探测到了 438 00:21:34,280 --> 00:21:38,550 他们知道 飞毛腿正朝这个方向袭来 439 00:21:38,550 --> 00:21:41,000 但是他们不知道是从哪来 440 00:21:41,000 --> 00:21:43,900 现在 就要靠爱国者系统的雷达 441 00:21:43,900 --> 00:21:48,910 来保护宰赫兰 并且追踪确定敌方导弹的位置了 442 00:21:48,910 --> 00:21:50,580 雷达很聪明 443 00:21:50,580 --> 00:21:53,770 实际上 它会追踪飞毛腿的位置 然后预测 444 00:21:53,770 --> 00:21:57,160 在雷达释放下一波脉冲时 它会到哪里 445 00:21:57,160 --> 00:21:58,870 这叫作距离门 446 00:21:58,870 --> 00:22:04,020 当爱国者决定已经过了足够时间 可以再去查一下导弹到哪里的时候 447 00:22:04,020 --> 00:22:09,420 它就会再查一次 448 00:22:09,420 --> 00:22:14,450 如果它去错误的地方检查 就不会发现任何目标物 449 00:22:14,450 --> 00:22:18,200 如果它未能发现任何目标物 就是假警报 450 00:22:18,200 --> 00:22:19,680 那么追踪就会停止 451 00:22:19,680 --> 00:22:22,970 来袭的飞毛腿从雷达屏幕上消失了 452 00:22:22,970 --> 00:22:26,050 几秒之后 它击中了兵营 453 00:22:26,050 --> 00:22:31,950 这个飞毛腿造成了28人死亡 这也是第一次海湾战争中发射的最后一枚飞毛腿 454 00:22:31,950 --> 00:22:37,700 悲剧的是 更新好的软件于第二天到达了宰赫兰 455 00:22:37,700 --> 00:22:41,800 软件问题被修好了 爱国者导弹有问题的这一页 456 00:22:41,800 --> 00:22:43,690 也终于被翻过 457 00:22:46,780 --> 00:22:50,710 爱国者实际上是 相控阵雷达截获目标 458 00:22:50,710 --> 00:22:51,960 的缩写 459 00:22:54,650 --> 00:23:00,840 好的 这个例子 发人深省 460 00:23:00,840 --> 00:23:03,430 幸运的是 这些低端错误 461 00:23:03,430 --> 00:23:06,220 我们一般不用烦恼 特别是 462 00:23:06,220 --> 00:23:07,360 这些早期程序中的问题 463 00:23:07,360 --> 00:23:10,450 你们遇到的大多数bug应该是逻辑上的 464 00:23:10,450 --> 00:23:12,900 或是句法上的 也就是代码没法正确运行 465 00:23:12,900 --> 00:23:14,140 这些问题你们也会很快发现它 466 00:23:14,140 --> 00:23:16,850 但接近学期末时 467 00:23:16,850 --> 00:23:20,620 有可能就要好好想一想 468 00:23:20,620 --> 00:23:22,960 你们程序的设计问题 以及根本的 469 00:23:22,960 --> 00:23:24,520 数据表示问题 470 00:23:24,520 --> 00:23:28,010 比如 我们会介绍MySQL 它是一个流行的数据库管理系统 471 00:23:28,010 --> 00:23:30,850 你可以用它在网上储存数据 472 00:23:30,850 --> 00:23:34,630 在学期末 你不仅要决定你要用什么样的数据 473 00:23:34,630 --> 00:23:38,790 还要决定你要用多少位 474 00:23:38,790 --> 00:23:42,740 要不要把日期储存成日期 把时间储存成时间 475 00:23:42,740 --> 00:23:46,890 以及你想要给使用你数据库的人 476 00:23:46,890 --> 00:23:47,680 多大的ID 477 00:23:47,680 --> 00:23:51,210 事实上 如果你们已经注册Facebook挺久了 478 00:23:51,210 --> 00:23:53,680 然后你知道怎样取得你的用户ID 479 00:23:53,680 --> 00:23:57,930 也就是你主页URL上有时会的显示的 480 00:23:57,930 --> 00:24:02,070 除非你选了其他的昵称显示在链接上 或者如果你用过Facebook的图像API 481 00:24:02,070 --> 00:24:05,510 也就是对公众开放的API 你可以通过它向Facebook要原始资料 482 00:24:05,510 --> 00:24:07,580 你就可以看到你的数字ID是什么了 483 00:24:07,580 --> 00:24:10,880 几年前 Facebook从用整数 484 00:24:10,880 --> 00:24:15,980 变成了用超长整数 因为随着时间流逝 485 00:24:15,980 --> 00:24:19,780 用户们创建了很多帐户 以及假的帐户 486 00:24:19,780 --> 00:24:24,630 他们很容易就用光了整数可以表示的400万个数字 487 00:24:24,630 --> 00:24:28,340 后面我们还会讲到类似的事 488 00:24:28,340 --> 00:24:30,750 好的 所以这就是转换 489 00:24:30,750 --> 00:24:31,670 和精确度的问题 490 00:24:31,670 --> 00:24:32,730 一些通知 491 00:24:32,730 --> 00:24:35,710 小班课在这周日 周一和周二正式开始 492 00:24:35,710 --> 00:24:39,080 在这周后面几天 你们通过邮件收到你们的小班分班情况 493 00:24:39,080 --> 00:24:42,570 你也会一同看到如何更改你的小班课时间 494 00:24:42,570 --> 00:24:45,660 以及你对课程的接受情况 如果它们有变化的话 495 00:24:45,660 --> 00:24:49,380 练习1和黑客版本1这周四要交 496 00:24:49,380 --> 00:24:52,450 如果有需要 497 00:24:52,450 --> 00:24:53,830 可以延迟到周五 498 00:24:53,830 --> 00:24:57,500 请注意 练习说明中还包括 499 00:24:57,500 --> 00:25:02,770 如何安装CS50工具的说明 make 和其他CS50工具 500 00:25:02,770 --> 00:25:06,540 像 style 50 它可以给你的代码样式 501 00:25:06,540 --> 00:25:10,230 提供反馈 还有check 50 它可以 502 00:25:10,230 --> 00:25:13,160 给你的代码准确性提供反馈 503 00:25:13,160 --> 00:25:16,850 抱歉 我们还在调整check50 504 00:25:16,850 --> 00:25:21,490 你们当中一些人确实在周五半夜4点 作业上线时 505 00:25:21,490 --> 00:25:25,130 就开始做了 有人注意到了我们还在修正的一些问题 506 00:25:25,130 --> 00:25:29,010 在这里对那些因此而焦虑的人说声抱歉 507 00:25:29,010 --> 00:25:30,340 这是我的错 508 00:25:30,340 --> 00:25:34,080 当我们解决问题时 我们会在CS50论坛上告知大家的 509 00:25:34,080 --> 00:25:35,700 关于分数 510 00:25:35,700 --> 00:25:38,990 在一两周后你们才会拿到练习的反馈 511 00:25:38,990 --> 00:25:40,640 因为现在你们还没有助教 512 00:25:40,640 --> 00:25:44,510 等有了助教之后 我们也会先改C语言的练习 513 00:25:44,510 --> 00:25:46,970 再回去改scratch的练习 514 00:25:46,970 --> 00:25:48,150 这样你们会先拿到与课程内容联系更紧密的反馈 515 00:25:48,150 --> 00:25:51,870 但是一般来说 按课程大纲 CS50的练习 516 00:25:51,870 --> 00:25:53,580 是按下面这四个方面来打分的 517 00:25:53,580 --> 00:25:55,760 范围 准确度 设计 和样式 518 00:25:55,760 --> 00:25:59,210 范围会是0到5间的一个数字 519 00:25:59,210 --> 00:26:01,830 代表你们做了多少 520 00:26:01,830 --> 00:26:03,750 一般 你会想要在这项拿5分 521 00:26:03,750 --> 00:26:05,300 至少你每样都尝试了 522 00:26:05,300 --> 00:26:09,330 注意这一项是呈增长型的 所以只做练习的一部分 523 00:26:09,330 --> 00:26:12,520 并不是个好主意 524 00:26:12,520 --> 00:26:15,610 准确度的重要性更明显些 525 00:26:15,610 --> 00:26:18,620 这一项就是 按要求 你的程序是否准确? 526 00:26:18,620 --> 00:26:21,510 这比其他两项占的比重要大 527 00:26:21,510 --> 00:26:24,450 因为我们觉得 你们会花更多的时间 528 00:26:24,450 --> 00:26:28,600 在找bug 让你们的代码顺利运行上 529 00:26:28,600 --> 00:26:31,540 而不是在格式排列和给变量起名字上 530 00:26:31,540 --> 00:26:33,800 这些都属于样式的范畴 531 00:26:33,800 --> 00:26:36,160 这不是说样式不重要 我们会在课程和小班课上 532 00:26:36,160 --> 00:26:37,920 提到样式的重要性 533 00:26:37,920 --> 00:26:40,520 样式表示你代码的美学 534 00:26:40,520 --> 00:26:43,980 你有没有好好选择变量的名字 535 00:26:43,980 --> 00:26:44,680 让它们短且能说明问题? 536 00:26:44,680 --> 00:26:47,840 你的代码是不是像你在课上看到的那样 按style 50里说的 537 00:26:47,840 --> 00:26:49,070 缩进了? 538 00:26:49,070 --> 00:26:51,220 最后 是设计这一项 539 00:26:51,220 --> 00:26:54,090 设计比较难概括 540 00:26:54,090 --> 00:26:55,000 因为它很主观 541 00:26:55,000 --> 00:26:58,610 但它也许是四项中最为重要的 如果按长远的价值来看的话 542 00:26:58,610 --> 00:27:02,050 就这一项 助教们也可以给你们提供 543 00:27:02,050 --> 00:27:04,110 一些有质量的反馈 544 00:27:04,110 --> 00:27:08,100 我们在CS50里 虽然有这些标准和分数 545 00:27:08,100 --> 00:27:11,350 说到底 我们有意只让它们占了很小的比重 546 00:27:11,350 --> 00:27:13,460 0到3分 或者0到5分 547 00:27:13,460 --> 00:27:17,800 我们不想在练习之间 和同学之间 用分数划下什么模糊的界线 548 00:27:17,800 --> 00:27:21,490 我们想要做的 是提供有质量的 易懂的反馈 549 00:27:21,490 --> 00:27:25,490 不论是纸质的还是口头上的 550 00:27:25,490 --> 00:27:27,050 你们也会逐渐熟悉你们的助教 551 00:27:27,050 --> 00:27:32,340 但一般来说 打分标准就是这样 552 00:27:32,340 --> 00:27:35,480 同时 记住 不要觉得5分中拿3分 553 00:27:35,480 --> 00:27:38,870 也就是60% 就是不合格了 554 00:27:38,870 --> 00:27:41,410 3分的意思是 一般 不错 555 00:27:41,410 --> 00:27:43,480 如果在学期一开始你就拿到3分 556 00:27:43,480 --> 00:27:46,340 就意思着你已经有个良好的开端了 557 00:27:46,340 --> 00:27:50,510 如果你拿了2分 确实要多注意点 558 00:27:50,510 --> 00:27:53,250 要好好利用小班课和办公室时间 559 00:27:53,250 --> 00:27:54,590 如果你拿4或5分 那很好 560 00:27:54,590 --> 00:27:57,430 不过我们想要看到的是学生的发展曲线 561 00:27:57,430 --> 00:28:00,575 这个每个人都不同 但是在学期开始 562 00:28:00,575 --> 00:28:04,100 在2到3分这个区间里 然后到学期末 在4到5分这个区间里 563 00:28:04,100 --> 00:28:05,440 这是我们想要看到的 564 00:28:05,440 --> 00:28:09,590 我们在最后打分时 会考虑到你从第一周 565 00:28:09,590 --> 00:28:12,170 到第十二周 所取得的进展 566 00:28:12,170 --> 00:28:16,380 你一开始怎样 我们不在乎 567 00:28:16,380 --> 00:28:19,330 只要你一直在进步 就可以了 568 00:28:19,330 --> 00:28:24,000 学术诚信--让我换到更严肃的声音 569 00:28:24,000 --> 00:28:28,510 这一门课每年送去学术诚信委员会的人 570 00:28:28,510 --> 00:28:30,950 都比其他课要多 571 00:28:30,950 --> 00:28:34,220 现在我们已经数不清这种事发生过多少次了 572 00:28:34,220 --> 00:28:37,090 不是说CS50的学生要比他们的同学 573 00:28:37,090 --> 00:28:38,690 要不诚信 574 00:28:38,690 --> 00:28:42,800 请注意 我们很善于发现这种事 575 00:28:42,800 --> 00:28:45,920 这是一堂计算机课的优势 576 00:28:45,920 --> 00:28:49,110 就是我们能 而且我们也是这么做的 将学生的练习两两比较 577 00:28:49,110 --> 00:28:51,470 不仅是和本届的学生比 还和往届的比 578 00:28:51,470 --> 00:28:55,080 我们 和这个班上的学生一样 会用Google 579 00:28:55,080 --> 00:28:57,440 会在像github这样的网站或论坛上找到代码 580 00:28:57,440 --> 00:29:00,840 在上面确实能找到CS50练习的解决办法 581 00:29:00,840 --> 00:29:02,710 但如果你们能找到 那我们也能找到 582 00:29:02,710 --> 00:29:07,130 而且我们要找这个很容易 583 00:29:07,130 --> 00:29:10,990 我想强调的是 这门课的学术诚信规定 584 00:29:10,990 --> 00:29:13,960 并不支持这种风气 585 00:29:13,960 --> 00:29:17,506 这一年我们对课程大纲进行了修改 586 00:29:17,506 --> 00:29:19,790 添加了许多细节进去 587 00:29:19,790 --> 00:29:22,860 但是最主要的原则是 要合理 588 00:29:22,860 --> 00:29:26,160 我们知道 与班上的同学合作 589 00:29:26,160 --> 00:29:30,550 从教学意义上来说 有很多益处 590 00:29:30,550 --> 00:29:33,700 两个或三个人站在白板前 591 00:29:33,700 --> 00:29:35,670 交流你们的想法 592 00:29:35,670 --> 00:29:39,480 写着伪代码 画出图 来讨论如果你们来写马里奥的话 593 00:29:39,480 --> 00:29:41,350 要怎样用伪代码把它写出来 594 00:29:41,350 --> 00:29:43,240 贪心的算法要怎么-- 595 00:29:43,240 --> 00:29:46,100 它在练习1中应该会怎样? 596 00:29:46,100 --> 00:29:50,440 注意 我们鼓励的行为 597 00:29:50,440 --> 00:29:51,470 是符合上述情况的 598 00:29:51,470 --> 00:29:53,890 在大纲中 你们会发现列出的一堆要点 599 00:29:53,890 --> 00:29:57,740 它们属于合理的范畴 还有一个不合理的列表 600 00:29:57,740 --> 00:30:00,740 帮助你们了解界线在哪里 601 00:30:00,740 --> 00:30:04,340 一般来说 简单的规定就是如果你在努力解决bug 602 00:30:04,340 --> 00:30:07,990 而你的朋友或同学正坐在你边上 603 00:30:07,990 --> 00:30:11,530 你给他们看你的代码 然后说 604 00:30:11,530 --> 00:30:13,700 你能帮我看看哪出错了么 是合理的 605 00:30:13,700 --> 00:30:17,110 我们通常不对另一方的回应做什么限制 606 00:30:17,110 --> 00:30:20,730 但是你的朋友或同学如果说 607 00:30:20,730 --> 00:30:22,510 看下我的然后想想吧 608 00:30:22,510 --> 00:30:24,400 这是不对的 609 00:30:24,400 --> 00:30:27,750 不过有另外一个人 一副头脑 一双眼睛 610 00:30:27,750 --> 00:30:31,620 来看看你的屏幕 或你的代码 然后说 611 00:30:31,620 --> 00:30:32,760 你确定你在这要放个循环么 612 00:30:32,760 --> 00:30:34,800 或者你确定你这里需要这个分号么 613 00:30:34,800 --> 00:30:37,090 或者 这个错误信息是这个意思 614 00:30:37,090 --> 00:30:39,580 这些都是合理的 我们鼓励的作法 615 00:30:39,580 --> 00:30:44,010 我先前举的例子简单来说就是 616 00:30:44,010 --> 00:30:47,350 学生们工作到很晚 然后做了错误的决定 617 00:30:47,350 --> 00:30:50,130 把他们的代码通过电邮 或者是dropbox 618 00:30:50,130 --> 00:30:51,610 或者在很晚的时候去google答案 619 00:30:51,610 --> 00:30:54,880 在这里我要鼓励你们 并恳请你们 如果你们无法避免地 620 00:30:54,880 --> 00:30:58,450 有这样压力大的时刻 比如截止日期要到了 621 00:30:58,450 --> 00:31:01,490 你不能再延迟因为已经周五了 请给课程的核心助教 622 00:31:01,490 --> 00:31:02,330 或是我发邮件 623 00:31:02,330 --> 00:31:04,790 就说 我已经快崩溃了 624 00:31:04,790 --> 00:31:06,660 让我们好好谈一下来解决这个问题 625 00:31:06,660 --> 00:31:10,400 在网上找答案或者是其他不合理的行为不是解决办法 626 00:31:10,400 --> 00:31:13,070 你们的同学中有很多人已经不在校园里了 627 00:31:13,070 --> 00:31:15,150 就是因为一时的错误决定 628 00:31:15,150 --> 00:31:17,840 但是越过这条线的确很容易 629 00:31:17,840 --> 00:31:22,950 这里是Reddit上的一张图 鼓励你们一下 630 00:31:22,950 --> 00:31:25,720 一切都会好的 631 00:31:25,720 --> 00:31:30,210 迅速帮大家加回忆一下 上次我们讲到哪了 632 00:31:30,210 --> 00:31:33,690 还记得上一周 我们介绍了条件 不是Scratch中的 633 00:31:33,690 --> 00:31:34,880 而是C语言中的 634 00:31:34,880 --> 00:31:38,300 我们见到了一些新句法 不过没介绍什么新想法 635 00:31:38,300 --> 00:31:42,630 我们讲了布尔表达式 我们可以用 或 也就是 ||将它连接 636 00:31:42,630 --> 00:31:46,490 或者是用和 也就是&&将它与连起来 表示左边和右边 637 00:31:46,490 --> 00:31:49,990 必须都为真 这才会运行 638 00:31:49,990 --> 00:31:53,150 然后我们简短讲了交换 639 00:31:53,150 --> 00:31:56,830 不过我说 它其实只是用不同句法达到同样目的 640 00:31:56,830 --> 00:31:59,270 如果你已经知道你的问题是什么的话 641 00:31:59,270 --> 00:32:00,160 我们看了循环 642 00:32:00,160 --> 00:32:03,340 一个for循环大概是最常见的 643 00:32:03,340 --> 00:32:05,330 或者说是人们本能下会使用的 644 00:32:05,330 --> 00:32:08,240 虽然它看上去有点难懂 你们不久就会看到许多它的例子 645 00:32:08,240 --> 00:32:11,590 就像你们上周末看到的那样 646 00:32:11,590 --> 00:32:14,280 while循环也可以做到一样的事 647 00:32:14,280 --> 00:32:17,550 但是如果你想要增加或者更新变量的值 648 00:32:17,550 --> 00:32:20,230 你得用手工来做更多事 649 00:32:20,230 --> 00:32:22,440 不像先前for循环那样 650 00:32:22,440 --> 00:32:25,310 然后还有do-while 循环 它可以让我们至少做某事一次 651 00:32:25,310 --> 00:32:28,460 当另一件事为真时 652 00:32:28,460 --> 00:32:31,550 这对那些 你想要至少提醒用户一次的游戏或者程序来说 653 00:32:31,550 --> 00:32:33,810 很适用 654 00:32:33,810 --> 00:32:37,110 如果他或她不配合的话 655 00:32:37,110 --> 00:32:38,420 你可能想要一遍又一遍重复 656 00:32:38,420 --> 00:32:41,270 有了变量 我们有这样的代码 657 00:32:41,270 --> 00:32:41,950 它可以变成两行 658 00:32:41,950 --> 00:32:44,830 你们以声明一个叫counter的整数 然后分号 659 00:32:44,830 --> 00:32:47,660 或者你也可以声明然后定义它 660 00:32:47,660 --> 00:32:49,950 同时赋它一个值 661 00:32:49,950 --> 00:32:51,890 最后 我们叫到了函数 662 00:32:51,890 --> 00:32:54,270 这个例子很好 因为 663 00:32:54,270 --> 00:32:55,840 它介绍了两种函数 664 00:32:55,840 --> 00:32:59,030 一个是GetString() 从用户用上拿字串 665 00:32:59,030 --> 00:33:02,040 但是我们用GetString()到现在 它其实有点有趣 666 00:33:02,040 --> 00:33:05,620 因为我们用它的时候 等于号的左边 667 00:33:05,620 --> 00:33:06,600 总是有东西 668 00:33:06,600 --> 00:33:09,830 这也就是说 GetString()会返回一个值 669 00:33:09,830 --> 00:33:11,970 返回 当然 是一个字串 670 00:33:11,970 --> 00:33:15,130 在左边 我们则把这个字串 671 00:33:15,130 --> 00:33:16,580 储存在一个叫name的变量里 672 00:33:16,580 --> 00:33:21,100 这和个printf不同因为printf 673 00:33:21,100 --> 00:33:23,540 至少在我们用它时 不会返回任何东西 674 00:33:23,540 --> 00:33:24,960 其实 它会返回一些东西 675 00:33:24,960 --> 00:33:26,380 只是我们不在乎那是什么 676 00:33:26,380 --> 00:33:29,090 但它会有所谓的副作用 677 00:33:29,090 --> 00:33:31,840 那我们之前用的时候 看到的副作用是什么呢? 678 00:33:31,840 --> 00:33:34,720 printf会做什么? 679 00:33:34,720 --> 00:33:37,780 它会在屏幕上显示某些东西 680 00:33:37,780 --> 00:33:38,380 文本啊数字啊什么的 681 00:33:38,380 --> 00:33:41,170 这只是副作用 因为它并没有真正 682 00:33:41,170 --> 00:33:41,900 把任何东西返回给我 683 00:33:41,900 --> 00:33:44,770 它不是在黑盒里产生的答案 684 00:33:44,770 --> 00:33:46,130 我可以伸手进去拿到 685 00:33:46,130 --> 00:33:49,160 它只是自己在做自己的事 就像Colton上周躲在黑盒子里那样 686 00:33:49,160 --> 00:33:52,560 他不知怎么 奇迹般地在板子上画起来了 687 00:33:52,560 --> 00:33:54,500 我并没有参与其中 688 00:33:54,500 --> 00:33:55,560 这就是副作用 689 00:33:55,560 --> 00:33:59,100 但如果我真的要伸手进去说 690 00:33:59,100 --> 00:34:02,040 哦 这是用户给我的字串 这就是有返回值了 691 00:34:02,040 --> 00:34:05,650 目前为止 我们只用了别人写好的函数 692 00:34:05,650 --> 00:34:09,219 但事实上 我们自己也可以写 693 00:34:09,219 --> 00:34:12,730 所以我要再一次用CS50工具 694 00:34:12,730 --> 00:34:16,020 让我关掉这个我刚才打开的窗口 695 00:34:16,020 --> 00:34:18,530 然后创建一个新文件 696 00:34:18,530 --> 00:34:22,400 我把它叫做positive.c 697 00:34:22,400 --> 00:34:24,770 所以我在这想要对正数做点什么 698 00:34:24,770 --> 00:34:27,219 所以我要输入int-- 699 00:34:27,219 --> 00:34:28,000 对不起-- 700 00:34:28,000 --> 00:34:31,840 #include stdio.h 701 00:34:31,840 --> 00:34:34,280 我们不要犯以前犯过的错误 702 00:34:34,280 --> 00:34:40,020 int main (void) 大括号 703 00:34:40,020 --> 00:34:41,639 然后我想要做以下的事 704 00:34:41,639 --> 00:34:44,600 我想要写个程序 让用户 705 00:34:44,600 --> 00:34:46,770 必须给我一个正整数 706 00:34:46,770 --> 00:34:50,969 在CS库中 没有GetPositiveInt 这样的函数 707 00:34:50,969 --> 00:34:52,610 只有GetInt() 708 00:34:52,610 --> 00:34:55,790 但没事 因为我有结构 我可以利用它 709 00:34:55,790 --> 00:34:59,360 给我要的值加上一些限制 710 00:34:59,360 --> 00:35:00,990 我可以这样 711 00:35:00,990 --> 00:35:02,780 好 int n 712 00:35:02,780 --> 00:35:04,920 如果你在跟着输入 注意我一会会 713 00:35:04,920 --> 00:35:06,430 回去然后做一些改动 714 00:35:06,430 --> 00:35:09,960 int n = GetInt() 715 00:35:09,960 --> 00:35:11,780 这会把一个整数放在n里 716 00:35:11,780 --> 00:35:13,830 让我说得更明白些 717 00:35:13,830 --> 00:35:23,270 让我说 我命令你要给我一个正整数 718 00:35:23,270 --> 00:35:23,550 好的 719 00:35:23,550 --> 00:35:25,250 给了一些指令 720 00:35:25,250 --> 00:35:26,270 现在我能做什么? 721 00:35:26,270 --> 00:35:29,840 从我简单的条件中我已经知道 722 00:35:29,840 --> 00:35:36,100 就像我在Scratch里那样 我可以说 如果n小于或等于0 723 00:35:36,100 --> 00:35:44,460 我可以说 这个不是正的 724 00:35:44,460 --> 00:35:45,560 然后我可以 725 00:35:45,560 --> 00:35:47,310 好 但我真的很要取得那个整数 726 00:35:47,310 --> 00:35:52,020 所以我可以回到这里 然后我把它复制然后缩进一下 727 00:35:52,020 --> 00:35:52,570 好了 728 00:35:52,570 --> 00:35:56,990 如果n小于或等于0那就这样做 729 00:35:56,990 --> 00:35:58,900 现在 如果用户不合作怎么办? 730 00:35:58,900 --> 00:36:01,560 那我要来借一下这个 731 00:36:01,560 --> 00:36:03,130 然后我来这里还有这里还有这里 732 00:36:03,130 --> 00:36:06,420 但明显 这不是解决办法 对不对 733 00:36:06,420 --> 00:36:07,810 因为好像看不到尽头 734 00:36:07,810 --> 00:36:13,100 如果我想命令用户给我一个正整数 735 00:36:13,100 --> 00:36:14,150 我可以先拿到那个整数 736 00:36:14,150 --> 00:36:15,620 然后检查这个整数 737 00:36:15,620 --> 00:36:18,570 然后我想要检查一遍又一遍又一遍 738 00:36:18,570 --> 00:36:21,680 很明显 在这里应该用什么结构更好? 739 00:36:21,680 --> 00:36:22,840 没错 某种循环 740 00:36:22,840 --> 00:36:25,430 所以我要刪掉几乎这里的全部 741 00:36:25,430 --> 00:36:27,320 我想要至少拿到这个整数一次 742 00:36:27,320 --> 00:36:28,890 所以我会输入 do-- 743 00:36:28,890 --> 00:36:32,110 我过会会回到while这里 744 00:36:32,110 --> 00:36:33,050 现在 要做什么? 745 00:36:33,050 --> 00:36:35,860 我要写下 int n gets GetInt() 746 00:36:35,860 --> 00:36:36,080 好的 747 00:36:36,080 --> 00:36:37,250 这不错 748 00:36:37,250 --> 00:36:39,750 我想要多频繁地做这个呢 749 00:36:39,750 --> 00:36:45,770 让我把printf放在循环里 这样我就可以一遍又一遍命令人 750 00:36:45,770 --> 00:36:46,740 如果我需要的话 751 00:36:46,740 --> 00:36:49,720 那我想要这个while条件做什么呢 752 00:36:49,720 --> 00:36:53,870 在什么情况下 我要一直重复这个? 753 00:36:53,870 --> 00:36:54,125 没错 754 00:36:54,125 --> 00:36:55,390 当n小于或等于0时 755 00:36:55,390 --> 00:36:58,180 所以我们已经大大重整了代码 756 00:36:58,180 --> 00:37:00,700 我们借用了一个很简单的结构 do-while循环 757 00:37:00,700 --> 00:37:04,690 我只把重要的几行代码偷过来 复制粘贴一下 758 00:37:04,690 --> 00:37:05,960 这其实不太明智 759 00:37:05,960 --> 00:37:09,790 现在我会把它粘贴进来 760 00:37:09,790 --> 00:37:12,990 那在这个程序最后 我想要做什么? 761 00:37:12,990 --> 00:37:16,810 我想要简单地说 谢谢提供-- 762 00:37:16,810 --> 00:37:18,980 然后我用%i表示整数 763 00:37:18,980 --> 00:37:23,270 \n,然后n; 764 00:37:23,270 --> 00:37:23,910 好的 765 00:37:23,910 --> 00:37:27,290 现在让我们看看运行程序时会发生什么 766 00:37:27,290 --> 00:37:30,600 我来 make positive 767 00:37:30,600 --> 00:37:30,880 不好 768 00:37:30,880 --> 00:37:31,600 一些错误 769 00:37:31,600 --> 00:37:32,960 让我滚动到前面 770 00:37:32,960 --> 00:37:34,020 不要从后往前看 771 00:37:34,020 --> 00:37:37,000 要从上往下看 以免他们层叠 772 00:37:37,000 --> 00:37:38,630 而真正错误的只有一个 773 00:37:38,630 --> 00:37:42,532 GetInt()函数声明不明 774 00:37:42,532 --> 00:37:43,020 好 775 00:37:43,020 --> 00:37:44,420 这还不够 776 00:37:44,420 --> 00:37:46,760 我又犯了同样的错误 但这次有点不一样 777 00:37:46,760 --> 00:37:51,940 我不仅需要包括stdio.h我还要包括cs50.h 778 00:37:51,940 --> 00:37:56,770 因为其中有GetInt的声明 它会教会工具 779 00:37:56,770 --> 00:37:58,760 或者教会C语言 GetInt()是什么 780 00:37:58,760 --> 00:37:59,550 所以我要重新保存 781 00:37:59,550 --> 00:38:02,040 我要无视其他错误 因为我希望 782 00:38:02,040 --> 00:38:05,210 它们与我刚修好的这个错误有关 783 00:38:05,210 --> 00:38:08,710 让我用make positive重新编译 回车 784 00:38:08,710 --> 00:38:09,020 不好 785 00:38:09,020 --> 00:38:09,985 还有3个错误 786 00:38:09,985 --> 00:38:12,650 让我滚动回第一个 787 00:38:12,650 --> 00:38:14,320 未使用的变量n 788 00:38:14,320 --> 00:38:15,850 我们没见过这个 789 00:38:15,850 --> 00:38:17,200 这个也有点难懂 790 00:38:17,200 --> 00:38:18,850 这是编译器的输出 791 00:38:18,850 --> 00:38:23,610 这里被高亮的一行 positive.c:9:13 792 00:38:23,610 --> 00:38:28,960 是在说 在positive.c的第9行 第13个字符 793 00:38:28,960 --> 00:38:31,510 也就是第13列 你犯了这个错 794 00:38:31,510 --> 00:38:34,230 也就是这里 它告诉我未使用的变量n 795 00:38:34,230 --> 00:38:35,790 所以 796 00:38:35,790 --> 00:38:37,150 第9行 797 00:38:37,150 --> 00:38:40,430 这里我用n 是要给它一个值 798 00:38:40,430 --> 00:38:44,200 但是编译器不高兴的是 我看上去好像没在使用它 799 00:38:44,200 --> 00:38:45,560 但等下 我有在使用它 800 00:38:45,560 --> 00:38:48,170 在11行 我在这使用了它 801 00:38:48,170 --> 00:38:52,430 但如果我向下滚到到positive.c11 802 00:38:52,430 --> 00:38:56,230 在第11行 第12个字符 编译器告诉我 803 00:38:56,230 --> 00:38:58,670 使用未声明的标识符n 804 00:38:58,670 --> 00:39:02,760 未声明的意思是 805 00:39:02,760 --> 00:39:04,970 我还没有定义这个变量是哪种数据类型 806 00:39:04,970 --> 00:39:05,500 但等一下 807 00:39:05,500 --> 00:39:09,150 我在第9行已经声明了呀 808 00:39:09,150 --> 00:39:11,100 所以这里是有人糊涂了 809 00:39:11,100 --> 00:39:14,900 要么是我要么是编译器 因为在第9行 810 00:39:14,900 --> 00:39:18,650 我已经声明了整数n 并且我要赋予它GetInt()返回的值 811 00:39:18,650 --> 00:39:22,930 然后在第11行我用了变量n来检查 812 00:39:22,930 --> 00:39:24,050 它的值是不是小于或等于0 813 00:39:24,050 --> 00:39:27,430 但这明显出了问题 为什么呢? 814 00:39:30,630 --> 00:39:32,490 再说一次? 815 00:39:32,490 --> 00:39:35,690 我得在进行循环前声明n 816 00:39:35,690 --> 00:39:36,370 但为什么呢? 817 00:39:36,370 --> 00:39:39,830 我是说 刚才我们才说 在一行中声明变量 818 00:39:39,830 --> 00:39:43,600 然后赋他们值是可行的 819 00:39:43,600 --> 00:39:46,790 一个全局变量--我们过会再回来看这个概念 820 00:39:46,790 --> 00:39:48,690 为什么你要我放它放在循环外呢? 821 00:40:03,170 --> 00:40:03,830 的确 822 00:40:03,830 --> 00:40:06,780 没错 823 00:40:06,780 --> 00:40:09,610 所以 虽然这比较不直观 但让我总结一下 824 00:40:09,610 --> 00:40:13,510 当你在这里的 do区块中声明n时 825 00:40:13,510 --> 00:40:16,320 也就是在这些大括号中声明时 826 00:40:16,320 --> 00:40:19,210 变量n有一个范畴 827 00:40:19,210 --> 00:40:23,210 这和我们打分系统里的范围没有关系--但它有一个范畴 828 00:40:23,210 --> 00:40:25,190 范畴在大括号之间 829 00:40:25,190 --> 00:40:28,460 也就是说 如果你在大括号中声明变量的话 830 00:40:28,460 --> 00:40:33,370 这个变量只在大括号中存在 831 00:40:33,370 --> 00:40:37,320 依据这个逻辑 虽然我在第9行声明了变量n 832 00:40:37,320 --> 00:40:41,910 当我到达第11行时 它还是从范畴中 833 00:40:41,910 --> 00:40:43,370 从内存中消失了 834 00:40:43,370 --> 00:40:47,370 因为第11行 很不幸 已经在大括号外了 835 00:40:47,370 --> 00:40:51,540 所以我不能按先前的方面去修复这个问题 836 00:40:51,540 --> 00:40:53,370 你一开始可能会这么做 837 00:40:53,370 --> 00:40:56,370 但是你没有循环什么东西呢? 838 00:40:56,370 --> 00:40:58,260 很显然 你没有循环GetInt 839 00:40:58,260 --> 00:41:01,320 所以我们应该把GetInt() 840 00:41:01,320 --> 00:41:04,420 放在循环里 因为这是我们想要用它一遍遍骚扰用户 841 00:41:04,420 --> 00:41:08,660 但是回到第6行就行了 842 00:41:08,660 --> 00:41:10,150 Int n; 843 00:41:10,150 --> 00:41:12,990 现在不用给它赋值 因为你还不需要 844 00:41:12,990 --> 00:41:16,220 但在这里 请注意 这是个极易犯的错误 845 00:41:16,220 --> 00:41:19,440 我不想用它盖掉我先前声明的n 846 00:41:19,440 --> 00:41:22,830 我想用已经存在的n 847 00:41:22,830 --> 00:41:25,780 所以在第10行 我赋予了n一个值 848 00:41:25,780 --> 00:41:28,580 但是在第6行 我声明了n 849 00:41:28,580 --> 00:41:32,940 那么现在在12行 我到底能不能用它呢? 850 00:41:32,940 --> 00:41:37,120 我可以 因为n在哪两个大括号间是声明了的? 851 00:41:37,120 --> 00:41:38,770 上面第5行这里的 852 00:41:38,770 --> 00:41:40,330 和第14行这里的 853 00:41:40,330 --> 00:41:49,770 所以如果我缩小界面 保存文件 回去运行make positive 854 00:41:49,770 --> 00:41:50,820 这一次编译成功了 855 00:41:50,820 --> 00:41:51,940 这已经是项进展了 856 00:41:51,940 --> 00:41:53,640 ./positive 回车 857 00:41:53,640 --> 00:41:56,060 我命令你给我一个正整数 858 00:41:56,060 --> 00:41:57,750 -1 859 00:41:57,750 --> 00:41:59,020 -2 860 00:41:59,020 --> 00:42:00,680 -3 861 00:42:00,680 --> 00:42:01,760 0 862 00:42:01,760 --> 00:42:03,000 1 863 00:42:03,000 --> 00:42:05,130 现在显示在屏幕上的 是谢谢你给我数字1 864 00:42:05,130 --> 00:42:07,400 因为好奇 让我来试一个别的 865 00:42:07,400 --> 00:42:09,600 我被告知要输入一个整数 866 00:42:09,600 --> 00:42:12,870 要是我输入lamb 会怎样? 867 00:42:12,870 --> 00:42:14,460 你们现在看到了一个不同的指示 868 00:42:14,460 --> 00:42:15,350 重试 869 00:42:15,350 --> 00:42:17,670 但是我在代码里并没有写重试 870 00:42:17,670 --> 00:42:22,320 所以你们觉得 这个重试的提示是从哪来的呢? 871 00:42:22,320 --> 00:42:23,540 是的 是GetInt()它自己有的 872 00:42:23,540 --> 00:42:26,650 所以CS50团员帮你们做的 至少在这前面几个星期 873 00:42:26,650 --> 00:42:30,400 是帮你们写好了一些检查错误的语句 874 00:42:30,400 --> 00:42:34,260 以保证如果你们使用GetInt() 你们至少会从用户手上得到一个整数 875 00:42:34,260 --> 00:42:35,460 而不是一个字串 876 00:42:35,460 --> 00:42:36,440 一个字符 877 00:42:36,440 --> 00:42:39,660 或者是其他什么东西 878 00:42:39,660 --> 00:42:40,510 你会得到一个整数 879 00:42:40,510 --> 00:42:41,890 但 它不一定是正数 880 00:42:41,890 --> 00:42:42,770 也不一定是负数 881 00:42:42,770 --> 00:42:44,550 这个我们保证不了 882 00:42:44,550 --> 00:42:48,960 但我们会让用户重试 重试 直到他或她 883 00:42:48,960 --> 00:42:49,810 合作为止 884 00:42:49,810 --> 00:42:53,085 相似地 如果我输入了1.23 这不是个整数 885 00:42:53,085 --> 00:42:58,400 但如果我输入 比如说 50 它就会给我我想要的值 886 00:42:58,400 --> 00:42:59,050 好的 887 00:42:59,050 --> 00:43:01,380 所以这还不错 888 00:43:01,380 --> 00:43:04,780 对于我们刚才做的这些 有没有问题? 889 00:43:04,780 --> 00:43:07,930 最重要的 不是循环 虽然我们前面见过它 890 00:43:07,930 --> 00:43:10,880 但并没有真正用到它 而是范畴的概念 891 00:43:10,880 --> 00:43:17,045 也就是变量只能在一定的范畴内被声明 被使用 892 00:43:17,045 --> 00:43:19,830 好的 让我回到你们先前提到的建议 893 00:43:19,830 --> 00:43:20,860 全局变量 894 00:43:20,860 --> 00:43:24,880 顺便说一句 我们发现这个问题的另一种解决办法 895 00:43:24,880 --> 00:43:28,880 但这是个不正确的方法 或是个设计得很糟糕的方法 896 00:43:28,880 --> 00:43:31,670 是将你的变量声明成一个全局变量 897 00:43:31,670 --> 00:43:34,610 我现在好像违反了我刚才对于范畴的定义 898 00:43:34,610 --> 00:43:37,680 因为在这个文件顶端和底端都没有大括号 899 00:43:37,680 --> 00:43:40,190 但是这个的涵义我们可以在第4行看到 900 00:43:40,190 --> 00:43:41,710 n是一个全局变量 901 00:43:41,710 --> 00:43:44,460 正如它名字所指 这个变量在哪里都可以使用 902 00:43:44,460 --> 00:43:45,790 实际上 Scratch里也有这个 903 00:43:45,790 --> 00:43:48,650 如果你想用变量 你可能会记得 你得选择 904 00:43:48,650 --> 00:43:50,780 这个变量是给这个sprite还是所有sprite的 905 00:43:50,780 --> 00:43:54,270 所有sprite就就是全局的另一种说法 906 00:43:54,270 --> 00:43:55,520 什么? 907 00:44:09,690 --> 00:44:10,990 这是一个好问题 908 00:44:10,990 --> 00:44:14,310 还记得在我代码的版本1中 909 00:44:14,310 --> 00:44:17,700 当我错误地在第9行声明并定义了n的时候 910 00:44:17,700 --> 00:44:19,980 我声明了它是个变量 911 00:44:19,980 --> 00:44:21,160 然后用赋值运算符给它赋了个值 912 00:44:21,160 --> 00:44:22,520 这带给我两个错误 913 00:44:22,520 --> 00:44:26,560 一 n没有被使用 二 914 00:44:26,560 --> 00:44:27,770 在第11行 它没有被声明 915 00:44:27,770 --> 00:44:31,120 第一个问题我当时没去管它 916 00:44:31,120 --> 00:44:35,130 声明一个变量然后不用它严格意义上来说并不是个问题 917 00:44:35,130 --> 00:44:38,540 但我们在CS50工具中 出于教学目的 918 00:44:38,540 --> 00:44:43,340 故意为之的 是我们提高了编译器的期望 919 00:44:43,340 --> 00:44:46,970 来保证你们在做的不是差不多正确 还是真的正确的事 920 00:44:46,970 --> 00:44:51,520 因为如果你声明了一个变量n但没有用它 921 00:44:51,520 --> 00:44:53,700 或者错误地使用它 那它在这有什么用? 922 00:44:53,700 --> 00:44:55,650 它实际上没有任何用 923 00:44:55,650 --> 00:44:58,980 如果你不把自己的电脑设定成这样 924 00:44:58,980 --> 00:45:01,960 经过一段时间 你写下的代码 很容易这边残留一点 那边残留一点 925 00:45:01,960 --> 00:45:04,390 几个月后 当你重新回来看代码 你就会想 926 00:45:04,390 --> 00:45:05,060 这行代码为什么在这? 927 00:45:05,060 --> 00:45:07,940 如果没有什么好的理由的话 那这行代码对于你或你的同事并没有好处 928 00:45:07,940 --> 00:45:10,650 反而成为了一个障碍 929 00:45:10,650 --> 00:45:12,540 顺便问一句 这个是从哪来的? 930 00:45:12,540 --> 00:45:16,410 还记得每次我们编译程序 931 00:45:16,410 --> 00:45:17,380 这些东西都会被输出 932 00:45:17,380 --> 00:45:18,350 所以我们会回来讲这个问题 933 00:45:18,350 --> 00:45:22,230 但是 make是一个工具 它可以运行真正的编译器clang 934 00:45:22,230 --> 00:45:24,830 自动进行编译这件事 935 00:45:24,830 --> 00:45:27,650 这个 我们最终会看到 和用一个特殊的叫debugger的程序 936 00:45:27,650 --> 00:45:29,060 来debug(排除故障)有关 937 00:45:29,060 --> 00:45:32,150 这个与优化代码--我们以后会讲到--有关 938 00:45:32,150 --> 00:45:33,620 Std=c99 939 00:45:33,620 --> 00:45:37,870 这表示 C语言的第1999年版本 C语言在99年前就存在了 940 00:45:37,870 --> 00:45:40,830 但是他们10多年前对它进行了一些很好的改进 941 00:45:40,830 --> 00:45:42,690 这里是和我们有关的东西 942 00:45:42,690 --> 00:45:45,880 这里在说 开启一个警告 943 00:45:45,880 --> 00:45:48,560 一个错误信息 不让同学们进行编译 944 00:45:48,560 --> 00:45:51,400 wall表示适用于很多东西 945 00:45:51,400 --> 00:45:53,060 不仅仅是对于相关的变量 946 00:45:53,060 --> 00:45:54,700 让我滚动至这一行底部 947 00:45:54,700 --> 00:45:56,430 我们后面也会再回到这里来讲 948 00:45:56,430 --> 00:45:59,040 显然 这是我在编译的文件的名称 949 00:45:59,040 --> 00:46:02,160 它会召回我正在输出的文件名称 950 00:46:02,160 --> 00:46:04,070 然后把它用作我可以重新运行的程序名称 951 00:46:04,070 --> 00:46:08,970 -lcs50表示使用CS50库 952 00:46:08,970 --> 00:46:12,390 以及CS50团队写下的0和1和他们先前编译好的东西 953 00:46:12,390 --> 00:46:13,490 将这些都整合到我的程序里 954 00:46:13,490 --> 00:46:16,130 有谁知道-lm是什么么? 955 00:46:16,130 --> 00:46:18,150 是数学库 就算你没有要做运算 956 00:46:18,150 --> 00:46:19,320 它也在那里 957 00:46:19,320 --> 00:46:22,620 它是make自动提供给我们的 958 00:46:22,620 --> 00:46:26,540 让我再举个例子 我会创建一个新文件 959 00:46:26,540 --> 00:46:30,560 然后把它保存成string.c 960 00:46:30,560 --> 00:46:37,980 我们发现 随着我们今天讲到了数据类型 961 00:46:37,980 --> 00:46:40,630 就有更多事情是在我们看不到的幕布后进行的 962 00:46:40,630 --> 00:46:42,290 让我迅速写一个程序 963 00:46:42,290 --> 00:46:44,510 Include stdio.h 964 00:46:44,510 --> 00:46:45,730 保存 965 00:46:45,730 --> 00:46:48,110 我不要再一次又一次地犯错误了 966 00:46:48,110 --> 00:46:50,540 Include cs50.h. 967 00:46:50,540 --> 00:46:54,870 然后我要输入int main(void) 968 00:46:54,870 --> 00:46:58,790 我想要写一个这样的程序 它能 969 00:46:58,790 --> 00:47:03,610 声明一个叫s的字串 然后从用户那拿到一个字串 970 00:47:03,610 --> 00:47:05,820 让我写一下这个说明 971 00:47:05,820 --> 00:47:09,960 请给我一个字串--这样用户就知道要做什么了 972 00:47:09,960 --> 00:47:13,190 然后在底下这里 我想要做以下的事 973 00:47:13,190 --> 00:47:16,060 for int i 得到0 974 00:47:16,060 --> 00:47:18,580 计算机科学家一般从0开始数 975 00:47:18,580 --> 00:47:20,340 但我们可以从1开始 如果我们想要的话 976 00:47:20,340 --> 00:47:27,240 下面我要说 i比s的字串长度要短 977 00:47:27,240 --> 00:47:28,430 strlen 978 00:47:28,430 --> 00:47:29,510 S-T-R-L-E-N- 979 00:47:29,510 --> 00:47:31,650 这是简洁版 因为输入起来更容易 980 00:47:31,650 --> 00:47:32,590 虽然看起来有点难懂 981 00:47:32,590 --> 00:47:35,290 这个函数我们以前没用过 但是它的作用就是 982 00:47:35,290 --> 00:47:37,810 把一个代表用户输入的字串长度的数字 983 00:47:37,810 --> 00:47:38,690 返回给我 984 00:47:38,690 --> 00:47:41,740 如果他们输入了hello 就会返回5 985 00:47:41,740 --> 00:47:42,890 因为hello有5个字母 986 00:47:42,890 --> 00:47:45,390 这个循环每重复一次 i++ 987 00:47:45,390 --> 00:47:49,170 这是标准的结构 虽然你们对它 988 00:47:49,170 --> 00:47:50,420 可能还不够熟悉 989 00:47:50,420 --> 00:47:53,220 现在每一次循环 请注意我要做什么 990 00:47:53,220 --> 00:47:56,690 我想要输出一个单个字符 991 00:47:56,690 --> 00:47:59,940 所以 %c \n起新一行 992 00:47:59,940 --> 00:48:00,990 然后你们知道我要做什么么? 993 00:48:00,990 --> 00:48:05,090 不管用户输入什么词 比如hello 994 00:48:05,090 --> 00:48:09,530 我想要输出H-E-L-L-O 一行一个字符 995 00:48:09,530 --> 00:48:13,080 换句话说 我想要在字串中得到单个字符 996 00:48:13,080 --> 00:48:16,770 目前为止 字串都是字符的序列 997 00:48:16,770 --> 00:48:21,690 我可以 998 00:48:21,690 --> 00:48:23,580 s[i]); 999 00:48:23,580 --> 00:48:25,640 然后我还要做一件事 1000 00:48:25,640 --> 00:48:30,570 strlen 是在一个叫string.h的文件里声明的 1001 00:48:30,570 --> 00:48:33,190 所以如果我想要用这个函数 我得告诉编译器 1002 00:48:33,190 --> 00:48:34,450 你会用到它 1003 00:48:34,450 --> 00:48:37,040 然后让我来编译这个叫string的程序 1004 00:48:37,040 --> 00:48:39,150 ./string 1005 00:48:39,150 --> 00:48:40,130 请给我一个字串 1006 00:48:40,130 --> 00:48:40,900 我会输入 1007 00:48:40,900 --> 00:48:43,040 HELLO 全大写 回车 1008 00:48:43,040 --> 00:48:47,390 注意 我把字符一个接一个输出出来了 1009 00:48:47,390 --> 00:48:51,450 这里我们学到的 一个新的细节就是 1010 00:48:51,450 --> 00:48:54,810 用中括号 可以把一个字串 1011 00:48:54,810 --> 00:48:55,840 用一个个字符表示 1012 00:48:55,840 --> 00:48:59,090 这是因为 一个字串 背后其实是 1013 00:48:59,090 --> 00:48:59,810 一堆字符组成的序列 1014 00:48:59,810 --> 00:49:02,010 但是这个的好处是 在你电脑的RAM里 1015 00:49:02,010 --> 00:49:05,300 Mac或者PC 它们是紧连在一起的 1016 00:49:05,300 --> 00:49:06,225 H-E-L-L-O-- 1017 00:49:06,225 --> 00:49:09,920 在内存中 他们是单独的 紧连的一起的字节 1018 00:49:09,920 --> 00:49:13,210 所以如果你想要拿第8个字节 在循环里也就是 1019 00:49:13,210 --> 00:49:16,900 [1],[2],[3],[4] 1020 00:49:16,900 --> 00:49:18,890 一直到5 1021 00:49:18,890 --> 00:49:23,330 这会在它自己的那一行输出H-E-L-L-O 1022 00:49:23,330 --> 00:49:26,320 为了吊一下大家的胃口 让我给你们展示下 1023 00:49:26,320 --> 00:49:31,950 你们后面会理解的东西 1024 00:49:31,950 --> 00:49:35,610 首先 我们在今天的例子里讲到的 1025 00:49:35,610 --> 00:49:38,300 实际上是iPhone越狱的初级版 1026 00:49:38,300 --> 00:49:40,800 越狱的意思是 让手机可以用不同的服务商 1027 00:49:40,800 --> 00:49:43,380 或者装你自己的软件 1028 00:49:43,380 --> 00:49:45,660 你们会发现这看上去非常难懂 1029 00:49:45,660 --> 00:49:46,520 但是看这个 1030 00:49:46,520 --> 00:49:50,420 iPhone显然是用一个for循环 一个if条件 一个else条件 1031 00:49:50,420 --> 00:49:52,580 和我们没见过的一堆函数越狱的 1032 00:49:52,580 --> 00:49:54,230 确实 看第一眼 1033 00:49:54,230 --> 00:49:55,620 你们没法理解它是在干什么 1034 00:49:55,620 --> 00:49:58,940 但是我们在现实生活中当作理所当然的事 1035 00:49:58,940 --> 00:50:02,040 实际上都大概可以被简化为这些基本的东西 1036 00:50:02,040 --> 00:50:02,820 也就是我们正在看的东西 1037 00:50:02,820 --> 00:50:06,680 让我打开另一个程序 holloway.c 1038 00:50:06,680 --> 00:50:08,970 这个也是你们不该了解的东西 1039 00:50:08,970 --> 00:50:12,440 我和CS50团队成员光看着它 也没法理解它 1040 00:50:12,440 --> 00:50:15,450 因为这是别人写的代码 1041 00:50:15,450 --> 00:50:19,630 通过国际c语言混乱代码大赛提交的 也就是说 1042 00:50:19,630 --> 00:50:24,670 你会写代码 编译 然后运行它 但是因为代码太过艰深 1043 00:50:24,670 --> 00:50:27,530 没人能懂 除非他们真正运行了它 1044 00:50:27,530 --> 00:50:29,940 所以 如果你们看这个代码 我看到了交换 1045 00:50:29,940 --> 00:50:30,870 我看到了main 1046 00:50:30,870 --> 00:50:33,800 我看到了这些中括号 表示某种数组 1047 00:50:33,800 --> 00:50:35,970 有人想猜一下这个我运行这个程序 1048 00:50:35,970 --> 00:50:37,220 Holloway后它会做什么么? 1049 00:50:39,940 --> 00:50:40,750 好 1050 00:50:40,750 --> 00:50:43,050 好的 1051 00:50:43,050 --> 00:50:44,690 很好 1052 00:50:44,690 --> 00:50:48,090 所以只有我和团队成员想不出这些是干什么的 1053 00:50:48,090 --> 00:50:51,670 最后 让我打开另一个程序 1054 00:50:51,670 --> 00:50:53,440 这一个 1055 00:50:53,440 --> 00:50:55,550 又一次 我们会把源代码放到网上 1056 00:50:55,550 --> 00:50:57,480 这个会比较好看一些 1057 00:50:57,480 --> 00:50:59,750 他们所做的就是敲击空格键很多次 1058 00:50:59,750 --> 00:51:01,320 但这是真的代码 1059 00:51:01,320 --> 00:51:04,790 所以如果你们觉得这个比较好看 如果我们真的运行它 1060 00:51:04,790 --> 00:51:08,970 你们最终会发现要怎样做这样的事 1061 00:51:08,970 --> 00:51:14,008 所以今天就讲到这 我们周三再见 1062 00:51:14,008 --> 00:51:18,440 [音乐播放] 1063 00:51:18,440 --> 00:51:23,380 在下一次CS50课上 助教们要来上演一场政变 1064 00:51:23,380 --> 00:51:24,112 他在那儿! 1065 00:51:24,112 --> 00:51:25,362 抓住他! 1066 00:51:29,912 --> 00:51:32,074 [播放音乐]