Lua 基础
Lua
Lua 是一种由C编写的解释性脚本语言。
基础
- 语言的基础 变量、数据结构、流程控制、编程风格等。
- 语言的模型 模块化方式、内存管理。
变量
- lua 所有变量都是弱类型限制的。
- 被
local修饰则为局部变量,仅在当前块中有效,过后自动释放。 - 无
local修饰则为全局变量,直接进入_G_ENV等全局数组中。
- 类型
| 类型 | 示例 | 备注 |
|---|---|---|
| nil | x=nil |
nil 就是null |
| boolean | x=false |
nil,false=false,true,非nil=true |
| number | x=1 y=1.2 |
number自适应于整型、浮点型 |
| string | x='str' |
字符串类型,无字符类型 |
| function | x=function()end |
函数亦是基本的变量类型 |
| coroutine | 协程类型 | |
| userdata | C/C++ 数据类型 | |
| table | x={} |
lua 核心数据结构 |
- 基本方式
-------------------------------------
-- 两种方式定义函数,效果一样
local function def()
-- 栈式返回值
return true,'hello world'
end
local def = function()
return true,'hello world'
end
-- 栈式赋值
local a,b = def()
-- 二元赋值
local a = 0 or 1 -- 此时 a 为 0
local a = nil or 1 -- 此时 a 为 1
-- 三元赋值
local a = true and 1 or 0 -- a 为 1
local a = false and 1 or 0 -- a 为 0
-- 最常见的方法
a = tostring(123456)
a = tonumber("123456")
a = type(a)
- string
-- base定义
str = 'hello world'
-- 常用定义,内部可携带:单引号且不用转义
str = "hello'-'world"
-- 多行字符串
str = [[
hello world!
]]
-- 字符串长度
print(string.len("str"),#str)
-- 字符串连接
print('hello '..'world')
-- 大小写转化
string.upper(a)
string.lower(a)
-- char ==> string
string.char()
-- string ==> char
string.byte()
-- 截取字串
string.sub(str,[start],[end])
-- 查找字串位置
string.find(str,sub_str,[start],[end])
-- % 占位符格式化字符串,%d数字 %s 字符串 %% %
string.format("hello %s %d","word",123)
- array
-- 数组定义方式
local x = {1,2,3,'4'}
-- 数组类型:table,实际并非如此
print(type(x))
-- 数组、字符串的下标都从 1 开始
print(#x, #'hello', x[1])
-- 数组新增
table.insert(x,1,10)
-- 数组删除某项值
table.remove(x,1)
-- 连接大量字符串,消耗少,速度快
table.concat(x,"-")
- 引用
- 标准引用都是强引用模式,一般对象亦可分为引用对象,已经非引用对象。
| ~ | 传递 | 复制 |
|---|---|---|
| 基础类型 | 值传递 | 深复制 |
| 引用类型 | 引用传递 | 浅复制 |
local num = 100
local tmp = { a = 100, b = 120 }
-- 此时执行 值传递
dat = num, dat = tmp.a
-- 此时执行 引用传递
local test = tmp
-- 弱引用
local tmp = {}
-- key,弱引用
setmetatable(tmp, { _mode = "k" })
-- value,弱引用
setmetatable(tmp, { _mode = "v" })
-- key-value, 弱引用
setmetatable(tmp, { _mode = "kv" })
table
- 说明
- lua数组、表并不一样,需要注意。
- 迭代器,可迭代所有table类型。
table数组以及表,内存是一致的,表需要遍历,数组可直接定位。
| ~ | pairs |
ipairs |
|---|---|---|
迭代时遇到nil |
跳过本次 | 停止迭代 |
x = {1,nil,3,4}
-- 1
for _,v in pairs(x) do
print(v)
end
-- 1 3 4
for _,v in ipairs(x) do
print(v)
end
- 映射表
-- 基本定义方式
local p = { a=10, b=20 }
-- 索引命中方式
print(p["a"], p.a)
-- 新增索引项
p.c = 100
-- 字符串索引,任意合法的字符串都可以作为键
p[" "] = 123456
-- 映射:将内存数值映为可读字段
local state = { login = 1, logoff = 0 }
- 元表
- index 元表,本质是添加一个附表。
- newindex 元表,则是在表添加新索引时执行的操作。
-- index 元表
local tmp = { x=1, y=2 }
setmetatable( tmp, { __index = {
z = 10
}})
-- 另一种方式
local tmp = setmetatable({x=1,y=2},{__index={z=10}})
-- 索引为nil或不在原始表中,则会在附表中查找
print(tmp.x, tmp.y)
-- newindex
local tmp = { x=1, y=2 }
setmetetable( tmp, { __newindex=function(tab,key,val)
-- 此处可不执行,用以锁定 table
rawset(tab,key,'val:'..str(val))
end
})
-- 新增索引,执行newindex方法
-- y = 'val:100'
tmp.y = 100
-- 此时y已存在,不执行newindex
-- y = 100
tmp.y = 100
-- rawget 获取表中 key(字符串) value
local dat = rawget(tab,"key")
-- 设置表 tab,key(字符串) val
rawset(tab,key,val)
- function
- 匿名函数,异步回调。
- 闭包函数,需要注意可能造成内存泄漏。
-- 异步回调
local function demo(args, fun)
print("function demo is running")
fun(args)
end
-- 将匿名函数做为参数传递至原方法内部
demo("hello world", function(args)
print(args)
end)
-- 闭包函数
local function demo(args)
-- 闭包函数返回后, args参数不会被释放
return function()
print(args)
end
end
demo("hello world")
控制
- 运算符
1. 算术运算
+ - * / // % ^ -- / 除法运算 // 整除运算
2. 关系运算
== ~= > >= < <=
3. 逻辑运算
and or not
4. others
. [] () {} , : # .. =
- 条件控制
-- 顺序判断
if true then
print(true)
elseif true then
print(true)
else
print(false)
end
-- 常用判断
if true then
print(true)
end
- 循环控制
--------------------------------------------
-- lua没有 `switch` 选择循环
-- lua没有` continue`退出,仅存在 `break`退出块。
-- while循环
while(true) do
print(true)
break
end
-- repeat循环,true为退出条件
repeat
print(false)
until false
-------------------------------------------
-- for 头部默认为内部块变量,执行完毕自动释放
-- start stop step
for i = 0,10,1 do
print(i)
end
-- k为索引,v为值,同时适配与表格和数组
for k,v in pairs(tab) do
print(k,'--',v)
end
--------------------------------------------
-- for 头部亦可引用外部变量
local i
for i = 0,10,1 do
print(i)
end
local k,v
for k,v in pairs(tab) do
print(k,'--',v)
end
--------------------------------------------
-- _ 虚变量引用
for _,v in pairs(tab) do
print(v)
end
- 异常控制
-- 若此处为false,程序中断并抛异常
assert(1 == 1)
-- 以保护模式执行某方法,程序不会中断
local ok,err = pcall(demo())
local function demo(x,y)
print(x/y)
end
-- 以保护模式执行 demo,参数在其后,成功返回true,失败返回 false,err
local ok, err = pcall(demo, 10,5)
print(ok,err)
local function print_err()
print(debug.traceback())
end
-- 执行 demo,出错执行 print_end,参数在其后
xpcall(demo,print_err(),0,10)
模块
面向对象
lua 面向对象是一种伪对象,实际是一个衍生的索引表。
- 模块,类,对象
- 模块:块私有变量或方法仅模块内部生效,外部无法使用
- 类: class仅可读写所有
.级的方法以及变量。- 对象:obj仅可读写所有
self级的变量以及方法,仅可读所有.级变量以及方法,强写class级变量实际是新建一个obj级变量,对整个class并不生效。
- 读写分离
模块,类,对象之间的读写应该分离以免出现系统性error,所有超出限度的强写实际都是新建一个当前等级的变量,原始上一级变量并未发生改变。
| 模块 | 类 | 对象 | |
|---|---|---|---|
| 模块 | 读写 | ||
| 类 | 读 | 读写 | |
| 对象 | 读 | 读 | 读写 |
-- class
--------------------------------------------
local class = {}
-- 模块私有方法,仅仅块内生效
local function example()
print("example")
end
-- 可以看作类变量
class.size = 100
-- 可以看作类方法
function class.print_size()
print(class.size)
end
-- 类new对象的方法
function class:new(x,y)
cla = {}
-- 对象变量
cla.x = x or 1
cla.y = y or 2
setmetatable(cla,{__index=self})
return cla
end
-- 对象方法
function class:print()
print(self.x,self.y)
end
return class
-------------------------------------------------
-- obj
-- 引用块
local class = require("example")
-- class访问
class.print_size()
-- obj 访问
local obj = class:new(10,20)
-- obj -> class 访问
obj.print_size()
面向切面
面向切面编程是一种特殊的设计方式,面向对象的主要目的是为了代码复用,面向切面则是一种函数结构化方法。
-- 状态码
local STATE = {
PREPARE = 0,
FIGHT = 1,
REWARD = 2,
REFRESH = 3,
}
-- 主方法
local function running = {}
running[STATE.PREPARE] = function(comp)
-- pass
end
running[STATE.FIGHT] = function(comp)
-- pass
end
running[STATE.REWARD] = function(comp)
-- pass
end
running[STATE.REFRESH] = function(comp)
-- pass
end
-- running 主程序
while(true) do
running[comp.state](comp)
end
高级特性
- 加载模块
- lua 全局变量存储在:解释器级变量
_G_ENV中。 - lua package.loaded:存储加载完成的模块。
- lua package.path:require 适配路径。
-- lua 模块加载的基本路径
local path = package.path
-- lua 检测模块是否已加载
if package.loaded["exp"] then
print("exp 模块已加载!")
end
-- lua 热更新指定模块
local function hotfix(file)
if package.loaded[file] then
package.loaded[file] = nil
end
require(file)
end
--------------------------------------------
local x = require("x")
-- 先扫描 require("x")路径
-- 查找需要被加载的路径是否已经被加载
-- 若模块未被加载,则加载模块,并返回索引
-- 若模块已被加载,则直接返回索引
-- 加载某路径下的文件,并执行
-- 执行语句则,加载编译,执行
dofile("luafile.lua")
-- 加载指定文件,封装为一个函数返回
-- 索引仅需加载一次
local load = loadfile("luafile.lua")
--------------------------------------------
-- 弱类型,编译解释
-- 所有变量,在第一次被加载的时候,会被确定下来
-- 加载时,会将代码编译为机器码,然后给解释器
-- 以后执行时,皆使用加载时的配置类型
local y = 100 -- 编译时确定为 int
local s = 'he' -- 编译时确定为 string
- 预加载机制
- 预加载模块时,变量与方法会进入内存。
- 预加载模块时,语句块仅会执行一次,并不会进入内存。
- 所有lua文件,在虚拟机启动时即需完成预加载,除非后续热更新。
local exp = require("example")
local ret = {}
-- 被预加载后,进入内存,但仅可局部使用
local inner = "inner value"
local function inner_fun()
print("hello world\n")
end
-- 被预加载后,进入内存,外部亦可使用
ret.outer = "outer value"
function ret.out_fun()
print("hello world\n")
end
-- 预加载时
-- function 内的语句块会进入内存
-- function 外的语句块会执行一次,但不进入内存
print(inner)
inner_fun()
-- return 模块
-- 语句块会被执行一次,然后丢弃
-- 所有成员全部会被加载进入内存
-- 若成员被local修饰则非本模块对象无法访问
return ret
- 内存管理
- lua GC由解释器隐士处理,亦可自行显示处理。
- lua
local变量会自动释放,var=nil亦可释放内存。 _G_ENV这两个内部存储全局变量。collectgarbage()GC管理。- stop the world,标记清除。
local x = 100
local y = { a=100, b=200 }
z = 100
-- 不推荐使用全局变量
if _G["z"] then
print("global var z"..z)
end
-- 释放全局变量
y = nil
-- 释放局部变量
x = nil
-- 释放table内变量
y.a = nil
-- 执行一次 GC
collectbardage("collect")
-- 计算当前程序所占总内存
local num = collectgarbage("count");
-- 慎用,解释器停止自动GC,必须手动处理,否则内存会爆炸
collectbardage("stop")
常用
记录一些常用的,易遗忘的方法以及小技巧。
常用模块
- 文件管理
local file = io.open(".../example.txt","w")
-- 读写外加参数即可
local str = file:read()
file:write("hellow world")
-- 关闭文件
io.close(file)
file:close()
lua 协程还是单线程工作,只是同一时间里只有一个协程在工作,且是守护线程。
拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
-- 线程总得是无限循环的方法
local function done(dat)
while()
print(dat)
end
-- 协程执行至此处则 dead
end
-- 创建协程 suspended 挂起状态
local cor = coroutine.create(done)
-- 获取指定协程的现行状态 dead,suspended,running
print(coroutine.status(cor))
-- 重启或开启协程,协程引用,外加参数(没有可以不加) running
coroutine.resume(cor,"hellow world")
-- 挂起当前正在运行的协程,并使得协程返回数据 dat,协程最多只有一个可以运行
coroutine.yield(dat)
-- 返回某个正在running 的协程的线程ID
coroutine.running()
生产者消费者模型
1.coroutine.resume(cor) :开启协程等待返回值 status,value
2.coroutine.yield(dat) :挂起运行协程返回 dat
-- 弱类型语言的好处
local produce
-- 暂停程序 1 秒,获取信息
local function step()
os.execute("sleep 1")
print(coroutine.running())
print(coroutine.status(produce))
end
-- 生产者模型
local function productor()
local i = 0
while true do
step()
i = i + 1
-- 挂起当前协程,并返回 i
coroutine.yield(i)
end
end
-- 消费者模型
local function consumer()
while true do
local status,value = coroutine.resume(produce)
step()
print(status,"--",value)
end
end
produce = coroutine.create(productor)
consumer()
常用方法
不需要解释,容易忘记的东西。
-- 执行系统cmd
os.execute("sleep 1")
-- 输出 格式化日期 2020-01-01 01:02:56
print(os.date("%Y-%m-%d %H:%M:%S"))
-- 输出当前时间戳
print(os.time())
-- 输出程序已经执行的时间
print(os.clock())
-- 将表转化为字符串
local function tostr(tab)
local str = "{ "
str = str.."\n"
for k, v in pairs(tab) do
if type(v) ~= "table" then
str = str.."[\""..k.."\"]"
str = str..":"
str = str..v
str = str..","
str = str.."\n"
else
str = str.."[\""..k.."\"]"
str = str..":"
str = str..tostr(v)
str = str..","
str = str.."\n"
end
end
str = string.sub(str, 1, -3)
str = str.."\n"
str = str .." }"
return str
end
常用设计
一些通用性的程序设计。
- 平滑
-- 模块尽量使用,if return
if true then
return "err"
end
-- 方法不变,选择不同的参数执行
local function execute(param)
print(param)
end
-- 参数不变,选择不同的方法执行
local Execute = {}
Execute[CODE] = function(param)
print(param)
end
- 容错
- 幂等
-- 程序设计时,根据实际的
进阶
- 性能测试
-- 执行所用CPU时长
local begin = os.clock()
-- 获取CPU时间戳
local beginTime = os.time()
print("hello world")
local over = os.clock()
local overTime = os.time()
-- 0.001212 0.001234 number
print(begin, over, type(over))
-- 1596786393 1596786393 number
print(beginTime, overTime, type(overTime))
- 遍历加速
- iparis 仅用于迭代数组类型
table,遇到nil会退出。 - pairs 则可用于所有类型
table,遇到nil则会跳过,当迭代表时是乱序的。
local tab = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
-- 0.038 ms, 此处 #tab 会一直执行,故必然拖慢速度
for i = 1, #tab do
print(tab[i])
end
-- 0.030 ms
local len = #tab
for i = 1, len do
print(tab[i])
end
-- 0.035 ms
for _,val in pairs(tab) do
print(val)
end
-- 0.043 ms
for _,val in ipairs(tab) do
print(val)
end
-- 遍历基础数组,速度并无太多差异
local tab = {
a = 1, b = 2, c = 3, d = 4, e = 5,
f = 6, g = 7, h = 8, i = 9, j = 10
}
-- paris 迭代 数组、表格
-- pairs 迭代表格时,顺序为随机序列
for k, v pairs(tab) do
print(k, v)
end
- 传值与引用
- 所有的基础类型对象都执行深复制。
- 所有的引用类型对象都执行浅复制。

浙公网安备 33010602011771号