Saki's 研究记录

Learn Lua in 15 Minutes 中文版

字数统计: 2.6k阅读时长: 11 min
2024/08/30

Origin:

Learn Lua in 15 Minutes by Tyler Neylon 中文版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
-- 两横代表单行注释.

--[[
这样是多行注释.
--]]

----------------------------------------------------
-- 1. 变量及流程控制.
----------------------------------------------------

num = 42 -- 数字是双精度的.
-- 别担心,64 位双精度里有 52 位
-- 用于存储精确 int 值;对于机器精度
-- 小于 52 位的整数来说不是问题

s = 'walternate' -- 类似 Python 的字符串.
t = "双引号也行"
u = [[ 双中括号
可以赋值
多行字符串.]]
t = nil -- 未定义变量 t; Lua 具有垃圾回收机制.

-- 用 do/end 这类关键字表示代码块:
while num < 50 do
num = num + 1 -- 没有 ++ 或者 += 这样的运算符.
end

-- If 代码块:
if num > 40 then
print('over 40')
elseif s ~= 'walternate' then -- ~= 表示不等于.
-- 等于用 == 表示; 字符串也可以用来比较.
io.write('not over 40\n') -- 默认控制台输出.
else
-- 变量默认是全局的.
thisIsGlobal = 5 -- 驼峰变量命名法.

-- 这样是局部变量:
local line = io.read() -- 控制台读取输入.

-- 字符串用 .. 连接起来:
print('Winter is coming, ' .. line)
end

-- 未定义变量返回 nil.
-- 这样做不会报错:
foo = anUnknownVariable -- 此时 foo = nil.

aBoolValue = false

-- 只有 nil 和 false 表示假; 0 和 '' 都是真!
if not aBoolValue then print('twas false') end

-- 'or' 两边一个真即为真; 'and' 两边一个假即为假.
ans = aBoolValue and 'yes' or 'no' --> 永远返回 'no'

karlSum = 0
for i = 1, 100 do -- 循环包含两端极值.
karlSum = karlSum + i
end

-- 用 "100, 1, -1" 即可实现倒计数:
fredSum = 0
for j = 100, 1, -1 do fredSum = fredSum + j end

-- 总之, 循环范围用 起始值, 终值[, 步进值] 表示.

-- 另一种循环:
repeat
print('the way of the future')
num = num - 1
until num == 0


----------------------------------------------------
-- 2. 函数.
----------------------------------------------------

function fib(n)
if n < 2 then return 1 end
return fib(n - 2) + fib(n - 1)
end

-- 支持闭包及匿名函数:
function adder(x)
-- 当 adder 被调用时
-- 返回函数被创建, 而且记得参数 x:
return function (y) return x + y end
end
a1 = adder(9)
a2 = adder(36)
print(a1(16)) --> 25
print(a2(64)) --> 100

-- 返回值, 函数调用参数和赋值语句
-- 都遵循一一对应的关系. 如果遇到长度不等的情况,
-- 不足的用 nil 弥补;
-- 多余的被丢弃.

x, y, z = 1, 2, 3, 4
-- 此时 x = 1, y = 2, z = 3, 然后4 被丢弃.

function bar(a, b, c)
print(a, b, c)
return 4, 8, 15, 16, 23, 42
end

x, y = bar('zaphod') --> 输出 "zaphod nil nil"
-- 此时 x = 4, y = 8, 然后 15..42 被丢弃.

-- 函数优先级最高, 可以是 local/global.
-- 下面两个等价:
function f(x) return x * x end
f = function (x) return x * x end

-- 下面两个也等价:
local function g(x) return math.sin(x) end
local g; g = function (x) return math.sin(x) end
-- 这里 'local g' 声明意味着可以用 g 引用自己.

-- 顺带一提, 三角函数使用的是弧度制.

-- 用一个参数调用函数可以不带小括号:
print 'hello' -- 正常工作.


----------------------------------------------------
-- 3. 表.
----------------------------------------------------

-- Table 是 Lua 里的唯一一种数据结构;
-- 类似于数组.
-- 像 php 的 arrays 或是 js 的 objects,
-- 它是由哈希索引的字典还能当列表使用.

-- 当作字典 / 映射使用举例:

-- 默认使用字符串作为索引键:
t = {key1 = 'value1', key2 = false}

-- 使用类似js的 . 操作符获取值:
print(t.key1) -- 输出 'value1'.
t.newKey = {} -- 增加一个键值对.
t.key2 = nil -- 从表中去掉key2.

-- 中括号可以使任何 (非nil) 值作为键:
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'}
print(u[6.28]) -- 输出 "tau"

-- 对于字符串和数字的键, 可以直接索引.
-- 但是对于表的键, 索引的是它的哈希.
a = u['@!#'] -- 此时 a = 'qbert'.
b = u[{}] -- 我们希望赋值 1729, 但是实际上赋的值是 nil:
-- b = nil 因为索引查找失败. 之所以失败
-- 此时的键是一个新表
-- 这个表的哈希与赋初值的时候并不相同.
-- 所以用字符串和数字当作键更便捷一点.

-- 表作为唯一参数的函数调用不用加小括号:
function h(x) print(x.key1) end
h{key1 = 'Sonmi~451'} -- Prints 'Sonmi~451'.

for key, val in pairs(u) do -- 表迭代.
print(key, val)
end

-- _G 是一个内置全局表.
print(_G['_G'] == _G) -- 输出 'true'.

-- 以表作为列表 / 数组:

-- 列表默认以数字为键:
v = {'value1', 'value2', 1.21, 'gigawatts'}
for i = 1, #v do -- #v 代表v列表的长度.
print(v[i]) -- 迭代从键 1 开始
end
-- 这里 '列表' 并不是一个专有类型. v 本质上是 table
-- 以连续数字为键的表, 可以当作列表使用.

----------------------------------------------------
-- 3.1 元表和元函数.
----------------------------------------------------

-- table 中可以设置元表
-- 可以用来覆写运算符. 后面还会看到
-- 类似js的prototype功能.

f1 = {a = 1, b = 2} -- 代表分数 a/b.
f2 = {a = 2, b = 3}

-- 这样写肯定报错:
-- s = f1 + f2

metafraction = {}
function metafraction.__add(f1, f2)
sum = {}
sum.b = f1.b * f2.b
sum.a = f1.a * f2.b + f2.a * f1.b
return sum
end

setmetatable(f1, metafraction)
setmetatable(f2, metafraction)

s = f1 + f2 -- 这样写将会调用 f1 元表上的 __add(f1, f2) 函数

-- f1, f2 不含对自己元表的引用, 这一点
-- 不同于js的prototype, 要想取得元表必须使用
-- getmetatable(f1) 这样的函数. 元表本身也是表
-- 里面的键是Lua事先定义好的, 比如 __add.

-- 因为s没有设置元表所以下面这么写行不通:
-- t = s + s
-- 下面的类似面向对象的写法就能解决这个问题.

-- 使用元表的 __index 键可以制造类似 . 属性的读取操作:
defaultFavs = {animal = 'gru', food = 'donuts'}
myFavs = {food = 'pizza'}
setmetatable(myFavs, {__index = defaultFavs})
eatenBy = myFavs.animal -- 多亏元表才可以这么写!

-- 表里没找到的键会在其元表的
-- __index 表里找, 没找到的话会在 __index 表元表的 __index 表里找, 如此递归地搜索下去.

-- __index 还能被赋予形如 function(tbl, key) 的函数
-- 以便在未找到键时直接自动调用.

-- 像 __index,add, .. 这些称作元函数.
-- 下面列举了元表里所有的预定义元函数.

-- __add(a, b) for a + b
-- __sub(a, b) for a - b
-- __mul(a, b) for a * b
-- __div(a, b) for a / b
-- __mod(a, b) for a % b
-- __pow(a, b) for a ^ b
-- __unm(a) for -a
-- __concat(a, b) for a .. b
-- __len(a) for #a
-- __eq(a, b) for a == b
-- __lt(a, b) for a < b
-- __le(a, b) for a <= b
-- __index(a, b) <fn or a table> for a.b
-- __newindex(a, b, c) for a.b = c
-- __call(a, ...) for a(...)

----------------------------------------------------
-- 3.2 面向对象和继承.
----------------------------------------------------

-- Lua里没有类的概念; 只能通过
-- 表和元表来实现类似功能.

-- 代码下面有解释.

Dog = {} -- 1.

function Dog:new() -- 2.
newObj = {sound = 'woof'} -- 3.
self.__index = self -- 4.
return setmetatable(newObj, self) -- 5.
end

function Dog:makeSound() -- 6.
print('I say ' .. self.sound)
end

mrDog = Dog:new() -- 7.
mrDog:makeSound() -- 'I say woof' -- 8.

-- 1. Dog 作为一个类; 实际上是个表.
-- 2. 函数 tablename:fn(...) 等价于
-- 函数 tablename.fn(self, ...)
-- 操作符 : 隐式插入了第一个参数 self.
-- 至于self的取值请参考下面的 7 和 8.
-- 3. newObj 作为 Dog 类的实例.
-- 4. self = 继承的类. 通常
-- self = Dog, 但是继承会改变它.
-- newObj 具有 self 上的函数因为我们把
-- newObj 的元表和 self 的 __index 都设置为了 self.
-- 5. 要记得 setmetatable 函数会将其第一个参数作为返回值.
-- 6. 类似于上面的 2, 但是这次的
-- self 不是类而是实例.
-- 7. 等同于 Dog.new(Dog), 所以 new() 里面的 self = Dog.
-- 8. 等同于 mrDog.makeSound(mrDog); 这里的 self = mrDog.

----------------------------------------------------

-- 继承示例:

LoudDog = Dog:new() -- 1.

function LoudDog:makeSound()
s = self.sound .. ' ' -- 2.
print(s .. s .. s)
end

seymour = LoudDog:new() -- 3.
seymour:makeSound() -- 'woof woof woof' -- 4.

-- 1. LoudDog 具有 Dog 的变量和函数.
-- 2. self 因为 new() 而具有一个 'sound' 键, 参见 3.
-- 3. 等同于 LoudDog.new(LoudDog), 然后查找元表得到
-- Dog.new(LoudDog) 因为 LoudDog 自己没有 'new' 键,
-- 而其元表的 __index = Dog.
-- 结果就是: seymour 的元表是 LoudDog, 然后
-- LoudDog.__index = LoudDog. 所以 seymour.key 就
-- = seymour.key, LoudDog.key, Dog.key, 这样就实现了
-- key 的继承.
-- 4. 'makeSound' 键在 LoudDog 里找到; 所以
-- 等同于 LoudDog.makeSound(seymour).

-- 需要的话, 子类的 new() 和其父类相似:
function LoudDog:new()
newObj = {}
-- 实例化 newObj
self.__index = self
return setmetatable(newObj, self)
end

----------------------------------------------------
-- 4. 模块.
----------------------------------------------------


--[[ 我把这部分内容注释掉以便其能
-- 作为 Lua 程序正常运行.
-- 假设我们有个文件 mod.lua, 其内容如下:
local M = {}

local function sayMyName()
print('Hrunkner')
end

function M.sayHello()
print('Why hello there')
sayMyName()
end

return M

-- 另一个文件就可以使用 mod.lua 里的函数:
local mod = require('mod') -- 运行 mod.lua 文件.

-- require 是加载模块的标准方法.
-- require 这里的作用相当于: (没明白的话可以接着往下看)
local mod = (function ()
<mod.lua 的脚本内容>
end)()
-- 就好像 mod.lua 是一个函数的函数体, 所以
-- mod.lua 里的本地变量在外面不能访问.

-- 这样可以运行因为这里的 mod = mod.lua 里的 M:
mod.sayHello() -- Says hello to Hrunkner.

-- 这样不能运行; 因为 sayMyName 是 mod.lua 里的本地函数:
mod.sayMyName() -- error

-- require 的返回值会自动缓存
-- 所以一个脚本只会执行一次, 即使它被 require 很多次.

-- 假设 mod2.lua 里包含函数 "print('Hi!')".
local a = require('mod2') -- 输出 Hi!
local b = require('mod2') -- 没有输出; a=b.

-- dofile 类似于 require 但并不缓存:
dofile('mod2.lua') --> Hi!
dofile('mod2.lua') --> Hi! (又执行了一次)

-- loadfile 载入 lua 文件但不运行它.
f = loadfile('mod2.lua') -- 手动调用 f() 才会开始执行.

-- loadstring 是 loadfile 的字符串版本.
g = loadstring('print(343)') -- 返回输出函数.
g() -- 输出 343; 之前并没有输出.

--]]

----------------------------------------------------
-- 5. 参考.
----------------------------------------------------

--[[

我希望用 Löve 2D 游戏引擎做游戏, 所以开始学习 Lua. 这是动机.

我一开始从 BlackBulletIV 的 Lua for programmers 中学习.
然后阅读了官方的 Programming in Lua 电子书.
这是方法.

关于 Lua 的介绍可以参考 lua-users.org.

这里并没有介绍 Lua 的标准库:
* string 库
* table 库
* math 库
* io 库
* os 库

其实本教程也是个 Lua 文件.
可以把它保存为 learn.lua 然后输入 "lua learn.lua" 来执行它!

这是我第一次给 tylerneylon.com 写稿.
本教程也可以在 github gist 上找到. 类似
这样写法的其他语言的教程, 在这里:

http://learnxinyminutes.com/

祝你愉快学 Lua!

--]]

以上。

CATALOG