Lua基础教程与实践

目录

  • Lua配置、安装、与运行
  • Lua编译与调试环境搭建
  • Lua基本语法
    • 1.交互式编程
    • 2.脚本式编程
  • Lua中的数据类型
    • 1.数据类型展示
    • 2.注意事项
  • Lua中的变量与函数
    • 全局变量
    • 局部变量
    • 非局部变量(先看闭包)
    • 变量值的交换
    • Lua中的函数
      • 通用理解
        • 函数:
        • 闭包
          • 1.闭包函数所返回的值仍然被保留并可用于下一次计算
          • 2.Lua每次在给新的闭包变量赋值时,都会让不同的闭包变量拥有独立的"非局部变量"
        • 正确的尾调用
          • 尾调用:goto到方法与直接调用函数创建新的栈的函数调用优化
          • 注意事项:
      • 函数原型
      • 函数中的常量与局部变量
      • 子函数原型
      • upvalue (实际代指变量而非值)
      • C闭包
      • Lua闭包
      • 关于闭包的理解(词法定界)
  • Lua处理从C#获得的数组、字典、结构体等
    • 方式1迭代器遍历以及转化为table后循环
    • 方式2类似C#的调用以及相关注意事项
  • ...待续(持续更新中)
  • 参考与引用

Lua配置、安装、与运行

1.进入Lua官网:http://www.lua.org——下载Lua
2.下载文件【 lua-5.3.4_Win64bin.zip 】
3.创建一个空文件夹“Lua”并在其中解压【 lua-5.3.4_Win64bin.zip 】压缩文件
4.复制“Lua”文件夹到一个不会被删除的地方,
5.路径最好不要是中文。( 推荐选择C盘 )
6.将此路径加入到环境变量中
7.cmd 中输入lua 查看安装是否成功

Lua编译与调试环境搭建

1.下载sublime
2.点击 菜单栏→→ 工具 →→ 编译系统 →→ 新编译系统
menu bar →→ Tools →→ Build System →→ new Build System
Lua基础教程与实践_第1张图片
3.复制代码到配置中
Lua基础教程与实践_第2张图片

    "cmd": ["lua","$file"],  
    "file_regex":"^(...*?):([0-9]*):?([0-9]*)",  
    "selector":"source.lua"  

3.保存到默认目录:命名为MyLua.sublime-build
注意:后缀一定要是 .sublime-build 。
保存,关闭Sbulime Text
4.重新打开Sbulime Text
点击 菜单栏→→ 工具 →→ MyLua(我们刚创建好的编译系统文件)
menu bar →→ Tools →→ MyLua
选中MyLua编译环境就可以运行了
5.保存完成→→点击“F7”或者“Ctrl+B”调试
Lua基础教程与实践_第3张图片

Lua基本语法

1.交互式编程

Lua 提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果。

Lua 交互式编程模式可以通过命令 lua -i 或 lua 来启用:
Lua基础教程与实践_第4张图片

2.脚本式编程

我们可以将 Lua 程序代码保存到一个以 lua 结尾的文件,并执行,该模式称为脚本式编程,如我们将如下代码存储在名为 hello.lua 的脚本文件中:

Lua基础教程与实践_第5张图片

Lua中的数据类型

1.数据类型展示

print(type(“Hello world”)) --> string
print(type(10.4*3)) --> number
print(type(print)) --> function
print(type(type)) --> function
print(type(true)) --> boolean
print(type(nil)) --> nil
print(type(type(X))) --> string

2.注意事项

Lua基础教程与实践_第6张图片

--返回值判断类型
if boostr == "true" then
elseif nilstr=nil then
end

Lua基础教程与实践_第7张图片

Lua中的变量与函数

全局变量

局部变量

非局部变量(先看闭包)

Lua基础教程与实践_第8张图片

变量值的交换

Lua基础教程与实践_第9张图片

Lua中的函数

通用理解

函数:

1.在Lua中有一个容易混淆的概念是,函数与所有其他值一样都是匿名的,即他们都没有名称。当讨论一个函数名时(例如print)。实际上是在讨论持有某函数的变量,这与其他变量持有各种值一个道理
2.一个函数实际上就是一条语句(赋值语句),这条语句创造了一种类型为函数的值
高阶函数:基于First-Class-Value的应用体现
Lua基础教程与实践_第10张图片

可以将表达式"function(x) end"视为一种函数的构造式,就像table的构造式{}一样。将这种函数构造式的结果称为一个”匿名函数”。

下面的示例显示了匿名函数的方便性,它的使用方式有些类似于Java中的匿名类,如:
    table.sort(test_table,function(a,b) return (a.name > b.name) end)

像sort这样的函数,接受另一个参数作为实参的,称其是一个”高阶函数“,高阶函数是一种 强大的编程机制,
应用匿名函数来创建高阶函数所需的实参则可以带来更大的灵活性。高阶函数并没有什么特权。
Lua强调将函数视为”first-class valeu",所以,高阶函数只是基于该观点的应用体现而已。

闭包

简单来讲,一个闭包 就是一个函数加上该函数所需访问的所有“非局部变量”。
将函数和内部的第二个函数称为闭包。

function newCounter() 
    local i = 0
    return function() --匿名函数
        i = i + 1
        return i
    end
end
c1 = newCounter()
print("The return value of first call is " .. c1())
print("The return value of second call is " .. c1())
--输出结果为:
--The return value of first call is 1
--The return value of second call is 2
1.闭包函数所返回的值仍然被保留并可用于下一次计算
2.Lua每次在给新的闭包变量赋值时,都会让不同的闭包变量拥有独立的"非局部变量"
function newCounter() 
    local i = 0
    return function() --匿名函数
        i = i + 1
        return i
    end
end
c1 = newCounter()
c2 = newCounter()
print("The return value of first call with c1 is " .. c1())
print("The return value of first call with c2 is " .. c2())
print("The return value of second call with c1 is " .. c1())
--输出结果为:
--The return value of first call with c1 is 1
--The return value of first call with c2 is 1
--The return value of second call with c1 is 2

正确的尾调用

尾调用:goto到方法与直接调用函数创建新的栈的函数调用优化

若没有”尾调用消除“的话,每次用户的移动都会创建一个新的stack,若干步之后就有可能导致栈溢出

注意事项:
在Lua中支持这样一种函数调用的优化,即“尾调用消除”。我们可以将这种函数调用方式视为goto语句,如:
function f(x) return g(x) end
由于g(x)函数是f(x)函数的最后一条语句,在函数g返回之后,f()函数将没有任何指令需要被执行,因此在函数g()返回时,可以直接返回到f()函数的调用点。由此可见,Lua解释器一旦发现g()函数是f()函数的尾调用,那么在调用g()时将不会产生因函数调用而引起的栈开销。这里需要强调的是,尾调用函数一定是其调用函数的最后一条语句,否则Lua不会进行优化。然而事实上,我们在很多看似是尾调用的场景中,实际上并不是真正的尾调用,如:
function f(x) g(x) end            --没有return语句的明确提示
function f(x) return g(x) + 1  --在g()函数返回之后仍需执行一次加一的指令。
function f(x) return x or g(x) --如果g()函数返回多个值,该操作会强制要求g()函数只返回一个值。
function f(x) return (g(x))     --原因同上。
在Lua中,只有"return ()"形式才是标准的尾调用,至于参数中(args)是否包含表达式,由于表达式的执行是在函数调用之前完成的,因此不会影响该函数成为尾调用函数。

函数原型

每个Lua函数都有一个原型,这是一个由GC管理的对象,它挂靠在函数上,为函数提供必要的信息,比如这个函数的操作码(opcodes),常量信息,本地变量信息,upvalue信息,和调试信息等等。

因为Lua函数中可以内嵌函数,所以原型对象里面也有一个内嵌原型的列表,由此形成一个函数原型的树。

原型结构是这样的:

typedef struct Proto {
  CommonHeader;
  // 固定参数的数量
  lu_byte numparams;  /* number of fixed parameters */
  // 是否有可变参数
  lu_byte is_vararg;
  // 该函数需要的栈大小
  lu_byte maxstacksize;  /* number of registers needed by this function */
  // upvalues数量
  int sizeupvalues;  /* size of 'upvalues' */
  // 常量数量
  int sizek;  /* size of 'k' */
  // 指令数量
  int sizecode;
  // 行信息数量
  int sizelineinfo;
  // 内嵌原型数量
  int sizep;  /* size of 'p' */
  // 本地变量的数量
  int sizelocvars;
  // 函数进入的行
  int linedefined;  /* debug information  */
  // 函数返回的行
  int lastlinedefined;  /* debug information  */
  // 常量数量
  TValue *k;  /* constants used by the function */
  // 指令数组
  Instruction *code;  /* opcodes */
  // 内嵌函数原型
  struct Proto **p;  /* functions defined inside the function */
  // 行信息
  int *lineinfo;  /* map from opcodes to source lines (debug information) */
  // 本地变量信息
  LocVar *locvars;  /* information about local variables (debug information) */
  // Upvalue信息
  Upvaldesc *upvalues;  /* upvalue information */
  // 使用该原型创建的最后闭包(缓存)
  struct LClosure *cache;  /* last-created closure with this prototype */
  // 源代码文件
  TString  *source;  /* used for debug information */
  // 灰对象列表,最后由g->gray串连起来
  GCObject *gclist;
} Proto;

函数中的常量与局部变量

函数中的常量就是那些字面量,比如下面代码:

local function fun()
	--ok true这些就是常量,Lua把所有的值都统一为TValue,常量也不例外,由TValue *k;保存。
    local x = 1
    local s = "ok"
    local b = true
    --而且常量只能是数字,布尔值,字符串,和nil这些基本类型,其他GC对象不可以是常量。由于常量不可变,所以直接保存在原型对象上就可以了。
end

函数中的固定参数,可变参数,和本地变量,都是局部变量,这些变量都存在函数关联的栈中,而栈中的元素就称为“寄存器”,maxstacksize指定该函数需要多少个寄存器,在创建Lua函数时就会在栈上预留这么多空间。因为可变参数的实际数量只有调用者才知道,所以maxstacksize不包含可变参数的数量。

locvars是一个局部变量的信息结构,主要用于调试的:

//  本地变量的信息
typedef struct LocVar {
  // 本地变量名
  TString *varname;   
  int startpc;  /* first point where variable is active */
  int endpc;    /* first point where variable is dead */
} LocVar;

子函数原型

struct Proto **p保存着内嵌函数的原型列表,比如下面的代码:

function func()
    local function sf1()
    end
    local function sf2()
    end
end

sf1和sf2就是内嵌函数,所以func的函数原型就有两个子原型。

upvalue (实际代指变量而非值)

upvalue其实就是外部函数的局部变量,upvalues是这些upvalue的信息列表,Upvaldesc结构如下:

typedef struct Upvaldesc {
  // 名字
  TString *name;  /* upvalue name (for debug information) */
  lu_byte instack;  /* whether it is in stack (register) */
  lu_byte idx;  /* index of upvalue (in stack or in outer function's list) */
} Upvaldesc;

instack指明这个upvalue会存在哪里,有两种情况要考虑:

uv如果是上一层函数的局部变量,且这个上层函数还在活动中,那么该局部变量一定还在上层函数的栈中。此时,instack为1,表明它在栈中,idx指定在栈中的索引,相对于上层函数的栈基址。
uv如果是上一层函数之外的局部变量,就像下面代码这样:

local x = 1
local function func()
    local function innerfunc()
        return x + 1
    end
end

x在上两层函数之外声明,Lua是这样解决这个问题的:首先func会把x当成upvalue记录下来,然后innerfunc再从func的upvalue数组寻找。所以这种情况下,instack为0,则idx表示上层函数uv列表的索引。

实际的upvalue引用是在函数对象中的,这里只是一个描述信息,函数对象要根据这个信息才能引用到upvalue。

C闭包

Lua在执行到fucntion … end表达式时,会创建一个函数对象,其结构如下:

typedef union Closure {
  CClosure c;
  LClosure l;
} Closure;

正好对应了C闭包和Lua闭包,C闭包结构如下:

// nupvalues upvalue数量
// gclist为灰对象列表,最后由g->gray串连起来
#define ClosureHeader \
    CommonHeader; lu_byte nupvalues; GCObject *gclist
// C闭包
typedef struct CClosure {
  ClosureHeader;
  lua_CFunction f;    // C函数指针
  TValue upvalue[1];  /* list of upvalues */    // update数组
} CClosure;
因为C函数相应简单,没有外层函数,所以upvalue其实就是保存在CClosure中的一个TValue数组。一个CClosure的实际大小通过sizeLclosure计算出,其内存布局如下:

| CClosure | TValue[0] | .. | TValue[nupvalues-1] |
因为CClosure的upvalue数组包含了一个元素,所以后面跟着的长度为nupvalues-1。通过luaF_newCclosure生成一个新的C闭包,实际应用中一般用lua_pushcclosure向栈顶压入一个新的C闭包,同时栈顶要装备好upvalue。函数实现如下:

// 生成一个C闭包并压入栈顶, n表示当前栈顶有多少个upvalue要与闭包关联
LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
  lua_lock(L);
  if (n == 0) {
    // 没有upvalue,它是轻量级C函数
    setfvalue(L->top, fn);
    api_incr_top(L);
  }
  else {
    // 有upvalue,它是一个C闭包
    CClosure *cl;
    api_checknelems(L, n);
    api_check(L, n <= MAXUPVAL, "upvalue index too large");
    // 新建C闭包
    cl = luaF_newCclosure(L, n);
    cl->f = fn;
    L->top -= n;
    // 保存upvalue
    while (n--) {
      setobj2n(L, &cl->upvalue[n], L->top + n);
      /* does not need barrier because closure is white */
    }
    setclCvalue(L, L->top, cl);
    api_incr_top(L);
    luaC_checkGC(L);
  }
  lua_unlock(L);
}

Lua闭包

Lua闭包结构如下:

// Lua闭包
typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;    // 函数原型
  UpVal *upvals[1];  /* list of upvalues */   // upvalue列表
} LClosure;
通过sizeLclosure宏可获得Lua闭包的大小,其内存布局如下:

| LClosure | UpVal* | .. | UpVal* |
UpVal是对upvalue的间接引用,它的结构这样:

struct UpVal {
  // 引用的值,该值可能在栈上(open),也可能是下面的TValue(close)
  TValue *v;  /* points to stack or to its own value */
  // 引用计数
  lu_mem refcount;  /* reference counter */
  union {
    // 当v指向栈上时,open有用,next指向下一个,挂在L->openupval上
    struct {  /* (when open) */
      UpVal *next;  /* linked list */
      int touched;  /* mark to avoid cycles with dead threads */
    } open;
    // 当v指向自己时,这个值就在这里
    TValue value;  /* the value (when closed) */
  } u;
};

LClosure中记录的是UpVal指针,这说明一个UpVal可能会被多个Lua闭包引用,refcount就是这个引用计数。UpVal长成这个样子,完全是因为它要解决作用域的问题。比如下面代码:

function add (x)
    return function (y)
        return x+y
    end
end

local add2 = add(2)
print(add2(5))

add函数调用完之后,参数x就超出作用域了,它本来在栈上,函数返回后它也会从栈中删除掉,但是add返回的函数对象还引用着这个x,这该怎么办呢?Lua是这样处理的。

UpVal有两种状态:

open状态 在这种情况下,其字段v指向的是栈中的值,换句话说它的外层函数还在活动中,因此那些外部的局部变量仍然活在栈上。
close状态 当外层函数返回时,就像上面代码那样,add2函数中的UpVal会变成关闭状态,即v字段指向自己的TValue,这样v就不依赖于外层局部变量了。
lua_State的openupval字段维护着一个open的链表,当创建一个Lua闭包时,调用luaF_findupval尝试从openupval链表中找到一个UpVal(根据函数原型的Upvaldesc信息),如果找得到就记录它并增加引用计数,如果找不到就创建一个新的UpVal,并加入openupval链表,原码如下:

// 查找栈上的uv。
UpVal *luaF_findupval (lua_State *L, StkId level) {
  UpVal **pp = &L->openupval;
  UpVal *p;
  UpVal *uv;
  lua_assert(isintwups(L) || L->openupval == NULL);
  // 查找open的uv, open的uv由L->openupval串起来一个链表
  while (*pp != NULL && (p = *pp)->v >= level) {
    lua_assert(upisopen(p));
    if (p->v == level)  /* found a corresponding upvalue? */
      return p;  /* return it */
    pp = &p->u.open.next;
  }
  /* not found: create a new upvalue */
  // 如果未找到,创建一个新的加入链表
  uv = luaM_new(L, UpVal);
  uv->refcount = 0;
  uv->u.open.next = *pp;  /* link it to list of open upvalues */
  uv->u.open.touched = 1;
  *pp = uv;
  uv->v = level;  /* current value lives in the stack */
  if (!isintwups(L)) {  /* thread not in list of threads with upvalues? */
    L->twups = G(L)->twups;  /* link it to the list */
    G(L)->twups = L;
  }
  return uv;
}

比如下面这段Lua代码:

local x = 1
local y = 2
local z = 3

local function f1()
    return x + 1
end

local function f2()
    return x + 2
end

执行到f1声明时,创建一个Lua闭包,并创建一个UpVal挂到openupval链表上,接着执行到f2声明,此时从openupval可以到过UpVal,就直接引用它。

外层函数执行完毕的时候,会调用luaF_close将openupval中的一些UpVal关闭,代码如下:

vmcase(OP_RETURN) {
    int b = GETARG_B(i);
    if (cl->p->sizep > 0) luaF_close(L, base);
...

// 关闭栈中的upvalues,从level往后的upvalue,如果引用计数为0释放之,否则拷贝到UpVal自己身上
void luaF_close (lua_State *L, StkId level) {
  UpVal *uv;
  while (L->openupval != NULL && (uv = L->openupval)->v >= level) {
    lua_assert(upisopen(uv));
    L->openupval = uv->u.open.next;  /* remove from 'open' list */
    if (uv->refcount == 0)  /* no references? */
      luaM_free(L, uv);  /* free upvalue */
    else {
      setobj(L, &uv->u.value, uv->v);  /* move value to upvalue slot */
      uv->v = &uv->u.value;  /* now current value lives here */
      luaC_upvalbarrier(L, uv);
    }
  }
}

luaF_close还会在其他地方执行,只要任何情况下留在栈中的局部变量被删除出栈,就会调这个函数。调完之后,UpVal本身就把局变量的值保存在自己身上了,这个过程对于函数是透明的,因为它总是间接的引用upvalue。

下图表示open和close的UpVal状态:
Lua基础教程与实践_第11张图片

关于闭包的理解(词法定界)

function fn()
    local i = 0
    return function()     -- 注意这里是返回函数的地址,不是执行
       i = i + 1
        return i
    end
end

c1 = fn()                      -- 接收函数返回的地址
print(c1())                    --> 1          --c1()才表示执行
--local i = 0的意思是重新创建一个新的变量,这里没有创建新的?
print(c1())                    --> 2

--再次调用fn,将创建一个新的局部变量i
c2 = fn()
print(c2())  -->1
print(c1())  -->3
print(c2())  -->2


闭包在Lua中是一个非常重要的概念,闭包是由函数和与其相关的引用环境组合而成的实体。闭包=函数+引用环境。子函数可以使用父函数中的局部变量,这种行为叫做闭包。lua中函数是一种类型,可以被存放在变量或者数据结构中,可以当做参数传递给另一个函数,也可以是一个函数的返回值,也可以在运行期间被创建。Lua中还有一个非局部变量的概念,可以理解为不是在局部作用范围内定义的一个变量,同时,它又不是一个全局变量,也就是大家说的upvalue。这种变量主要应用在嵌套函数和匿名函数中(这个变量的环境就是前面说的引用环境)。在Lua函数中再定义函数,称为内嵌函数,内嵌函数可以访问外部函数已经创建的所有局部变量,而这些变量就被称为该内嵌函数的upvalue(upvalue实际指的是变量而不是值),这些变量可以在内部函数之间共享。于是成全了Lua中闭包。

function Closure()
	local ival = 10           	--upvalue
	function InnerFun1()     	--内嵌函数
		print(ival)
	end

	function InnerFun2()
		print("Before",ival)
		ival = ival + 10
		print("After", ival)
	end

	return InnerFun1, InnerFun2
end

--将函数赋值给变量,此时变量a绑定了函数InnerFun1,b绑定了函数InnerFun2
local a, b = Closure()

--调用a
a()

--调用b
b()


Lua处理从C#获得的数组、字典、结构体等

方式1迭代器遍历以及转化为table后循环

local testData = CS.LuaCallCSUtils.GetTestData()  --为一个字典

local iter = testData:GetEnumerator()

local list = {}

while iter:MoveNext() do
	--数组或list直接获取Current
	--local v = iter.Current
    local k = iter.Current.Key
    local v = iter.Current.Value
    list[k] = v  --转为table
    print(k, v)

end
local t = obj.array:ToTable()
for i = 1, #t do
    print("table:"..t[i])
end

方式2类似C#的调用以及相关注意事项

Lua基础教程与实践_第12张图片

print("*****************Lua调用C#数组******************");
local Lesson3=CS.Lesson3();

--Lua使用C#数组相关知识

--长度 userdata

--C#怎么用 lua就怎么用
print(Lesson3.testArray.Length);

--访问元素

print(Lesson3.testArray[0]);


--遍历要注意 虽然lua中索引从1开始
--但是数组是C#那不得规则 所以 还是要按照C#来
--注意最大值 一定要减1 lua中是可以取到最后一个值得 nil

for i=0,Lesson3.testArray.Length-1 do
	print(Lesson3.testArray[i]);
end

--Lua创建一个C#得数组 lua中表示数组和List可以用表
--但是创建C#中的数组,使用Array类中的静态方法即可

local Array2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32),10);
print(Array2.Length);
print("修改前"..Array2[0]);
Array2[0]=100;
print("修改后"..Array2[0]);
print("*****************Lua调用C# List******************");

Lesson3.testList:Add(1);
Lesson3.testList:Add(2);

--长度
print(Lesson3.testList.Count);

--遍历

for i=1,Lesson3.testList.Count-1 do
	print(Lesson3.testList[i])
end

print(Lesson3.testList);

--Lua创建一个List对象
--老版本
local list2 = CS.System.Collections.Generic["List`1[System.String]"]();
print(list2);
list2:Add("老版本创建List");
print(list2[0]);

--新版本>V2.1.12
local List_StringTemp = CS.System.Collections.Generic.List(CS.System.String);
local list3 = List_StringTemp();
list3:Add("新版本创建List");
print(list3[0]);


print("*****************Lua调用C#数组、List、字典相关知识点******************");
Lesson3.testDic:Add(1,"123");
print(Lesson3.testDic[1]);
--遍历
for k,v in pairs(Lesson3.testDic) do
	print(k,v)
end
--Lua中创建一个字典对象(新版本)

local Dic_String_Vectory3 = CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3);
local dic2 = Dic_String_Vectory3();
dic2:Add("123",CS.UnityEngine.Vector3.right);
--lua中创建的字典,key是string时,通过中括号是获取不到值的
print(dic2["123"]);--nil
--使用TryGetValue 两个返回值 第一个返回值为是否获取到 第二个为获取到的值
print(dic2:TryGetValue("123"));
--如果要通过健来获取值 通过固定方法
print(dic2:get_Item("123"));
--修改也是固定
dic2:set_Item("123",nil);

print(dic2:get_Item("123"));


…待续(持续更新中)

参考与引用

https://www.cnblogs.com/youxin/p/3796497.html
https://www.runoob.com/lua/lua-tutorial.html
https://blog.csdn.net/ChinarCSDN/article/details/78667262
https://blog.csdn.net/Yeyushenfan/article/details/83039573
https://zhuanlan.zhihu.com/p/98917625

你可能感兴趣的:(游戏研发备忘录,游戏,lua,遍历,基础,unity)