1. Getting Starting
Chunks
-
Lua 语句之间无需分隔符, 分号可选, 通常只在一行中有多个语句时才使用分号
-
To exit the interactive mode and the interpreter, just type the end-of-file control character ( ctrl+D in UNIX, ctrl+Z❩ in Windows)
-
You can use the
-i
option to instruct Lua to start an interactive session after running the given chunk如
%lua -i hello_world.lua
表明执行完此文件后打开交互Lua, 对调试非常有用 -
Another way to run chunks is with the
dofile
function, which immediately executes a file如在交互Lua中
> dofile("path_to_your_luafile.lua")
该文件就会加载到交互Lua中
Lexical Conventions
- You should avoid identifiers starting with an underscore followed by one or more upper-case letters (e.g.
_VERSION
); they are reserved for special uses in Lua -
注释 单行注释:
--
块注释:--[[ print(10) --不会执行 --]]
取消块注释技巧
---[[ print(10) --会执行 --]]
Global Variables
-
Global variables do not need declarations; you simply use them
未声明的变量直接使用也不会报错, 只是返回nil
print(abc) -->nil
-
If you assign nil to a global variable, Lua behaves as if the variable had never been used
给变量赋值nil将回收此变量内存
The Stand-Alone Interpreter
-
文件首行以
#!/usr/bin/env lua
开头, 可以让lua解释器直接执行此文件:lua [options] [script [args]]
-
lua -e "print('hello world')"
和ruby一样 -
lua -i [文件或者-e "语句"]
执行后进入交互模式 -
-l
加载一个lib,-l
选项会调用 requirelua -i -la -lb
执行完a文件和b文件后, 进入交互模式 -
交互模式下的print:
> =math.sin(3)
前面有等号(貌似就是关键字return), 会打印出执行结果, 否则执行了不会有结果 -
LUA_INIT_5_2
LUA_INIT
Lua 会查找环境变量LUA_INIT
的值,如果变量存在并且值为@filename,Lua 将加载指定文件。如果变量存在但不是以@开头,Lua 假定 filename 为 Lua 代码文件并且运行他 -
Lua 使用所有参数构造 arg 表。脚本名索引为 0,脚本的参数从 1 开始
lua script a b c
arg[0] script arg[1] a …
2. Types and Values
-
八种基本类型: nil, boolean, number, string, userdata, function, thread, table
函数
type(value)
可以返回value的类型, 此方法返回值是个string -
nil
a global variable has a nil value by default, before its first assignment
you can assign nil to a global variable to delete it
-
boolean
The boolean type has two values,
false
andtrue
in Lua, any value can represent a condition
条件判断中, false, nil 将是 false, 其他的都是true
-
number
The number type represents real (double-precision floating-point) numbers. Lua has no integer type
-
string
string 不可以变
取长度操作
#
如print(#_VERSION)
字符串字面量: 单双引号均可, 单双引号内都支持转移字符(反斜杠转移)
长字符串字面量可以使用
[[
]]
进行定义, 为了避免和内容冲突, 还可以在其中添加任意的=
, 如[==[
]==]
对字符串执行数字的操作, 将会进行字符串转换到数字:
print('5' +6) => 11
同样数字也可以转为为字符串:
print(10 .. 20) => '1020'
但是比较运算不会进行转换:
print('11' == 11) => false
最佳实践: 不要依赖自动转换
显示的转换:
tonumber('2') => 2
` tonumber(‘x2’) => nil -
table
table是object, 是可以用字符串, 数据进行索引的对象
key可以是除了nil外的其他所有类型的值, key可以是混合不同类型, ‘1’, 1, ‘01’ 都是不同的key
key 可以是字符串(record)或者数字(list)甚至对象, 如果key需要是表达式计算,用
[]
对于字符串的key, 对table键值对的读写类似javascript: 可以用
table_object[key_name]
也可以用table_object.key_name
赋值nil给指定key, 用于删除此key.
用table表示数组: 无法显式声明长度, 索引惯例是从1开始, 对于长度, 有的做法会将其存到
n
key上.#
返回table最后的连续数字索引构造
list 字面量:
a = {10, 20, 'fox'}
table字面量:
a = {x=10, y=20}
混合型:
a = { x = 'm', y = 'n', {x = 0}, --a[1] {['x'..'y'] = 1} --动态计算的key }
you can always use a semicolon instead of a comma in a constructor. I usually reserve semicolons to delimit different sections in a constructor
{x=10, y=20; "one", "two"}
每次调用构造函数,Lua 都会创建一个新的 table,可以使用 table 构造一个 list
list = nil for line in io.lines() do list = {next=list, value=line} end
理解:以上代码能起作用, 说明list存于next时, 直接存的是对象地址, 虽然传的是对象, 但是对象先求值(对象地址)
-
function
functions are first-class values in Lua
Lua can call functions written in Lua and functions written in C
-
Userdata and Threads TODO
3. Expressions
-
比较:
<, >, <=, >=, ==, ~=
==, ~=
可以对于不同类型的比较 不同类型的值会视为不等, nil只等于自己本身<, >, <=, >=
只能用于2个string或者2个number, 不同类型大小比较将报错Lua compares tables and userdata by reference
-
逻辑运算
and or
短路运算, 返回值, 而不是返回逻辑结果not
总是返回bool值技巧:
空值保护:
x = x or y
等价于if not x then x = y end
三元运算:
x and y or z
等价于其他语言中的x ? y : z
(lua中没有三元运算) -
连接
..
因为字符串不可变, 连接会创建新的字符串 -
长度
#
a sequence is a table where the numeric keys comprise a set 1 … n.
the length operator is unpredictable for lists with holes (nils). It only works for sequences, which we defined as lists without holes.
Remember that any key with value nil is actually not in the table.
In particular, a table with no numeric keys is a sequence with length zero
4. statements
- 支持多重赋值:
a, b = 10, 20
可用于变量交换 - 多重赋值更慢
- 多重赋值主要用于交换变量, 以及接收函数的多个返回
- local variables have their scope limited to the block where they are declared.
代码块:指一个控制结构内,一个函数体,或者一个 chunk(变量被声明的那个文件或者文本串)
因此交互命令下的一句
local a=2
执行之后立即过期, 可以用一个do...end
解决 - It is good programming style to use local variables whenever possible
Control Structures
-
if then end
if then else end
if then elseif then else end
while do end
repeat until ...(lua中的do while)
-
Unlike in most other languages, in Lua the scope of a local variable declared inside the loop includes the condition 比如在repeat里定义的local变量, 在后面的until表达式里还是可见
-
numeric for:
for 循环变量 = 起始表达式, 终止表达式, [递增表达式, 可选, 默认为1] do end
循环变量
自动声明为局部变量(没有显示的local), 并且只在循环内有效- 运行条件是:
循环变量<=终止表达式
- If you want a loop without an upper limit, you can use the constant
math.huge
- all three expressions are evaluated once, before the loop starts
- you should never change the value of the control variable
-
Generic for
- the control variable is a local variable automatically declared by the for statement, and it is visible only inside the loop
- you should never change the value of the control variable
pairs
用于遍历一个table, 如for k, v in pairs(a) do print(k, v) end
-
迭代器:
io.lines
pairs
ipairs
string.gmatch
pairs 会迭代所有键值对, 包括数字索引(数组)和其他所有 ipairs 只遍历从1开始的连续数字索引
-
goto TODO
5. function
-
每个函数都有自动的return (返回nil,类似javascript, 而不是像ruby返回最后一个表达式)
-
Lua 语法要求 break 和 return 只能出现在 block 的结尾一句(也就是说:作为 chunk的最后一句,或者在 end 之前,或者 else 前,或者 until 前)
如果要非要在中间return, 需要用do end包起来:
do return end
(do end 可以单独使用, 以定义一个代码块) -
调用时候的括号:
- 如果调用时无参数, 必须有括号
- 如果只有一个参数, 且参数是字符串或者table, 则可以省略括号
-
o:foo(x)
与o.foo(o, x)
等价 -
Lua 函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用 nil 补足, 不可以设置默认值
-
函数声明没有默认值设定机制, 只能在函数中:
n = n or 1
-
Multiple Results: 如
string.find(origin, target)
返回开始索引和结束索引(索引) -
When a function call is the last (or the only) argument to another call, all results from the first call go as arguments
如果做为另一个函数的参数的函数调用不是最后一个参数的话, 此函数返回值将只有第一个
-
如果函数调用是在表达式中, Lua adjusts the number of results to one
-
A constructor also collects all results from a call, without any adjustments:
t={function_return_many()}
As always, this behavior happens only when the call is the last expression in the list; calls in any other position produce exactly one result:
t={funciton_return_many_but_only_get_one(), 1}
-
You can force a call to return exactly one result by enclosing it in an extra pair of parentheses:
print((funciton_return_many_but_only_get_one()))
-
table.unpack
receives an array and returns as results all elements from the array, starting from index 1it uses the length operator to know how many elements to return, 但你也可以手动指定需要的范围:
print(table.unpack({10,20,30,40}, 2, 3)) => 20, 30
左闭右闭类似ruby中
func(*input_array)
-
可变参数函数:
function name(...)
三个点代表可变参数(书上叫做extra arguments)Lua将函数的参数放在一个叫arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数, 参见http://book.luaer.cn/_38.htm
We call the expression
...
a vararg expression. It behaves like a multiple return function, returning all extra arguments of the current function{...}
返回一个table, 但是去掉了其后置nil的值(如果传入参数有后置nil的话)table.pack(...)
也返回一个table, 也去掉了nil, 但是带有一个n
属性, n的长度是包括了nil的长度for k, v in pairs({1,2; a=nil, b=8}) do print(k, v) end
输出没有a去掉后缀nil和命名nil其实是table自带的功能
-
Variadic functions can have any number of fixed parameters before the variadic part:
function fwrite(fmt, ...)
-
lua不支持命名参数, 可以通过传入table来模拟
6. More about Functions
-
函数名实际是指向函数的一个变量
定义函数的两种方式,和javascript类似
[local] function foo (x) return 2*x end
[local] foo = function (x) return 2*x end
函数作为对象属性:
Lib.foo = function (x,y)...
或者
Lib = { foo = function (x,y)... }
或者
Lib = {}; function Lib.foo (x,y)...
-
function是一等对象, with proper lexical scoping(It means that functions can access variables of their enclosing functions)
闭包是一个函数加上它可以正确访问的 upvalues(外部的局部变量 external local variable)
-
函数是匿名的,
function foo() end
等价于foo = function() end
, 构造一个函数, 赋值给变量 -
高阶函数: 接收函数作为参数的函数
迭代器
- 闭包是一个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)
编译·运行·调试
-
dofile:
调用了loadfile, 如果 loadfile 失败, dofile 使用 assert 抛出错误
无加载记录, 每次重复执行
-
loadfile:
容错函数
编译代码成中间码并且返回编译后的 chunk 作为一个函数,而不执行代码
发生错误的情况下, 返回 nil 和错误信息
运行一个文件多次的话,loadfile 只需要编译一次,但可多次运行。dofile 却每次都要编译 TODO
(推测)总是在全局环境中编译他的串
-
loadstring
编译代码成中间码并且返回编译后的 chunk 作为一个函数,而不执行代码
发生错误的情况下, 返回 nil 和错误信息
总是在全局环境中编译他的串
-
require
类似dofile, 区别:
Lua将require搜索的模式字符串放在变量package.path中。当Lua启动后,便以环境变量LUA_PATH的值来初始化这个变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。如果require无法找到与模块名相符的Lua文件,就会找C程序库。C程序库的搜索模式存放在变量package.cpath中。而这个变量则是通过环境变量LUA_CPATH来初始化的
先搜索
package.path
如果找到用loadlib
加载; 如果没找到, 搜索package.cpath
如果找到用loadlib
加载require 会判断是否文件已经加载避免重复加载同一文件, 在
package.loaded
记录了所有加载过的模块(模块有返回值则记录返回值, 如果没有返回值则记录true)一个文件将被作为一个function来执行加载, 因此文件中的局部变量, 函数将对外不可见, 全局变量,函数对外可见. 可以在文件最后加上return返回想要输出的对象
require will change each interrogation mark in the template by filename,which is modname with each dot replaced by a “directory separator”(such as “/” in Unix))
传入require中的dot, 会被解释成为目录分隔符
错误处理
-
error (message [, level])
抛出异常, 类似ruby raise, level 指的是引发错误的函数调用栈层 -
函数错误通常指导原则: 易于避免的异常引发一个错误(调用error), 否则返回nil
-
多数函数设计的返回值是:
value, message = callsome()
我把这种函数叫做容错函数如果发生错误, value 为nil, message 为错误消息; 如果没有错误, value为正常返回值
-
assert(dosomething, message)
类似于dosomething ? dosomething : error(message)
lua常见技巧:
result = assert(容错函数调用)
; 期望得到正确的返回值, 否则就抛异常 -
lua中的try-catch: pcall
status, error_or_result = pcall(somefunction)
成功的话, status是true,
error_or_result
是somefunction的返回值发生异常的话, status是false,
error_or_result
是错误消息, 不一定是字符串, 是抛出异常时调用error时的参数 -
print(debug.traceback())
任何时刻手动打印当前调用栈
metatables
-
table 和userdata 的实例可以有各自不同的metatable, 其他类型的值共享其类型所属的单一metatable
-
lua 代码中只能设置table的metatable
-
字符串默认设置了metatable, 其他类型默认没有
-
Lua 选择 metamethod 的原则:如果第一个参数存在带有__add 域的 metatable,Lua使用它作为 metamethod,和第二个参数无关;
否则第二个参数存在带有__add 域的 metatable, Lua 使用它作为 metamethod 否则报错
算术运算不关心混合类型的, 只要符合以上两条规则之一即可
关系元算不支持混合类型运算,如果你试图比较一个字符串和一个数字,Lua 将抛出错误.相似的,如果你试图比较两个带有不同 metamethods 的对象,Lua 也将抛出错误
-
库定义的metamethods
print 函数总是调用 tostring 来格式化它的输出。然而当格式化一个对象的时候,tostring 会首先检查对象是否存在一个带有__tostring 域的 metatable
setmetatable/getmetatable 函数也会使用metafield ,在这种情况下 ,可以保护metatables。
假定你想保护你的集合使其使用者既看不到也不能修改 metatables。如果你对metatable设置了__metatable 的值,getmetatable将返回这个域的值,而调用setmetatable将会出错
-
表相关的metamethods
__index
不需要非是一个函数,他也可以是一个表。但它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数; 当他是一个表的时候, Lua将在这个表中看是否有缺少的域-- create a namespace Window = {} -- create the prototype with default values Window.prototype = { x=0, y=0, width=100, height=100 } -- create a metatable Window.mt = {} -- declare the constructor function function Window.new (o) setmetatable(o, Window.mt) return o end Window.mt.__index = function (table, key) return Window.prototype[key] end w = Window.new{x=10, y=20} print(w.width) --> 100
以上lua代码几乎就是实现了javascript原型链集成, 一个明显的区别是
setmetatable(o, Window.mt)
在javascript中是语言实现的, lua 需要编码者实现的__newindex
metamethod 用来对表更新, 当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod, 如果存在则调用这个函数而不进行赋值操作。像__index 一样,如果 metamethod 是一个表,解释器对指定的那个表, 而不是原始的表进行赋值操作。(这里和js有点区别, js改属性不涉及原型, 但是lua却可能)
-
rawset(table, key, value)
rawget(table, key)
可以跳过metamethod -
算术元方法:
__add __mul __sub __div __unm __mod __pow
-
连接操作:
__concat
-
关系元方法:
__eq __lt __le
-
tostring:
__tostring(this)
对print有效 -
__metatable
设置此值, 用于禁止元表的读写 -
__index(table, key)
可以是一个函数或者table, 当读取table的key不存在时, 调用此方法/table -
__newindex(table, key, value)
可以是一个函数或者table, 当设置table的key不存在时, 调用此方法/table
环境
- 全局变量属于
_G
Packages
模块文件约定是返回一个全局的table, 模块文件如果没有显示return, 返回值将是true
包定义的几种格式:
-
最原始: 写死包名, 完整包名为内部调用命名空间
complex = {} function complex.new (r, i) return {r=r, i=i} end -- defines a constant `i' complex.i = complex.new(0, 1) function complex.add (c1, c2) return complex.new(c1.r + c2.r, c1.i + c2.i) end function complex.sub (c1, c2) return complex.new(c1.r - c2.r, c1.i - c2.i) end return complex
-
统一内部调用命名空间(不依赖固定包名)
local P = {} complex = P -- package name P.i = {r=0, i=1} function P.new (r, i) return {r=r, i=i} end function P.add (c1, c2) return P.new(c1.r + c2.r, c1.i + c2.i) end return complex
-
(文件名)动态模块名称, 便于模块改名
--将模块名设置为require的参数,这样今后重命名模块时,只需重命名文件名即可。 local modname = ... local M = {} _G[modname] = M M.i = {r = 0, i = 1} --定义一个模块内的常量。 function M.new(r,i) return {r = r, i = i} end function M.add(c1,c2) return M.new(c1.r + c2.r,c1.i + c2.i) end function M.sub(c1,c2) return M.new(c1.r - c2.r,c1.i - c2.i) end --返回和模块对应的table。 return M
-
去掉内部命名空间前缀(javascript)
local function new (r, i) return {r=r, i=i} end local function add (c1, c2) checkComplex(c1); checkComplex(c2); return new(c1.r + c2.r, c1.i + c2.i) end ... --export complex = { new = new, add = add, sub = sub, mul = mul, div = div, } return complex
-
(setfenv TODO) 5.2 已经去除