Lua 飞速入门

对 Rainmeter 有点兴趣,觉得可以作为我的画画时间的 dashboard。但我的逻辑明显需要一些数据处理,因此需要编程能力,而 RunCommand 不够方便,它的输出没法结构化,这时候使用它内嵌的 Lua 脚本是有必要的。所以,第一次地,我真的要去把 Lua 过一次了。上次想去接触 Lua,还是 Redis 有个滑动窗口的需求,当时是直接上 AI 了(因为这个算法明显会有些复杂)。

总之,记录我对 Lua 和 Rainmeter 的学习,实践优先,理解优先,按照我最喜欢的学习方式去学习。先把 Lua 过一遍。

Lua 我用 Rainmeter 的嵌入式环境,我就不自己下环境了,找个 在线解释器 去测试代码和语法。

Lua 入门

Lua 是一门(假装自己)面向对象的,动态类型的解释型语言,对空值非常松散(最典型的例子是,任何变量标识符都认为是合法变量但返回 nil,这让拼写错误非常致命。保持警惕!它比没有”use strict”的 js 还松散,得当成 perl 那类看待),语法类似 ruby。

lua 没有 try-catch 式的异常处理,它的异常处理是类似函数式的(感觉有点像 haskell),这里我不提及。lua 的协程我也不去学习。

Lua 有基于原型的 OOP,但我不学习。基于闭包的封装足够我用了。

lua 使用下划线命名法……但 tmd,Rainmeter 的 API 是大驼峰……我……我入乡随俗用下划线命名好了?

数据类型

Lua 有 nil,number,string,table,boolean,function(一等公民),userdata(C 扩展)……

  1. number 类型同 js——双精度浮点数,其中 53 位保存整数
  2. Lua 只有一个容器类型——table,数组和哈希表均是它(这其实也像 js?)。
  3. boolean 字面量是 true 和 false,取反是 not;and,or 语义和 python 相同,不等于是 ~=
  4. 空值是 nil(未初始化的变量也是它;没有 js 一样的 undefined)
  5. 只有 nil 和 false 是假值,其它均为真(空 table 也是真(不过对这样一个我不能算熟悉的语言,我不会利用这种太需要记忆的特性啦))
  6. 字符串使用单引号或双引号均可
  7. 关于字符串和数字操作——
    1. 使用+-*/等去操作数字
    2. 使用..去连接字符串
    3. 使用#去获取字符串长度(#'hello' == 5)
    4. 使用..操作数字时,会把数字当作字符串操作,得到字符串;使用+-*/等操作字符串时,会把字符串当作数字操作,得到数字
    5. 转换失败会报错(异常),使用 tonumber,tostring 函数进行显式转换,转换失败返回 nil
    6. 检查操作符不允许字符串和数字互操作,除了==~=,而数字和字符串总是不相等的,10 ~= '10'

语句

for 中可以 break,但不能 continue。lua 同样提供了 goto,但我不提它。

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
-- if
score = 100
if score >= 90 then
print("优秀")
elseif score >= 60 then
print("及格")
else
print("不及格")
end

-- while
i = 1
while i <= 5 do
print(i)
i = i + 1
end

-- do-while
i = 1
repeat
print(i)
i = i + 1
until i > 5

-- range(start_inclusive, end_inclusive)
for i = 1, 5 do
print(i)
end

-- range(start_inclusive, end_inclusive, step)
for i = 1, 5, 2 do
print(i)
end

-- kv 迭代器
t = {a = 1, b = 2, c = 3}
for key, value in pairs(t) do
print(key, value)
end

注释,作用域

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
-- Lua 的注释以--开头
--[[
这种多行注释的语法我还第一次见
]]
print('Hello, World!') -- 按照惯例

if true then
local world = 1 -- 使用 local 定义局部变量,局部变量有词法作用域
end
print(world) -- nil

-- 如果不使用 local,就没有词法作用域,甚至连函数作用域都没有!
function omg_wtf()
some_global_variable = (some_global_variable or 0) + 1
end
print(some_global_variable) -- nil
omg_wtf()
print(some_global_variable) -- 1
omg_wtf()
print(some_global_variable) -- 2
-- 所以,全局变量应当谨慎使用,但我的需求下多用用也 OK

-- 以及,function x()... 其实也是语法糖,等价于 x = function()...
-- 所以,function 前面也能加 local,表示它是局部的 function,不会暴露给外界(require 这个 lua 文件的家伙)
local function local_fn()
print('weird')
end

函数和函数调用

函数是一等公民。函数调用不检查参数匹配

多个返回值

lua 没有 tuple,但允许函数返回多个值,并使用多个变量去接受这个函数的返回值,无法使用一个变量捕获所有返回值,除非使用 table。就这个行为来看,lua 似乎倾向“展开”函数的多个返回值,但有反例去反驳这个理解——a, b, c = f(), 3,如果以展开理解应该是得到1, 2, 3,结果得到的是1, 3, nil。我的建议是——不要这么写。

1
2
3
4
5
6
7
8
9
10
11
12
function f()
return 1, 2
end

a = f()
print(a) -- 1

b, c = f()
print(b, c) -- 1 2

d = {f()} -- 好家伙,解构?我懒得学到这个程度
print(d[1], d[2])

table

Lua 的 table 花活儿繁多,lua 使用 table 去实现数组和映射表,实际上,Lua 的模块就是 table。这里先讨论 table 作为 table 的部分,再讨论 table 用于封装的部分。

初始化

关于 table 的字面量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local t = {} -- empty table

local arr = {1, 2, 3} -- array-like table
-- 千注意,万注意:lua 的数组是 one-indexed
print(arr[1], arr[2]) -- 1 2

local dict = {
a = 1, b = 2, ['hello' .. 'world'] = 3
} -- dict-like,注意可以像 js 一样连索引也传表达式

print(dict['a'], dict['helloworld'])

-- 也可混合形式,这个好像和 js 是一样的?我也不熟悉 js 是咋玩而这种花活儿的
-- 总之 lua 会自己记录一个索引,一个一个去应用
-- 我的建议是——不要使用 tmd 混合表……但也要意识到可能会有家伙会去使用混合表
local weird_dict = {
1, 2, hello='world'
}
print(weird_dict[1], weird_dict[2], weird_dict['hello'])

方法

Lua 中,table 自己没有方法,都是使用 table 库函数去操作的,就像 js 的 object

我既然在脑子里还是想要区分一个 table 究竟是数组还是映射表,我就该在方法上也作出区分。

数组操作

  • table.insert(table, [pos,] value):在特定位置后插入(否则在末尾插入)
  • table.remove(table, [pos]):移除特定位置
  • #t:长度
  • table.unpack(table):解构,使用多个变量接受数组中的所有值
  • table.sort(table, [comp]):排序,可传入比较器函数
  • table.concat(table, [sep, [i, [j]]]):join 操作,i 和 j 是要进行 join 的那部分数组的下标范围,闭区间

没有切片操作,需要自己实现

映射表操作

  • next(t) == nil:判空
  • t[k] = nil:删除键

映射表长度必须用 pairs 遍历计数

关于成员和调用

下面展示成员定义和访问,以及方法调用上的花活儿。Lua 像 js 一样,.[] 均可访问成员,但不像 python——没有什么描述符协议,这使得绑定方法时不会自动绑定 self

注意这个:,明显是为了方便面向对象编写才提供的,使用该语法编写函数时,自动添加 self 作为第一个参数以让它成为方法;使用该语法调用方法时,自动使用调用者作为第一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local tb = {
a = 1,
b = 2,
lmao = function(self) -- self 必须写,lua 不像 js,为 object 内的 function 也没有特殊处理
print('LMAO')
end
}

-- 等价于 tb.hello = function(self) ...
-- 或者 tb['hello'] = function(self) ...
function tb:hello()
print(self.a, self.b)
end

tb.world = function(self)
print(self.a, self.b)
end

print(tb.a) -- 1
print(tb['a']) -- 1
print(tb.hello) -- function: 0x.....
print(tb:world()) -- 1 2 , 等价于 tb.world(tb)
print(tb.hello(tb)) -- 1 2

You guess what?我就学这些。毕竟我用 lua 是只把它当作 Rainmeter 和我的 python 脚本的胶水去用。如果有字符串和日期处理的需求,我再专门去学就是了。它不是 Python,我不会把它当成我的主力。


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!