lua学习笔记
table{},sequence,#
sequence也是table,只是把这个从1开始的系列提取出来的部分。(从1开始)
#长度,针对string,sequence
table如果key为table这样的复杂结构,是用pointer来hash和rawequal,所以不要指望你setmetatable,添加__equal了,让key1==key2,就可以用key2从table中取到key1的value。(毕竟lua都没提供__hash)
所以复杂结构作为table的key,感觉基本是没有用的。
要对table的实现有个了解。copy下云风的博客http://blog.codingnow.com/2005/10/lua_table.html
“lua 的 table 充当了数组和映射表的双重功能,所以在实现时就考虑了这些,让 table 在做数组使用时尽量少效率惩罚。
lua 是这样做的。它把一个 table 分成数组段和 hash 段两个部分。数字 key 一般放在数组段中,没有初始化过的 key 值全部设置为 nil 。当数字 key 过于离散的时候,部分较大的数字 key 会被移到 hash段中去。这个分割线是以数组段的利用率不低于 50% 为准。 0 和 负数做 key 时是肯定放在 hash 段中的。
string 和 number 都放在一起做 hash ,分别有各自的算法,但是 hash 的结果都在一个数值段中。hash 段采用闭散列方法,即,所有的值都存在于表中。如果hash 发生碰撞,额外的数据记在空闲槽位里,而不额外分配空间存放。当整个个表放满后,hash 段会扩大,所有段内的数据将被重新 hash ,重新 hash 后,冲突将大大减少。
这种 table 的实现策略,首先保证的是查找效率。对于把 table 当数组使用时将和 C 数组一样高效。对于 hash 段的值,查找几乎就是计算 hash 值的过程(其中string 的 hash 值是事先计算好保存的),只有在碰撞的时候才会有少许的额外查找时间,而空间也不至于过于浪费。在 hash 表比较满时,插入较容易发生碰撞,这个时候,则需要在表中找到空的插槽。lua 在table 的结构中记录了一个指针顺次从一头向另一头循序插入来解决空槽的检索。每个槽点在记录 next 指针保存被碰撞的 key 的关联性。”
词法作用域
先思考这个程序运行结果,说出这里每个赋值的含义
local z = 11111;
do
local x =22
do
x = 33
local y = 33;
y = 333
end
print(x)
print(y)
y = 22;
z = 22222;
_G.z = 33333;
end
print(x)
print(y)
print(z)
print(_G.z)
这里面有多个table的,_G,_ENV(这个是module的table,5.2之后才有),各个block的table。
因为lua的global var不需要declaration,这会导致一些typo 引起的bug,lua用strict.lua来设置_G的metatable,来运行时检测。哎,没有静态检测。
lua的词法作用域规则就是从最内层到最外层一层一层找name,找到就返回。所以多用local,效率还会高。
closure和for
很多看似是function,内部实现是Closure,但Closure的类型概念不需要暴漏给用户,但要理解还是要知道的。
closure可想象成一个类instance的方法,它是个function但携带了object。
而closure 和for循环结合,是个经典问题。我们来看看
local func ={}
for i=1,3 do
local f = function()
print(i)
end
table.insert(func, f)
end
for k, f in pairs(func) do
f()
end
猜猜这个会打印什么?关于closure,关于作用域。会打印(1,2,3)(i在for block里。并且比c#实现的正确)
var funcs =[];for(var i =0; i <3; i++){// let's create 3 functions
funcs[i]=function(){// and store them in funcs
console.log("My value: "+ i);// each should log its value.};}for(var j =0; j <3; j++){
funcs[j]();// and now let's run each one to see}
js的这个会打印什么?猜猜,3,3,3(毕竟i在module空间)
var actions = new List<Action>();
for (var i = 0; i < 3; i++)
{
actions.Add(() => System.Console.WriteLine(i));
}
actions.ForEach(a=>a());
System.Console.ReadLine();
c#这个呢,在c#5都会是3,3,3(测试过了)。5之后是1,2,3(没测试)
再想想java是怎么处理的,java根本不让你compile过。因为i不是effective final。
来一个更有趣的
local func ={}
for i=1,3 do
local f = function()
print(i)
i=100+i
end
table.insert(func, f)
f()
end
for k, f in pairs(func) do
f()
end
这个会打印什么,要完全理解,得需要理解 the implementation of Lua 5.0里的函数和闭包 中upvalue的处理。
为什么需要声明local,而不是local by default。这是个FAQ,http://lua-users.org/wiki/LocalByDefault
"Local by default is wrong. Maybe global by default is also wrong, [but] the solution is not local by default." (Roberto)
The reason is that 'local by default' does not mix well with
lexical scoping. The languages that use this either do not
have proper lexical scoping (and thus don't have the problem)
or need some ugly workarounds. It's a common language design
mistake (IMHO).
Proper lexical scoping really requires that you have a choice
between mutating a binding (assignment) and creating a new
binding (declaration). Alas, the 'local by default' rule mixes
them and makes an assignment do both. Consequently you cannot
modify a variable in an outer scope with a simple assignment
(because that would create a new local variable with the same
name).
Unless of course you introduce an extra declaration to make up
for this. E.g. in Python this is the 'global' statement. But
this only allows you access to the outermost scope (module level).
This has little to do with proper lexical scoping.
The only real workaround in Python is to create a mutable
container (a list) in the outer scope. But then you have to use
list access and list assignment everywhere (x[1] = x[1] + 1).
Yuck. That's why closures in Python are ... uh ... unpopular.
To avoid making this a Python flamefest, here's an insight
from Ruby's language designer:
| [...] I needed block local variables, so I made the current local
| variable scoping rule, which is "when a new local variable
| appears first in a block, it is valid only in the block".
|
| And *I was wrong*. This rule is the single biggest design flaw
| in Ruby. You have to care about local variable name conflict,
| and you will have totally different result (without error)
| if they conflict.
|
| So, we are talking about a part of many-year-long effort of
| fixing this flaw. [...]
|
| [ http://www.rubygarden.org/ruby?LocalVariablesAndBlocks ]
statement,expression,语句和表达式
do end相当于{},
repeat until 在中间local变量 util条件也能用
for i=1,10 do print(i) end 中的i自动为local,(numberic for,generic for中的变量都自动local)只在for循环里面可见。
return只能出现在block的最后,如果要放中间请用do return end
goto 的label要::label::这样写。
local fact = function (n)
if n == 0 then return 1
else return n*fact(n-1) -- buggy
end
end
这个有错,一点作用域的bug,调用fact(n-1)时候,local fact还没定义好。
所以得写
local fact
fact = function(n)...
或者用语法糖 local function foo (<params>) <body> end。
它会展开为 local foo; foo = function (<params>) <body> end
function f2() return 11, 22 end
print(f2())
print((f2()))
第二个返回一个11,因为()是个expression,当多返回值用于expression时只取第一个。
不定个数的参数,... {...}, table.pack, table.unpack
尾递归消除。tail-call elimination,就是因为到结尾了,第一个栈不会再用了,所以当第二次调用传参数
不再stack.push了,而是直接修改第一次的栈。所以不会栈溢出了。
错误处理2种方式1,return error code。2,raise an error。如果能轻易避免的,用2, 负责用1。
使用assert可由1转2。使用pcall可由2转1
要traceback,xpcall
iterators,迭代器的写法
for var_1, ..., var_n in <explist> do <block> end
等价于
do
local _f, _s, _var = <explist>
while true do
local var_1, ... , var_n = _f(_s, _var)
_var = var_1
if _var == nil then break end
<block>
end
end
利用这个可以写《1》stateless。《2》用closure来做,<explist>只返回 _f。《3》用table来表示_s,通常可以把_var也放到table里,从而_f只用一个参数。《4》稍后还有coroutines。使用优先级从1,2,3,4顺序排列。
closure比table更cheaper更快,这个倒是挺没想到的
其实这个不应该叫iterator,for部分才是iterator,叫generator可能更合适,可是java已经把它叫成iterator了。
true iterator的风格应该是allwords(f)。里面传个函数。回调的风格。
这种不如generator风格。generator更灵活,跟语言结合更好。break,return。等可中间打断。
coroutine,4个状态:suspended, running, dead, normal
coroutine.create .resume .yield
.wrap 用来coroutine-》iterator
mt={} ,setmetatable(o, mt)
mt .__add, .__mul, .__lt, .__index, .__pair
prototype={...} mt.__index = prototype .__index除了等于function,也可以等于一个table,可以模拟继承
rawget(t, i)
如何面向对象?
Account = {balance = 0 }
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:withdraw (v)
self.balance = self.balance - v
end
a = Account:new({balance = 0})
a:withdraw(100)
用:这个语法糖相当于传了个self做第一个参数。这个符合了lua 提供机制而不是策略 的设计准则。先用setmetatable
设置好a的__index,这样在a:withdraw时就能找到类Account这个table,然后发现withdraw函数,
再加上语法糖约定通过self得到调用者a,这样就完成了面向对象类和对象的模拟。
注意和一般OO语言的区别,在动态语言中balance这个成员变量并没有new了就创建。而是需要显示创建,或者
a = Account:new()
new传空参数时,读取时用的类Account的balance。只有写入时才会创建对象a的balance。
如何继承?
SpecialAccount = Account:new{limit = 1000}
s = SpecialAccount:new()
s:withdraw()
继承出奇的简单。就是meta再meta的过程。
多重继承呢?
不要用这种特性吧,有这种需求考虑组合
ide支持?
动态语言的ide支持还真不容易做,我希望lua for idea再更新能支持这种auto compeletion。如果不支持,考虑自己加上。
要不要这种OO风格?
还在思考中,我倾向于使用,这种是ide友好的。如果完全动态,ide很难做。除非动态带来的好处非常大,如果不大,就用类型概念。
如何创建模块?
不要用module函数,使用
local M = {}
function M.foo() print("foo", x) end
return M
这样的风格吧。还有一种都定义local 最后return {foo=foo}。在PIL里作者认为这两种都可以,但我们原则第一种吧 原因请参考 http://lua-users.org/wiki/ModuleDefinition。module已经被deprecated 了
dofile(),load()()。。都是在global env上做的。我们用require来做。不会多次加载。
1,c调用lua
lua_call
void lua_call (lua_State *L, int nargs, int nresults);
协议为:
调用前虚拟栈的内容是:function,arg1,arg2,...,argN,
lua_call最后会把栈里内容都pop出来,然后再塞上res
调用后虚拟栈的内容是:res1,res2,...,resN
若果有错发生,longjmp到外层。如果不想要longjmp,用lua_pcall,它会塞一个error message到栈上
The following example shows how the host program can do the equivalent to this Lua code:
a = f("how", t.x, 14)
Here it is in C:
lua_getglobal(L, "f"); /* function to be called */
lua_pushliteral(L, "how"); /* 1st argument */
lua_getglobal(L, "t"); /* table to be indexed */
lua_getfield(L, -1, "x"); /* push result of t.x (2nd arg) */
lua_remove(L, -2); /* remove 't' from the stack */
lua_pushinteger(L, 14); /* 3rd argument */
lua_call(L, 3, 1); /* call 'f' with 3 arguments and 1 result */
lua_setglobal(L, "a"); /* set global 'a' */
2,lua调用c
lua_CFunction
typedef int (*lua_CFunction) (lua_State *L);
协议为:
调用前虚拟栈为arg1,arg2,...,argN,
CFunction不需要pop出这些arg,只要push res进栈,然后返回res个数就行了。
调用后虚拟栈为arg1,arg2,...,argN,res1,res2,...,resN
如果有错发生,调用lua_error,或luaL_error,这会让lua raise an error。
static int foo (lua_State *L) {
int n = lua_gettop(L); /* number of arguments */
lua_Number sum = 0.0;
int i;
for (i = 1; i <= n; i++) {
if (!lua_isnumber(L, i)) {
lua_pushliteral(L, "incorrect argument");
lua_error(L);
}
sum += lua_tonumber(L, i);
}
lua_pushnumber(L, sum/n); /* first result */
lua_pushnumber(L, sum); /* second result */
return 2; /* number of results */
}
3,为什么要通过virtual stack呢?
当我们在lua和c中exchange values时 2个问题,1,dynamic and static type system mismatch。2,automatic 和manual memory management mismatch。
比如在lua中写a[k] =v ; a可以是很多种类型(metatable)。比如我们要在c中调用这个操作settable,针对每个参数类型我们会需要非常多的settable。组合爆炸啊。
也许用union type比如lua_Value来union 所有的lua values。我们可以声明settable为
void lua_settable(lua_Value a, lua_Value k, lua_Value v);
这个办法2个缺点,1是没法轻易用其他语言(java,c#)表示。2是没法及时gc,以为不能及时知道那个参数不用了。
(个人觉得作者给出的这两个理由有点牵强了)所以lua设计了这个virtual stack的方式。
4,lua和c#如何交互呢?
还是通过c接口为中介。来交换数据
所以ulua,slua都是
1,包含了Plugins下面的.dll,.so文件,来导出lua c的接口给c#用。
2,包含了CodeGen来生成代码
3,包含了LuaWrap或LuaObject这些生成代码,来导出unity的c#类给lua用。
5,为什么大家都用lua5.1版本呢?
因为luajit只支持5.1现在。为了在android机器上有较好的性能,所以都5.1了。
6,slua,ulua 性能比较
ulua用了cstolua。结论slua的确会快不少。
用slua吧。ulua集成了一堆不别要的东西,cjson,protobuf,。。。还自己lua实现了Vector3这些,感觉太不纯粹。
在windows上测试的。应该都用了jit。
for i=1,200000 do
transform.position=transform.position
end
ulua: 0.438, slua:0.26,0.22
for i=1,200000 do
transform:Rotate(Vector3.up, 90)
end
ulua:0.60, slua:0.65
for i=1,2000000 do
local v = Vector3(i,i,i)
Vector3.Normalize(v)
end
ulua:1.33, slua:1.18
for i=1,200000 do
local v = GameObject()
end
ulua:4.63,slua:2.84
for i=1,20000 do
local v = GameObject()
v:AddComponent(SkinnedMeshRenderer.GetClassType())
local c=v:GetComponent(SkinnedMeshRenderer.GetClassType())
end
ulua:1.68,slua:0.90
for i=1,200000 do
local t=Quaternion.Euler(100,100,100)
local q=Quaternion.Slerp(Quaternion.identity,t,0.5)
end
ulua:0.31,slua:0.001