Lua学习笔记

      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管理

参考


0.零基础 http://www.sikiedu.com/my/course/75

建议开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】


1.介绍

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。


2.数据类型以及其他

1.8种数据类型介绍

类型名 简单介绍 备注
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 = 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参数可选, 默认为数组部分末尾.

 

2.自定义迭代函数举例

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

3.其他特性

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

for var=exp1,exp2,exp3 do  
    <执行体>  
end  

var=exp1 var=exp1+exp3 当 var = x+exp3 > exp2 时退出循环。

 

2.泛型for循环

a = {"one", "two", "three"}
for i, v in ipairs(a) do
    print(i, v)
end
   
   

3.元表MetaTable以及面向对象

元表是对表的扩展,使得对表操作更方便。是一个对表进行操作的行为的集合。

1.关联 元表和普通表

        setmetatable(普通表,元表)

        元表 =  getmetatable(普通表)

2.元表可以定义哪些操作(设置哪些键值对)

__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有这个字段,有直接返回对应的值;
2.没有,再看有没有元表以及元表有没有__index字段,

如果有是函数,就会将table和key传入调用此函数
如果是表,就会去找这个表对应的key,找到则返回,没找到则会继续找这个table的元表的__index,也就是说会一直向上寻找。(面向对象,继承)

__newindex

{__newindex= function(tab,key,value)……end}

{__newindex= 表}

当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法

 

已存在的索引键,就是操作普通表。

索引键赋值时,会调用元方法,即对元表进行赋值操作,或者调用函数。

 

可以在元方法中使用了 rawset 函数来更新普通表

 

__call {__cal = function(tab,arg)……end}

将table作为方法调用(括号操作符)时会调用元表的__call

 

比如a是表,执行 a()

__metatable {__metatable = “lock”} 防止修改元表元素

3.面向对象:Table:函数名, self,__index

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

4.协同程序Coroutine

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)

5.模块Module

1.模块调用require

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中,所以也就能访问了。

2.module函数

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()

3.require搜索模块的路径

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 来加载它。)

4.require的实现代码

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

6.I/O操作

-- 以”简单模式“操作文件,一次只能操作一个文件 均使用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()

7.GC管理

对于脚本语言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快捷键

你可能感兴趣的:(脚本语言)