[音乐播放]
好的 欢迎回到CS50
这是第二周的开始
帮一个朋友传话
如果你现在 或是以后
想要教中学生
计算机的话 请点击此链接
他们现在很需要老师
特别是如果你们已经对计算机有所了解之后
上一次 我们介绍了C语言中的几种数据类型
你们也许已经开始在练习1中
进行实践了
我们还讲到了字元
用比较专业的说法来说的话 就你们现在的了解 字元是什么
它是一个字符 但是让我们更精确些
我们说字符 或者一个字元时 是什么意思？
一个非数值型的字符
也不一定
事实上 数字 甚至是标点符号 字母
也是用字元这种数据类型表示的
所以它不一定是按字母顺序排列的
什么?
它是个ASCII字符
如果我们回到第0周 当时我们请志愿者上台来扮演字节
一些人举手 一些人不举手 每人代表一比特
但是他们8个人一起 就代表了一个字节
我们在那堂课上还介绍了ASCII的概念
也就是数字和字母间的对应
ASCII使用8位
来表示一个字符
相应的 如果8位中的每一位可以有两种值
0或1
也就是说 这个人
可以是0或者是1
这个人有2种可能 这个人有2种可能 这个人有2种可能
那么一共就有2乘2乘2乘2乘2乘2
也就是2的8次方种可能
所以用8位 一共可以
表示256种字符
你们当中会亚洲语言的人会知道
在这世界中 可不只有A B C D这些字符
的确 ASCII 对于世界中很多语言来说 都不够用
不过这个我们以后再讲
现在 只要知道在C语言中 如果你想表示一个字母
一个标点 或者任何本质上是字符的东西 用字元就行了
它有一个字节或者是8位
那么int呢?
int是一个整数
如果你们还记得的话，一个整数一般有多少位?
有谁记得么?
一般是32位
实际上 这要取决于你在用什么电脑
但是在大多数电脑里 是32位
或者是4字节 因为8乘以4等于32
int是用来存储数字的
不论是负数 正数 还是0
如果你们32位 而且你只想关注正数的话
有谁知道电脑可以表示出
多少种大于0的整数?
2的32次方 也就差不多是400万
2的次方将是我们在计算机科学中不断见到的主题
我们会发现 与它们打交道还比较容易
虽然在脑中计算它们可能有点困难
所以我们就说 差不多是400万
超长整数
你们可以猜到
比整数要长
那有多少位呢?
64位 或者是8字节
这就意味着 你们可以表示更大的数字
不管是更大的正数还是更大的负数
浮点数呢?
浮点数有32位
它是一个实数 带有小数点
但如果你在小数点后想要放更多的数字
或是你想表示更大的分数？？
你可以用一个双精度浮点数 它有64位
但这里我们会有个有趣的发现
如果整数只要32位 超长整数也有64位的限制
那么自然产生这样一个问题 如果你想要数到
大于400万的整数呢?
那你就用一个超长整数
但如果你想要数到 超2的64次方的数字呢?
那会是一个超级大的数
但也许最后 你会想要关注这样值
特别是如果你们要开始用数据库 并且开始收集
大量的数据 并且将特定的数字赋给这些数据之后
所以我们就面临着一个问题
同样 浮点数值 -- 浮点数或双精度浮点数--
如果你只有有限个数的位 你一共能够
用它们表示多少种数字呢?
有了小数点之后 答案会不太明显
但是肯定是有限个数的数字
如果你有有限个数的位 有限个数的人
有限个数的的灯泡 那你肯定只能表示有限个数
的浮点数
但是在这世界上一共有多少个数字呢?
无限个
所以因为在电脑里 我们没有无限大的空间
或是RAM 这就构成了问题
所以我们会面对一些挑战性很大的事
那就让我们来试着讲一下
让我打开gedit
我会保存一个叫floats0.c的文件
这样它就和网上的例子名字一样了
我会将它定义为--
我要输入 int main void 就像我们经常做的那样
在这个程序里 我要声明一个浮点数
也就是一个32位的变量 叫f 这是我们任意取的
然后我要把 就 十分之一 0.1好了 存在里面
我要表它表示为 1除以10
这在C语言中是完全合理的
在第二行里 我只想输出这个值
还记得吗 我们可以用我们已经很熟悉的printf
我们不想用%i代表一个整数
我们要用%f代表一个浮点数
然后我要输入\n",f;
这就是我的程序了
已经出现了一个bug
我至少犯了一个错 有谁想要解释一下
是什么错么?
嗯?
是的
我忘记在最上面输入 #include了 造成的结果是
如果我试图编译它的话 编译器就会说
未定义符号或者类似的东西
它会看不懂printf这类的东西
所以我要 #include 然后保存文件
这下好多了
但我今天要指出一个新细节
除了要明确像%f, %i, %s这样的占位符以外
你还可以影响占位符的行为
比如 拿浮点数值来说
如果我只想显示小数点后一位的话 我可以输入0.1f
换句话说 我把f和%号中加了一个0.1
这就告诉printf 在小数点后面
有可能有一大堆数
但是我只想要看到一位
那么我现在就来保存这个文件 然后到我的终端窗口
然后我会输入 make float 0 回车
我看到了句难懂的句子
在这周后下一周 我们分解它之后 你们会开始理解它
不过现在就让我运行float 0
不好
出于某种原因 这又有了一个bug
我挺确定十分之一 或者是1除以10 不是0.0
也许我只是没有看到足够的数位
为什么不让我说.2来看小数点后两位 而不是一位呢
让我回到我的终端窗口 然后敲击几下向上键
来看一下我的历史
重新make float 0 然后再向上
现在 回车
现在我比较确定这是错的
我也可以改成3或者是4 但我大概会一直看到0
那么bug在哪里呢?
1除以10应该是0.1
有人想要猜一下 根本上的问题在哪里么?
什么?
他们都是整数
所以呢?
所以1除以10 这是我在算术里做的事
然后我得到了0.1
没错
就是这个问题
当你在电脑里 用一个整数除以另一个整数的时候
电脑就会默认你想得到一个整数
问题在于 0.1并不是一个整数
它是一个实数
所以电脑默认的举措 就是
把小数点后的东西都扔掉
它不会四舍五入什么的
它只会把小数点后的所有东西都扔掉
现在这就解释得通了
因为我们只看到0
但等一下
我看到的不是整数0
我看到的是0.00
这个要怎么解释呢?
如果1除以10是0 但我看到了0.00
它在哪里被转换成了实数呢?
没错
在上面第五行这里 当我存储0.1的时候
它被截成了0 在浮点数里面来说 这就相当于
把它存成了一个浮点数 而不是一个整数
另外 我还用了printf来显示
小数点后两位的数 虽然小数点后可能根本没有东西
这感觉不太妙 是不是
显然 你没法用电脑计算
至少在这个精度要求下
但是肯定是有解决办法的
最简单的解决方法是什么呢
最直观的想法?
什么?
把整数变成--
没错
即使我还不太确定这里发生了什么
如果根本问题是因为它们是整数的话
我为什么不把它改成10.0 把它改成1.0 重新保存
让我回到底部重新编译
然后重新运行
好了--
现在 我的十分之一显示成0.10了
好
这不错
让我再提出另一种解决办法
让我们倒带一下 回到刚才
说十分之一的时候
让我把这个文件重新保存成另一个名字
这样就有存档了
所以这是版本一
然后让我再做一个版本
我们把它叫做版本2.0?
而我要--你们知道么
在这情况下 加上.0实际上行得通
但是假设1是个变量
假设10是个变量
换句话说 假设我不能把.0硬编码
在这个算术表达式最后
实际上 我可以在括号中做一个叫做casting（转换）的事
我可以把整数10转换成一个浮点数 然后我也可以
把整数1转换成浮点数
然后算术上 就会变成1.0除以10.0了
其结果会放在f里 就像以前一样
所以如果我把它重新编译成make floats 2然后运行 floats 2
我会得到一样的结果
所以这是一个我设计出来的例子
用了转换来解决问题
但一般来说 转换的力量非常强大
特别是在一周之后的练习2中 当你想要把一种数据类型
转换成另一种的时候 说到底 它们都是用相同的方式表示的
说到底 我们目前为止说的所有东西
都是整数 只不过不明显而已
如果这听上去太低端 那么
他们其实都是数字
就算是字符 请回忆一下第0周的内容
归根到底也是数字
也就是说 我们可以将两种类型的数字相互转换
如果他们只是一位一位的数字的话
同样 我们也可以将数字和字母之间
进行相互转换
而转换(casting)是编程中的一项机制
让你将一种数据类型变成另一种
不幸的是 它并没有我想像的那么简单
我要回到floats1 那个简单的版本
也就是我们把.0加在两个数后面的版本
为了帮大家回忆一下 让我重新编译一下
make floats 2--
对不起 是 make floats 1
现在 运行floats 1
在最底下 注意 我确实得到了0.1
好的 问题解决了
但其实还没有
我现在有点好奇 所以我要回到
我的printf语句 然后说
我想要确定 这真的是十分之一
然后我想要看到 小数点后5位
这并不是问题
我把2变成5 我用make重新编译
我重新运行floats 1
看上去很好
这个检查已经足够让我安心了 但我想要更有冒险精神些
我要把0.5变成0.10
我想要看到小数点后10位
让我来重新编译然后重新运行floats 1
我实际上有点后悔要做这个测试
因为好像我的数学算得不那么准了
但等一下 也许这只是意外
也许电脑只是临时抽风
让我把它改成小数点后20位
然后告诉自己我懂得怎么计算
我知道怎么编程
make floats 1 重新编译 不妙
这实在是差得有点远
所以到底发生了什么?
直觉上来说 根据我们对于数据类型大小的猜测
这背后可能发生了什么问题?
什么?
没错
如果你想要精度达到如此之高
小数点后20位 这精度真的非常高
除非你有任意位的数 否则你不可能
表示一个任意数
但是我们没有
浮点数 我们只有32位
所以这32位 --就像我们的志愿者在台上举手或放下那样
--的变化是有限的 我们只能用它
来表示一定数量的实数
所以最终 电脑
会开始偷工减料
电脑可以在我们面前把这些细节藏起来一阵
但是如果我们开始深入调查这些数字
我们会发现
它是在把十分之一这个概念近似化了
所以我们发现 不幸的是 我们只能用
电脑精确表示一定数目的数字
至少对于一定数量的位 或者是一定大小的RAM来说
不幸的是 这在现实世界中可能会带来一定后果
如果人们不了解这个 或者想当然以为
他们让电脑做什么 电脑就会做什么
而不懂这些根本的表达细节
确实 不像C语言 在一些语言中 这些细节用户是看不到的
那么一些不好的事可能会发生
我想 我们可以退后一步
这个视频大约8分钟
它是几年前出的 可以给我们一些启发
如果你在这个现实的世界里 不注意这些细节的话
可能会发生什么
我们能不能把灯调暗一下
我们现在回到Modern Marvels中的
的工程学灾难
电脑--
我们已经学会接受它们带来的
恼人的问题了
Bugs 病毒 和软件的小毛病
都是方便要付出的代价
但是对于高科技 高速度的军事及太空程应用来说
这些小问题 有可能变成大灾难
在1996年6月4日 科学家们正准备发设没有载人的Ariance 5火箭
它携带了科学卫星 以探测
地球磁场怎样与太阳风相互作用
这个火箭是为欧洲空间局造的
在法属圭亚那岸边发设
发设后大约37秒
他们发现了不对劲的地方
喷嘴处旋转得很不正常
发设后40秒 很明显 火箭出了问题
那时 他们决定要销毁它
安全管理官员很有勇气 按下了按钮
在火箭可能对公众安全造成威胁前 将它炸毁
这是Ariance 5号火箭的处女行
它的毁灭是因为火箭内部软件问题
问题是 一个数字需要64位
来表达
然后他们想要把它转换为一个16位的数字
他们以为那个数字不会太大
因为这个64位的数字中很多位都是0
他们错了
一个软件程序不能接受
另一个程序产生的数字 是问题的根源所在
软件开发是新科技当中相当耗费钱财的一部分
Ariane 4号火箭很成功
所以其中的很多软件也被Ariane5使用了
基本的问题是 Ariane 5更快速
加速更快
而软件没有考虑到这一点
火箭遭到销毁在经济上造成了极大损失
而这只是因为一个小小的软件错误
但这并不是第一次 数据转换问题造成
火箭科技中的问题了
1991年 海湾战争刚开始
爱国者号导弹也经历了类似的数字转换问题
造成了28名美国士兵死亡 超过100人受伤
爱国者 本是应该在飞毛腿导弹袭击时 保护人们的
但却发射失败了
当伊拉克入侵科威特时 美国在1991年初实行了沙漠风暴战略打击
爱国者导弹的炮塔被运去保护沙特阿拉伯
和以色列不被伊拉克的飞毛腿导弹袭击
爱国者导弹是美国的中程地对空导弹
由Raytheon公司研制
爱国者导弹拦截器的长度
大概是6米
重约907公斤
携带的弹头
大约是68公斤重
这个弹头具有高爆性
周围还有弹片
弹头的弹壳设计类似霰弹
导弹每四个放在一个集装箱内
由半拖挂车运输
爱国者导弹防御系统的历史
至少有20年
最初 它的功能是空中防御
用来击落敌机
在第一次海湾战争中 随着战事 军方想要
用它来拦截飞毛腿 而不是飞机
伊拉克空军不是什么大问题
让军方头疼的 是飞毛腿导弹
所以他们想要将爱国者升级
拦截速度5倍于音速的敌方导弹
本身就很有挑战性了
当爱国者仓促服役时 军方并没有发现
伊拉克方面改进了飞毛腿 让拦截它几乎成为不可能
发射出来的飞毛腿导弹
并不稳定
它们会摇晃
其摇晃的原因是伊拉克人为了使本来射程为300公里的导弹
射程达到600公里 把弹头重量减轻了
他们把弹头重量减轻了
所以当爱国者试图拦截飞毛腿时
大多数时候 绝大多数时候
它会从飞毛腿旁边擦过
当爱国者的系统操作员发现爱国者未能击中目标后
他们会触发爱国者弹头 这样如果它落到地面上
也不会造成伤亡
这也是大多数人在在空中看到的大火球时
他们误以为是成功拦截了飞毛腿
虽然在夜间爱国者好像能成功摧毁飞毛腿
但其在宰赫兰的表现
是无法抹去的
在那里 爱国者的雷达系统没能探测到飞毛腿的存在
由于软件问题 没有发射
是以色列人先发现 系统运行时间运长
系统中电脑中的时钟造成的时间差距
就越多
在宰赫兰惨剧发生约两周前
以色列人报告国防部 系统正在损失时间
在运行约8小时后 他们发现系统
已经不再准确
国防部回应 让所有的爱国者导弹炮塔
都不要让系统运行太久
他们并没有说太久是多久
8小时?
10小时?
1000小时??
没有人知道
在宰赫兰的爱国者导弹炮塔
以及它内部出错的时钟在25日晚上时
已经运行了超100小时
它的时间精度大概是十分之一秒
十分之一是个很有趣的数字
因为它不能用二进制精确表示 也就是说 它不能在
任何电脑当中被表示
这有点难以至信 但是举个例子
我们用三分之一这个数好了
三分之一不能用小数精确表示
因为三分之一是0.333无限下去
用小数是无法精确表示它的
这和爱国者的问题是同一类型
系统运行时间越长 时间的偏差就越严重
运行100小时后 时间差
大约为三分之一秒
但是对于锁定速度5倍于音速的导弹来说
追踪距离上的错误会超过600米
这对当时在宰赫兰的士兵来说 是个致命的错误
当时 飞毛腿发射被预警卫星
探测到了
他们知道 飞毛腿正朝这个方向袭来
但是他们不知道是从哪来
现在 就要靠爱国者系统的雷达
来保护宰赫兰 并且追踪确定敌方导弹的位置了
雷达很聪明
实际上 它会追踪飞毛腿的位置 然后预测
在雷达释放下一波脉冲时 它会到哪里
这叫作距离门
当爱国者决定已经过了足够时间 可以再去查一下导弹到哪里的时候
它就会再查一次
如果它去错误的地方检查 就不会发现任何目标物
如果它未能发现任何目标物 就是假警报
那么追踪就会停止
来袭的飞毛腿从雷达屏幕上消失了
几秒之后 它击中了兵营
这个飞毛腿造成了28人死亡 这也是第一次海湾战争中发射的最后一枚飞毛腿
悲剧的是 更新好的软件于第二天到达了宰赫兰
软件问题被修好了 爱国者导弹有问题的这一页
也终于被翻过
爱国者实际上是 相控阵雷达截获目标
的缩写
好的 这个例子 发人深省
幸运的是 这些低端错误
我们一般不用烦恼 特别是
这些早期程序中的问题
你们遇到的大多数bug应该是逻辑上的
或是句法上的 也就是代码没法正确运行
这些问题你们也会很快发现它
但接近学期末时
有可能就要好好想一想
你们程序的设计问题 以及根本的
数据表示问题
比如 我们会介绍MySQL 它是一个流行的数据库管理系统
你可以用它在网上储存数据
在学期末 你不仅要决定你要用什么样的数据
还要决定你要用多少位
要不要把日期储存成日期 把时间储存成时间
以及你想要给使用你数据库的人
多大的ID
事实上 如果你们已经注册Facebook挺久了
然后你知道怎样取得你的用户ID
也就是你主页URL上有时会的显示的
除非你选了其他的昵称显示在链接上 或者如果你用过Facebook的图像API
也就是对公众开放的API 你可以通过它向Facebook要原始资料
你就可以看到你的数字ID是什么了
几年前 Facebook从用整数
变成了用超长整数 因为随着时间流逝
用户们创建了很多帐户 以及假的帐户
他们很容易就用光了整数可以表示的400万个数字
后面我们还会讲到类似的事
好的 所以这就是转换
和精确度的问题
一些通知
小班课在这周日 周一和周二正式开始
在这周后面几天 你们通过邮件收到你们的小班分班情况
你也会一同看到如何更改你的小班课时间
以及你对课程的接受情况 如果它们有变化的话
练习1和黑客版本1这周四要交
如果有需要
可以延迟到周五
请注意 练习说明中还包括
如何安装CS50工具的说明 make 和其他CS50工具
像 style 50 它可以给你的代码样式
提供反馈 还有check 50 它可以
给你的代码准确性提供反馈
抱歉 我们还在调整check50
你们当中一些人确实在周五半夜4点 作业上线时
就开始做了 有人注意到了我们还在修正的一些问题
在这里对那些因此而焦虑的人说声抱歉
这是我的错
当我们解决问题时 我们会在CS50论坛上告知大家的
关于分数
在一两周后你们才会拿到练习的反馈
因为现在你们还没有助教
等有了助教之后 我们也会先改C语言的练习
再回去改scratch的练习
这样你们会先拿到与课程内容联系更紧密的反馈
但是一般来说 按课程大纲 CS50的练习
是按下面这四个方面来打分的
范围 准确度 设计 和样式
范围会是0到5间的一个数字
代表你们做了多少
一般 你会想要在这项拿5分
至少你每样都尝试了
注意这一项是呈增长型的 所以只做练习的一部分
并不是个好主意
准确度的重要性更明显些
这一项就是 按要求 你的程序是否准确?
这比其他两项占的比重要大
因为我们觉得 你们会花更多的时间
在找bug 让你们的代码顺利运行上
而不是在格式排列和给变量起名字上
这些都属于样式的范畴
这不是说样式不重要 我们会在课程和小班课上
提到样式的重要性
样式表示你代码的美学
你有没有好好选择变量的名字
让它们短且能说明问题?
你的代码是不是像你在课上看到的那样 按style 50里说的
缩进了?
最后 是设计这一项
设计比较难概括
因为它很主观
但它也许是四项中最为重要的 如果按长远的价值来看的话
就这一项 助教们也可以给你们提供
一些有质量的反馈
我们在CS50里 虽然有这些标准和分数
说到底 我们有意只让它们占了很小的比重
0到3分 或者0到5分
我们不想在练习之间 和同学之间 用分数划下什么模糊的界线
我们想要做的 是提供有质量的 易懂的反馈
不论是纸质的还是口头上的
你们也会逐渐熟悉你们的助教
但一般来说 打分标准就是这样
同时 记住 不要觉得5分中拿3分
也就是60% 就是不合格了
3分的意思是 一般 不错
如果在学期一开始你就拿到3分
就意思着你已经有个良好的开端了
如果你拿了2分 确实要多注意点
要好好利用小班课和办公室时间
如果你拿4或5分 那很好
不过我们想要看到的是学生的发展曲线
这个每个人都不同 但是在学期开始
在2到3分这个区间里 然后到学期末 在4到5分这个区间里
这是我们想要看到的
我们在最后打分时 会考虑到你从第一周
到第十二周 所取得的进展
你一开始怎样 我们不在乎
只要你一直在进步 就可以了
学术诚信--让我换到更严肃的声音
这一门课每年送去学术诚信委员会的人
都比其他课要多
现在我们已经数不清这种事发生过多少次了
不是说CS50的学生要比他们的同学
要不诚信
请注意 我们很善于发现这种事
这是一堂计算机课的优势
就是我们能 而且我们也是这么做的 将学生的练习两两比较
不仅是和本届的学生比 还和往届的比
我们 和这个班上的学生一样 会用Google
会在像github这样的网站或论坛上找到代码
在上面确实能找到CS50练习的解决办法
但如果你们能找到 那我们也能找到
而且我们要找这个很容易
我想强调的是 这门课的学术诚信规定
并不支持这种风气
这一年我们对课程大纲进行了修改
添加了许多细节进去
但是最主要的原则是 要合理
我们知道 与班上的同学合作
从教学意义上来说 有很多益处
两个或三个人站在白板前
交流你们的想法
写着伪代码 画出图 来讨论如果你们来写马里奥的话
要怎样用伪代码把它写出来
贪心的算法要怎么--
它在练习1中应该会怎样?
注意 我们鼓励的行为
是符合上述情况的
在大纲中 你们会发现列出的一堆要点
它们属于合理的范畴 还有一个不合理的列表
帮助你们了解界线在哪里
一般来说 简单的规定就是如果你在努力解决bug
而你的朋友或同学正坐在你边上
你给他们看你的代码 然后说
你能帮我看看哪出错了么 是合理的
我们通常不对另一方的回应做什么限制
但是你的朋友或同学如果说
看下我的然后想想吧
这是不对的
不过有另外一个人 一副头脑 一双眼睛
来看看你的屏幕 或你的代码 然后说
你确定你在这要放个循环么
或者你确定你这里需要这个分号么
或者 这个错误信息是这个意思
这些都是合理的 我们鼓励的作法
我先前举的例子简单来说就是
学生们工作到很晚 然后做了错误的决定
把他们的代码通过电邮 或者是dropbox
或者在很晚的时候去google答案
在这里我要鼓励你们 并恳请你们 如果你们无法避免地
有这样压力大的时刻 比如截止日期要到了
你不能再延迟因为已经周五了 请给课程的核心助教
或是我发邮件
就说 我已经快崩溃了
让我们好好谈一下来解决这个问题
在网上找答案或者是其他不合理的行为不是解决办法
你们的同学中有很多人已经不在校园里了
就是因为一时的错误决定
但是越过这条线的确很容易
这里是Reddit上的一张图 鼓励你们一下
一切都会好的
迅速帮大家加回忆一下 上次我们讲到哪了
还记得上一周 我们介绍了条件 不是Scratch中的
而是C语言中的
我们见到了一些新句法 不过没介绍什么新想法
我们讲了布尔表达式 我们可以用 或 也就是 ||将它连接
或者是用和 也就是&&将它与连起来 表示左边和右边
必须都为真 这才会运行
然后我们简短讲了交换
不过我说 它其实只是用不同句法达到同样目的
如果你已经知道你的问题是什么的话
我们看了循环
一个for循环大概是最常见的
或者说是人们本能下会使用的
虽然它看上去有点难懂 你们不久就会看到许多它的例子
就像你们上周末看到的那样
while循环也可以做到一样的事
但是如果你想要增加或者更新变量的值
你得用手工来做更多事
不像先前for循环那样
然后还有do-while 循环 它可以让我们至少做某事一次
当另一件事为真时
这对那些 你想要至少提醒用户一次的游戏或者程序来说
很适用
如果他或她不配合的话
你可能想要一遍又一遍重复
有了变量 我们有这样的代码
它可以变成两行
你们以声明一个叫counter的整数 然后分号
或者你也可以声明然后定义它
同时赋它一个值
最后 我们叫到了函数
这个例子很好 因为
它介绍了两种函数
一个是GetString() 从用户用上拿字串
但是我们用GetString()到现在 它其实有点有趣
因为我们用它的时候 等于号的左边
总是有东西
这也就是说 GetString()会返回一个值
返回 当然 是一个字串
在左边 我们则把这个字串
储存在一个叫name的变量里
这和个printf不同因为printf
至少在我们用它时 不会返回任何东西
其实 它会返回一些东西
只是我们不在乎那是什么
但它会有所谓的副作用
那我们之前用的时候 看到的副作用是什么呢?
printf会做什么?
它会在屏幕上显示某些东西
文本啊数字啊什么的
这只是副作用 因为它并没有真正
把任何东西返回给我
它不是在黑盒里产生的答案
我可以伸手进去拿到
它只是自己在做自己的事 就像Colton上周躲在黑盒子里那样
他不知怎么 奇迹般地在板子上画起来了
我并没有参与其中
这就是副作用
但如果我真的要伸手进去说
哦 这是用户给我的字串 这就是有返回值了
目前为止 我们只用了别人写好的函数
但事实上 我们自己也可以写
所以我要再一次用CS50工具
让我关掉这个我刚才打开的窗口
然后创建一个新文件
我把它叫做positive.c
所以我在这想要对正数做点什么
所以我要输入int--
对不起--
#include stdio.h
我们不要犯以前犯过的错误
int main (void) 大括号
然后我想要做以下的事
我想要写个程序 让用户
必须给我一个正整数
在CS库中 没有GetPositiveInt 这样的函数
只有GetInt()
但没事 因为我有结构 我可以利用它
给我要的值加上一些限制
我可以这样
好 int n
如果你在跟着输入 注意我一会会
回去然后做一些改动
int n = GetInt()
这会把一个整数放在n里
让我说得更明白些
让我说 我命令你要给我一个正整数
好的
给了一些指令
现在我能做什么?
从我简单的条件中我已经知道
就像我在Scratch里那样 我可以说 如果n小于或等于0
我可以说 这个不是正的
然后我可以
好 但我真的很要取得那个整数
所以我可以回到这里 然后我把它复制然后缩进一下
好了
如果n小于或等于0那就这样做
现在 如果用户不合作怎么办?
那我要来借一下这个
然后我来这里还有这里还有这里
但明显 这不是解决办法 对不对
因为好像看不到尽头
如果我想命令用户给我一个正整数
我可以先拿到那个整数
然后检查这个整数
然后我想要检查一遍又一遍又一遍
很明显 在这里应该用什么结构更好?
没错 某种循环
所以我要刪掉几乎这里的全部
我想要至少拿到这个整数一次
所以我会输入 do--
我过会会回到while这里
现在 要做什么?
我要写下 int n gets GetInt()
好的
这不错
我想要多频繁地做这个呢
让我把printf放在循环里 这样我就可以一遍又一遍命令人
如果我需要的话
那我想要这个while条件做什么呢
在什么情况下 我要一直重复这个?
没错
当n小于或等于0时
所以我们已经大大重整了代码
我们借用了一个很简单的结构 do-while循环
我只把重要的几行代码偷过来 复制粘贴一下
这其实不太明智
现在我会把它粘贴进来
那在这个程序最后 我想要做什么?
我想要简单地说 谢谢提供--
然后我用%i表示整数
\n,然后n;
好的
现在让我们看看运行程序时会发生什么
我来 make positive
不好
一些错误
让我滚动到前面
不要从后往前看
要从上往下看 以免他们层叠
而真正错误的只有一个
GetInt()函数声明不明
好
这还不够
我又犯了同样的错误 但这次有点不一样
我不仅需要包括stdio.h我还要包括cs50.h
因为其中有GetInt的声明 它会教会工具
或者教会C语言 GetInt()是什么
所以我要重新保存
我要无视其他错误 因为我希望
它们与我刚修好的这个错误有关
让我用make positive重新编译 回车
不好
还有3个错误
让我滚动回第一个
未使用的变量n
我们没见过这个
这个也有点难懂
这是编译器的输出
这里被高亮的一行 positive.c:9:13
是在说 在positive.c的第9行 第13个字符
也就是第13列 你犯了这个错
也就是这里 它告诉我未使用的变量n
所以
第9行
这里我用n 是要给它一个值
但是编译器不高兴的是 我看上去好像没在使用它
但等下 我有在使用它
在11行 我在这使用了它
但如果我向下滚到到positive.c11
在第11行 第12个字符 编译器告诉我
使用未声明的标识符n
未声明的意思是
我还没有定义这个变量是哪种数据类型
但等一下
我在第9行已经声明了呀
所以这里是有人糊涂了
要么是我要么是编译器 因为在第9行
我已经声明了整数n 并且我要赋予它GetInt()返回的值
然后在第11行我用了变量n来检查
它的值是不是小于或等于0
但这明显出了问题 为什么呢?
再说一次?
我得在进行循环前声明n
但为什么呢?
我是说 刚才我们才说 在一行中声明变量
然后赋他们值是可行的
一个全局变量--我们过会再回来看这个概念
为什么你要我放它放在循环外呢?
的确
没错
所以 虽然这比较不直观 但让我总结一下
当你在这里的 do区块中声明n时
也就是在这些大括号中声明时
变量n有一个范畴
这和我们打分系统里的范围没有关系--但它有一个范畴
范畴在大括号之间
也就是说 如果你在大括号中声明变量的话
这个变量只在大括号中存在
依据这个逻辑 虽然我在第9行声明了变量n
当我到达第11行时 它还是从范畴中
从内存中消失了
因为第11行 很不幸 已经在大括号外了
所以我不能按先前的方面去修复这个问题
你一开始可能会这么做
但是你没有循环什么东西呢?
很显然 你没有循环GetInt
所以我们应该把GetInt()
放在循环里 因为这是我们想要用它一遍遍骚扰用户
但是回到第6行就行了
Int n;
现在不用给它赋值 因为你还不需要
但在这里 请注意 这是个极易犯的错误
我不想用它盖掉我先前声明的n
我想用已经存在的n
所以在第10行 我赋予了n一个值
但是在第6行 我声明了n
那么现在在12行 我到底能不能用它呢?
我可以 因为n在哪两个大括号间是声明了的?
上面第5行这里的
和第14行这里的
所以如果我缩小界面 保存文件 回去运行make positive
这一次编译成功了
这已经是项进展了
./positive 回车
我命令你给我一个正整数
-1
-2
-3
0
1
现在显示在屏幕上的 是谢谢你给我数字1
因为好奇 让我来试一个别的
我被告知要输入一个整数
要是我输入lamb 会怎样?
你们现在看到了一个不同的指示
重试
但是我在代码里并没有写重试
所以你们觉得 这个重试的提示是从哪来的呢?
是的 是GetInt()它自己有的
所以CS50团员帮你们做的 至少在这前面几个星期
是帮你们写好了一些检查错误的语句
以保证如果你们使用GetInt() 你们至少会从用户手上得到一个整数
而不是一个字串
一个字符
或者是其他什么东西
你会得到一个整数
但 它不一定是正数
也不一定是负数
这个我们保证不了
但我们会让用户重试 重试 直到他或她
合作为止
相似地 如果我输入了1.23 这不是个整数
但如果我输入 比如说 50 它就会给我我想要的值
好的
所以这还不错
对于我们刚才做的这些 有没有问题?
最重要的 不是循环 虽然我们前面见过它
但并没有真正用到它 而是范畴的概念
也就是变量只能在一定的范畴内被声明 被使用
好的 让我回到你们先前提到的建议
全局变量
顺便说一句 我们发现这个问题的另一种解决办法
但这是个不正确的方法 或是个设计得很糟糕的方法
是将你的变量声明成一个全局变量
我现在好像违反了我刚才对于范畴的定义
因为在这个文件顶端和底端都没有大括号
但是这个的涵义我们可以在第4行看到
n是一个全局变量
正如它名字所指 这个变量在哪里都可以使用
实际上 Scratch里也有这个
如果你想用变量 你可能会记得 你得选择
这个变量是给这个sprite还是所有sprite的
所有sprite就就是全局的另一种说法
什么?
这是一个好问题
还记得在我代码的版本1中
当我错误地在第9行声明并定义了n的时候
我声明了它是个变量
然后用赋值运算符给它赋了个值
这带给我两个错误
一 n没有被使用 二
在第11行 它没有被声明
第一个问题我当时没去管它
声明一个变量然后不用它严格意义上来说并不是个问题
但我们在CS50工具中 出于教学目的
故意为之的 是我们提高了编译器的期望
来保证你们在做的不是差不多正确 还是真的正确的事
因为如果你声明了一个变量n但没有用它
或者错误地使用它 那它在这有什么用?
它实际上没有任何用
如果你不把自己的电脑设定成这样
经过一段时间 你写下的代码 很容易这边残留一点 那边残留一点
几个月后 当你重新回来看代码 你就会想
这行代码为什么在这?
如果没有什么好的理由的话 那这行代码对于你或你的同事并没有好处
反而成为了一个障碍
顺便问一句 这个是从哪来的?
还记得每次我们编译程序
这些东西都会被输出
所以我们会回来讲这个问题
但是 make是一个工具 它可以运行真正的编译器clang
自动进行编译这件事
这个 我们最终会看到 和用一个特殊的叫debugger的程序
来debug(排除故障)有关
这个与优化代码--我们以后会讲到--有关
Std=c99
这表示 C语言的第1999年版本 C语言在99年前就存在了
但是他们10多年前对它进行了一些很好的改进
这里是和我们有关的东西
这里在说 开启一个警告
一个错误信息 不让同学们进行编译
wall表示适用于很多东西
不仅仅是对于相关的变量
让我滚动至这一行底部
我们后面也会再回到这里来讲
显然 这是我在编译的文件的名称
它会召回我正在输出的文件名称
然后把它用作我可以重新运行的程序名称
-lcs50表示使用CS50库
以及CS50团队写下的0和1和他们先前编译好的东西
将这些都整合到我的程序里
有谁知道-lm是什么么?
是数学库 就算你没有要做运算
它也在那里
它是make自动提供给我们的
让我再举个例子 我会创建一个新文件
然后把它保存成string.c
我们发现 随着我们今天讲到了数据类型
就有更多事情是在我们看不到的幕布后进行的
让我迅速写一个程序
Include stdio.h
保存
我不要再一次又一次地犯错误了
Include cs50.h.
然后我要输入int main(void)
我想要写一个这样的程序 它能
声明一个叫s的字串 然后从用户那拿到一个字串
让我写一下这个说明
请给我一个字串--这样用户就知道要做什么了
然后在底下这里 我想要做以下的事
for int i 得到0
计算机科学家一般从0开始数
但我们可以从1开始 如果我们想要的话
下面我要说 i比s的字串长度要短
strlen
S-T-R-L-E-N-
这是简洁版 因为输入起来更容易
虽然看起来有点难懂
这个函数我们以前没用过 但是它的作用就是
把一个代表用户输入的字串长度的数字
返回给我
如果他们输入了hello 就会返回5
因为hello有5个字母
这个循环每重复一次 i++
这是标准的结构 虽然你们对它
可能还不够熟悉
现在每一次循环 请注意我要做什么
我想要输出一个单个字符
所以 %c \n起新一行
然后你们知道我要做什么么?
不管用户输入什么词 比如hello
我想要输出H-E-L-L-O 一行一个字符
换句话说 我想要在字串中得到单个字符
目前为止 字串都是字符的序列
我可以
s[i]);
然后我还要做一件事
strlen 是在一个叫string.h的文件里声明的
所以如果我想要用这个函数 我得告诉编译器
你会用到它
然后让我来编译这个叫string的程序
./string
请给我一个字串
我会输入
HELLO 全大写 回车
注意 我把字符一个接一个输出出来了
这里我们学到的 一个新的细节就是
用中括号 可以把一个字串
用一个个字符表示
这是因为 一个字串 背后其实是
一堆字符组成的序列
但是这个的好处是 在你电脑的RAM里
Mac或者PC 它们是紧连在一起的
H-E-L-L-O--
在内存中 他们是单独的 紧连的一起的字节
所以如果你想要拿第8个字节 在循环里也就是
[1],[2],[3],[4]
一直到5
这会在它自己的那一行输出H-E-L-L-O
为了吊一下大家的胃口 让我给你们展示下
你们后面会理解的东西
首先 我们在今天的例子里讲到的
实际上是iPhone越狱的初级版
越狱的意思是 让手机可以用不同的服务商
或者装你自己的软件
你们会发现这看上去非常难懂
但是看这个
iPhone显然是用一个for循环 一个if条件 一个else条件
和我们没见过的一堆函数越狱的
确实 看第一眼
你们没法理解它是在干什么
但是我们在现实生活中当作理所当然的事
实际上都大概可以被简化为这些基本的东西
也就是我们正在看的东西
让我打开另一个程序 holloway.c
这个也是你们不该了解的东西
我和CS50团队成员光看着它 也没法理解它
因为这是别人写的代码
通过国际c语言混乱代码大赛提交的 也就是说
你会写代码 编译 然后运行它 但是因为代码太过艰深
没人能懂 除非他们真正运行了它
所以 如果你们看这个代码 我看到了交换
我看到了main
我看到了这些中括号 表示某种数组
有人想猜一下这个我运行这个程序
Holloway后它会做什么么?
好
好的
很好
所以只有我和团队成员想不出这些是干什么的
最后 让我打开另一个程序
这一个
又一次 我们会把源代码放到网上
这个会比较好看一些
他们所做的就是敲击空格键很多次
但这是真的代码
所以如果你们觉得这个比较好看 如果我们真的运行它
你们最终会发现要怎样做这样的事
所以今天就讲到这 我们周三再见
[音乐播放]
在下一次CS50课上 助教们要来上演一场政变
他在那儿!
抓住他!
[播放音乐]