2023-05-26    2023-09-07    3568 字  8 分钟

i.e. Programming Basics - Statements & Functions

上集讲到用机器码写程序,还要处理那么多底层细节,对写大型程序是个巨大障碍。为了脱离底层细节,开发了编程语言,让程序员专心解决问题,不用管硬件细节。

:: 底层各类太多了,编写麻烦,调试麻烦,改动麻烦……

语句

今天我们讨论大多数编程语言都有的基本元素,就像口语一样,编程语言有"语句"。语句表达单个完整思想,比如"我想要茶"或者"在下雨",用不同词汇可以代表不同含义,比如"我想要茶"变成"我想要独角兽",但没法把"我想要茶"改成"我想要雨"- 语法毫无意义。

规定句子结构的一系列规则叫 语法 (syntax),英语有语法,所有编程语言也都有语法。 a = 5 是一个编程语言语句,意思是创建一个叫 a 的变量,把数字 5 放里面,这叫"赋值语句",把一个值赋给一个变量。

为了表达更复杂的含义,需要更多语句,比如 a = 5 b = 10 c = a + b ,意思是,变量 a 设为 5,变量 b 设为 10 ,把 a 和 b 加起来,把结果 15 放进变量 c 。注意,变量名可以随意取。除了 a b c,也可以叫苹果、梨、水果。计算机不在乎你取什么名,只要不重名就行,当然取名最好还是有点意义,方便别人读懂。

程序由一个个指令组成 ,有点像菜谱:烧水、加面,等 10 分钟,捞出来就可以吃了。程序也是这样,从第一条语句开始,一句一句运行到结尾。

刚才我们只是把两个数字加在一起,无聊,我们来做一款游戏吧 !🤖🔫 当然,现在这个学习阶段,来编写一整个游戏还太早了,所以我们只写一小段一小段的代码,来讲解一些基础知识。

假设我们在写一款老派街机游戏:Grace Hopper 拍虫子,阻止虫子飞进计算机造成故障,关卡越高,虫子越多。Grace 要在虫子损坏继电器之前抓住虫子,好消息是她有几个备用继电器。

开始编写时,我们需要一些值来保存游戏数据,比如当前关卡数、分数、剩余虫子数、Grace 还剩几个备用继电器,所以我们要"初始化"变量 ,“初始化"的意思是设置最开始的值。

关卡=1  分数=0  虫子数=5  备用继电器=4   玩家名=Andre

为了做成交互式游戏,程序的执行顺序要更灵活,不只是从上到下执行,因此用 “控制流语句”。控制流语句有好几种,最常见的是 if 语句,可以想成是 “如果 X 为真,那么执行 Y”,用英语举例就是 “如果累了,就去喝茶”,如果 “累了” 为真,就去喝茶,如果 “累了” 为假,就不喝茶。

if 语句就像岔路口,走哪条路取决于 “表达式” 的真假,因此这些表达式又叫 “条件语句”。在大多数编程语言中,if 语句看起来像这样: if [条件], then [一些代码],结束 if 语句 。比如, if [第一关],then [分数设为 0] ,因为玩家才刚开始游戏,同时把虫子数设为 1,让游戏简单些。

注意,依赖于 if 条件的代码,要放在 IF 和 END IF 之间,当然,条件表达式 可以改成别的,比如: "分数 >10" 或者 "虫子数 <1" 。

if 还可以和 else 结合使用,条件为假会执行 else 里的代码。如果不是第 1 关,else 里的指令就会被执行,Grace 要抓的虫子数,是当前关卡数 * 3 ,所以第 2 关有 6 个虫子,第 3 关有 9 个虫子,以此类推。else 中没有改分数,所以 Grace 的分数不会变。

![[assets/Pasted image 20230526124545.png]]

这里列了一些热门编程语言 if-then-else 的具体语法。具体语法略有不同,但主体结构一样。

if 语句 根据条件执行一次。如果希望根据条件执行多次,需要"条件循环”。比如 while 语句,也叫 “while 循环”,当 while 条件为真,代码会重复执行。不管是哪种编程语言,结构都是这样。假设到达一定分数会冒出一个同事,给 Grace 补充继电器,棒极了!把继电器补满到最大数 4 个 , 我们可以用 while 语句来做。

来过一遍代码。

假设同事入场时, Grace 只剩一个继电器。当执行 while 循环,第一件事是检查条件 - 继电器数量<4? ,继电器数量现在是 1,所以是真,进入循环!

碰到这一行: 继电器数量 = 继电器数量 + 1 。看起来有点怪,变量的赋值用到了自己。

我们讲下这个,总是从等号右边开始,"继电器数量+1" 是多少? 当前值是 1,所以 1+1=2 ,结果存到"继电器数量",覆盖旧的值,所以现在继电器数量是 2 。

现在到了结尾,跳回开始点。和之前一样,先判断条件,看要不要进入循环 - 继电器数量<4? 。是,继电器数量是 2,所以再次进入循环! 2+1=3 ,3 存入"继电器数量" 。回到开头 。3<4? 是!进入循环。 3+1=4 ,4 存入"继电器数量",回到开头。 4<4? ,不!现在条件为假,退出循环,执行后面的代码。

while 循环就是这样运作的!

另一种常见的叫 “for 循环”,不判断条件,判断次数,会循环特定次数,看起来像上图。现在放些真正的值进去,上图例子会循环 10 次,因为设了变量 i ,从 1 开始,一直到 10 。for 的特点是,每次结束, i 会 +1 ,当 i 等于 10,就知道循环了 10 次,然后退出。我们可以用任何数字,10, 42, 10 亿 。

假设每关结束后给玩家一些奖励分,奖励分多少取决于继电器剩余数量,随着难度增加,剩下继电器会越来越难。因此奖励分会根据当前关卡数,指数级增长,我们要写一小段代码来算指数。指数是一个数乘自己,乘特定次数。用循环来实现简直完美!

首先,创建一个叫"奖励分"的新变量,设为 1 (看上图),然后 for 循环,从 1 到 [当前关卡数] ,[奖励分] x [继电器剩余数],结果存入 [奖励分] ,比如继电器数是 2,关卡数是 3 ,for 会循环 3 次,奖励分会乘 继电器数量 x 继电器数量 x 继电器数量 ,也就是 1×2×2×2,奖励分是 8,2 的 3 次方。这个指数代码很实用,其他地方可能会用到。如果每次想用就复制粘贴,会很麻烦,每次都要改变量名。如果代码发现问题,要补漏洞时,要把每一个复制黏贴过的地方都找出来改,而且会让代码更难懂。

少即是多!

我们想要某种方法,把代码"打包",可以直接使用,得出结果,不用管内部复杂度。

这又提升了一层抽象!

函数

为了隐藏复杂度,可以把代码打包成 “函数”,也叫 “方法” 或 “子程序”(有些编程语言这么叫)。其他地方想用这个函数,直接写函数名就可以了。

现在我们把指数代码变成函数。

第一步,取名。叫什么都行,比如"快乐独角兽",但因为是算指数,直接叫"指数"合适一些。

还有,与其用特定变量名,比如 “继电器” 和 “关卡数”,用更通用的名字,比如 底数 (Base) 和 指数 (Exp),Base 和 Exp 的初始值需要外部传入,剩余代码和之前一样。现在完成了,有函数名和新变量名。

最后,我们还需要把结果 交给使用这个函数的代码,所以用 RETURN 语句,指明返回什么。

完整版代码是这样!

现在可以随意用这个函数,只需要写出名字 然后传入 2 个数字就可以了。如果要算 2 的 44 次方,写 exponent(2,44) ,结果是 18 万亿左右。幕后原理是,2 和 44 存进 Base 和 Exp ,跑循环,然后返回结果。我们来用这个新函数 算奖励分,首先,奖励分初始化为 0 ,然后用 if 语句,看剩不剩继电器(看上图的 > 0),如果还剩,用指数函数,传入 [继电器数][关卡数] ,它会算 [继电器数][关卡数] 次方,存入奖励分。这段算奖励分的代码,之后可能还会用,也打包成一个函数。没错,这个函数 (CalcBonus) 会调用另一个函数 (Exponent) 。还有!这个 CalcBonus 函数,可以用在其他更复杂的函数 。

我们来写一个函数,每一关结束后都会调用,叫 LevelFinished (关卡结束)- 需要传入 [剩余继电器数] 、 [关卡数] 、[当前分] ,这些数据必须传入。里面用 CalcBonus 算奖励分,并加进总分,还有,如果 当前分 > 游戏最高分 ,把新高分和玩家名 存起来,现在代码变得蛮"花哨"了。函数调函数调函数。

我们写这样一行代码时,复杂度都隐藏起来了,不需要知道内部的循环和变量,只知道结果会像魔术一样返回,总分 53 。 但是这不是魔术,是抽象的力量! 如果你理解了这个例子,就明白了函数的强大之处和现代编程的核心。

比如浏览器这样的复杂程序,用一长串语句来写是不可能的,会有几百万行代码,没人能理解。所以现代软件由上千个函数组成,每个负责不同的事。如今超过 100 行代码的函数很少见,如果多于 100 行,应该有东西可以拆出来做成一个函数。模块化编程不仅可以让单个程序员独立制作 App,也让团队协作可以写更大型的程序。不同程序员写不同函数,只需要确保自己的代码工作正常,把所有人的拼起来,整个程序也应该能正常运作!

现实中,程序员不会浪费时间写指数函数这种东西,现代编程语言有很多预先写好的函数集合,叫 “库” ,由专业人员编写,不仅效率高,而且经过了仔细检查。几乎做所有事情都有库,网络、图像、声音。

我们之后会讲这些主题。

但在此之前,我们先讲算法。好奇吗?应该!

下周见

:: 该章节其实没有多少内容,简单介绍了语句和函数,其中的例子用语言描述起来简直‘累’,还是动画来的直观。