当前位置:网站首页>Lua 编程学习笔记
Lua 编程学习笔记
2022-07-07 05:14:00 【tough is tough】
Lua 编程学习笔记
Lua编程语言给人的感觉是小巧,简洁,解释性语言,弱类型,主要用于游戏开发,嵌入式脚本开发。
此次学习源于写饥荒脚本,用饥荒学习Lua绝对是不错的一个实战。
一、环境安装
首先在官网下载
Lua下载是一个压缩文件,解压即可用,但是需要先编译一下。
默认是没有可执行程序的,需要使用命令制作一下。
在当前src
目录下打开终端窗口,执行命令
make
make
之后生成lua
和luac
两个可执行程序
这样我们就可以用Lua
这个可执行程序执行我们编写的.lua文件了。
我使用的是IDE是Idea,Idea需要编写Lua需要下载一个插件:EmmyLua
使用这个插件可以高亮显示还有方法提示,可以Tab键自动补全。
安装后运行Lua文件的需要配置文件的解释程序。
这样整体的开发环境就搭配好了。
二、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 个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
- 判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;
- 如果 __index 方法是一个表,则重复 1、2、3;
- 如果 __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
边栏推荐
- Niu Mei's mathematical problem --- combinatorial number
- LeetCode简单题之找到一个数字的 K 美丽值
- Introduction to basic components of wechat applet
- 海信电视开启开发者模式
- ROS Bridge 笔记(05)— carla_ackermann_control 功能包(将Ackermann messages 转化为 CarlaEgoVehicleControl 消息)
- 机器人教育在动手实践中的真理
- Call pytorch API to complete linear regression
- Vulnerability recurrence easy_ tornado
- CTF-WEB shrine模板注入nmap的基本使用
- 在 Rainbond 中一键安装高可用 Nacos 集群
猜你喜欢
2022 Inner Mongolia latest advanced fire facility operator simulation examination question bank and answers
QT learning 26 integrated example of layout management
JS cross browser parsing XML application
eBPF Cilium实战(2) - 底层网络可观测性
让Livelink初始Pose与动捕演员一致
Network learning (II) -- Introduction to socket
Leetcode simple question: find the K beauty value of a number
OpenVSCode云端IDE加入Rainbond一体化开发体系
【數字IC驗證快速入門】15、SystemVerilog學習之基本語法2(操作符、類型轉換、循環、Task/Function...內含實踐練習)
[quickstart to Digital IC Validation] 15. Basic syntax for SystemVerilog Learning 2 (operator, type conversion, loop, Task / Function... Including practical exercises)
随机推荐
Leetcode simple question: find the K beauty value of a number
[step on the pit series] H5 cross domain problem of uniapp
Complete linear regression manually based on pytoch framework
JS quick start (I)
Minimum absolute difference of binary search tree (use medium order traversal as an ordered array)
Qinglong panel -- finishing usable scripts
Vulnerability recurrence easy_ tornado
Blob 对象介绍
Uniapp mobile terminal forced update function
Use of JMeter
漏洞复现-Fastjson 反序列化
buureservewp(2)
Rainbond 5.7.1 支持对接多家公有云和集群异常报警
贝叶斯定律
Network learning (II) -- Introduction to socket
在 Rainbond 中一键安装高可用 Nacos 集群
Niu Mei's mathematical problem --- combinatorial number
[quick start of Digital IC Verification] 15. Basic syntax of SystemVerilog learning 2 (operators, type conversion, loops, task/function... Including practical exercises)
Empire CMS collection Empire template program general
[quick start of Digital IC Verification] 17. Basic grammar of SystemVerilog learning 4 (randomization)