• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Lua 程序设计笔记

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

记录所有需要记录的

  • 逻辑运算符认为 false 和 nil 是假(false),其他为真,0 也是 true
  • string是不可变量
  • Lua 语法要求 break 和 return 只能出现在 block 的结尾一句(也就是说:作为 chunk的最后一句,或者在 end 之前,或者 else 前,或者 until 前)
  • 举个具体的例子,如果我们只想要 string.find 返回的第二个值。一个典型的方法是使用哑元(dummy variable,下划线):
local _, x = string.find(s, p) 
-- now use `x' 
  • 命名参数,Lua 语法支持函数调用时实参可以是表的构造
rename{old="temp.lua", new="temp1.lua"} 
function rename (arg) 
return os.rename(arg.old, arg.new) 
end 
  • Lua 中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
    • 第一类值指:在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
    • 词法定界指:嵌套的函数可以访问他外部函数中的变量。这一特性给 Lua 提供了强大的编程能力。 我们称作外部的局部变量(external local variable)或者 upvalue。
      闭包是一个函数以及它的 upvalues。
    • upvalue:嵌套函数的外部函数的局部变量。匿名函数使用 upvalue i 保存他的计数,当我们调用匿名函数的时候 i 已经超出了作用范围,因为创建 i 的函数 newCounter 已经返回了。然而 Lua 用闭包的思想正确处理了这种情况。
c2 = newCounter() 
print(c2()) --> 1 
print(c1()) --> 3 
print(c2()) --> 2 
c1、c2 是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包。
  • 6.3 正确的尾调用(PPrrooppeerr TTaaiill CCaallllss)
    Lua 中函数的另一个有趣的特征是可以正确的处理尾调用(proper tail recursion,一些书使用术语“尾递归”,虽然并未涉及到递归的概念)。
    尾调用是一种类似在函数结尾的 goto 调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用。例如:
function f(x) 
return g(x) 
end

g 的调用是尾调用。
例子中 f 调用 g 后不会再做任何事情,这种情况下当被调用函数 g 结束时程序不需要返回到调用者f;所以尾调用之后程序不需要在栈中保留关于调用者的任何信息。一些编译器比如 Lua 解释器利用这种特性在处理尾调用时不使用额外的栈,我们称这种语言支持正确的尾调用,由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的.
可以将尾调用理解成一种 goto 不是传统的函数调用

  • 7.1 迭代器与闭包
    看一个稍微复杂一点的例子:我们写一个迭代器遍历一个文件内的所有匹配的单词。为了实现目的,我们需要保留两个值:当前行和在当前行的偏移量,我们使用两个外部局部变量 line、pos 保存这两个值
function allwords() 
local line = io.read() -- current line 
local pos = 1 -- current position in the line 
return function () -- iterator function 
 while line do -- repeat while there are lines 
 local s, e = string.find(line, "%w+", pos) 
 if s then -- found a word? 
 pos = e + 1 -- next position is after this word 
 return string.sub(line, s, e) -- return the word 
 else 
 line = io.read() -- word not found; try next line 
 pos = 1 -- restart from first position 
 end 
 end 
return nil -- no more lines: end of traversal 
end 
end 
for word in allwords() do
 print(word) 
end
------------------------  闭包 ----------------------
lua_pushcclosure 向lua中注册c函数的过程

向lua中注册c函数的过程是通过lua_pushcclosure(L, f, n)函数实现的
 
       流程:  1. 创建一个 sizeof(CClosure) + (n - 1) * sizeof(TValue)大小的内存, 这段内存是 CClosure + TValue[n], 并做gc簿记[这点太重要了, 为什么lua要控制自己世界中的所有变量, 就是因为它要做gc簿记来管理内存],  isC= 1 标示其是一个C闭包.
             2. c->f = f绑定c函数.    ---------  闭包.功能抽象 = f
             3. env = 当前闭包的env[这说明了被创建的闭包继承了创建它的闭包的环境].  ----------- 闭包.env = env
             4. 把栈上的n个元素赋值到c->upvalue[]数组中, 顺序是越先入栈的值放在upvalue数组的越开始位置, c->nupvalues指定改闭包upvalue的个数.  ---------- 闭包.upvalue = upvalue
             5. 弹出栈上n个元素, 并压入新建的Closure到栈顶.



关于lua的闭包(Closure)和Upvalue
upvalue:嵌套函数的外部函数的局部变量
function func(a) <== 这个函数返回值是一个函数
return function ()
    a = a + 1    <== 这里可以访问外部函数func的局部变量a,这个变量a就是upvalue
    return a 
end
end

func返回一个匿名函数,可用变量接取之。该匿名函数有一个upvalue a(有点像C函数的static变量),初值为首次调用func时的参数

闭包:一个匿名函数加上其可访问的upvalue
c = func(1) <== c现在指向一个拥有upvalue a = 1的匿名函数,c也被称作一个闭包
c()          <== 返回2
c()          <== 返回3
c2 = func(1) <== c2现在指向另外一个拥有upvalue a = 1的匿名函数,c2也被称作一个闭包
c2()         <== 返回2
下面是示例代码:
int 
generatecclosure(lua_State *L)        /* 闭包产生器 */
{
    lua_pushnumber(L, 0);             /* 压入第一个upvalue */ 
    lua_pushnumber(L, 0);             /* 压入第二个upvalue */
    lua_pushcclosure(L, cclosure, 2); /* 压入闭包的同时也把upvalue置入该闭包的upvalue表 */
    return 1;                         /* 返回闭包 */
}

int 
cclosure(lua_State *L)
{
    double upval1, upval2;
    upval1 = lua_tonumber(L, lua_upvalueindex(1)); /* 注意upvalue索引1,2是闭包依赖的,不会和其他的闭包中的索引冲突 */
    upval2 = lua_tonumber(L, lua_upvalueindex(2));
    upval1++; upval2++;    
    lua_pushnumber(L, upval1); lua_replace(L, lua_upvalueindex(1));/* 更新upvalue1 */
    lua_pushnumber(L, upval2); lua_replace(L, lua_upvalueindex(2));/* 更新upvalue2 */
    lua_pushnumber(L, upval1 + upval2);
    return 1;
}
------------------------------------------------------------------------
  • 范性 for 的执行过程:
    • 首先,初始化,计算 in 后面表达式的值,表达式应该返回范性 for 需要的三个值:
      迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。
    • 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
    • 第三,将迭代函数返回的值赋给变量列表
    • 第四,如果返回的第一个值为 nil 循环结束,否则执行循环体。
    • 第五,回到第二步再次调用迭代函数。
更具体地说:
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 
  • 8.3 错误
    对于程序逻辑上能够避免的异常,以抛出错误的方式处理之,否则返回错误代码

  • 协程
    协同看起来只是一种复杂的调用函数的方式,真正的强大之处体现在yield 函数,它可以将正在运行的代码挂起。Lua 中协同的强大能力,还在于通过 resume-yield 来交换数据。
    Lua的协同称为不对称协同(asymmetric coroutines),指“挂起一个正在执行的协同函数”与“使一个被挂起的协同再次执行的函数”是不同的,有些语言提供对称协同(symmetric coroutines),即使用同一个函数负责“执行与挂起间的状态切换

  • (直到 Lua 4.0 为止,所有的比较运算符被转换成一个,a <= b 转为 not (b < a)。然而这种转换并不一致正确。当我们遇到偏序(partial order)情况,也就是说,并不是所有的元素都可以正确的被排序情况。例如,在大多数机器上浮点数不能被排序,因为他的值不是一个数字(Not a Number 即 NaN)。根据 IEEE 754 的标准,NaN 表示一个未定义的值,比如 0/0 的结果。该标准指出任何涉及到 NaN 比较的结果都应为 false。也就是说,NaN <= x 总是 false,x < NaN 也总是 false。这样一来,在这种情况下 a <= b 转换为 not (b < a)就不再正确了。)

  • 但相等比较从来不会抛出错误,如果两个对象有不同的 metamethod,比较的结果为false,甚至可能不会调用 metamethod。这也是模仿了 Lua 的公共的行为,因为 Lua 总是认为字符串和数字是不等的,而不去判断它们的值。仅当两个有共同的 metamethod 的对象进行相等比较的时候,Lua 才会调用对应的 metamethod。只需要<=就可以了
    在我们关于基和操作的例子中,有类似的问题存在。<=代表集合的包含:a <= b表示集合a是集合b的子集。这种意义下,可能a <= b和b < a都是false;因此,我们需要将__le和__lt的实现分开:

Set.mt.__le = function (a,b)    -- set containment
    for k in pairs(a) do
       if not b[k] then return false end
    end
    return true
end
 
Set.mt.__lt = function (a,b)
    return a <= b and not (b <= a)
end
最后,我们通过集合的包含来定义集合相等:
Set.mt.__eq = function (a,b)
    return a <= b and b <= a
end
  • (注意:print 函数总是调用 tostring 来格式化它的输出)。

  • 当格式化一个对象的时候,tostring 会首先检查对象是否存在一个带有__tostring 域的 metatable。如果存在则以对象作为参数调用对应的函数来完成格式化,返回的结果即为 tostring 的结果。

  • 假定你想保护你的集合使其使用者既看不到也不能修改 metatables。如果你对 metatable 设置了__metatable 的值,getmetatable 将返回这个域的值,而调用 setmetatable 将会出错

  • __index metamethod 在继承中的使用非常常见,所以 Lua 提供了一个更简洁的使用方式。__index metamethod 不需要非是一个函数,他也可以是一个表。但它是一个函数的时候,Lua 将 table 和缺少的域作为参数调用这个函数;当他是一个表的时候,Lua 将在这个表中看是否有缺少的域。像__index 一样,如果 __newindex 的 metamethod 是一个表,解释器对指定的那个表,而不是原始的表进行赋值操作。

  • 可以使用 setfenv 函数来改变一个函数的环境。Setfenv 接受函数和新的环境作为参数。除了使用函数本身,还可以指定一个数字表示栈顶的活动函数。数字 1 代表当前函数,数字 2 代表调用当前函数的函数
    下面这段代码是企图应用 setfenv 失败的例子:

a = 1 -- create a global variable 
-- change current environment to a new empty table 
setfenv(1, {}) 
print(a) 
导致:
stdin:5: attempt to call global `print' (a nil value)
一旦你改变了你的环境,所有全局访问都使用这个新的表,如果她为空,你就丢失所有你的全局变量,甚至_G,所以,你应该首先使用一些有用的值封装(populate)她,比如老的环境:
a = 1 -- create a global variable 
-- change current environment 
setfenv(1, {_G = _G}) 
_G.print(a) --> nil 
_G.print(_G.a) --> 1 
现在,当你访问"global" _G,他的值为旧的环境,其中你可以使用 print 函数。
你也可以使用继承封装(populate)你的新的环境:
a = 1 
local newgt = {} -- create new environment 
setmetatable(newgt, {__index = _G}) 
setfenv(1, newgt) -- set it 
print(a) --> 1 
在这段代码新的环境从旧的环境中继承了 print 和 a;然而,任何赋值操作都对新表进行,不用担心误操作修改了全局变量表。另外,你仍然可以通过_G 修改全局变量:
-- continuing previous code 
a = 10 
print(a) --> 10 
print(_G.a) --> 1 
_G.a = 20 
print(_G.a) --> 20 
当你创建一个新的函数时,他从创建他的函数继承了环境变量
  • 中间有nil的数组,#操作是不可靠的

  • Lua 中的特殊字符如下:( ) . % + - * ? [ ^ $
    ‘%’ 用作特殊字符的转义字符,因此 ‘%.’ 匹配点;’%%’ 匹配字符 ‘%’。转义字符 '%'不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。当对一个字符有疑问的时候,为安全起见请使用转义字符转义他

  • io.write 、io.read写入和读取标准输入输出中的一行
    在编写代码时应当避免像 io.write(a…b…c);这样的书写,这同 io.write(a,b,c)的效果是一样的。但是后者因为避免了串联操作,而消耗较少的资源。原则上当你进行粗略(quick and dirty)编程,或者进行排错时常使用 print 函数。当需要完全控制输出时使用 write。

  • Write 函数与 print 函数不同在于,write 不附加任何额外的字符到输出中去,例如制表符,换行符等等。还有 write 函数是使用当前输出文件,而 print 始终使用标准输出。
    另外print函数会自动调用参数的tostring方法,所以可以显示出表(tables)函数(functions)和 nil

  • read 函数从当前输入文件读取串,由它的参数控制读取的内容:

    • “*all” 读取整个文件
    • “*line” 读取下一行
    • “*number” 从串中转换出一个数值
    • num 读取 num 个字符到串
      io.read("*all")函数从当前位置读取整个输入文件。如果当前位置在文件末尾,或者文件为空,函数将返回空串。特别的,io.read(0)函数的可以用来测试是否到达了文件末尾。如果不是返回一个空串,如果已是文件末尾返回 nil
  • I/O 库提供三种预定义的句柄:io.stdin、io.stdout和 io.stderr。因此可以用如下代码直接发送信息到错误流(error stream)。
    io.stderr:write(message)

int lua_gettop (lua_State *L);  函数 lua_gettop 返回堆栈中的元素个数,它也是栈顶元素的索引。注意一个负数索引-x 对应于正数索引 gettop-x+1void lua_settop (lua_State *L, int index); lua_settop 设置栈顶(也就是堆栈中的元素个数)为一个指定的值。如果开始的栈顶高于新的栈顶,顶部的值被丢弃。否则,为了得到指定的大小这个函数压入相应个数的空值(nil)到栈上。
void lua_pushvalue (lua_State *L, int index); 函数 lua_pushvalue 将堆栈上指定索引处的值复制一份到栈顶
void lua_remove (lua_State *L, int index); lua_remove 移除指定索引位置的元素,并将其上面所有的元素下移来填补这个位置的空白
void lua_insert (lua_State *L, int index); lua_insert 移动栈顶元素到指定索引的位置,并将这个索引位置上面的元素全部上移至栈顶被移动留下的空隔
void lua_replace (lua_State *L, int index); lua_replace 从栈顶弹出元素值并将其设置到指定索引位置,没有任何移动操作

Lua 5.3 参考手册 各API介绍 https://cloudwu.github.io/lua53doc/contents.html

所有元方法:
元方法
__index
__newindex
__pairs
__len
__tostring __tostring 元方法用于修改表的输出行为
__call __call 元方法在 Lua 调用一个值时调用
__metatable
__gc userdata专用

__concat – ‘…’

其他
__add 对应的运算符 ‘+’.
__sub 对应的运算符 ‘-’.
__mul 对应的运算符 ‘*’.
__div 对应的运算符 ‘/’.
__mod 对应的运算符 ‘%’.
__unm 对应的运算符 ‘-’.
__concat 对应的运算符 ‘…’.
__eq 对应的运算符 ‘==’.
__lt 对应的运算符 ‘<’.
__le 对应的运算符 ‘<=’.


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
mac下Nginx+lua模块编译安装发布时间:2022-07-22
下一篇:
Lua基础之MetaTable(6)发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap