• Lua基础


    table

    基本原理:
    table是一种特殊的容器,可以向数组一样按照索引存取,也能按照键值对存取。

    local mytable = {1,2,3} --相当于数组
    local mytable = {[1]=1,[2]=2,[3]=3} --和上面等价
    local mytable = {1,2,3,[3] = 4} --隐式赋值会覆盖掉显式赋值
    local mytable = {1,2,5,a=9,b="bo"} --支持索引存储,也支持键值对存储
    print(mytable[4])--输出为nil,键值对存储不能用索引访问
    print(mytable.a) --正确访问方式
    mytable.id = "1001" --不用事先定义,使用的时候直接赋值,类似python
    --我们收数据的时候只用收table一种数据类型就可以了,约定好里面有什么数据后直接取即可
    mytable.userdata = {atk = 15, def = 20} --table可以存放很多数据类型,并且可以嵌套
    function mytable:test(p)
      print(p)
      print(mytable[1])
    end
    
    for i,v in  pairs(mytable) do  --遍历方式,输出的是索引和内容,或者键值对,忽略nil项
      print(i)
      print(v)
    end
    for i,v in ipairs(mytable) do  --遍历方式,输出的是索引和内容,并且不能遍历键值对,并且遍历索引时遇到nil会停止输出
      print(i)
      print(v)
    end
    if next(mytable) == nil then --next第二个参数是索引位,判断索引位的下一位,如果为空就判断第一位
      print("为空")
    end
    
    print(#mytable)  --判断table长度,但注意table里面不能有nil,否则长度计算会在遇到nil的地方停止
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    底层实现:
    table:底层实现分数组部分和哈希表部分。数组部分,从 1 开始做整数数字索引,这可以提供紧凑且高效的随机访问;数组部分存储在 TValue *array 中,其长度 信息存储在 int sizearray 中。哈希表存储在 Node *node,哈希表的大小用 lu_byte lsizenode 表示,lsizenode 表示的是 2 的几次幂,而不是实际大小,因 为哈希表的大小一定是 2 的整数次幂。哈希冲突后,采取开放地址法,应对 hash碰撞。每个 Table 结构,最多会由三块连续内存构成:
    (1) 一个 table 结构
    (2) 一块存放了连续整数索引的数组
    (3) 一块大小为 2 的整数次幂的哈希表

    string底层

    Lua 中的字符串(string)底层实现使用了一种叫作"短字符串优化"(Short String Optimization)的策略。该策略基于以下两种情况来存储字符串:

    短字符串:长度小于等于 40 字节的字符串会直接存储在 Lua 内部的字符串对象中,而不需要额外的内存分配。这种情况下,字符串的数据会被直接存储在字符串对象的数据字段中。

    长字符串:长度超过 40 字节的字符串则会分配额外的内存空间来存储字符串数据。Lua 使用一个单独的内存块来存储长字符串的数据,并在字符串对象中存储指向该内存块的指针。

    无论是短字符串还是长字符串,Lua 的字符串都是不可变的(immutable)。这意味着一旦创建了一个字符串,就不能再修改它的内容。如果需要对字符串进行修改,必须创建一个新的字符串。

    Lua 的字符串实现具有一些优点和注意事项:

    • 节约内存:短字符串直接存储在字符串对象中,无需额外的内存分配,节约了内存空间。长字符串使用单独的内存块存储,可以避免字符串占用过多的 Lua 内存。

    • 高效性能:由于字符串不可变,可以在多个 Lua 值之间共享相同的字符串对象,避免了重复的字符串复制操作,提高了性能。

    • 注意拼接操作:由于字符串不可变,每次对字符串进行拼接操作都会创建一个新的字符串对象。如果需要频繁进行字符串拼接操作,可能会导致大量的内存分配和对象创建,影响性能。在这种情况下,可以使用 Lua 中的字符串缓冲区(如 table.concat 函数)或者考虑使用 LuaJIT 提供的 FFI 接口来进行高效的字符串拼接。

    元表

    local mytable = {1,2,3}
    local mymetatable = {
      __index = {b=6,c="c"}  
    }
    mytable = setmetatable(mytable, mymetatable)  --元表扩展了普通表的功能
    getmetatable(mytable)  --获取元表
    print(mytable.c)  --table里面没有的话会查找元表中的index来调用
    
    
    local mymetatable = {
      __index = function(table,key)   --访问table中没有的元素调用元表中的index
                  if key == "b" then
                    return "hello"
                  else
                    return nil
                  end
                  end  --如果index是一个方法
      __newindex = function(table,key)    --给table和元表中没有的数组元素赋值或给一个变量赋值会执行newindex
                      print("被调用")
                      return "NoValue"
                      end
      __call = function(mytable, newtable)  --把元表当做方法(函数)使用的时候调用call
                  sum = 0
                  for i = 1, #mytable do 
                    sum = sum + mytable[i]
                    end
                  for i = 1, #newtable do 
                    sum = sum + newtable[i]
                    end
                  return sum
                end
      __tostring = function(mytable)     --想要直接输出这个表的时候调用
                    return #mytable
                    end
      __add = function(mytable, newtable)  --两个表进行相加操作的时候调用
          for i = 1, table.maxn(newtable) do
             table.insert(mytable, table.maxn(mytable)+1,newtable[i])
          end
          return mytable
       end
    
    
    
      
    }
    mytable = setmetatable({1,2,3}, mymetatable)
    function mytable:test(p)
      print(p)
    end
    print(mytable.b)  --会调用index里面的方法输出hello
    mytable.c = 10    --设置没有定义的变量会触发__newindex,一般里面做一些报错提醒
    mytable[1] = 6    --已经存在的数组元素,不会触发__newindex
    mytable[5] = 666  --不存在的数组元素,会触发__newindex
    newtable = {10,20,30}
    print(mytable(newtable))  --调用__call
    print(mytable)       --调用__tostring
    mytable = mytable + newtable
    print(mytable)
    
    ---查找和赋值不调用index和newindex函数的方法
    print(rawget(mytable ,mytable.w))  --输出的是nil
    rawset(mytable,"w",1)   --设置不存在的变量不会调用newIndex
    
    
    
    mytable:test(5)           --这两种调用方式等价,冒号是一个语法糖
    mytable.test(mytable,5)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    模块

    一个文件想要用另外一个文件的变量,就要用到require。但是如果我们不想每次使用都require,我们可以把lua文件定义成module,这样只需要require一次加载进来,就能在所有文件中访问这个文件的变量。
    普通用法

    require("math_func")
    local ret = math_abs(-7)
    print(ret)
    
    
    • 1
    • 2
    • 3
    • 4

    定义Module的方式:

    module("device",package.seeall)
    
    function get_device_name()
      return "guest9527:
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    local name = device.get_device.name()
    
    • 1

    接口

    如果我们不想把一个lua文件中的所有变量都暴露出去,我们可以在文件中定义一个table作为接口,把所有变量设为local,把想要暴露的添加到table中,然后返回即可。

    function math_abs(value)
      if(value < 0) then
        return -value
      end
      return value
      
    end
    function math_vec_length_2(vec)
      return vec.x * vec.x + vec.y - vec.y
    end
    function _test_func(...)
      print("test")
    end
    local list = {
      abs = math_abs,
      lenth = math_vec_length_2,
      test_func = _test_func,
    }
    
    return list
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    lua的数据类型

    table
    function
    nil
    boolean
    number:包括整数,浮点数等
    string

    userdata:userdata类型主要用来表示在C/C++中定义的类型,即用来实现扩展lua,这些扩展代码通常是用C/C++来实现的。对lua 虚拟机来说userdata只是提供了一块原始的内存区域,可以用来存储任何东西,并且在lua中userdata没有任何预定义的操作。注意这块分配的额外内存是由Lua垃圾收集器来管理的,无须关心起释放等情况。
    thread:线程
    注意:数据类型不是固定了,一个数据赋值了整型之后,也可以赋值字符串。
    注意:函数也是变量的一种,可以随意赋值给变量
    注意:字符串之间的拼接用…

    local a = 5
    print(type(a))  --打印类型
    foo2 =function foo(x)  --函数的赋值
          print(x)
          end
    foo2(x)
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    点和冒号的区别

    冒号隐式地传递了一个调用这个函数的表的示例进去,可以省略一个参数。

    local a = {}
    --下面两个函数等同
    function a.test(self,a,b)  --这里self必须传进去本身才行
      print("atest",self)
    end
    function a:test(a,b)
      print("atest",self)  --冒号包含了self机制
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    两者不能混着用,如果定义的是冒号,用的时候是点,就会导致找不到self而报错。

    闭包

    语法域:函数可以嵌套在另一个函数中,内部函数可访问外部函数的局部变量,这样可以用来实现面向对象的编程
    闭包:lua编译一个函数的时候,会生成一个原型prototype,包含了函数体对应的虚拟机指令,函数体中的常量,调试信息。运行的到函数的时候会创建一个新的数据对象,对象中包含了响应函数原型的引用和一个数组,包含了所有upvalue的引用(传进来的值),这个数据对象称为闭包。
    注意:lua支持函数有多返回值

    function f1(n)
      local fuction f2()
        print(n)
      end
      return f2
    end
     
    g1 = f1(2021)
    g1()
    g2 = f1(2022)
    g2()
    
    function create(n)
      local function foo()
        local function foo1()
          print(n)
        end
        local function foo2()
          n = n + 10
        end
        return foo1,foo2
      end
      return foo
    end
    f0 = create(2021)
    f1,f2 = f0()
    g1,g2 = f0()
    f1()
    f2()
    g1()
    g2()
    f1()
    
    --闭包可以作为高阶函数的参数
    table.sort(t,function(t1,t2) return t1.param > t2.param end)
    --重写(类似面向对象),可以用来添加验证
    local oldOpen = io.open
    local accessOk = function(filename,mode)
                        --方法体
                        end
    io.open = function(filename,mode)
                  if accessOk(filename,mode) then
                    return oldOpen(file,mode)
                  else
                    return nil,"校验失败"
                  end
              end
    
    --实现迭代器
    function values(t)
      local i = 0
      return function() i=i+1 return t[i] end
      end
    t = {1,2,3,4,5,6}
    iter = values(t)
    print(iter())
    print(iter())
    print(iter())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    C#和lua的相互调用

    c语言可以和lua直接通信,lua就是c语言开发的,C#要和lua通信,需要先调用C语言,C语言再调用lua,Xlua插件封装了C#调用C语言的接口。
    C 调用 Lua 实际上是:由 C 先把数据放入栈中,由 Lua 去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回 C。
    Lua 调 C 也一样:先编写自己的 C 模块,然后注册函数到 Lua 解释器中,然后由Lua 去调用这个模块的函数。
    Lua虚拟栈:从底往上是1到n,从顶往下时-1到-n,lua里面用到的数据类型都可以入栈

    Lua实现只读表

    local readOnly
    readOnly = function(t)
      for k,v in pairs(t) do
        if type(v) == "table" then
          t[k] = readOnly(v)
        end
      end
      local nt = {}
      local mt = {
        __index = t,
        __newindex = function(t,key,value)
          error("this is a readonly table")
          end
      }
      setmetatable(nt,mt)
      return nt
    end
    
    local a = {x=1,y=2}
    a = readOnly(a)
    a.x = 3 --错误
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    当我们对readOnly的table进行赋值会调用newIndex函数,从而抛出错误

    Lua GC

    从Lua 5.1开始,采用三色增量标记清除算法。好处:它不必再要求GC一次性扫描所有的对象,这个GC过程可以是增量的,可以被中断再恢复并继续进行的。3种颜色分类如下:

    白色:当前对象为待访问状态,表示对象还没有被GC标记过,这也是任何一个对象创建后的初始状态。换言之,如果一个对象在结束GC扫描过程后仍然是白色,则说明该对象没有被系统中的任何一个对象所引用,可以回收其空间了。

    灰色:当前对象为待扫描状态,表示对象已经被GC访问过,但是该对象引用的其他对象还没有被访问到。

    黑色:当前对象为已扫描状态,表示对象已经被GC访问过,并且该对象引用的其他对象也被访问过了。当GC完后被重置为白色。
    伪代码:

    每个新创建的对象颜色为白色
     
    //初始化阶段
    遍历root节点中引用的对象,从白色置为灰色,并且放入到灰色节点列表中
     
    //标记阶段
    当灰色链表中还有未扫描的元素:
        从中取出一个对象并将其标记为黑色
        遍历这个对象关联的其他所有对象:
            如果是白色:
            标记为灰色,加入灰色链表中
     
    //回收阶段
    遍历所有对象:
        如果为白色:
            这些对象都是没有被引用的对象,逐个回收
        否则:
            重新加入对象链表中等待下一轮的GC检查
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    【JVM基础】方法区
    javaweb基于ssm的仓库管理系统
    C#委托传值
    Termius 8 for Mac(多协议服务器连接软件)
    用python把所有出现snprintf的c文件添加_snprintf
    零零信安-D&D数据泄露报警日报【第36期】
    使用深度图像实现照片虚化效果
    STM32F103C8T6基于Arduino框架下利用定时器跑RBG灯闪烁
    【测试开发】软件测试——基础篇
    13.罗马数字转整数
  • 原文地址:https://blog.csdn.net/tianjuewudi/article/details/134188774