函数
「松饼人物编辑器:从入门到欧耶」系列教程(三)
上一讲的习题参考答案
1) a
, b
, c
满天飞,滥用隐式类型转换(等等)
2) 合法:doge
, 非法:good-doge
(因为有中划线)
3) number, string, nil, boolean
4) false
可维护性了解一下
所谓「可维护」,就是指改起来方便。 改起来方便的代码会越改越好, 而改起来不方便的代码则吃枣药丸。
很多学生党写代码都不怎么重视可维护性。 这是因为他们写的东西,无论是作业、课题还是竞赛中提交的答案, 都是「一次性」的代码 —— 今天完成任务以后,明天就再也不用改了。 对于一次性代码来讲,可维护性自然并不重要; 但如果想写一个真正「有用」的东西,就不能这么玩了 —— 因为这些东西将来是要反复修改的 —— 加功能、调细节、改 bug。 若不重视可维护性, 每当想新增一些功能、调整一些细节、修复一些 bug 的时候, 都会分分钟突然失去梦想x
所以可维护性大法好。
函数
通过函数,可以简单粗暴地提高代码的可维护性。
举一个粟子:
print("我爽美如画x")
print("我爽画中仙x")
print("我爽总冠军x")
print("我爽裱三家x")
三狗测试,见输出的一首好湿。 好湿啊好湿,我永远喜欢狮子原爽.jpg。
后来不知怎么回事,从爽厨变成了魔法少女厨, 所以修改了代码:
print("军师美如画x")
print("军师画中仙x")
print("军师总冠军x")
print("我爽裱三家x")
结果不小心漏改了最后一行, 输出前三句都是军师,最后一句成了我爽,这就尴尬了。
怎么办?
- 把代码改过来不就得了?
- 那么下次要是又变成了龙华厨,岂不是要再冒一次漏改的风险?
- 下次再改的时候小心点不就得了?
- 你慢慢小心x
- 我永远喜欢末原恭子.jpg,下次不会再改了x
- 你慢慢永远喜欢x
所以函数大法好。有了函数,这代码就好改了:
function poetize(name)
print(name .. "美如画x")
print(name .. "画中仙x")
print(name .. "总冠军x")
print(name .. "裱三家x")
end
poetize("军师")
这样一来,下次改吹龙华的时候,只需改一处, 剩下的都不用动。
函数不但好改,而且还可以反复使用:
function poetize(name)
print(name .. "美如画x")
print(name .. "画中仙x")
print(name .. "总冠军x")
print(name .. "裱三家x")
end
poetize("我爽")
poetize("军师")
poetize("龙华")
poetize("姬子")
三狗测试,见以上代码又吹我爽,又吹军师,又吹龙华,又吹姬子。
每吹一人,都只需简单加上一行代码。
只要愿意,甚至可以随时来上一句poetize("京狗")
,十分方便。
如果不用函数,这么一波吹下来,得写上大量的重复代码,
不旦写着麻烦,看着不爽,将来改起来也麻烦。
所以,函数大法好。
多用函数,简化代码,延年益寿,岂不美哉x
代码详解
function poetize(name)
print(name .. "美如画x")
print(name .. "画中仙x")
print(name .. "总冠军x")
print(name .. "裱三家x")
end
poetize("军师")
这段代码定义了一个叫poetize
的函数,
然后以"军师"
为参数调用了一下它。
- 定义函数时,需要在函数名后面加一个「参数列表」。
所谓「参数列表」,就是一个括号,里面写上任意个变量名。
本例中我们只写了一个
name
变量。 - 调用函数时,先写函数名,再加个括号, 括号里填上参数就行了。
函数有多个参数时,用逗号分隔:
function poetize(name, suffix)
print(name .. "美如画" .. suffix)
print(name .. "画中仙" .. suffix)
print(name .. "总冠军" .. suffix)
print(name .. "裱三家" .. suffix)
end
poetize("军师", "x")
poetize("军师", "!")
函数也可以没有参数。 定义和调用没有参数的函数的时候,在函数名后面加一对空的括号:
function poetize()
print("我爽美如画")
end
poetize()
返回值
函数可以有一个计算结果,比如:
function plus1(x)
return x + 1;
end
print(plus1(2))
结果输出 3。
这里我们用到了return
——
return
就是给出这个函数的计算结果。
不过之前「我爽」的那个函数里没有return
。
在 Lua 中,函数可以有return
,也可以没有return
。
没有return
函数计算结果为nil
:
function f(x)
end
a = f(2)
print(a)
结果输出 nil
。另外return
后面不加表达式时,计算结果也是nil
:
function f(x)
return
end
a = f(2)
print(a)
return
意为「返回」,函数的计算结果也叫「返回值」。
「返回」的意思是说,哪儿来的回哪儿去。
举个栗子:
function f(x)
return x .. x
end
function g(x)
y = f(x) .. "美如画"
return y .. "!"
end
print(g("我爽"))
三狗测试,见输出「我爽我爽美如画!」。 这个「我爽我爽美如画!」是怎么算出来的呢?
这段代码干了三件事:
- 定义
f
函数 - 定义
g
函数 - 调用
print
函数
其中,定义函数是不会产生输出的,
产生输出的是最后的print
那一行代码:
print(g("我爽"))
在 Lua 中,调用一个函数之前,会先把参数的值求出来。
所以在执行到print(g("我爽"))
时,
会先去求g("我爽")
的值,再用求出来的结果去继续调用print
。
于是这段代码接下来就会跳到g
的定义,开始求g("我爽")
的值。
g
的定义一共有两行,其中第一行为:
y = f(x) .. "美如画"
由于我们传入的x
是"我爽"
,这一行此时相当于是:
y = f("我爽") .. "美如画"
也就是将f("我爽")
和"美如画"
拼接成一个新的字符串,赋值给y
。
为了得到这个拼接的结果,
Lua 接下来会先去求f("我爽")
的值。
于是我们跳到的f
的定义。f
的定义里只有一行:
return x .. x
由于我们传入的x
是"我爽"
,这一行相当于:
return "我爽" .. "我爽"
也就是:
return "我爽我爽"
return
后面的表达式的求值结果,就是这个函数的求值结果。
接下来就是把这个结果「返回」的时候了。
返回结谁?从哪儿来的,就返回给谁。
刚才是从这一行来的:
y = f("我爽") .. "美如画"
现在f("我爽")
的值求完了,这一行也就成了:
y = "我爽我爽" .. "美如画"
也就是:
y = "我爽我爽美如画"
接下来,继续执行g
的后面一行:
return y .. "!"
这行现在相当于:
return "我爽我爽美如画" .. "!"
也就是:
return "我爽我爽美如画!"
接着,再把这个结果返回给当初调用g
的地方,也就是print
那一行:
print(g("我爽"))
现在相当于:
print("我爽我爽美如画!")
于是这段代码最终输出了「我爽我爽美如画!」。
提前返回
return
语句通常写在函数结尾,但也可以提前出现。
return
出现在函数中途时,函数后面的部分都会被跳过,
直接返回到调用函数的地方。
提前返回常用于错误处理:见事不好,调头就跑。
function f(name)
if name == "京狗" then
print("帅哥你谁啊")
return
end
print(name .. "美如画")
end
f("我爽")
f("京狗")
测试知先后输出「我爽美如画」和「帅哥你谁啊」。 当输入参数为京狗时,美如画的部分并没有被执行。
提前返回有助于提早排除有毒的输入参数, 减少缩进层级,使后面的代码写起来更爽。
练习题
1) 预测以下代码输出:
function f(x)
return x + 1
end
function g(x)
return 2 * f(x)
end
a = f(1) + g(2)
print(a)
2) 预测以下代码输出:
function f()
print("f1")
return "f2"
end
function g()
print("g1")
print(f())
print("g2")
end
g()
3) 完成repeat3
的定义,使代码输出「嗷嗷嗷」。
function repeat3(str)
-- TODO 在此处填写实现
end
print(repeat3("嗷"))
下一讲:局部变量