Lua基础
- Lua没有main的概念,只能嵌入到一个宿主程序中工作,这个宿主程序被称作embedding program 或 host.
\ddd
: ddd是一串最多三位的十进制数字, 用来描述一个字符. \10
, 如果该字符后面接一个数字5,则\0105
- 下面五种描述了完全相同的字符串:
a = “abc\n123”;
a = [[abc
123]];
a = [==[ -- [后面立刻跟了一个换行符被忽略
abc
123]==];
- 长注释
--[ .......
... ]
- 换行
这种类型的字符串可以包含多行,且不会解释转义序列;如果第一个是换行符会自动忽略;
[[
/type
...
]]
基础数据类型
nil
,number
,string
,boolean
(nil
,false
为假其它都为真包括0),function
,userdata
userdata
特性:- 用来将任意C数据保存在Lua变量中,该类型仅被定义了赋值和相同性判断;
- 使用metatable, 可以为userdata自定义一组操作,;
- 不能在Lua中创建出来, 也不能在Lua中修改, 只能通过API;
thread
:table
:- 可以用任何东西做索引, 可以包含任何类型的值(nil除外);
a.name
来表示a[“name”]
;
注:
table
,function
,userdata
,thread
变量本身不会真正的存放值, 仅仅放了一个对象的引用;- 数字与字符串之间的自动转换, 需要完全掌握数字怎么转换成字符串, 可以使用字符串库中的format函数;
- 三种变量: 全局, 局部, table域
对全局和table域的访问的含义可以通过metatable
来改变,如t[i]
等效于gettable_event(t,i)
;
全局变量:
- 是放在一个特定的lua table域中, 这个table叫做
environment table
简称环境, 每一个函数都有对一个环境的引用, 函数可见的全局变量都放在这个函数所引用的环境表里, 可以调用getfenv
取得环境,setfenv
改变环境。 - 对全局变量x的访问等价于
_env.x
, 又等价于gettable_event(_env, x)
Chunk
Lua的一个执行单元, 一个Chunk
就是一串语句段, 每个语句段以分号结束;
Lua把一个Chunk
当作一个拥有不定参数的匿名函数, 因此Chunk
可以定义局部变量, 接收参数, 和返回值;Block
语句块, 语法上说,语句块就是语句段, 一个语句块可以被显式的写成一个单独的语句段do block end
assignment
赋值
多个变量以,
分开, 左边多的变量会被赋成nil
, 右边多的变量会忽略, 赋值是先运算完再进行赋值;metatable
改变全局table
域中的赋值操作:t[i] = val;
等价于settable_event(t,i,val);
对于全局变量的赋值x=val;
等价于_env.x = val;
即settable_event(_env, “x”, val);
控制语句
while exp do block end;
repeat block until exp;
if exp then block {else if exp then block} end;
return [explist]
用于从函数或chunk中返回值, 且返回的值个数不止一个;- break用于从while/for/repeat中跳出循环;
注
break
/return
必须用于一个语句块的最后一句,即chunk
最后一句end
前until
前else
前, 如果要在一个语句块的中间return
或 break
, 则可以do break end
;
for
- 数字形式
for v= start, end, step do block end
;
step
没给出的情况为1,start end step
三个表达式只运算一次, 计算只在循环开始之前,且必须为数字;v
的值是局部变量, 当for
循环结束后就不能再使用;- 迭代器函数形式(泛型
for
):
for namelist in explist do block end
namelist
如:var1
,var2
,var3
,…;
explist
只会被计算一次, 返回三个值: 迭代器函数, 状态, 迭代器初始值;
循环变量是局部变量, 循环结束后不能再使用;
for i,v in ipairs(a) do print(v) end
–遍历值
for k in pairs(t) do print(j) end
–遍历键
表达式
- 如果一个表达式做为一个独立的语句出现,则其返回值将会被对齐到零个元素, 即忽略所有的返回值;
- 如果表达式作为列表的最后一个元素,则不会有任何对齐;
任何情况下,Lua将表达式看成单一元素, 忽略掉除第一个元素外的任何值;
如:
f();
返回0个结果
g(f(), x);
中f()
仅取第一个元素
g(x, f());
中f()
取所有的元素
a, b, c = x, f();
中f()
调整为2个结果
a, b, c = f(), x;
中f()
取1个结果,c = nil
a, b = ... ;
中a
,b
分别取可变参数的第1, 2个元素
a, b, c = f();
中f()
调整为3个元素{f()};
用f()
的所有元素创建一个列表;{...}
可变参数创建一个列表;
{f(), nil};
中f()
被调整为1个结果;- 被括号括起来的表达式永远被当作一个值, 除非函数调用(如上面)才可能会是多个值.
(f(x,y))
无论f(x,y)
返回多少个值,永远只取第一个值!
总结
出现
f()
在最后一个表达式时才取全部数据, 否则只取1个元素
操作符
比较操作符
数字和字符串的比较与常规的比较一样; 对table
,userdata
,thread
以及function
的比较是以引用的方式进行:两个对象只有在指向同一个的东西时才会相等
;逻辑操作符
and
,or
,not
把false
nil
为假其它都为真;
and
和or
遵循短路原则,and
在第一个为false
或nil
时返回第一个,否则返回第二个;or
在第一个不为false
或nil
时返回第一个,否则返回第二个; (该方法在完成真假判断的同时还有一定的选择功能)..
字符串连接操作符, 数字后面加..
时必须先加上空格以防解释成小数;10 .. 2
#
取长度操作符, 长度是字节数, 一个字符一个字节;table
的长度n
是以最后一个非nil
所在的下标来计算的, 即t[n] ~=nil
而
t[n+1] = nil
;- 数组是
1...n
, 如果数组中存在nil
值则#t
则可能得到任意一个nil值的下标;
table
构造
每次table
构造子被执行,就会造出一个新的table
;
用table
实现一个list
:
list = nil;
local i = 0;
for line in io.lines() do
-- 逆向构建一个list, next指向之前的list
list = {next = list, value = line}
i = i + 1;
if(i == 5) then break end
end
-- 遍历
while list ~= nil do
print(list.value)
list = list.next
函数
函数调用
prefixexp arg
prefix
与arg
是先被求值, 如果prefixexp
是function
, 则这个函数就会被参数调用, 否则prefixexp
的元方法会被调用, 元方法的第一个参数就是prefixexp
的值, 后面的参数就是arg
;
参数的构造:args = ([explist]);
=tableconstructor;
=string;
调用方法
predixexp
:name args
,类似v.name(args)
等效v.name(v.args)
f{fields}
等效f({fields})
;
而这样的形式:f’string’
或f”string”
/f[[string]]
都等效(‘string’)
,这里的参数列表仅仅是一个字符串;尾调用
return functioncall;
尾调用: 被调用的函数将重用调用它的函数的堆栈,因此无论多少此尾调用层次都不会造成过多的资源消耗; 同时尾调用将删除调用它的函数的任何调试信息
;函数定义
function funcname funcbody;
local function name funcbody;
function t.a.b.c.f () body end
等效于t.a.b.c.f = function () body end;
local function f () body end
等效于local f; f = function () body end;
C++中可以通过函数指针来调用不同的函数,但函数指针的参数是不变的, 而Lua中是可以调用可变参数的可变函数,通过
unpack()
来实现可变参数:
func(unpack(a)); --a为可变参数
...
变长参数
变长参数...
Lua中将参数存放在一个arg
的表中;- 函数返回多个值时, 可以用
select(index, func)
来选择值,选择的范围是第index
个到最后一个; - 命名参数:
即函数可以直接用一个表来作为参数, 同时在表中对参数进行命名,这样在函数定义中容易应用
function f(arg)
return arg.v1 + arg.v2
end
print(f{v1 = 4,v2 = 5}); --> 9
形参列表
如果实参该表达式处于另一表达式的中间,则表达式只返回一个值, 如果放于最后一个, 则不会做调整, 参考前面的表达式
...
变长形参
实参中存在表达式, 则表达式放于最后位置时会做相应的调整,多余的实参传入...
中可视规则
变量的局部性类似C++
函数是first class即函数和其它值一样可以被存放在变量中,也可以放在表中,函数的参数和函数的返回值;
闭包 closure
词法界定: 一个函数嵌套另一个函数时, 内部的函数可以访问外部的函数的局部变量; 这些局部变量在内部函数中被称为upvalue
外部局部变量; 这个功能与C++不同,如下例:
function out()
local i = 1;
return function () local j = 0; j = j + 1; i = i + 1; return i,j; end
end
print(out()());
print(out()()); --两个函数生成了不同的函数对象, out()返回不同的函数指针
local j = out(); --一个函数指针, i为堆栈中的局部变量
print(j()); --此时i = 1 + 1;j = 0 + 1; <-->2,1
print(j()); -- 见下面说明
上面第二句
print(j())
,表现出了与C++的不同处,C++上面j()返回后内存被销毁,此处i将重新分配内存;而Lua的闭包功能就是函数仍然保留了前面j对象的内存, 则局部变量i=2保留到了当前的函数, 而j是局部变量,函数会重新得到所以不一样,因此 i = 2 + 1;j = 0 + 1; <–>3,1
- 函数重定义
函数可以存储在普通的变量内, 可以很方便的重定义或预定义函数;通过do ..end
来封装则可以创建安全的运行环境–也称作沙箱;
function add(a,b) return a + b end
do
local he= add;
add = function (a,b) return a*b end; -- 函数重定义
print(he(2,3)); -- he保留了旧的函数运算:2 + 3
print(add(2,3)); -- 重定义函数:2 * 3
end
print(add(2,3)); -- 重定义:2*3
上面的例子中, 将旧的add函数保存在了he局部变量中, 通过do..end包装, 可以保证he的局部性, 同时如果在end前不执行add = he, 则add函数会被永久替换;
协同程序
协同程序类似多线程,但其实现并不是多线程的机制,而只是类似非抢占线程, 同一时刻只有有一个协程在运行(没有真正实现并发), 只能被显示的挂起
coroutine.yield(co)
,没参数时挂起自己和恢复coroutine.resume(co);
协程相对多线程的优点:
- 1.协程是单线程, 协程的切换仅仅是程序控制,没有线程上下文的切换问题, 所以执行效率高;
- 2.协程不需要锁机制, 也不需要处理互斥访问, 不会有死锁;
协程注意点:
co = coroutine.create(function()...end)
协程创建的参数只有一个: 将要运行的协同程序封装成的函数, 返回的co
为thread
类型三种状态:
挂起
,运行
,停止
;
协同程序刚开始被创建时是挂起状态,resume
终止状态下的协同程序时会返回false
和error information
resume传递额外的参数:
thd = function(a) --协同程序
while true do
if not a then a = 0 end
print(a .. ‘\n’)
coroutine.yield()
end
end
--每次通过thread类型创建协程时都会生产新的协程
co1 = coroutine.create(thd)
print(coroutine.resume(co1)) -->thd , nil
print(coroutine.resume(co1, 4)) -->thd, nil
这里第一次启动协程的参数才是里面的
a
,后面传人的参数没有用,因为协程一直在while循环中运行或挂起,外部传人的变量并实际上不会改变局部变量a;
不对称性: 即挂起一个正在执行的协程的函数与使一个挂起的协程再次执行的函数不是同一个函数, 所以由执行到挂起之间状态的转换函数是不同的
yield
与resume
之间的数据传递:
yield
可以传人参数, 参数会返回给下一次对该函数使用resume
的协程, 如协程p中执行挂起时有yield(a,b)
, 那么如果下次有local status, v1, v2 = coroutine.resume(p);
则v1 = a, v2 = b
co = coroutine.create(
function ()
local x = 0;
while true do
x = io.read()
coroutine.yield(x+1, x+2)
end
end
)
print(coroutine.resume(co));
--可以看成是函数调用,然后返回了相应的值
-- 如果输入2; 则打印: true 3 4
环境
thread
,function
,userdata
除了metadata
外还关联一个环境表,多个对象可以共享一个环境表;Coroutine
类似多线程,就具体以后再看
C API
堆栈
Lua
和C
传递值是通过一个虚拟栈来实现,栈上的每一个元素都是lua
值(nil, string, number..); 每次lua
调用C
都会得到一个新的栈, 该栈独立于C函数本身, 也独立于以前的栈, 里面包含lua
传给C
的数据也包含C
返回给lua
的结果;- 该堆栈的操作可以看成一个数组, 可以通过索引来查找内容, 1~n表示栈上的绝对位置即数组的位置; 而栈顶位于n的位置;因此-1~-n表示栈顶到栈底的过程;0不能作为索引;
伪索引
函数可以接受一个有效的索引–伪索引, 通过它可以访问一些不在栈上的lua
值, 可以用来引用线程环境, 函数环境, 注册表等,
线程的环境通常在伪索引LUA_GLOBALSINDEX
, C环境则放在伪索引LUA_ENVIRONINDEX
处;
只要给定环境表的位置就可以访问该表,如访问全局变量的值
lua_getfield(L, LUA_GLOBALSINDEX, varname)
C closure
C函数被创建出来, 可能需要一些值关联起来, 从而生成一个C closure, 这些被关联的值叫做upvalue, 这些值对应的伪索引用lua_upvalueindex宏生产;注册表
用于保存任何C代码想要保存的lua值, 伪索引LUA_REGISTRYINDEX