lua基础的关键点

(一)lua虚拟机

Lua体积很小,往往使用静态链接嵌入到程序内部,在发布应用时不需要附带任何的运行时支持。

通过luaL_newstate 创建Lua虚拟机时,第一块申请的内存将用来存储global_State(全局状态机)和lua_State(主线程)实例。为了避免内存碎片的产生,同时减少内存分配和释放的次数,Lua采用了一个小技巧:利用一个LG结构,把分配lua_State和global_State的行为关联在一起。这个LG结构是在C文件内部定义,而不存在公开的H文件中,仅供该C代码文件使用,因此这种依赖数据结构内存布局的用法负作用不大。
global_State
一个lua虚拟机中只有一个, 它管理着lua中全局唯一的信息, 主要是以下功能
1. 内存分配策略及其参数, 在调用lua_newstate的时候配置它们. 也可以通过lua_getallocf和lua_setallocf随时获取和修改它
2. 字符串的hashtable, lua中所有的字符串都会在该hashtable中注册.
3. gc相关的信息. 内存使用统计量.
4. panic, 当无保护调用发生时, 会调用该函数, 默认是null, 可以通过lua_atpanic配置.
5. 注册表, 注意, 注册表是一个全局唯一的table.
6. 记录lua中元方法名称 和 基本类型的元表[注意, lua中table和userdata每个实例可以拥有自己的独特的元表--记录在table和userdata的mt字段, 其他类型是每个类型共享一个元表--就是记录在这里].
7. upvalue链表.
8. 主lua_State, 一个lua虚拟机中, 可以有多个lua_State, lua_newstate会创建出一个lua_State, 并邦定到global_state的主lua_State上.
global_State主要是管理lua虚拟机的全局环境.
lua_State
1. 要注意的是, 和nil, string, table一样, lua_State也是lua中的一种基本类型, lua中的表示是TValue {value = lua_State, tt = LUA_TTHREAD}
2. lua_State的成员和功能
    a. 栈的管理, 包括管理整个栈和当前函数使用的栈的情况.
    b. CallInfo的管理, 包括管理整个CallInfo数组和当前函数的CallInfo.
    c. hook相关的, 包括hookmask, hookcount, hook函数等.
    d. 全局表l_gt, 注意这个变量的命名, 很好的表现了它其实只是在本lua_State范围内是全局唯一的的, 和注册表不同, 注册表是lua虚拟机范围内是全局唯一的.
    e. gc的一些管理和当前栈中upvalue的管理.
    f. 错误处理的支持.
3. 从lua_State的成员可以看出来, lua_State最主要的功能就是函数调用以及和c的通信.
lua_State主要是管理一个lua虚拟机的执行环境, 一个lua虚拟机可以有多个执行环境.
lua_newstate函数的流程
经过上面的分析, 可以看出newstate = [new 一个 global_state] + [new 一个 lua_State], 现在看一下它的流程, 很简单
1. 新建一个global_state和一个lua_State.
2. 初始化, 包括给g_s创建注册表, g_s中各个类型的元表的默认值全部置为0.
3. 给l_s创建全局表, 预分配l_s的CallInfo和stack空间.
4. 其中涉及到了内存分配统统使用lua_newstate传进来的内存分配器分配.

2. 创建新lua执行环境
lua_State *luaE_newthread (lua_State *L)
创建一个新的lua_State, 预分配CallInfo和stack空间, 并共享l_gt表, 注意, 虽然每个lua_State都有自己的l_gt, 但是这里是却将新建的lua_State的l_gt都指向主lua_State的l_gt.
注意, lua_State是lua运行的基础[CallInfo]和与c通信的基础[stack], 在新的lua_State上操作不会影响到原来的lua_State:), 这个是协程实现的基础. 这里顺便提一下协程, 这里先引一段lua创始人的话:" 我们不信任基于抢占式内存共享的多线程技术. 在 HOPL 论文中, 我们写道: "我们仍然认为, 如果在连 a=a+1 都没有确定结果的语言中, 无人可以写出正确的程序." 我们可以通过去掉抢占式这一点, 或是不共享内存, 就可以回避这个问题."协程的基础就是"去掉抢占式, 但共享内存", 这里的共享是在lua虚拟机的层面上的, 而不是通常意义上的share memory, 这里的共享内存直接就指的是不同线程[lua_State]之间, 共享lua_State.l_gt全局表, 全局表可以作为不同协程之间的通信环境, 当然也可以用lua_xmove函数, 协程的事先说到这里.
一个和多lua_State相关的函数是: 在同一个lua虚拟机里传递不同lua_State的值
void lua_xmove (lua_State *from, lua_State *to, int n)
把from栈上的前n个值弹出, 并压入到to栈中.

(二)lua关键点

1 string .. 前后必须有空格才是连接符的操作 .. 的效率是优于string.format string.format会遍历找到%,然后调用C方法

2 tonumber()如果转换失败会返回一个nil
3 lua不会暗中产生table的副本
例如1: a={}   a["x"] = 10   b = a   a = nil 此时b是好用的,直到b=nil时,table才会被删除
例如2: a={}   x = "y"   a[x] = 10  a.x是不存在的,但是a.y是存在的
table.maxn会返回一个table最大的正索引数
例如:a = {}  a[1000] = 1 table.maxn(a) --- 是1000
4 a%b计算结果的符号永远与第二个参数相同 
x = math.pi
x-x%0.01  ---最后输出为3.14
5 对于table、userdata和函数,lua是作引用比价的
6 所有的逻辑操作符将false 和nil视为假,其余都为真,and 和 or 都是短路求值
7 table的构造式{x=0,y=0}等价于{["x"]=0,["y"]=0}
7 如果想要严格控制某些局部变量的作用域时,do  end 是很好的做法
8 if  elseif end
while XXX do  end
local sqr = x/2
repeat 
sqr = (sqr + x/sqr)/2
local error = math.abs(sqr^2-x)
until error < x/10000  --此处可以访问error
9 in ipairs 和 in pairs的区别
pairs可以遍历表中所有的key,并且除了迭代器本身以及遍历表本身还可以返回nil;

但是ipairs则不能返回nil,只能返回数字0,如果遇到nil则退出。它只能遍历到表中出现的第一个不是整数的key

两者的效率比较:table中分为2个存储空间, 一个是线性数组空间(TValue *array), 和一个hash空间(Node *node) 
当我们使用 pairs 和 ipairs 会产生两种不同的迭代器, 一个仅仅遍历数组, 一个遍历所有的值
pairs遍历所有的值
ipairs仅仅遍历数组 当调用ipairs的时候, 在线性数组中遇到第一个nil时便停止遍历, 不管后面还是否有值, 这点在遍历的时候要格外注意
如果我们明确table中的数据全部存放在线性数组中, 调用ipairs或者pairs均可, 并无太大差异(注意ipairs时中间不要出现nil值, 否则会导致遍历中断), 如果我们明确遍历hash表中的值, 则使用pairs, 其效率会较遍历array中的差 

如果函数调用不是一系列表达式的最后一个元素,那么将只产生一个值:

10 x,y = foo2(),20 -- x="a",y = 20
x,y = foo0(),20,30  --x=nil y=20 ,30
print(foo2(),1) --- a 1
print(foo2().."x") --- ax
因为在f(g())时,g()的返回个数会调整为f()的参数个数
t = {foo2()} -- t = {"a","b"}
但是 t = {foo2(),4} -- t[1] = "a" t[2] = 4
11 unpack接收一个数组作为参数,并从下标1开始返回该数组的所有元素:
print(unpack{10,20,30})----10 20 30
a,b = unpack{10,20,30}----a = 10 b = 20
f(unpack(a))unpack会返回所有值,这些值作为f的实参
f = string.find
a = {"hello","ll"} --- 3 4 与调用string.find("hello","ll")一样
function unpack(t,i)
i = i or 1
if(t[i]) then
return t[i],unpack(t,i+1)
end
end
12 ... 变长参数
function  add( ... )
local s = 0;
for i ,v in ipairs(...) do
s = s + v
end
return s
end
多指恒定式: function id( ... ) return ... end
function foo1( ... )
print("calling foo: ",...)
return foo(...)
end
如果变长参数传入nil,此时需要用selelct函数 
for i = 1 , select("#",...) do --select("#",...) 会返回所有变长参数的总和,包括nil
local arg = select(i,...)
end
隐含的局部table变量“arg”来接收️所有的变长参数,这个table有一个名为n的字段,记录变长参数的总数
local arg = {...} arg.n = select(#,...) --缺点,每次都会创建一个table
lua中参数传递机制具有位置性
rename(old="1.lua",new="2.lua")--这是无用的
rename{old="1.lua",new="2.lua"}
function rename(arg)
return os.rename(arg.old,arg.new)
end
13 lua中的函数是第一类值,具有特定的词法域。第一类值意味着函数可以存储到任何变量或者table中,可以作为实参传递给其他函数,还可以作为其他函数的返回值。
lua中的函数有特定的词法域,指一个函数可以嵌套在另一个函数中,内部的函数可以访问外部的函数。
a = {p = print}
a.p("hello")--hello
sin = a.p
sin(10,20) -- 10,20
function  foo( x )
return 2*x
end
foo = function (x) return 2 * x end --语法糖
多用于排序 table.sort(t,function (a,b) return(a.name > b.name)end)
sort函数可以接受另一个函数作为实参,称为高阶函数

14 闭合函数closure 一个闭合就是一个函数加上该函数所需访问的所有“非局部变量”

function newCounter()

local i = 0
return function()
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) -- 1
print(c1()) -- 2
c2 = newCounter()
print(c2()) -- 1
闭包除了用于排序外,回调也经常用:
function digitButton(digit)
return Button{ label = tostring(digit),action = function()
add_to_display(digit)
end
}
end
当digitButton函数执行完后,当时的局部变量digit已经超出作用范围,但此时闭包可以用digit
还可以利用闭包创建一个安全的运行环境,用的就是重新定义一个函数,并在新的实现中调用原来的函数
do
local old = io.open
local accessok = function(filename,mode)
检查权限
end 
io.open = function(filename,mode)
if accessok(filename,mode) then
return old(filename,mode)
else
return nill,"access denied"
end
  end
end
该程序可以通过新的版本调用原来的版本,并且外部无法得到原来的版本了。

15.lua支持一种特殊的语法糖:
local function f(<参数>)
<函数体>
end
local fact = function(n)
if n == 0 then return 1
else return n * fact(n-1) ---错误 因为此时fact尚未定义完成,如果希望好用需要提前 local fact
end
end
或者利用lua的语法糖
local function fact(n)
if n == 0 then return 1
else return n*fact(n-1)
end
end
lua函数的尾调用:function f(x) return g(x) end 在尾调用后程序不需要保存任何关于f(x)这个函数的栈信息了。当g返回时,执行控制权可以直接返回到调用f的那个点上,因此在进行尾调用的时不耗费任何栈信息了,这种实现称为支持尾调用消除/因此一个程序可以拥有无数嵌套的尾调用,不会造成栈溢出
function foo(n)
if n > 0 then return foo(n-1) end
end
return g(x) + 1  必须做一个加法
return x or g(x) 必须为一个返回值
return (g(x)) 必须为一个返回值

16.迭代器:就是一种可以遍历一种集合中所有元素的机制。
for word in allwords() do
print(word)
end
allwords()函数会进行查找,当返回nil时迭代结束。但是这个迭代需要创建一个新的closure。一般来说创建一个closure的代价低于table

17.编译、执行与错误

function dofile(filename)
local f = assert(loadfile(filename))--loadfile产生错误会返回nil
return f()
end
loadstring 与 loadfile类似
f = loadstring("i = i + 1")
i = 0
f() ; print(i) --1
f() ; print(i) --2
i = 32 
local i = 0
f = loadstring("i = i + 1; print(i)")
g = function () i = i + 1; print(i) end
f()  --- 33
g()  --- 1
g与其的操作了局部的i,但是f操作了全局的i loadstring 总是在全局环境中变异它的字符串
为了避免使用全局变量可以:
local l = io.read()
local f = assert(loadstring("local x = ...;return " .. l))
for i = 1 , 20 do
print(string.rep("*",f(i)))
end
local x = ...用来声明了一个局部变量x,然后在调用f时传入了实参i,i就变成了变长实现的表达式的值了。也不会调用全局的x
local f = package.loadlib(path,"luaopen_socket")--package.loadlib将一个C函数作为lua函数返回
如果需要在lua中处理错误,则必须使用函数pcall来包装需要执行的代码。
if pcall(foo) then
---
else
--- 错误处理代码
end
pcall会以一种保护模式来调用他的第一个参数,如果发生错误返回false。pcall在返回错误消息时,他已经销毁了调用栈的部分内容,因此希望得到有意义的调用栈,可以使用xpcall,该函数接受一个被调用函数外还接受另一个参数--错误处理函数,错误发生时,会在调用栈展开前调用错误处理函数。debug.traceback会根据调用栈来构建一个扩展的错误消息。print(debug.traceback())

18.链表的实现:list = {next = list , value = v}
local l = list 
while l do
l = l.next
end
table.concat 会将给定列表中的所有字符串连接起来,并返回连接的结果。
local t = {}
for line in io.lines() do
t[#t + 1] = line .. "\n"
end
local s = table.concat(t)
也可以 table.concat(t,"\n") .. "\n"----在每一个字符串之间插入分隔符,并且在结尾处也添加一个换行。

19.元表:
lua中的每一个值都有一个元表,table和userdata可以有各自独立的元表,而其他的类型的值则共享其类型所属的单一元表。lua中创建一个新的table时不会创建元表。
t = {}
print(getmetable(t))----nil
t1={}
setmetatable(t,t1)
assert(getmetatable(t)==t1)
任何table都可以作为任何值的元表,而一组相关的table可以共享一个通用的元表,一个table设置可以作为它自己的元表,用于描述其特有的行为。
设计一个具有默认值的table:
function setDefault(t,d)
local mt = {__index = function() return d end}
setmetatable(t,mt)
end
tab = {x=10,y=20}
print(tab.x,tab.z) --- 10 , nil
setDefault(tab,0)
print(tab.x,tab.z) --- 10  0 当不存在时就调用元方法,返回0
此时,setDefault中不同的table使用不同的元表,若所有人table默认值的table使用同一个元表。可以使用一个额外的字段来保持默认值,保持key的唯一性,只需创建一个新的table
local key = {}
local mt = {__index = function (t) return t[key] end}
function setDefault(t,d)
t[key] = d
setmetatable(t,mt)
end
(1)设计跟踪table的访问:
local index = {}
local mt = {
__index = function(t,k)
print("*access to element "..tostring(k))
return t[index][k]
__newindex = function(t, k v)
print("upodate of element "..tostring(k) .. "to" .. tostring(v))
t[index][k] = v
end
}
function track(t)
local proxy = {}
proxy[index] = t
setmetatable(proxy,mt)
return proxy
end
(2)设计只读的table:

利用代理实现,跟踪所有对table的更新操作,并引发一个错误就可以。对于__index元方法直接使用原table来代替函数。
function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(t , k , v)
error("attempt tp update a read-only table",2)
end
}
setmetatable(proxy,mt)
return proxy
end

20.模块和包

一个包就是一系列的模块。一个模块就是一个程序库,可以通过require来加载,然后便得到一个全局变量,表示一个table。
require "<模块名>"
function require(name)
if not package.loaded[name] then
local loader = finderloader(name)
if loader == nil then
error("unable to load module " .. name)
end
package.loaded[name] = true
local res = loader(name)
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end

21.类

lua中.方法需要self.:方法会隐藏self,:只是一个语法糖
a = Account:new{balance=0}
a:deposit(100.00)
A = {balance = 0}
function A:new(o)
o = o or {}
setmetatable(o,self)
self.--index = self
return o
end


lua可以通过两个table来实现私密性。一个table用来保存对象的状态,另一个用于保存对象的操作,称为接口。对象本身通过第二个table来访问,,为了避免未授权的访问,表示状态的table不保存在其他table中,而只是保存在方法的closure中。
function newAccount (initialBalance)
local self = {balance = initialBalance}
local withdraw = function(v)
self.balance = self.balance - v
end
local deposit = function(v)
self.balance = self.balance + v
end
return {
withdraw = withdraw,
deposit = deposit
}
end
a = newAccount(100)
acc1.withdraw(40)

 

 

你可能感兴趣的:(lua)