好的
欢迎回来
这是CS50第二周 到目前为止 我们用了函数
但是大部分时间 我们把这当作理所当然
我们用了printf 它的副作用是
在屏幕上输出东西
我们用了GetInt 还有get float
但如果你想要自己创造属于自己的函数
你们当中一些人 虽然练习1没有要求
但已经这样做了
让我们回到 问用户要名字
然后在屏幕上输出这个例子
但是尝试提取出我们目前代码中的一些共性
我的意思是
我会创建一个新程序
我叫它 hello.c
在顶上 我会include stdio.h
为了先发制人 我也要把CS50库包括进去
这样编译器就不会骂我了
然后就让我声明 int main (void)
然后在这里 在这里我想开始把一些功能外包给一些函数
我会来写这些函数
虽然它们现在还不存在
比如说 假设我想要写一个函数
它能让我输出hello, 然后某个用户的名字
我们不想再用printf hello, %s了
如果有个函数叫print name 而不是 printf 不是很好么
所以换句话说 我希望能写出一个
能做类似事情的程序
首先 我要说 printf 你的名字 这就提示用户
把他或她的名字给我 然后我会用大家已经熟悉的string s
来声明一个字串
给我一个类型为字串的变量 命名为s
使用GetString的结果存储在其中
但是在过去的几周内 我都是用很麻烦的方式做的 hello, %s/n
换句话说 我们已经看过这个例子很多次了
这是个很细节的例子 因为它只占了一行代码
所以重复输入这些也没什么问题
但是如果这一行代码已经成为负担
或者几周后 它就不再是一行 而是10行代码
你觉得每次要复制粘贴
或者重新打它一遍太麻烦
如果能要不再输入printf hello, %s 什么的
而是用一个叫print name的函数
接受一个参数
换句话说 它接受input
然后分号
这样一个函数 如果它存在的话 不是很好么
这样我就不用担心printf是什么 %s是什么
以及其他那些复杂又不那么有趣的东西了
它们很有用
但是print name 不幸的是 40多年前没被人创造出来
没人想到要写它
这时 有一门编程语言的好处就体现出来了
就像在scratch里你可以自定义图块一样 在C语言和其他大多数语言中
你可以自己定义你的功能 和自定义你的函数
虽然 我们自动就能获得main
我们还是能自己声明函数
我要在这上面腾出点位置 然后我要来声明我的函数
虽然一开始 它看上去会有点怪
但我们后面会再回到讲它
我要输入 void 这表示
这个函数会做某事 它有个副作用 但它不像 get int或get string
不会返回给我任何东西
我要叫这个函数print name 然后我要说
这个函数会接受一个字串
我要叫它string name
我想起什么名都可以 但我想要我的代码可以自文档化
这也就是说 如果你们中任何一个人打开了这个文件开始读它
你可以从input的名字上猜到它的功能
在这底下 我会输入一对大括号
请注意 我在第4到第7行 还是遵照了我
这一周多来遵照的规律
组成了main
换句话说 print name是另外一个函数
现在 编译器不知道要自动使用它
因为我才刚把它发明出来 但是编译器知道要自动使用main
然后在第13行 我在召出我自己的函数
因为我在上面第四行 在main之前就声明了我的函数
这会告诉编译器
"print name"是什么意思以及它会做什么
所以就好像是在Scratch里放了一块新的自定义图块一样
在这里 我可以把我们经常见到的一堆代码放上去
printf %s hello, %s/n"
我要在这里放什么呢?
S?
在这个情况下 我想放name
注意 这里和先前有点不同
因为我在声明自己的函数
我选择叫它print name 而我在括号中表示
这个函数有一个参数 它的类型是字串
所以它是一个字或词组或者类似的东西--而我把参数名称叫name
这也就是说 唯一的变量 就是name
S只在哪两个大括号中存在?
第10行到第14行 就像我们在周一
不能使用S一样 不过我可以把S放到print name里
Print name正好给了它一个假名 一个化名 一个昵称
就叫name 然后现在在这一行使用它
现在让我保存 缩小窗口
然后让我编译hello
看上去很好
没有出现错误 ./hello 回车
我的名字是什么?
David
然后 hello David
并没有太让人激动 不过请想一想
你现在像我们在Scratch中一样
有足够的原料来自己创造函数了
但是事情并没有这么简单
假设我没有想太多
就把这个函数写在这了
感觉很合理对吧
在Scratch中 对你把脚本放哪并没有要求
你可以一个放这 一个放这 一个放这
如果不整齐放的话看上去会比较乱
但是脚本在屏幕上在哪并不重要
不幸的是 C语言 不像Java或Python这些语言
顺序很重要
因为注意下面会发生什么
默认运行的函数 当然是main
main会在第8行使用print name 但不幸的是
编译器到第11行才会知道有print name这个东西存在
但这太迟了
所以让我们make hello
不好 两个错误生成了
让我滚动回最上面 每一次 我们都应该这样处理
注意 它对我喊说
"print name 函数隐式声明"
我们以前看到过这个信息 "print name 函数隐式声明"
我们在什么时候遇到过这个错误?
当我没有包括库到程序里时
如果我忘了包括cs50.h的话 我想用get string或get int的话就不行
但这在里 print name这个函数并不在哪个库里不是么
它是真的在这个文件里 所以问题到底在哪?
不幸的是 在C语言中 如果你想让一个叫print name的函数存在的话
它一定要你在你代码的最顶端
这样后面的函数就可以用它了
但说实话 很快这就会开始误导人
我个人喜欢把main放在最前面 因为这样
一看之下 就知道这个程序是要做什么的了
另外 你还可能遇到极端情况 比如
x会使用y但y也要使用x
你就没法决定要把哪个放在上面
但是在C中 其实我们可以简单地解决这个问题
让我在上面腾一点空间
然后虽然看上去有点累赘 但我要在这里教编译器
有一个叫print name的函数存在 它接受字串
然后我要叫它name;
那么现在在第4行 我们以前没见过这个
我们声明了函数print name 但它只是保证说
这个函数我们后面会定义它 甚至会执行它
我可以暂时不管这个了
因为这是这个函数的定义 也是执行这个函数
要做的最后一步
这是有点傻有点烦人 但是C语言就是这样的
这是因为它是按字面意义理解你的话 就像电脑那样
只会做你叫它做的事 所以顺序很重要
所以请记住这一点 然后请注意一些我们重复看见的规律
你们大概会 如果你们还没有遇见的话
看见像这个一样 第一眼看上去很难懂的信息
但如果你开始找像"隐式声明"这样的关键词
这里提到的是函数 而且有时你会发现一个绿色的^号
它会告诉你问题出在哪里
你就可以从那开始解决你的问题了
对于这样写你们自己的函数 有没有什么问题?
让我们来做点更激动人心的事
这一次我们不要仅仅用这个副作用是输出东西的函数
让我来保存一个新文件 我把它叫做positive.c
虽然这个和上次的会有所不同
这一次 我想要再执行一次上节课的positive.C
也就是反复问用户要一个正整数的程序
不过上一次我用了get int
如果有一个函数 叫get positive int
让我可以把这部分功能外包出去 不是更好么
这里的差别在于 我们会执行 get positive int
但不像print name 只有副作用 它不会返回给我
一个数字或者一个字串
get positive int 当然 我希望 它会给我一个正整数
所以让我们行动起来
Include cs50.h, Include standard io.h.
Int main void.
这里 我要输入 int n =
get positive int
get int已经存在了 因为我们的团队写了它
我在这里要假设get positive int存在
然后我会输入printf thanks for the %i/n",n
如果我现在编译这个程序 我在屏幕下方的的终端窗口中
会发生什么?
我大概会得到和先前相同的错误
那就让我们试一下
make positive
我们又一次看到了 get positive int函数隐式声明
我们可以用几种方法解决这个问题
我会用简单的办法 在这上面
声明 get positive int这个函数
我需要所谓的 签名
签名就是程序
的这个第一行代码
所以get positive int会返回什么东西呢?
一个整数
我是说 在情况下 它会把一个正整数这样东西返回给你
但这种情况并不存在
我们在数据类型中没有见过正整数
所以我们必须接受我们可以使用的数据类型十分有限
但我们可以返回一个整数 然后相信它会是正的
就叫它get positive int
它的参数呢?
它会接受input么
它需要input么
所以它不需要提前知道什么
get string不用 get int也不用
printf需要 - 它需要有人把input 放到其中去 - 然后
print name也需要input 但是 get positive int不需要
所以我会清楚地告诉编译器 void
void表示没有其他任何东西
所以void表示在这些括号间 不需要放任何东西 分号
现在 到我的文件的底部
我只是习惯地把main放在顶端 这样训练一下挺好
因为这样一来 无论是你还是其他人 在任何时候打开你的文件
这些功能都会在
你可以从头开始研究
现在我要复制一下这个 get positive int void
但我在这里不会加一个分号
我要输入左大括号
然后我要借用一些周一的想法
你们还记得 我们曾做过 如果某事为真
就执行某事
我是怎么做到的呢?
我先说 给我一个正整数
这是一个提示
我可以说任何的话
然后我用了什么?
Int n ＝ get int
这里没有参数
当你用一个函数时 你不用输入void
你只有在声明函数时才要这样做
告诉编译器它会看到什么
所以你不用自己输入void
现在我的条件是什么?
是 n不等于正数 但这只是伪代码
我要怎样才能表达得更简洁呢?
小于或等于0
请注意 你可以同时表示小于与等于
虽然这是两个不同的符号 你可以
用键盘这样表达
但这里还是有个bug 我上次也犯了这个错误
我需要声明--
没错
我需要在循环外声明n
所以我要把n放到这上面来 然后我不想
在这里重新声明n 除非我有了一个新的变量
我只是想在这把值赋给它
现在我的工作还没有结束
但让我假装我都做完了
make positive 现在出现了一个新错误
控制达到非void函数底端
这是一条新的错误信息 但如果你能把这些词分开看
它指向错误的来源
控制
控制指的是程序中操作的顺序
电脑在控制下 但是产生了错误
它到达了非void函数的底端
这里它是在说什么函数呢?
哪一个函数是非void的?
是get positive int 这有点绕人
它从某种意义上来说是void的
它的参数可以是void 的 但是它的output
类型会是n
所以左边的字是所谓的返回类型
里面的字是函数会接受的
0或是更多的参数
所以我需要做什么呢?
现在在我的代码里 第21行 也就是光标所在处
我在变量n中储存了一个正整数
我要怎么把它给main呢?
很直观地
Return n;
所以 就像Colton把一张写着答案的纸 放在黑盒里
将它给返回给了我
在代码中 你想要达到这个效果 只要写 return n就行
这就像是Colton把东西递回给我一样
这样一来 get positive int会把一个正整数
返回给谁呢?
这个值最后会去哪呢?
会到这个变量n里 然后我们继续第9行
所以换句话说 就操作顺序来说 这个程序运行了
然后编译器意识到 你需要库哟
那让我从里面抓点东西出来
哦 你想要standard IO库?
那我就从里面抓点东西出来
当读到第4行时 编译器会对自己说什么?
你保证你会执行叫get positive的函数的
但我们后面会来看这个 在这些行附近
Int main void 表示我程序的主要部分在这
第7行只是个大括号
第8行是在说 在左边 给我一个32位的整数 把它叫做n
在右手边 它说 获取一个正整数
让我们停一下 因为我的鼠标不再往下滚了
我的鼠标现在又开始向下了 因为get positive int开始运行了
整数n被声明了
做以下的事
printf 给我一个正整数
从用户手中获取一个整数 把它存储在n 里 然后也许一次次重复这件事
这个循环表示 这个代码也许会一直这样上下来回地执行
但当用户终于配合 给了我一个正整数
我到了第21行 在这里 数字会被返回
那现在我应该高亮什么?
9
控制回到了第9行
也就是说 第9行现在有了控制权
这也是一直以来 程序里面发生的事情
但当我们用像printf或get string这种其他人写的代码时
控制权被交给了别人的代码行
一行一行地进行
我们只是看不见这个过程
也想像不出来 因为它发生在这个在硬盘的其他文件中
在哪我们不知道
现在让我们来编译然后运行它
Make positive
编译了 这是进步
./positive
给我一个正整数
让我们不配合一下
－1
0
让我们给它50
谢谢给我50 现在 控制权又回来了
对这个有什么问题么?
什么?
[学生提问]
请再说一遍
好问题
你们可能会注意到这里有个平行结构
在第12行 我说 get positive int返回了一个整数
但依据同样的逻辑 在第6行 那main就返回了一个整数
但我们在程序中没有提到过什么
我们从没有提过这个关键词 返回
所以在C语言中 至少在我们现在用的这个1999年的版本中
这个是自动完成的
任何时候 当你执行一个程序 执行main这个函数时
如果你没有作其他规定 这个函数就会默认把0返回给你
0只是约定俗成的数字
返回0就表示 一切正常
这样有其他400万种出错的可能
如果返回了1 这可能表示
有哪里出错了
也可以返回2 这表示另一个地方出错了
也可以返回400万 这表示又有另一个地方出错了
现在如果你们想一下自己的PC或MAC 你们可能会记得
从你用的某个软件里看到过难懂的错误信息
有时它上面会有人类能读懂的解释
但时常 上面只有一个代码或是一个数字
如果你们不记得遇过这样的情况 那以后可以注意一下
这就是那些代码表示的东西
它们在Microsoft Word和其他程序中都有
这样如果你发送错误报告的话 你就可以说 我遇到了错误45号
然后公司的程序员就可以在错误代码表上查一下
然后说 因为是有这个bug
所以用户得到了这个错误信息
但说实话 至少对于我们头几个程序来说 这个解释起来有点冗长
所以我们就
一直省略了这部分
但是一直以来 编译器自动给每一个main函数
都加了这一行 这只是出于惯例
给你节省时间而已
[学生发言]
你不需要在main中包含它
不用的
如果你要执行一个这样的函数时 你确实要包括它
否则的话这函数就不会运行成功了
但是在main中 不需要这样
在一两周内 我们想要表示错误时
我们就会习惯这个了
这个问题很好
快速插一句 这周五
我们不会有午餐活动 但是团队成员和部分学生会进行晚餐活动
如果你想参加 请去cs50.net/rsvp报名
本周五晚6点
位置有限 但我们差不多每周都会举行这个活动
所以这周位置没有的话也没关系
上周一我们留下的悬念是 我们实际可以给字串编索引
这就是说 我们可以取第一个字符
第二个 第三个 这样一直下去
因为一个字串 比如hello 是个5个字母组成的字
放在一个个盒子里一样
你可以从这些盒子里
找到我们在周一介绍的什么句法?
就是你键盘上的中括号
这就表示 去0号位置
我们是从0开始数的 所以[0]表示h
[1]表示有e 这样
所以一直以来 我们在用字串 然后输入"hello"
"world"等等的时候
它其实是被存储在这样的盒子里
猜一猜
这样的盒子在你电脑里实际是什么?
[学生回答]
什么?
字符
所以一个字符 在字串里的字符
占8位 或是一个字节
这样你至少关于电脑的内存是什么
有个模糊的概念
计算机至少有两种内存
一种是硬盘上 你用来永久保存东西的内存
一般这个容量很大 你可以存电影 音乐之类的
另一种内存叫 RAM R-A-M 缓存
这种内存是当你电脑
在运行然后如果停电 或者电池没电时
存在RAM里的东西就会消失
因为它不是永久的
一般来说 你会有一兆 两兆 可能会有更大的RAM
RAM的好处是 它比硬盘快得多
现在它甚至比固态硬盘还要快 但是一般来说它很贵
所以容量比较小
今天我们讲得其实是RAM 这种内存
只有当你电脑有电的时候才存在
当我们输入H-E-L-L-O 回车 H就被储存到
RAM中的一个字节中去 E去了另一个字节
这个字的其余部分也是这样
还记得我们上一次可以这样做
让我打开string.c这个文件
还记得它长得是这样
让我向上滚动 把它变成上次的样子
string length of s
请看这个程序
我们包括了CS50库 这样我们就可以用get string了
我们包括了standard io.h 这样我们就可以用printf了
为什么我们要包括string.h呢?
这是周一新讲的
我们想要知道字串长度
Str leng
很多年前 人们决定 我们要简洁些
所以我们把它叫作str leng而不是string length
这就是我们包括了string.h后可以用的东西
这个讲过了
这个有点新东西
在第22行--我会再回来讲这个--但现在
除非你读了文档 或者你已经学过C语言
否则你不会知道这是什么
get string有时会出问题
如果用户真的不合作 或者他/她就是不输入任何东西
或者输入太长的东西
让电脑内存受不了的话 理论上来说
get string可能会返回一个不是字符串的东西
它可能会返回一个特殊的值叫NULL 全大写
NULL这个是所谓的标记
它是个特殊的值 表示有不好的事情发生了
它表示字串的缺失
所以简单来说 我在检查NULL
对于str leng和其他C语言中的函数 如果它们是在等待一个字串
但你却给了他们字串的缺失 即NULL
电脑或是那个程序就会崩溃
它会停止工作
它会显示错误信息
不好的事会发生
虽然这个现在定义得还比较模糊
--但在一两周内你们会了解得更清楚--在第22行
这里的只是个例子 为了自身安全进行错误排查
这样就算有那么百万分之一的机会有错误产生 我的程序也不会崩溃
所以如果s不等于某种错误的东西的话 我就有了这个for循环
那在这里我们就有种新句法
我这有个for循环 是从0一直到s的长度的
然后这里 我在印出要s[i] 但虽然s是字串
为什么我突然开始用%c而不是%s了呢?
它是个字符 对不对
S是个字串 但s加上中括号 s[i]当i是0
或者是1或者是2的时候 它是字串中的一个字符
所以printf需要知道 它将得到的是个字符
还记得这个程序实际上做的是什么么?
把它在一列中印出来
没错
它会把我输入的字在一列中印出来 每个字符一行
让我们再看一遍
make string
编译好了 ./string
让我输入 H-E-L-L-O 回车 我确实得到了我想要的 每行一个字符
那么让我优化一下
请想一想 特别是以前编过程的人
第24行 效率好像不太高
也就是说 这还不是最好的设计
至少当你记得str leng是什么后 这确实很直接
但是这可能会做一些傻事
可能会是什么呢?
[学生回答]
没错
它每次都要检查s的长度
虽然我们知道H-E-L-L-O 永远都是5个字符这么长
每一次经过这个循环的时候 这个数字5是不变的
我可能在增加i的值 但是每一次循环时
s的长度是多少?
是5 是5 是5 但我还是
一次又一次地问这个问题
说实话 电脑的速度是如此快
没人会发现这有什么差别 但是如果编译器不帮你修正的话
它一般也是不会帮你的 这种设计缺陷会开始叠加
至少在这种机器里
所以我会这样做
我要在我的第一个变量i后面加一个逗号
我要再加一个变量n
数字我们习惯用n 然后我会把字串s的长度
赋值给它
然后我要把我的条件改成什么?
我要把我的条件改成 当i小于n时
现在 我要检查s的长度几次?
一次 但是一次次比较i与n的大小没关系
因为它们是不变的
就现在而言 只要知道当你用一个函数时
是要有一定开销的 不是说让你从此不用函数了
但是当有这样一行代码时--过不久这一行代码就会变得更有趣
--如果有空 就想一想
如果我输入了这行代码 它会被执行多少次?
你会慢慢发现 这个会改变
你程序的表现
事实上 几年前我们做的一个练习
就是关于实行一个拼写检查器 你们可能记得第0周我们提过这个
但是这个拼写检查器要支持一个
有超过15万词的词典
你们需要写代码将这些词装进RAM里
装进我们刚才在屏幕上看到的 一个个盒子里
然后你必须马上回答一个问题 这个词有没有拼错?
答得越快越好
这个词有没有拼错?
我们过去几年的做法是 虽然这是完全非强制性的
把它办成一个比赛
让那些用较少RAM 较短时间 较少CPU周期的学生
进行一个排行榜的前几名
然后我们会把它放在课程主页上
你们完全可以选择要不要参加 但这是关于程序设计的
当我们打好基础后 就会有越来越多
在上面添砖加瓦 进行设计的机会
让我回到这个图
然后再多讲一些
这确实是个字串 我们也已经利用了几个库
standard io.h 它其中有
printf
printf 还有其他的
cs50.h 它其中有get int, get string还有其他的 string.h
它其中有str leng
但我们发现还有一个
说句实话 有很多很多给库的声明函数的头文件
但这里的ctype.h会对我们很有帮助
因为我要来
实行另一个程序
让我打开我先前写的的
叫capitalize.c的文件 然后看看它是怎么工作的
注意我在这个版本里用了三个大家熟悉的文件
注意在第18行 我得到了一行文字
在第21行 我在声明以下的代码
会把s变大写 无论用户输入什么字 我是怎么做到这个的呢?
我在--
--根据上一次的教训--
我在声明i还有n 然后在重复字串中的字符
然后24到27行中的这堆代码
用一般人的话来说 在做什么?
针对小写字母
没错
如果s[i]--如果s的第i个字符
也就是字串中的一个字元--大于等于小写a
--还记得两个&表示 和 --
同时这个字符 s[i] 也小于等于小写z
这就表示它是个小写a或b或c或……或者小写z
也就是说它是小写的
这样的话我要做什么呢?
我可以想出很难懂的方式
但是我们还是来分解一下吧
我要用printf 印出%c因为我想要
把这个字符印在屏幕上
然后我要拿s[i]也就是s的第i个字符
为什么我在这玩了个小花样 用小写a减大写A?
这会给我什么呢?
[学生回答]
没错
我不记得--
大写A是65 我不记得小写a是什么了
但没关系
电脑知道的
所以我们说 小写a减大写A
这样让一个字元减另一个字元是有点奇怪 但字元背后其实是什么?
它们是数字
所以不管这些数字是什么 让电脑
而不是我 记住它们就行了
所以小写a减大写A会给我一个差值
它是32 对于小写b减大写B也一样
对其他的也一样
幸好这个值是恒定的
所以我在说 用小写字母 减掉这个差值
这样就会把s[i]
从小写变成大写了 而我不用去想
也不用记住 当那8个志愿者上台的时候
他们表示的那些数字是什么
那么现在 其他情况 如果它不是像24行中说的那样
是小写字母的话 就把它印出来
我只想动那些
本来就是小写的字母
所以让我们看一下这个
make capitalize
编译好了 好的
./capitalize.
让我用小写输入 H-E-L-L-O 回车
注意到 它们被变成了大写
让我换个词试试
不如用D-A-V-I-D 第一个D大写 就像一般名字的写法那样?
回车
注意 它还是正确的
它把第一个D 通过else 结构 没动过就印出来
这里 要记住几件事
一 如果你想一并检查两个条件
你可以用 和 把它们连接
你可以这样比较字符 把它们当作数字
但说实话这实在太难懂了
如果不给我多一点时间仔细想 我是不可能知道要怎么自己从头
想到要怎么做的
如果有人给我写好了个函数 叫 is lower(是小写)
用来判断一个字符是不是小写 不是很好么
谢天谢地 那个写ctype.h的人正是做了这样一件事
让我回到上面加上ctype
然后回到底下重写这几行
如果它叫 is lower 我声明 s[i]
然后我要把这两行一起删掉
我希望有别人写了叫is lower的函数
然后我发现有人确实写了 而且在ctype.h里声明了
现在27行我不用改变什么 31行也不用
但是注意我让我的代码变紧凑了许多
现在看上去更干净了
现在看起来也更容易了 因为这个函数
名字取得很好 从名字就能知道它会做什么
现在让我保存
我要缩小一下界面
就像在Scratch中你们可以用布尔表达式一样 布尔表达式有真与假两个值
这就是is lower会返回给你的东西
让我重新编译一下
重新跑一下
现在再试一次 H-E-L-L-O 回车
不错
再试一次 确保它不会出错
这也变大写了
但这还不够好 因为另外我得花很多时间去研究
比如 写论文时去研究的东西
就是这一行
如果有个函数叫 to upper (变大写) 不是很好?
我们发现在ctype.h里就有
让我输入--
让我回到这一行
我现在要在这里 对s的第i个字符 把%c替换成
使用to upper这个函数得到的结果
现在 它看上去比较平衡了
我需要注意我用了多少组括号
这样 现在它看上去更干净了
现在这个程序设计上越来越好了
因为它越来越易读 并且保持了其正确性
Make capitalize
./capitalize
H-E-L-L-O 让我们再跑一次 D-A-V-I-D 好
所以我们还是比较满意的
但是再看到 to upper
我认为对to upper我们还可以做一些调整
这能使它变得更好 让代码更紧凑
让我们在设计上能得到满分
去掉什么会比较好呢?
看到 这一块代码有那么长 但做的却是很简单的事
顺便提一句 你们在上周末的超级小班课上可能看到了
如果你只有一行代码的话 你并不一定需要一对大括号
虽然 我们建议保留它们
因为会让条件更清楚 就像Scratch 中的U型块一样
如果to upper拿到input时 input不是大写
就把它变成大写 那如果是另一种情况
input已经是大写了 怎样会比较好?
就不要动它
也许它就是这样做的
我可以试一下 然后希望它是这样处理的
但让我再给大家介绍一个东西
我不想再在底下用这个自带的终端窗口
还记得这个正方形图标可以让你放大终端窗口
至全屏吗?
它们的名字有点怪
但这些叫做手册页 man是manual 的缩写
我可以通过输入man来得到它们--
我应该输入什么?
man to upper
注意 如果电脑里有这样一个函数
在这种情况下 也就是操作系统linux
会给我一组很难懂的output
慢慢你们会发现 它们的格式是固定的 所以你就会开始习惯它了
注意在to upper最上方
和to lower里 是一样的
那个写了这个的人是在省时间 把它全放在了一页上
而这些东西的目的就是
把一个字母换成大写或小写
注意 在提要中 手册页就教我
要使用这个东西的话 需要包括哪些文件
它给了我这些函数的签名 两个都给了
虽然现在我们只关心其中的一个
这里是一个描述
如果可能的话to upper把字母c变成大写
这并不是很有帮助 但是再让我看到返回值
也就是它返回给我的东西
这个值要么是转换后的大写字母 要么是c
如果转换不成功的话
c是什么?
原始的字符
我们知道它是原始字符 是因为回到上面看提纲
那个写出这个函数的人决定
要把to upper和to lower的input叫作c
他们可以给它取任何名字
但他们决定简单地叫它c
所以我去咨询了手册页
这一句让我确定 如果它不是一个小写字母
它就会把c还给我 这很棒 因为
我就不用写我的else条件了
让我回到gedit 然后我要做这个
我要复制我的printf语句
我要到for循环里然后印出它来
然后把整个if结构都去掉
这不是个坏主意 这也符合我们一直以来所教你们的
但是这并不必要
当你意识到库中存在一些别人写好的函数
或是你写在文件其他地方的函数
你就可以用它 这会让代码紧凑
我说的好的样式 就比如这个人把函数叫做
to upper或者前面的 is lower 这就很有用
因为它们描述性很强
你不会想要叫你的函数x, y或z
因为它们携带的意义要少很多
对刚才进行的调整有什么问题吗?
我们学到的很重要的一点 是对你们的练习来说
练习1 不太确定 但对于练习2及以后的练习来说
虽然代码是正确的 但并不表示它们是完美的
或是设计得好的
这是另一件我们需要开始考虑的事
这是你电脑内存中的一个字串
但如果你在RAM中有一堆字符 比如H-E-L-L-O
假设你的程序用了get string很多次
你用了get string一次 然后你又用了一次
那会发生什么?
换句话说 如果你有一行代码 不管在什么情形下
比如string s得到 --
让我们这样
string name = get string
假设这行代码是用来问用户名字的
下一行是用来问用户学校的
而下一行又是问别的的
假设我们一直在问用户
要一个又一个字串
它们会同时被储存在内存里
一个不会伤到另一个
学校不会把其他的盖掉
但它们都会存在内存中么
如果我们开始在屏幕上画东西
我们可以把它当黑板来用 如果这个黑色三角表示
我电脑的内存 那我就开始把它们分成小方块
每一个代表一字节的内存
不过说实话 如果你现在有1G RAM的话
你电脑里就有一百万字节内存 也就是一百万个这样的方块
所以这个比例并不准确
但是我们可以继续画这些比例不对的方块
它们在一起 表示我电脑的内存
现在我们就画点点点好了
换句话说 当我现在用get string提示用户
给我一个字串时 会发生什么?
如果用户输入hello 最后会变成H-E-L-L-O
但假设用户输入了--
事实上 我不该说hello
因为我们是在问他们名字
所以如果我可以的话 让我重新做一次
如果我输入D-A-V-I-D作为我的名字
但注意 第二行代码还是get string用来要学校
那用户输入的下一个字会去哪呢?
也许它会进H-A-R-V-A-R-D中
即使我把它分两行来画 它们一起
只是你电脑RAM里的一堆字节
现在有了个问题 因为如果我在如此合理
但又如此天真地使用RAM 你会无法区别什么东西呢?
哪是开头哪是结尾 对不对?
它们好像都混到一起了
我们发现电脑不会这样
让我倒退一下
Harvard并没有紧接在用户名字后面 用户实际上得到了
一个特别的字符 是电脑自己
为他或她加上的
\0 也就是nul character(空字符) 它叫做nul
不是N-U-L-L 但是写作 /0
它只是所有位都是0的标记 出现在
用户输入的第一个词与第二个词之间
所以Harvard现在实际上是这一组字符
后面还有一个/0
换句话说 有了这些标记 8位连续的0
你就可以区别字符了
所以一直以来 "hello"实际上是"hello"加上\0
同时 在电脑里 RAM
可能要更大一些
让我来做另一件事
我们在画的这些方块实际上是 没错
字串 但更广泛地说 它们是数组(array)
数组是一块连续的内存
你一般用中括号表示数组
我们后面会经常看到它
但让我打开 就叫它 ages
注意 我们可以怎样玩一些一样的花样
这里又多了一点句法
程序里的第17行 --实际上 让我先跑一下程序
这样我们就知道它会做什么了
让我用make ages来编译这个程序
./ages.
这房间里有多少人?
就说3个好了
第一个人的年龄?
18, 19和20
现在 这有点荒谬 但是我刚写了一个程序
让这三个人年龄增加
这里显然我们有机会有些有趣的算法
还好 计算是对的
18变成19 19变成20 这样
但这里我想说明的
是我们是怎样储存这些人的年龄的
让我们放大一下 看看这里到底发生了什么
首先 前面这几行大家应该比较熟悉了
我是在提示用户给我屋子里面人的数量
然后我用了get int及do while循环去重复这一过程
我们前面看过这样的结构 不过第27行是新的
并且会变得越来越有用
注意 27行不同之处是我看上去在声明一个
叫ages的整数 但等一下
它不只是int ages
还有这些中括号 n被包在里面
在这里 [n]不是在printf语句里
而是单独在第27行里 这一行是在说 给我n个整数
每一个的数据类型都是整数
所以这是可以想像成是个桶 里面有三个整数一个接一个
所以我就有了3个变量
另一种情况 会是这样的
如果我想要第一个学生的年龄 我可能会这样
如果我想要第二个学生的年龄 我可能会这样
如果我想要第三个学生的年龄 我可能会这样
如果我们要屋里所有人的年龄--
我是说 这会是大量的复制粘贴工作
再加上如果我编译了这个程序后 一个学生从这门里走进来
那我的变量数目就不对了
所以数组的好处就是 如果你觉得
自己在不断复制粘贴 有很大可能 你采取的不是最好的方法
数组是动态的
我并不知道屋子里会有多少人
但是我知道会有n个 在某个时候 我会弄清n是多少的
这一行代码现在表示 给我像这样一块内存
里面盒子的个数完全取决于
用户输入的n这个数字
现在 这个程序剩余的部分
和我们刚才用字符作例子的 就差不多一样了
注意 从第30行开始 我有一个for循环
所以当我得到了数组后 我会重复给y赋值 从0一直到n
我有这个指导性的printf信息 说
给我#%i这个人的年龄 也就是第一个人 第二个 第三个
为什么我要这么做?
因为 一般人喜欢从1开始数 而计算机科学家
喜欢从0开始数
计算机科学家不会用这种程序的
所以我们就像正常人一样 从1开始数
在第33行 注意这个有点不同的句法
数据类型为数组的变量中的第i个年龄 会得到一个整数
最后 这下面的就只是算法了
我决定在另外一个循环里声明过了一段时间
那现在在这个循环里 这些行会被执行
一年之后 i这个人就i岁了 但注意
这并不是变量i
这是%i 表示一个整数
注意 作为第一个占位符 我放了i加1
这样我们就能像正常人一样数数了
然后 他们的年龄 i岁 我拿了ages[i]
--为什么我要加1呢?
因为他们年龄增长了
因为我选了这个程序
他们年龄增长了1岁
我可以随便输入任何数字
所以这些都有什么关系呢?
让我回到这里来
然后向你们描绘一下下面会发生什么事
我们在练习2中 就会开始
进入密码的世界
所以这是一个字符串 多个字元组成的序列
这表示什么呢?
这个不在幻灯片的网上版本中
所以我声明 这个等于这个 这是个广告
已经有点历史了 如果你们还记得的话
所以这是加密或者密码学的一个例子
如果你真的想要安全地送出
或是与人分享信息 就像是这一条信息
你可以打乱字母
但一般来说 这些字不是随意打乱的
通过某种方式 它们被转换了 --不好意思
这是下一次的一个小剧透
所以你可以把O对到B
注意 大小写上它们也对应
显然 r变成了e
显然 F-H-E-R变成了S-U-R-E 所以我们发现 是有对应关系的
这里这个对应挺傻的 如果有人已经发现的话?
这个叫做 Rot 13也就是旋转13
它是加密中最笨的一种 因为它就是
把每个数字加上了13 说它笨的原因是
如果你手上有时间 外加一支笔的话
或者你就用脑子想一想 你可以试过所有的可能
1，2，3.……25 然后把整个字母表旋转一次
总终 你是能破解这个信息的
所以如果你在上学时 这样传递信息给你最好的朋友的话
你的老师可能只是读了一遍
就得出了答案
你大概知道是为什么了
在现实世界中 密码要比这复杂地多
这是一台有用户名和密码的电脑中
文本中的一个片段 你的密码大概就长这样
它被存在你的硬盘里 但是加了密
它不是把字母旋转 A变成B B变成C
这要复杂地多 它用了我们说的
密钥加密
这幅图用几个图标告诉了我们这些
在左边 我们有我们称为称文本的东西
在密码的世界中 纯文本是最原始的信息
用英语会法语或者任何一种语言写成
如果你想加密它 我们会把它转成图 通过挂锁
这样别人写的某种算法 某数函数或者某种程序
就会把字母打乱 而不是简单地
往每个字母上加上13
经历了这个过程 你得到的就是密文
听上去挺有感觉的
它的意思是 纯文本加密后的版本
只有当你有同样的秘密 13或是减13时
你能才破解这样的信息
在练习2中 在黑客版本里
你就需要写代码来破解这些密码
尝试找出它们是什么 以及它们是怎样被加密的
不过 我们会给大家提供一些指导
在普通版的练习中 我们会介绍一些密码
加密机制 一种叫Caesar 一种叫Vigenere
它们也是旋转密码 也就是A变成某个东西 B变成另外的什么
但是你需要用程序 因为有用到密钥
密钥一般是一个数字或者关键词
只有信息的发送者和接收者才懂得
在现实世界中 也有类似的东西
比如说这个 说是孤儿安妮的解密环
你可以用这些可旋转的密码
让A变成了某个字母 B变成了某个字母
它外面有个齿轮 里面也有一个 如果你旋转它们
你就能把字母对应上了
然后得到一个密码
今天的悬念是
如果你在12月24日打开电视
你可以24小时一直看这电影看到吐
但是今天 我只会给你们看2分钟 出于教学目的
给你们看一个叫Ralphie的小孩的圣诞故事
[视频播放]
请广而告之 Ralph Parker今日正式成为了
小孤独安妮秘社的成员
享受一切荣誉与福利
签名 小孤儿安妮
签名的另一方 Pierre Andre 用墨水签的呢
9岁 就有荣誉与福利了
[广播]
好啦 让我们开始吧
我才不用听那些走私犯还是海盗的乱吼乱叫呢
明晚请收听 黑海盗船的
最终冒险
现在 是安妮给秘密社团成员
的秘密信息
记住 孩子们
只有安妮秘社的成员才能解出安妮的秘讯
记住 安妮指望着你们呢
把指针转到B2
消息在这
12,11,2
我在我第一次秘密会议上
25,14,11,18,16
Pierre今晚声音很洪亮
我听得出来 今晚的讯息非常重要
3,25
这是来自安妮的讯息
记住 不能告诉任何人
90秒后 我进入了房子里唯一一个
能让一个9岁男孩单独破解密码的房间里
啊 B 我看向下一个字母
是E 第一个词是"be" S 现在很容易了
U 25
这是R
加油 Ralphie
我得走了
我马上就下来 妈
天
T O Be sure to (一定要)
一定要做什么呢?
小孤儿安妮到底想说什么
一定要做什么呢?
Ralphie Randy得走了
你能不能出来下
好的 妈
我马上来
我已经快解出来了
实在是太紧张了
到底是什么了
星球的命运就悬在此间了
Ralphie Randy要走啦
我马上就来好么
快了
我的手指动得飞快
我的头脑像捕兽夹一样
每个毛孔都在震动
就快要想清楚了
是的 是的 是的
记得喝你的阿华田
阿华田
这是条破广告?
靠
[视频播放结束]
这是CS50 以上就是练习2
下周再见
在下一节CS50课上
到目前为止我们还没有说到的话题是
是函数指针
函数指针是一个公共函数的地址
就像是--
靠--