Lua脚本是一种轻量级的动态语言,简单,方便学习,一般在游戏中用来实现游戏逻辑功能,而且还能实现热更新的功能。下面简单介绍了一下自己的学习lua之路。从一个简单的视频入手,有个大概了解。然后选择下载合适的lua解释器。先介绍了基本的数据类型,然后介绍了几个比较大的特性:元表,协同程序,模块,IO操作,CG管理。
目录
0.零基础 http://www.sikiedu.com/my/course/75
环境
正文
1.介绍
2.数据类型以及其他
1.8种数据类型介绍
2.自定义迭代函数举例
3.其他特性
3.元表MetaTable以及面向对象
1.关联 元表和普通表
2.元表可以定义哪些操作(设置哪些键值对)
3.面向对象:Table:函数名, self,__index
4.协同程序Coroutine
5.模块Module
1.模块调用require
2.module函数
3.require搜索模块的路径
4.require的实现代码
6.I/O操作
7.GC管理
参考
建议开1.5倍速观看
看视频【任务5】【任务6】安装lua环境
数据类型:【任务11】
重要数据类型:
表Table:【任务16-17 38-40】
函数Function:【任务18-19 25-26】
模块:【任务41】————表实现
元表metatable:【任务43-48】————表扩展
协程:【任务50-54】————thread类型
面向对象:【任务60-65】————表和元表和函数
垃圾回收:【任务59】
Lua(www.lua.org)是由C语言实现的一种轻量级脚本语言,通过Lua解释器(Lua虚拟机)把他解析成字节码运行。
可以下载一个SciTE,进行lua脚本的编写和运行(或VScode LuaIDE)。
嵌入式语言,C#和C++。
=========
补充:用luajit.exe编译成字节码时间比luac.exe编译成字节码时间长。
原生解释器lua.exe:
可以直接使用 lua.exe 运行 lua脚本
或者
先使用 luac.exe 把原生脚本(zhushi.lua) 编译成是一个字节码脚本(out.lua),lua.exe 也可以运行这个字节码脚本。
LuaJIT解释器luajit.exe
可以直接使用 luajit.exe 运行 lua脚本(打开luajit.exe 直接输入 print(_VERSION) 查看lua版本)
或者
先使用 luajit.exe 把原生脚本(zhushi.lua) 编译成是一个字节码脚本(out.lua),luajit.exe 也可以运行这个字节码脚本。
把luac.exe 生成的 字节码脚本 让luajit.exe 运行 无法运行,无法兼容。
关于LuaJIT的介绍可以看浅入浅出JIT。
类型名 | 简单介绍 | 备注 |
nil | 值也为nil,未初始化的类型为nil值也为nil,没有返回值的函数的返回值也是。 | 将变量置为nil,用来标记可以清空内存,销毁内存。 |
boolean | 值为 false 和true | 只有值false和nil是false,其他都为true |
number | 数值类型,整数实数 | |
string | 1.统一采用8位字符编码 2.定义“” ‘’ 或多行 [[ ]] 3.拼接 .. 4.长度# 5.很多内置方法 string.upper , string.lower 等 6.还有各种转义字符 /n /t 等 |
定义: table1 = {["name1"] = 1, [1] = "1111"} 正确 table1 = {name1 = 1} 正确 使用: table1["name1"] table1[1] 正确 table1."name1" 错误 table1.name1 正确 |
function | 1.定义: 函数名如果省略就是匿名函数。 function 函数名(参数) end 《==》等价于 函数名 = function(参数)……end 2.可以不return,也可以return多个值 3.支持可变参数(变长参数),用 ... 指代, 1)遍历变长参数可以用{...} 比如 local super = {...} for i,super_class in ipairs(super) do -- end 2)也可以用 arg 获取可变参数 arg[1] arg[2] 等等 即arg= {...} |
迭代函数pairs,ipairs ,参数都是表 其中 ipairs 会读取表中key从1开始连续的value值组成数组。 (pairs的迭代函数 输出的元素是没有顺序的) (不管是哪个迭代函数,都不会输出元素为nil的) |
userdata | 该类型可以将任意的c语言数据存储到lua变量中,但是只能进行赋值和相等性测试。lua和C++相互调用。 | file = io.open("mytest.lua", "r") type(file) 是 “userdata”类型 |
thread | 由lua虚拟机实现的一种数据结构。从lua脚本看,一个协程就是一个线程类,不是操作系统上的线程概念。 | colObj = coroutine.create(写成函数对象) type(colObj) 是 “thread”类型 |
table | 1.似于C++中的字典,允许以类似于键值对的方式来索引值,通过构造式{} 来实现 。 2.table.contact:把value连接成string 3.数组类型的table的插入操作: table.insert(tab, [pos,]value); 4.元表也是一个表,下面介绍 |
table是引用类型。 可以定义 tab1={5,4,3,2},就相当于数组类型 即 tabl[1] = 5 tabl[2] = 4 tabl[3] = 3…… lua中索引值都是从1开始,而不是0。(比如数组类型的table的索引,比如string的字符索引)
如果要把number类型作为键值,用[number] tab2={[1]=5,[3]=4} print(tab2[3]) --4
table.remove (table [, pos]) 返回table数组!!!部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。
table.insert (table, [pos,] value): 在table的数组!!!部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾.
|
ipairs的实现方式 myIpairs
tab = {name = 1,4,name1 = "111",6}
tab[3] = "qqq"
function iter (a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function myIpairs (tab)
-- 迭代函数、状态常量、控制变量
return iter, tab, 0
end
-- 使用自定义的迭代函数 代替 ipairs,实现相同的功能
for v,p in myIpairs(tab) do
print(v..p)
end
0.自带函数
assert | 首先检查的是第一个参数是否返回false或者出现错误,如果否,则assert简单返回,如果是以第二个参数抛出异常信息。 | local n = assert(tonumber("a"), "invalid input is not a number") assert(vtb ~= nil, "class without vtb") |
1.只有前面加local才是局部变量,否则都是全局变量。能局部就写局部。
所有的全局变量都存在一个表, 即_G表。
(_大写字母 是 Lua的保留字的命名规则,所以尽量不要这么命名变量)
-- 定义一个全局变量
gName = "lalalala";
-- 可以有三种输出
print(gName);
print(_G["gName"]);
print(_G.gName);
2.单行注释-- 多行注释 --[[ --]]
3.逻辑运算符 and or
对于运算符and来说,如果它的第一个操作数为假,就返回第一个操作数;不然返回第二个操作数。
对于运算符or来说,如果它的第一个操作数为真,就返回第一个操作数,不然返回第二个操作数。
print(0 and 4) --4
print(nil and 4) --nil
print(0 or 4) --0
print(nil or 4) --4
4.循环结构
for循环 | 1.数值for循环:exp3不写默认为1 var=exp1 var=exp1+exp3 当 var = x+exp3 > exp2 时退出循环。
2.泛型for循环 |
元表是对表的扩展,使得对表操作更方便。是一个对表进行操作的行为的集合。
setmetatable(普通表,元表)
元表 = getmetatable(普通表)
__add | 加。类似还有__sub __mul __div __mod {__add = function(tab, newtable)……end} |
a和b表相加时,a+b 1.先判断a和b两者之一是否有元表, 2.再检查该元表中是否有一个叫__add的字段; 如果找到了该字段,就调用该字段对应的值,这个值对应的是一个方法; |
__tostring | {__tostring = function(tab)……end} | 将table作为string传入时会调用元表的__tostring
比如 print(a) a是表 1.先判断a有没有元表, 2.如果有检查有没有__tostring字段,有则调用其值即方法。 |
__index | {__index= function(tab,key)……end} 或 {__index= 表} |
访问一个table的字段时, 1.先看table有这个字段,有直接返回对应的值; 如果有是函数,就会将table和key传入调用此函数 |
__newindex | {__newindex= function(tab,key,value)……end} 或 {__newindex= 表} |
当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法
对已存在的索引键,就是操作普通表。 对新索引键赋值时,会调用元方法,即对元表进行赋值操作,或者调用函数。
可以在元方法中使用了 rawset 函数来更新普通表
|
__call | {__cal = function(tab,arg)……end} | 将table作为方法调用(括号操作符)时会调用元表的__call
比如a是表,执行 a() |
__metatable | {__metatable = “lock”} | 防止修改元表元素 |
1.实现类与对象和继承的方式:
1)xiaoming 继承自 People:通过定义了People的new函数,再让 xiaoming的初始化 通过 new 函数返回 。
2)Teacher 继承自 People:直接设置 setmetatable(Teacher, {__index = People}。
local People = {
name = ''
}
--self means this in C++
--use ":", then you can use the key word "self"
function People:say()
print(self.name)
end
--the construct function "new", usually write below 构造函数
function People:new(name)
local o = {name = name}
setmetatable(o, {__index = self})
return o
end
--the metatable of xiaoming is People
local xiaoming = People:new('xiaoming')
xiaoming:say()
local Teacher = {}
function Teacher:say()
print('teacher ' .. self.name)
end
--say People is the base of Teacher
setmetatable(Teacher, {__index = People})
--the metatable of MrLi is Teacher
local lilaoshi = Teacher:new('MrLi')
lilaoshi:say()
复杂的实现方式【待看】
https://blog.codingnow.com/cloud/LuaOO
https://github.com/kikito/middleclass
2.函数定义的时候 使用冒号,而不是点:
这样可以在函数体内直接使用参数self(该self指向调用者本身,相当于C++中的this)
调用的时候 lilaoshi:say() ===>也可以写成 lilaoshi.say(lilaoshi)
3. self.__index = self 是什么意思?
以下2种写法等价 1 和 2 等价。
2 这么写比较好理解。conf 相当于 Account的子类。
--1. self.__index = self
Account = {balance = 0}
function Account:new(conf)
conf = conf or {}
setmetatable(conf,self)
self.__index = self
return conf
end
--2. {__index = Account}
Account = {balance = 0}
function Account:new(conf)
conf = conf or {}
setmetatable(conf, {__index = self})
return conf
end
colObj = coroutine.create(协程函数) -------创建coroutine
coroutine.resume(colObj,协程函数的参数) -----启动coroutine,直到遇到yield挂起暂停
coroutine.yield(resume的返回参数)----挂起暂停coroutine
coroutine.status(colObj)-----coroutine的状态:dead,suspend,running。
coroutine.running()----返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线
程号
具体用法看实例:
function foo (a)
print("foo 函数输出", a)
return coroutine.yield(2 * a) -- 返回 2*a 的值
end
-- 匿名函数实现的协程函数
co = coroutine.create(function (a , b)
print("第一次协同程序执行输入", a, b)
-- 调用foo 第一次挂起 resume的返回值为 true 和 4
-- 第二次resume 传入的参数 为赋值给了 r
local r = foo(a + 1)
print("第二次协同程序执行输入", r)
-- 第二次挂起resume的返回值true 11 -9
-- 第三次resume 传入的参数 为赋值给了 r, s
local r, s = coroutine.yield(a + b, a - b)
print("第三次协同程序执行输入", r, s)
-- 结束协程,resume的返回值true 10 ”结束协同程序“
return b, "结束协同程序"
end)
--[[
a,b = coroutine.resume(co, 1, 10)
print(b)
---]]
-- 开始执行协程
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "Para_2")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "Para_3_1", "Para_3_2")) -- true 10 end
print("---分割线---")
-- 第四次resume 因为协程已经结束了 所以resume的返回值 false
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
coroutine.wrap()----创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复
co1 = coroutine.wrap(
function(i)
-- 协程函数内容
print(i);
end
)
--col is a "function" type
print(type(co1))
--start coroutine
co1(5)
zhushi.lua 就相当于一个 模块
-- 文件名为 zhushi.lua
-- 1.定义一个表 tabA。
tabA = {}
-- 2.为这个表添加 key value
tabA.constant = "这是一个常量"
function tabA.func1()
io.write("这是一个公有函数!\n")
end
-- 3.定义的一个局部函数,跟tabA没关系
local function func2()
print("这是一个私有函数!")
end
-- 4.不能把这个函数放到 fun2 的前面 否则 fun2 就是一个 nil 值
function tabA.func3()
func2()
end
-- 5.正常的输出语句,跟tabA没关系。
print("可以进行输出!")
-- 6.返回tabA,当其他require这个文件的时候,作为返回值。
-- 其实把什么作为返回值都可以,如果没有返回值的话 require 函数的返回值就是加载成功为true
-- 当然一个符合规范的模块还应返回这个tabA,并且最好把 5 的print 去掉。
return tabA
调用require,会判断是否在内存中,如果不在才进行加载,即运行下面代码只输出一次“可以进行输出”
--方法一
-- 第一次 require ,会加载
require("zhushi")
-- 如果 tabA定义成local就不能用这种加载方式,而应该用方法二
print(tabA.constant)
tabA.func3()
--方法二
-- 第二次 require ,已经加载过了,就不加载。
-- 定义一个别名 m 指代 tabA
local m = require "zhushi"
print(m.constant)
m.func3()
require的作用:
相当与#include作用类似,加载了该模块,那么就可已使用模块中的全局函数和全局数据(如表等等)
require “xxx”后,会将xxx中的全局函数和数据放到表_G中,所以也就能访问了。
lua 从 5.1 开始终于官方提供统一的 module 实现标准了,module函数
由于在默认情况下,module 指令运行完后,整个环境被压栈,所以前面全局的东西再看不见了,如果要进行外部访问,必须在调用它之前,为需要访问的外部函数或模块声明适当的局部变量。具体方法见下面事例。
mytest.lua 文件名,在module函数中的 modulename 也需要写成 "mytest"。
--[[ 方法一
local print=print
module('mytest')
--]]
-- 方法二
--module('mytest',package.seeall);
--[[ 方法三
-- 并且修改下面的全局函数: print--》_G.print
local _G=_G
module('mytest')
--]]
function play()
print("那么,开始吧")
end
function quit()
print("你走吧,我保证你不会出事的,呵,呵呵");
end
加载模块运行,把上面的方法一 二 三的打开,都可以运行。如果只有 module(’mytest‘) 会提示 print函数找不到
game = require "mytest"
game.play()
game.quit()
require函数在搜素加载模块时,有一套自定义的模式,如:
?;?.lua;c:/windows/?;/usr/local/lua/?/?.lua
在上面的模式中,只有问号(?)和分号(;)是模式字符,分别表示require函数的参数(模块名)和模式间的分隔符。
如:调用require "sql",将会搜索以下的文件:
sql
sql.lua
c:/windows/sql
/usr/local/lua/sql/sql.lua
require 用于搜索 Lua 文件的路径(上述例子中的模式)是存放在全局变量 package.path 中,
当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。
如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化,luaconf.h 中的 LUA_PATH_DEFAULT。(修改了luaconf.h中的路径后,我们需要重新生成新的lua静态lib库,然后我们的程序使用新的静态lib,这样才能起效)
(如果找得到,那么 require 就会通过 package.loadfile 来加载模块。)
否则,就会去找 C 程序库,搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。搜索的是 so 或 dll 类型的文件。
(如果找得到,那么 require 就会通过 package.loadlib 来加载它。)
function require(name)
if not package.loaded[name] then
local loader = findloader(name)
if loader == nil then
error("unable to load module" .. name)
end
package.loaded[name] = true
local res = loader(name)
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
-- 以”简单模式“操作文件,一次只能操作一个文件 均使用io.XXX 进行操作
-- 1读文件
file = io.open("mytest.lua", "r")-- “w”写 “a”追加
io.input(file)-- 设置默认输入文件
print(io.read())
io.close(file)
--2追加内容
file = io.open("test.lua", "a") -- 没有这个文件 会新生成一个
io.output(file)-- 设置默认输出文件
io.write("-- test.lua 文件末尾注释")
io.close(file)
-- 以”完全模式“操作文件,一次能操作多个文件 均使用file:XXX 进行操作
file = io.open("mytest.lua", "r")
print(file:read())
file:close()
对于脚本语言lua,它采用的是自动内存管理机制,所以使用时无需考虑内存的释放和分配,直接用即可
线程数据类型 和 table数据类型
lua实现了一个增量标记的扫描收集器
垃圾收集器(间隙率,步进倍率)
回收函数 collectgarbage("count") collectgarbage("collect")
https://www.jianshu.com/p/3317a7f6628a
http://www.runoob.com/lua/lua-coroutine.html
https://blog.csdn.net/p358278505/article/details/74221214 非常全的VsCode快捷键