Lua

298 阅读9分钟

Lua

niuxingxing.feishu.cn/docs/doccnp…

注释

单行注释 --

多行 --[[ ]]--

image.png

image.png

String、function、userdata、thread、table可以被垃圾回收器进行回收,nil、boolean、number不被垃圾回收所管理

数据类型

nil

不存在的引用默认为nil,nil进行比较需要加上“”,type()类型返回为String

image.png

boolean

false和nil 为 false

其他所有和数字 0 为 true,

number

string

  • 所有字符串做运算时会自动进行转换为数字
  • 使用..进行字符串拼接
  • #输出string的长度

image.png

image.png

字符串操作

string.upper(x) -- 全部转换为大写
string.lower(x) -- 小写

-- 字符串替换 replace方法
-- s原始字符串 ,target要替换的内容,r替换成的,num默认全部替换
-- return s字符串的copy,替换的总次数
string.gsub(s, target, r, num)


--字符串反转
string.reverse(arg)

-- 格式化输出
string.format("val is :%d", 4)  -- val is 4
string.format("val is :%f", 4)  -- val is 4.000000

-- 长度
string.len("abc")

-- findAll 方法
-- 返回一个迭代器,每一个和pattern匹配的字符
string.gmatch(str, pattern)

-- find
-- str源字符,pattern正则表达式,起始位置
string.match(str, pattern)
string.match(str, "%a+") --按照空格分隔字符串

-- substring
local substring = string.sub(sourcestr, start, end)

image.png

image.png

table

niuxingxing.feishu.cn/docs/doccng… 放牛星星表总结

表( Table )是 Lua语言中最主要(事实上也是唯一的)和强大的数据结构 。

使用表, Lua语言可以以一种简单、统一且高效的方式表示数组、集合、记录和其他很多数据结构 。 Lua语言也使用表来表示包( package)和其他对象。 当调用函数 math.sin 时,我们可能认为是“调用了 math 库中函数 sin ”;而对于 Lua语言来说,其实际含义是“以字符串sin为key检索表 math”。

local t = {}
t = {"banana","orange","apple"}

默认开始索引为1
table遍历
1. ipairs
2. pairs

ipairs 遇到nil就停止

-- concate()将table中元素进行拼接
table.concat(t) -- bananaorangeapple
table.concat(t, ",") -- banana, orange, apple
table.concat(t,", ", 2,3)) -- orange, apple

-- insert(), 插入元素
table.insert(t, "mango") -- 尾部插入
table.insert(t, index, "mango") -- 指定位置插入
table.remove(t) -- 移除最后一个

-- sort()
-- 默认从小到大排序
local test0 ={1,9,2,8,3,7,4,6}
table.sort(test0, function(a, b) return a > b end) -- 从大到小

-- 使用#会在索引中断处停止计算,计算table大小工具
function getLength(t)
	local len = 0
	for k, v in pairs(t) do
		len = len + 1
		print(k .. " " ..v)
	end
	return len
end

Lua table默认API不提供深拷贝

---克隆对象(建议用于克隆Class对象)
---@param  any 对象
---@return  any 克隆对象
function Clone(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for key, value in pairs(object) do
            new_table[_copy(key)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end

function 函数

function testFun(a, b)
    return a + b
end

-- 可变参数
function average(...)
   result = 0
   local arg={...}    --> arg 为一个表,局部变量
   for i,v in ipairs(arg) do
      result = result + v
   end
   print("总共传入 " .. #arg .. " 个数")
   return result/#arg
end

--返回可变参数长度
select('#', ...)

可变长参数

-- ...
function foo(...)
    local params = {...} or {}
    local count = 0
    for k, v in ipairs(params) do
        print(k, v)
        count = count + 1
    end
    print(string.format("count is %d", count))
end

foo(2, 3, 1, 55, print, "s")
--1       2
--2       3
--3       1
--4       55
--5       function: 00D84C70
--6       s
-- count is 6
  1. upValue上值,上值就是闭包中的局部变量。内嵌函数可以访问外包函数中创建的局部变量。

  2. 闭包Clourse,闭包是一个动态生成的数据对象,包含了函数原型的引用、全局表的引用和一个数组(包、Upvalue)。 虚拟机栈会创建一个closure对象包并且包含了function中所有的变量进入栈。每次调用function的时候都可以读取到变量

Continue.................................

循环

  • While循环
  • for循环
  • repeat until (do while)

Conditional

if(布尔表达式)
then
   --[ 在布尔表达式为 true 时执行的语句 --]
end
if(布尔表达式)
then
   --[ 布尔表达式为 true 时执行该语句块 --]
else
   --[ 布尔表达式为 false 时执行该语句块 --]
end
--[ 定义变量 --]
a = 100;
b = 200;

--[ 检查条件 --]
if( a == 100 )
then
   --[ if 条件为 true 时执行以下 if 条件判断 --]
   if( b == 200 )
   then
      --[ if 条件为 true 时执行该语句块 --]
      print("a 的值为 100 b 的值为 200" );
   end
end

goto

用于跳转到指定 ::tag:: 处

goto room1 -- initial room 
  ::room1:: 
  do 
      local move = io.read() 
          if move == "south" then 
          goto room3 
      elseif 
          move == "east" then 
          goto room2 
      else
           print("invalid move") 
          goto room1 -- stay in the same room 
      end 
  end 
 
  ::room2:: 
  do 
      local move = io.read() 
          if move == "south" then 
          goto room4 
      elseif 
          move == "wast" then 
          goto room1 
      else
           print("invalid move") 
          goto room2 -- stay in the same room 
      end 
  end 
 
   ::room3:: 
  do 
      local move = io.read() 
          if move == " north" then 
          goto room1 
      elseif 
          move == "east" then 
          goto room4 
      else
           print("invalid move") 
          goto room3 -- stay in the same room 
      end 
  end 
  
  ::room4:: 
  do 
      print("Congratulations, you won!") 
  end

元表

元表用来定义一个表的基本行为。 多个表和userdata可以共享同一个表。

所有的number共同引用一个元表。

所有的string共享同一个元表

Metatable

setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
getmetatable(table): 返回对象的元表(metatable)。

-- rawget方法获取table[index]的真实值, 不会查找元表的__index中的 value
rawget(table, index)
-- 下面这段代码含义
setmetatable(train_set, 
{
  __index = function(t, i) 
    return {t.data[i], t.label[i]}
  end
})

 等价于:
 local my_metatable = {
     __index = function(t, i)
         return {t.data[i], t.label[i]}
     end
 }
 setmetatable(train_set, my_metatable)
 
 -- 当去访问train_set[4] 并且 key 4为nil不存在表中的时候
 -- __index = function(train_set, 4)

__index

使用table根据key去获取value,如果表中没有指定key或者table的类型不是table,就去查找元表的__index。__index的值可以为函数或者表

__newIndex

不存在的key进行赋值的时候,就会触发__newindex方法

t ={}
local _t = t
t = {}

local mt = {
    -- get
    __index = function (t, k)
        print("Access to element" .. tostring(k))
        return _t[k]
    end,

    -- set
    __newindex = function(t, k, v)
        print("Successful set element \"" .. tostring(k) .. "\" as" .. tostring(v))
        _t[k] = v
    end
}

setmetatable(t, mt)
t[2] = "new"
print(t[2]

--[[
Successful set element "2" asnew
Access to element2
new 
--]]

__call

调用func()当func类型不是function的时候,__call触发

-- 当元表中设置了__call之后就可以把table a当作function()去使用
local a = setmetatable({}, __call = function(self, index)
    error(index)
end

a(1) -- 1

__tostring

定义表的输出形式,print(table)的默认形式

image.png

image.png

Lua模块与包

作用域 SCOPE

Lua语言中的变量在默认情况下是全局变量,所有的局部变量在使用前必须声明 。 与全局变量不同,局部变量的生效范围仅限于声明它的代码块。一个代码块( block)是一个控制结构的主体,或是一个函数的主体,或是一个代码段(即变量被声明时所在的文件或字符串

  • 局部变量可以避免由于不必要的命名而造成全局变量的混乱
  • 局部变量还能避免同一程序中不同代码部分中的命名冲突
  • 访问局部变量比访问全局变量更快
  • 局部变量会随着其作用域的结束而消失,从而使得垃圾收集器能够将其释放。

声明为local的变量仅在当前作用域下存在,不会被保存在_G表中

模块化加载module

每个模块可以是一个table结构,包含成员变量和成员方法

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
 
-- 定义一个常量
module.constant = "这是一个常量"
 
-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end
 
local function func2()
    print("这是一个私有函数!")
end
 
function module.func3()
    func2()
end
 
return module

-- 使用require进行加载模块
local module = require("module")
module.func1()

加载使用.so 动态链接库

local path = "xxxxx"
local f, error = loadlib(path, "function_name")
f()

Lua面向对象

I/O 输入输出

简单模型

当前输入和当前输出流

io.write 相比较 print为完全可控制的输出,不会在最终的输出结果中添加诸如制表符或换行符这样的额外内容。此外,函数 io.write 允许对输出进行重定向,而函数 print 只能使用标准输出 。

print()可以自动为参数调用tostring

io.write在将数值转换为字符串时遵循一般的转换规则。可以使用string.format

io.input(filename) --以只读模式打开指定文件,并将文件设置为当前输入流。

io.write(a, b, c)
io.write(string.format("sin(3) = %.4f\n", math.sin(3))) --> sin(3) = 0.1411

io.read() 函数 io.read 可以从当前输入流中读取字符串,其参数决定了要读取的数据: image.png

-- 对文件进行排序
local lines = {}
for line in io.lines() do
    lines[#lines + 1] = line
end
table.sort(lines)

-- write all the lines

for _, l in ipairs(lines) do

io.write(l, "\n")
end

io.read(0)是一个特例,它常用于测试是否到达了文件末尾 。 如果仍然有数据可供读取,它会返回一个空字符串;否则,则返回 nil。

完全模式

io.open(),第一个参数是要打开文件的文件名,第二个参数为io mode。

r只读,w只写,a追加,b代表二进制

local f = assert(io.open(filename, "r"))
local t = f:read("a")
f:close()

Error Handling异常处理

  • 使用error()错误处理: 没有返回值地抛出异常方法
  • 使用assert()进行错误处理
  • pcall()return 一个状态码,pcall是一种安全的调用方式,当发生Exception的时候就会中断程序执行pcall指定的handler

使用error抛出异常,使用pcall捕获异常

Stack 虚拟机和栈 及相关数据结构

相关内容可参考第2、3章 github.com/lichuang/Lu…

Lua使用基于寄存器的虚拟机,虚拟机存储OPCODE,解释器将字节转换成虚拟机指令,存储到intructions,虚拟机读取指令生成机器码交由CPU执行。

Lua通过虚拟机栈和C进行参数传递,当Lua CallC function的时候会创建一个独立虚拟机栈保存所有函数调用所需要的数据。 虚拟机中的Stack作为一个中间中转,C Call Lua可以让Lua从栈中读取数据。相反,Lua也可以封装为数据结构放入栈中供C读取。来实现双向通信。

流程关系: LuaState -> 解析lua脚本.lua -> proto(function)-> Closure -> 推入Stack -> 生成CallInfo函数调用的结构体 -> LClourse指针 -> 执行特定Lua OPCODE

image.png

Closure : lua运行时的函数实例对象 proto :lua内部代表一个cloure原型的对象,有关函数部分信息都保存在这里

image.png

image.png

closure是运行期的对象,与运行期关系更大;而与编译期相关的其实是proto对象,他才是编译过程真正需要生成的目标对象。

调用lua_load api,将一个chunk进行编译。lua_load根据当前chunk生成一个mainfunc proto,然后为这个proto创建一个closure放到当前的栈顶,等待接下来的执行。Chunk内部的每个function statement也都会生成一个对应的proto,保存在外层函数的子函数列表中。所有最外层的function statement的proto会被保存到mainfunc proto的子函数列表中。所以,整个编译过程会生成一个以mainfunc为根节点的proto树

CallInfo

image.png

LuaInit

image.png

Garbage Collection垃圾回收

stringtablefunctionuserdatathread 五种数据类型可以被垃圾回收。

image.png

Incremental Garbage Collection 增量垃圾回收

Generational 分代回收