当前位置:网站首页>Lua 编程学习笔记

Lua 编程学习笔记

2022-07-07 05:14:00 tough is tough

Lua 编程学习笔记

Lua编程语言给人的感觉是小巧,简洁,解释性语言,弱类型,主要用于游戏开发,嵌入式脚本开发。

此次学习源于写饥荒脚本,用饥荒学习Lua绝对是不错的一个实战。

一、环境安装

首先在官网下载

Lua下载是一个压缩文件,解压即可用,但是需要先编译一下。
LuaContent

默认是没有可执行程序的,需要使用命令制作一下。

在当前src目录下打开终端窗口,执行命令

make

make之后生成lualuac两个可执行程序
makeFile
这样我们就可以用Lua这个可执行程序执行我们编写的.lua文件了。

我使用的是IDE是Idea,Idea需要编写Lua需要下载一个插件:EmmyLua
EmmyLua
使用这个插件可以高亮显示还有方法提示,可以Tab键自动补全。

安装后运行Lua文件的需要配置文件的解释程序。
ideaConfig
这样整体的开发环境就搭配好了。


二、Lua 基本语法

1. 注释

单行注释

-- Lua 的单行注释使用 `--`
print("Hello Lua")

多行注释

--[[ 多行注释1 多行注释2 多行注释3 ]]
print("Hello Lua")

2. 标识符

和其他编程语言一样,Lua的标识符由字母、数字以及下划线组成,且不能以数字开头,不能是关键字
比如:__index

3. 变量

Lua作为弱类型语言,变量的定义不需要声明数据类型。

-- 整数变量 a
a = 1

-- 字符串变量 a
a = "Test"

-- 布尔类型 a
a = false

变量的作用域默认是全局,只有声明为local的变量才是局部变量

弱类型语言的变量使用很随意,如果变量的数据类型使用错误,只有在运行时才能发现,体现的是规范大于约定的思想。

4. 数据类型

虽然变量的定义不需要声明数据类型,但是Lua大体上还是分为8种数据类型

  • nil
  • number
  • string
  • boolean
  • function
  • table
  • userdata
  • thread

nil代表空值,被赋值为nil的变量将清空,userdata和thread目前还没接触,其他类型只有nil和table的概念比较特殊。

注:Lua中的布尔值只有nil和false为条件表达式的否判断。

-- number类型也是真值
a = 1
if a then
    print("a是ture")
end 

-- 函数类型也是真值
function fn() 
    print("fn函数")
end
if fn then
    print("fn函数只要不为nil也是真值")
end 

5. Lua 运算符

1)算术运算符

  • + 加法
  • - 减法
  • * 乘法
  • / 除法(真实运算,不会取整数,Java里边10/3=3,这里10/3=3.3333333333333)
  • % 求余
  • ^ 幂运算( 2^3=8.0)
  • - 负号,对数字前面直接加取负数
  • // 整数运算,这个才是同Java的除法运算,10/3=3

2)逻辑运算符

  • and 逻辑与运算
  • or 逻辑或运算
  • not 逻辑非运算

3)关系运算符

  • >
  • <
  • ==
  • >=
  • <=
  • ~=

注意Lua的不等于写法~= 这个还是挺新鲜的写法。

4)其他运算符

Lua 没有类似++, --的操作,循环时候一定要注意!

  • .. 拼接字符串,同Java的 + 拼接字符串
  • # 返回字符串或者表的长度
print(#"Hello")     -- 输出 5

tab = {
    1,2,3}
print(tab)          -- 输出3

三、循环与流程控制

Lua循环和其他语言类似,只是需要注意基本的格式。

1. 循环

三种循环方式

  • while
  • for
  • repeat … until
-- 方式一 while 循环
a = 0
while a < 10 do
    print("while循环"..a)
    a = a + 1
end

-- 方式二 for 循环
for i = 1, 10 do
    print("for循环"..i)
end

-- 方式三 repeat 循环
c = 0
repeat
    print("repeat循环"..c)
    c = c + 1
until c > 10

repeat … until 循环好比是Java中的 do…while循环,至少执行一次。

2. 流程控制

-- if 结构
if a>b then
    return a
end

-- if elseif else 结构
if a > 90 then
    print("优秀")
elseif a > 80 then
    print("良好")
elseif a > 60 then
    print("及格")
else
    print("不及格")
end

循环和流程控制都没有特殊的,需要注意的是基本格式,Lua里边无论函数还是循环控制,都是以end结尾

四、函数

Lua函数比较特殊,第一次遇到可以返回多个值。

1. 基本定义

-- 方式一
function f1()
    -- do something
end

-- 方式二
local f2 = function(arg1,arg2,arg3) 
    -- do something
    return arg1,arg2,arg3
end

Lua的函数修饰符只有local定义是否为局部函数,参数可选。主要是可以类似函数f2,可以返回多个数据。

接收函数返回值的时候也可以多个变量一一对应接收。

function f3()
    return 1,2,3
end

a,b,c = f3()
print(a,b,c)    -- 输出 1,2,3

2. 可变参数

函数接收可变参数有个内置变量arg这个需要主要,他是一个表table

-- `...` 代表可变参数
function f4(...)
    local arg = {
    ...}

    -- 遍历方式一
    for k, v in pairs(arg) do
        print(k,v)
    end

    -- 遍历方式二
    for k, v in ipairs{
    ...} do
        print(k,v)
    end

    print(#arg)                         -- 定义 local = {...} 可通过 #arg 获取参数个数
    print(select("#",...))       -- select("#",...) 获取可变参数个数
end

f4(1,2,3,4)                             -- 最后打印 4

-- 函数作为参数传递

function f5(func)
    local ret = func
    
end

注:可变参数的获取方式和数据获取方式是重点

另外,ipairs和pairs的主要区别是ipairs遇到nil则会停下来,而pairs遍历不会。

for k,v in pairs(table) do … end 这是一种基本固定的遍历表的写法,类似Java中的增强for循环。

3. 函数作为参数传递

函数作为参数传递给下个函数时,函数内可以调用这个函数做一些数据处理

function max(a,b)
    if a > b then
        return a
    else
        return b
    end
end

function printMax(a,b,maxFunc)
    print(maxFunc(a,b))
end

printMax(2,3,max)

五、表与数组

表是Lua中最常用的数据结构了,基本上数据的使用都是通过表,但我理解更像是一个Map容器,存储一些key,value的键值对。

1. 基本定义

-- 数组没有单独的表示方式,数组就是用表表示的,只不过没有key,默认key就是1,2,3...连续索引
-- 一对花括号就代表这是一个表,前面的数据类型有一个就是table表数据类型
tab = {
    }

-- 也可以初始化(数组)
fruits = {
    "apple","peach","banana"}

-- 这个更应该被认为是表,以指定的key-value形式存在
table_define = {
    
    key1 = "val",
    key2 = "val2",
    key3 = "val3"
}

-- 或者给表增加数据
tab["key"] = "value"

-- 赋值 nil 就清空了一个表
tab = nil

-- 表的引用赋值给另一个
newFruits = fruits
fruits = nil            -- newFruits 指向的表实际还在,这里只是清空了fruits的引用而已

2. 表操作的常用方法

注:Lua中的索引都是从1开始的,而不是0!

2.1 插入数据

tab = {
    }
-- 默认插入表的末尾
table.insert(tab,"value1")
print(tab[1])

-- 插入指定索引位置
table.insert(tab,1,"value2")

print(tab[1])

2.2 删除数据

fruits = {
    "apple", "peach", "banana", "orange"}

fruits[1] = nil
print(fruits[1])        -- nil

table.remove(fruits,2)
print(fruits[2])        -- banana

-- 默认移除表最后的数据
table.remove(fruits)
for k,v in pairs(fruits) do
    print(v)            -- banana
end

2.3 拼接表数据

tab = {
    "Lua", "C#", "Java"}

-- 拼接数据元素
print(table.concat(tab))                   -- LuaC#Java

-- 指定拼接的分隔符
print(table.concat(tab," "))               -- Lua C# Java

-- 指定分隔符和指定索引范围内数据拼接
print(table.concat(tab," ",2,3))           -- C# Java

2.4 排序

tab = {
    "Lua", "C#", "Java"}

-- 调用table的排序方法
table.sort(tab)

六、模块与包

这里才是最接近编程开发的概念了,将代码进行模块化管理,但是Lua并没有真正的包Module的概念,包也是通过表来实现的。

1. 模块定义

Module = {
    }

Module.name = "模块包"

Module.func = function() 
    print("文件module.lua中的函数func")
end

-- 可以通过local封装,只能通过方法调用
local address = "SZ"
Module.getAddress = function()
    return address
end

local func_local = function()
    print("local函数")
end

Module.getLocalFunc = function()
    func_local()
end

2. 包的引入

包的引入使用关键字require

-- 两种写法,之类的module,是文件名,module.lua这个文件就被引入进来了
require "module"

require("module")

-- 引入包之后就可以访问包的内容了
print(Module.name)
Module.func()

print(Module.getAddress())
print(Module.getLocalFunc())

七、元表

元表理解更像是定义那样,更微小的单元,他允许我们去定义一个表之外的行为,除了赠删改之外的动作。比如两个表进行相加。

1. 基本定义

myTable = {
    }    -- 普通表
myMetaTable = {
    }    -- 元表
-- 会返回普通表
myTable = setmetatable(myTable,myMetaTable) -- 将myMetaTable设置为myTable的元表

getmetatable(myTable)   -- 返回 myMetaTable 元表 

2. 元方法

2.1 __index 元方法

当查找普通表中的key对应值为没有时,则去调用此方法寻找对应的表中的数据。

理解更像是对普通表的一种扩展。

other = {
    key = 3}
t = setmetatable({
    },{
    __index = other})  -- 普通表是空的,元表存在元素 key = 3

print(t.key)    -- 3

如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。

mytable = setmetatable({
     key1 = "value1" } , {
     
    __index = function(mytable,key)
        if  key == "key2" then
            return "metatablevalue"
        else
            return nil
        end
        
    end
})
print(mytable.key1,mytable.key2)
总结

来源菜鸟教程

Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

  1. 在表中查找,如果找到,返回该元素,找不到则继续
  2. 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
  3. 判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;
    1. 如果 __index 方法是一个表,则重复 1、2、3;
    2. 如果 __index 方法是一个函数,则返回该函数的返回值。

2.2 __newIndex 元方法

__newIndex针对不存在的key进行赋值时候调用

mytable = setmetatable({
    key1 = "value1"}, {
    
    __newindex = function(mytable, key, value)
        rawset(mytable, key, "\""..value.."\"")
    end
})

mytable.key1 = "new value"
mytable.key2 = 4

print(mytable.key1,mytable.key2)

2.3 __tostring 元方法

__tostring使得print(table)时候可以输出字符串而不是表的引用

mytable = setmetatable({
     10, 20, 30 }, {
    
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
                sum = sum + v
        end
    return "表所有元素的和为 " .. sum
  end
})
print(mytable)

八、协同程序

协同程序实际上就是协同函数,让函数可以再执行到一半的时候挂起,然后再继续执行下面的代码,知道执行启动该协同函数才会继续执行协同函数肚饿代码。

1. 常用方法:

  • coroutine.create(func) 创建一个协同函数,参数为函数
  • coroutine.resume() 启动创建的协同函数,搭配coroutine.create()
  • coroutine.yield() 暂停协同函数,参数可以作为函数的返回值,同return效果
  • coroutine.status() 查看协同函数的状态,存在三种:suspended,dead,running分别为暂停,死亡,运行
  • coroutine.wrap() 同create+resume,创建并启动,停止后好像没法继续启动
  • coroutine.running() 返回正在跑的coroutine,一个正在跑的coroutine就是一个线程

2. 经典生产消费问题

local co

-- 生产者每生产一个就提供给消费者
function productor()
    local i = 0
    while true do
        i = i + 1
        print("生产者生产\t"..i)
        sleep(1)
        coroutine.yield(i)
    end
    print("无产品生产,生产者结束")
end

function consumer()
    while coroutine.status(co) ~= "dead" do
        print("消费者唤醒生产")
        local status, value = coroutine.resume(co)
        print("消费者消费\t"..value)
        sleep(1)
    end
    print("消费者得知生产结束,消费结束")
end

function sleep(n)
    local t = os.clock()
    while os.clock() -t <=n do end
end

-- 创建生产者协同函数
co = coroutine.create(productor)
-- 开始消费
consumer()

注:Lua语言没有原生的线程睡眠函数,这里定义的也不是很懂,大概理解是os.clock返回程序运行时长,用时间差判断得到睡眠时间。

九、文件IO

文件IO分为简单模式和完全模式,文件打开针对权限分为:

  • r 只读,文件必须存在
  • w 只写,以覆盖的方式写入
  • a 只写,以附加的方式写入
  • r+ 可读写,文件必须存在
  • w+ 可读写,文件存在则覆盖,文件不存在则创建
  • a+ 可读写,以附加的方式写入
  • b 二进制文件,如果打开的是二进制文件需要加上 b
  • 表示可读可写

1. 简单模式

-- 以只读的方式打开一个文件
file = io.open("tmp.txt","r")

-- 设置io的输入文件是file
io.input(file)

-- 读取一行
line = io.read()

-- 关闭文件也要指定文件
io.close(file)

2. 完全模式

-- 以只读的方式打开tmp.txt文件
file = io.open("tmp.txt", "r")

-- 直接读取一行,不用io.input(file)指定输入文件了
line = file:read()

-- 关闭文件
file:close()

上面的read方法都是默认读取一行数据,可以指定read()函数的参数,代表不同的读取方法

  • *n 读取一个数字并返回,必须是数字
  • *a 读取整个文件内容
  • *| 读取一行(默认方式)测试时候好像一直报错,未知原因
  • number 举例:file.read(4) 指定读取4个字符

十、面向对象

Lua的面向对象的使用应该才是最终极的形态了。

面向对象逃不开三个特征,封装、继承、多态。

  • 封装 有效封装属性,合理对外暴露。
  • 继承 对父类属性的扩展,比如存在人这个模型,可以将学生对象继承自父类对象人,使其具备人有的一些属性方法。
  • 多态 一种类型的多种形态,按照Java的说法就是父类的引用指向子类的对象,比如数据都是动物,但是可以不同的形态如,猫,狗都属于动物

对象

-- Lua 里边都是表数据,没有对象的概念,所以所谓类,对象都是模拟出来的一种形态

-- 定义一个Person类
Person = {
    
   name = nil,
   age = nil
}

-- 定义方法
Person.eat = function(self)
   print(self.name.."在吃东西")
end

-- 调用
Person.name = "周杰伦"
Person.age = 42
Person.eat(Person)

-- 如果还有一个Person对象,则需要再次重复定义上面的Person,所以可以模拟一个类的构造方法出来
function Person:new(o)
   local t = o or {
    }
   setmetatable(t, {
    __index = self})
   return t
end

-- eat 方法改进,后面编程都是以这种形式出现的居多
function Person:eat()
   print(self.name.."在吃东西")
end

p1 = Person:new()
p1.name = "黄圣依"
print(p1:eat())

十一、饥荒源码Lua学习

饥荒里边的源码都是Lua文件,采用ECS(Entity, Components, System)框架,即实体、组建、系统。

这种框架和面向对象不同,他更多的是面向数据;
实体理解就是对象,一个一个的实物,比如游戏里边的花、猪人,兔子等,而组件则是行为,比如花存在可采集的属性,所以Pickable.lua文件存在组件中代码。

而系统则是控制整个游戏的机制玩法。系统不关心组件存在什么行为,组件不关系实体具体是什么。

饥荒中类的定义
class.lua文件–基本上看完感觉上面的Lua都白学了,吐了。

-- class.lua
-- Compatible with Lua 5.1 (not 5.0).

local TrackClassInstances = false

ClassRegistry = {
    }

if TrackClassInstances == true then
   global("ClassTrackingTable")
   global("ClassTrackingInterval")

   ClassTrackingInterval = 100
end

local function __index(t, k)
   local p = rawget(t, "_")[k]
   if p ~= nil then
      return p[1]
   end
   return getmetatable(t)[k]
end

local function __newindex(t, k, v)
   local p = rawget(t, "_")[k]
   if p == nil then
      rawset(t, k, v)
   else
      local old = p[1]
      p[1] = v
      p[2](t, v, old)
   end
end

local function __dummy()
end

local function onreadonly(t, v, old)
   assert(v == old, "Cannot change read only property")
end

function makereadonly(t, k)
   local _ = rawget(t, "_")
   assert(_ ~= nil, "Class does not support read only properties")
   local p = _[k]
   if p == nil then
      _[k] = {
     t[k], onreadonly }
      rawset(t, k, nil)
   else
      p[2] = onreadonly
   end
end

function addsetter(t, k, fn)
   local _ = rawget(t, "_")
   assert(_ ~= nil, "Class does not support property setters")
   local p = _[k]
   if p == nil then
      _[k] = {
     t[k], fn }
      rawset(t, k, nil)
   else
      p[2] = fn
   end
end

function removesetter(t, k)
   local _ = rawget(t, "_")
   if _ ~= nil and _[k] ~= nil then
      rawset(t, k, _[k][1])
      _[k] = nil
   end
end

function Class(base, _ctor, props)
   local c = {
    }    -- a new class instance
   local c_inherited = {
    }
   if not _ctor and type(base) == 'function' then
      _ctor = base
      base = nil
   elseif type(base) == 'table' then
      -- our new class is a shallow copy of the base class!
      -- while at it also store our inherited members so we can get rid of them
      -- while monkey patching for the hot reload
      -- if our class redefined a function peronally the function pointed to by our member is not the in in our inherited
      -- table
      for i,v in pairs(base) do
         c[i] = v
         c_inherited[i] = v
      end
      c._base = base
   end

   -- the class will be the metatable for all its objects,
   -- and they will look up their methods in it.
   if props ~= nil then
      c.__index = __index
      c.__newindex = __newindex
   else
      c.__index = c
   end

   -- expose a constructor which can be called by <classname>(<args>)
   local mt = {
    }

   if TrackClassInstances == true and CWD~=nil then
      if ClassTrackingTable == nil then
         ClassTrackingTable = {
    }
      end
      ClassTrackingTable[mt] = {
    }
      local dataroot = "@"..CWD.."\\"
      local tablemt = {
    }
      setmetatable(ClassTrackingTable[mt], tablemt)
      tablemt.__mode = "k"         -- now the instancetracker has weak keys

      local source = "**unknown**"
      if _ctor then
         -- what is the file this ctor was created in?

         local info = debug.getinfo(_ctor, "S")
         -- strip the drive letter
         -- convert / to \\
         source = info.source
         source = string.gsub(source, "/", "\\")
         source = string.gsub(source, dataroot, "")
         local path = source

         local file = io.open(path, "r")
         if file ~= nil then
            local count = 1
            for i in file:lines() do
               if count == info.linedefined then
                  source = i
                  -- okay, this line is a class definition
                  -- so it's [local] name = Class etc
                  -- take everything before the =
                  local equalsPos = string.find(source,"=")
                  if equalsPos then
                     source = string.sub(source,1,equalsPos-1)
                  end
                  -- remove trailing and leading whitespace
                  source = source:gsub("^%s*(.-)%s*$", "%1")
                  -- do we start with local? if so, strip it
                  if string.find(source,"local ") ~= nil then
                     source = string.sub(source,7)
                  end
                  -- trim again, because there may be multiple spaces
                  source = source:gsub("^%s*(.-)%s*$", "%1")
                  break
               end
               count = count + 1
            end
            file:close()
         end
      end

      mt.__call = function(class_tbl, ...)
         local obj = {
    }
         if props ~= nil then
            obj._ = {
     _ = {
     nil, __dummy } }
            for k, v in pairs(props) do
               obj._[k] = {
     nil, v }
            end
         end
         setmetatable(obj, c)
         ClassTrackingTable[mt][obj] = source
         if c._ctor then
            c._ctor(obj, ...)
         end
         return obj
      end
   else
      mt.__call = function(class_tbl, ...)
         local obj = {
    }
         if props ~= nil then
            obj._ = {
     _ = {
     nil, __dummy } }
            for k, v in pairs(props) do
               obj._[k] = {
     nil, v }
            end
         end
         setmetatable(obj, c)
         if c._ctor then
            c._ctor(obj, ...)
         end
         return obj
      end
   end

   c._ctor = _ctor
   c.is_a = function(self, klass)
      local m = getmetatable(self)
      while m do
         if m == klass then return true end
         m = m._base
      end
      return false
   end
   setmetatable(c, mt)
   ClassRegistry[c] = c_inherited
   -- local count = 0
   -- for i,v in pairs(ClassRegistry) do
   -- count = count + 1
   -- end
   -- if string.split then
   -- print("ClassRegistry size : "..tostring(count))
   -- end
   return c
end

function ReloadedClass(mt)
   ClassRegistry[mt] = nil
end

local lastClassTrackingDumpTick = 0

function HandleClassInstanceTracking()
   if TrackClassInstances and CWD~=nil then
      lastClassTrackingDumpTick = lastClassTrackingDumpTick + 1

      if lastClassTrackingDumpTick >= ClassTrackingInterval then
         collectgarbage()
         print("------------------------------------------------------------------------------------------------------------")
         lastClassTrackingDumpTick = 0
         if ClassTrackingTable then
            local sorted = {
    }
            local index = 1
            for i,v in pairs(ClassTrackingTable) do
               local count = 0
               local first = nil
               for j,k in pairs(v) do
                  if count == 1 then
                     first = k
                  end
                  count = count + 1
               end
               if count>1 then
                  sorted[#sorted+1] = {
    first, count-1}
               end
               index = index + 1
            end
            -- get the top 10
            table.sort(sorted, function(a,b) return a[2] > b[2] end )
            for i=1,10 do
               local entry = sorted[i]
               if entry then
                  print(tostring(i).." : "..tostring(sorted[i][1]).." - "..tostring(sorted[i][2]))
               end
            end
            print("------------------------------------------------------------------------------------------------------------")
         end
      end
   end
end
原网站

版权声明
本文为[tough is tough]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_45411898/article/details/125650475