[Unity] Lua 学习笔记

内容摘抄自runoob.com

1.Lua特性

1 轻量化
2 可扩展: 提供了非常易于使用的扩展接口和机制,宿主语言C/C++提供功能,Lua可以直接使用。
3 其它:

  • 支持面向过程和函数式编程
  • 自动内存管理:只提供一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象
  • 语言内置模式匹配,闭包,函数也可以看作一个值,提供多线程(协同进程,并非操作系统所支持的线程)支持
  • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承与重载。

2.基本语法

1 所有变量定义是默认为全局,如果访问一个没有定义的变量不会发生错误,得到的结果为nil
2 删除一个全局变量,只需要将变量赋值为nil
3 交互式编程,在命令行中输入程序就可以立即看到效果
4 脚本式编程,可以将lua程序保存到以lua结尾的文件中,使用$ lua xxx.lua可以得到结果。
5 单行注释--,多行注释--[[ xxxxx --]]

3.数据类型

  Lua为动态类型语言,变量不需要类型定义,只需要为变量赋值。值可以存储在变量中,作为参数传递或结果返回。共8个基本类型变量。
[Unity] Lua 学习笔记_第1张图片

3.1 nil (空类型)

  nil类型与C/C++中的nullptr相类似。表示没有任何的有效值,例如打印一个没有赋值或者声明的变量那么就会输出一个nil值。
   注意: 对于全局变量tablenil还有删除作用,将全局变量table中的某一个变量赋为nil,那么就相当于把这个值删除。
nil做比较时需要加上双引号,因为使用type(xxx) == "nil"作比较时,type(xxx)的返回值为字符串类型。

3.2 string (字符串类型)

表示字符串的几种方式

  • 单引号 'xxxxxx'
  • 双引号 "xxxxxx"
  • 一块字符串 [[xxx 可以是多行]]

注意: 在对一个数字字符串进行算数操作时,Lua会自动将该数字字符串转换成为一个数字,并进行计算。
字符串连接使用 .. 进行连接,字符串计算使用#计算

示例:

>print(2 + "2")
>10:48:23.13-346: 4

>print("2" + "2")
>10:48:33.107-949: 4

>print("2 + 2")
>10:48:46.700-762: 2 + 2

>print("-2e2" * 3)
>10:49:00.837-608: -600

>print("-2e2" * "3")
>10:49:11.445-243: -600

>print("a" .. "b")
>10:49:24.361-15: ab

>print("hello" .. " world")
>10:49:42.610-106: hello world

>print(#"hello world")
>10:49:57.587-2: 11

3.3 table (表)

创建方式:

  • 创建一个空表
    local tbl1 = {}
  • 直接初始化表
    local tbl2 = {"apple", "pear", "orange", "grape"}

  table中可以存储任意类型的数据类型,也就是第一个key-value可以和第二个键值对是完全不同的数据类型,数组的索引可以是数字字符串.

a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11
for k, v in pairs(a) do
    print(k .. " : " .. v)
end

结果:
$ lua table_test.lua 
key : value
10 : 33

  在Lua中,表的默认索引为1

-- table_test2.lua 脚本文件
local tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
    print("Key", key)
end

脚本执行结果为:
$ lua table_test2.lua 
Key    1
Key    2
Key    3
Key    4

  table不会固定长度大小,有新数据添加时table长度会自动增长,没初始化的项都是nil

-- table_test3.lua 脚本文件
a3 = {}
for i = 1, 10 do
    a3[i] = i
end
a3["key"] = "val"
print(a3["key"])
print(a3["none"])

脚本执行结果为:
$ lua table_test3.lua 
val
nil

3.4 function (函数)

  function可以与C/C++语法中一样,可以将函数值当作指针值,将其存储在变量中,那个变量就相当于函数名,通过那个变量就可以调用函数。

-- function_test.lua 脚本文件
function factorial1(n)
    if n == 0 then
        return 1
    else
        return n * factorial1(n - 1)
    end
end
print(factorial1(5))
factorial2 = factorial1
print(factorial2(5))

脚本执行结果为:
$ lua function_test.lua 
120
120

匿名函数

-- function_test2.lua 脚本文件
function testFun(tab,fun)
        for k ,v in pairs(tab) do
                print(fun(k,v));
        end
end

tab={key1="val1",key2="val2"};
testFun(tab,
function(key,val)--匿名函数
        return key.."="..val;
end
);

结果:
$ lua function_test2.lua 
key1 = val1
key2 = val2

3.5 thread (线程)

  在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。

  线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。

3.6 userdata (自定义类型)

  userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。

4.变量

变量类型

  • 全局变量: 所有变量均为全局变量,在函数中或者语句块中定义的变量也是全局变量
  • 局部变量: 使用local显式声明的变量称为局部变量,局部变量的作用域从声明位置开始至所在语句块结束。

4.1 变量间赋值

  给变量赋值可以改变变量的值,和变量的基本类型。
  Lua可以对多个变量同时赋值,变量列表和值列表用都好隔开,赋值语句右边的值会依次赋给左边的变量

a, b = 10, 2 * x  ===>  a = 10; b = 2 * x

赋值语句Lua会计算右边所有的值然后再去执行赋值操作,可以利用此特性交换变量的值,不需要像C/C++那样需要使用中间变量进行值的交换

x, y = y, x  ===>  swap 'x' for 'y'
a[i], a[j] = a[j], a[i]  ===>  swap 'a[i]' for 'a[j]'

注意

如果变量个数与给定值的个数不一致时,Lua会以变量个数为基础采取如下策略

  • 变量个数 > 值的个数 --> 按照变量个数补足nil
  • 变量个数 < 值的个数 --> 忽略多余的值
>a, b, c = 0, 1
>print(a, b, c)
>13:45:34.893-234: 0    1    nil

>a, b = a + 1, b + 1, b + 2
>print(a, b)
>13:46:01.732-839: 1    2

>d, e, f = 0
>print(d, e, f)
>13:46:17.848-803: 0    nil    nil

多值赋值常用于交换变量,或者将函数的返回值返回给变量

a, b = f()
f()返回两个值,第一个赋给a,第二个赋给b。

尽可能的使用局部变量,有两个好处:
1.避免命名冲突。
2.访问局部变量的速度比全局变量更快。

4.2 索引

对 table 的索引使用方括号[]。Lua也提供了. 操作。

t[i]
t.i – 当索引为字符串类型时的一种简化写法
gettable_event(t,i) – 采用索引访问本质上是一个类似这样的函数调用

> site = {}
> site["key"] = "www.runoob.com"
> print(site["key"])
www.runoob.com
> print(site.key)
www.runoob.com

5.循环

5.1 while循环

while(condition)
do
   statements
end

statements(循环体语句) 可以是一条或多条语句,condition(条件) 可以是任意表达式,在 condition(条件) 为 true 时执行循环体语句

5.2 for循环

1.数值for循环

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

varexp1变化到exp2,每次变化以exp3为步长递增var,并执行一次 “执行体”。exp3是可选的,如果不指定,默认为1。

2.泛型for循环

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

i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。

5.3 repeat…until循环

  repeat...until循环语句不同于forwhile循环,for 和 while 循环的条件语句在当前循环执行开始时判断,而 repeat...until循环的条件语句在当前循环结束后判断,相当于C/C++中的do{ }while()循环。

repeat
   statements
until( condition )

循环条件判断语句(condition)在循环体末尾部分,所以在条件进行判断前循环体都会执行一次。
如果条件判断语句(condition)为 false,循环会重新开始执行,直到条件判断语句(condition)为 true 才会停止执行。

5.4 循环嵌套

Lua 编程语言中 for 循环嵌套语法格式:

for init,max/min value, increment
do
   for init,max/min value, increment
   do
      statements
   end
   statements
end

Lua 编程语言中 while 循环嵌套语法格式:

while(condition)
do
   while(condition)
   do
      statements
   end
   statements
end

Lua 编程语言中 repeat…until 循环嵌套语法格式:

repeat
   statements
   repeat
      statements
   until( condition )
until( condition )

5.5 goto语句

语法格式:goto Label
Label 的格式::: Label ::

local a = 1
::label:: print("--- goto label ---")

a = a+1
if a < 3 then
    goto label   -- a 小于 3 的时候跳转到标签 label
end
i = 0
::s1:: do
  print(i)
  i = i+1
end
if i>3 then
  os.exit()   -- i 大于 3 时退出
end
goto s1

有了 goto,我们可以实现 continue 的功能:

for i=1, 3 do
    if i <= 2 then
        print(i, "yes continue")
        goto continue
    end
    print(i, " no continue")
    ::continue::
    print([[i'm end]])
end

6.流程控制

Lua中 0 为 true

if语句

if(布尔表达式)
then
   --[ 在布尔表达式为 true 时执行的语句 --]
end

if…else语句

if(布尔表达式)
then
   --[ 布尔表达式为 true 时执行该语句块 --]
else
   --[ 布尔表达式为 false 时执行该语句块 --]
end

if…elseif…else 语句

if( 布尔表达式 1)
then
   --[ 在布尔表达式 1true 时执行该语句块 --]

elseif( 布尔表达式 2)
then
   --[ 在布尔表达式 2true 时执行该语句块 --]

elseif( 布尔表达式 3)
then
   --[ 在布尔表达式 3true 时执行该语句块 --]
else 
   --[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end

if嵌套语句

if( 布尔表达式 1)
then
   --[ 布尔表达式 1true 时执行该语句块 --]
   if(布尔表达式 2)
   then
      --[ 布尔表达式 2true 时执行该语句块 --]
   end
end

7.函数

定义格式

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。
function_name: 指定函数名称。
argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
function_body: 函数体,函数中需要执行的代码语句块。
result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。

7.1 函数作为参数

myprint = function(param)
   print("这是打印函数 -   ##",param,"##")
end

function add(num1,num2,functionPrint)
   result = num1 + num2
   -- 调用传递的函数参数
   functionPrint(result)
end
myprint(10)
-- myprint 函数作为参数传递
add(2,5,myprint)

7.2 多返回值

接受多返回值时,就可以用到上面说到的多个变量去接收res1, res2,...resn = fun()

function maximum (a)
    local mi = 1             -- 最大值索引
    local m = a[mi]          -- 最大值
    for i,val in ipairs(a) do
       if val > m then
           mi = i
           m = val
       end
    end
    return m, mi
end

print(maximum({8,10,23,12,5}))

7.3 可变参数

  Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 … 表示函数有可变的参数。

function add(...)  
local s = 0  
  for i, v in ipairs{...} do   --> {...} 表示一个由所有变长参数构成的数组  
    s = s + v  
  end  
  return s  
end  

// 调用
print(add(3,4,5,6,7))  --->25

例如,计算几个数的平均值:

function average(...)
   result = 0
   local arg={...}    --> arg 为一个表,局部变量
   for i,v in ipairs(arg) do
      result = result + v
   end
   print("总共传入 " .. #arg .. " 个数")
   return result/#arg
end

print("平均值为",average(10,5,3,4,5,6))

也可以通过 select("#",…) 来获取可变参数的数量:

function average(...)
   result = 0
   local arg={...}
   for i,v in ipairs(arg) do
      result = result + v
   end
   print("总共传入 " .. select("#",...) .. " 个数")
   return result/select("#",...)
end

print("平均值为",average(10,5,3,4,5,6))

有时候可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:

function fwrite(fmt, ...)  ---> 固定的参数fmt
    return io.write(string.format(fmt, ...))    
end

fwrite("runoob\n")       --->fmt = "runoob", 没有变长参数。  
fwrite("%d%d\n", 1, 2)   --->fmt = "%d%d", 变长参数为 12

函数技巧
  通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select('#', …)或者 select(n, …)

select('#', …) 返回可变参数的长度。
select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。

function f(...)
    a = select(3,...)  -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数
    print (a)
    print (select(3,...)) -->打印所有列表参数
end

f(0,1,2,3,4,5)

结果
2
2       3       4       5
do  
    function foo(...)  
        for i = 1, select('#', ...) do  -->获取参数总数
            local arg = select(i, ...); -->读取参数,arg 对应的是右边变量列表的第一个参数
            print("arg", arg);  
        end  
    end  
 
    foo(1, 2, 3, 4);  
end

结果:
arg    1
arg    2
arg    3
arg    4

8.运算符优先级

[Unity] Lua 学习笔记_第2张图片

9.字符串操作

1 字符串全部转为大写字母: string.upper(argument)

2 字符串全部转为小写字母: string.lower(argument)

3 在字符串中替换: string.gsub(mainString,findString,replaceString,num)

mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换),如:

> string.gsub("aaaa","a","z",3);
zzza    3

4 在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil: string.find (str, substr, [init, [end]])

> string.find("Hello Lua user", "Lua", 1) 
7    9

5 字符串反转: string.reverse(arg)

> string.reverse("Lua")
auL

6 返回一个类似printf的格式化字符串: string.format(...)

> string.format("the value is:%d",4)
the value is:4

7 char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符): string.char(arg) 和 string.byte(arg[,int])

> string.char(97,98,99,100)
abcd
> string.byte("ABCD",4)
68
> string.byte("ABCD")
65

8 计算字符串长度: string.len(arg)

string.len("abc")
3

9 返回字符串string的n个拷贝: string.rep(string, n)

> string.rep("abcd",2)
abcdabcd

10 链接两个字符串: ..

> print("www.runoob.".."com")
www.runoob.com

11 string.gmatch(str, pattern)
  回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil。

> for word in string.gmatch("Hello Lua user", "%a+") do print(word) end
Hello
Lua
user

12 string.match(str, pattern, init)
  string.match()只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为1。
  在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil。

> = string.match("I have 2 questions for you.", "%d+ %a+")
2 questions

> = string.format("%d, %q", string.match("I have 2 questions for you.", "(%d+) (%a+)"))
2, "questions"

9.1 字符串截取

字符串截取使用 sub() 方法。
string.sub() 用于截取字符串,原型为:

string.sub(s, i [, j])

参数说明:
s:要截取的字符串。
i:截取开始位置。
j:截取结束位置,默认为 -1,最后一个字符。

9.2 字符串格式化

  string.format()函数来生成具有特定格式的字符串, 函数的第一个参数是格式 , 之后是对应格式中每个代号的各种数据。
  由于格式字符串的存在, 使得产生的长字符串可读性大大提高了。这个函数的格式很像 C 语言中的printf()

转义码

%c - 接受一个数字, 并将其转化为ASCII码表中对应的字符
%d, %i - 接受一个数字并将其转化为有符号的整数格式
%o - 接受一个数字并将其转化为八进制数格式
%u - 接受一个数字并将其转化为无符号整数格式
%x - 接受一个数字并将其转化为十六进制数格式, 使用小写字母
%X - 接受一个数字并将其转化为十六进制数格式, 使用大写字母
%e - 接受一个数字并将其转化为科学记数法格式, 使用小写字母e
%E - 接受一个数字并将其转化为科学记数法格式, 使用大写字母E
%f - 接受一个数字并将其转化为浮点数格式
%g(%G) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式
%q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
%s - 接受一个字符串并按照给定的参数格式化该字符串

为进一步细化格式, 可以在%号后添加参数. 参数将以如下的顺序读入:

(1) 符号: 一个+号表示其后的数字转义符将让正数显示正号. 默认情况下只有负数显示符号.
(2) 占位符: 一个0, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格.
(3) 对齐标识: 在指定了字串宽度时, 默认为右对齐, 增加-号可以改为左对齐.
(4) 宽度数值
(5) 小数位数/字串裁切: 在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位.

string.format("%c", 83)                 -- 输出S
string.format("%+d", 17.0)              -- 输出+17
string.format("%05d", 17)               -- 输出00017
string.format("%o", 17)                 -- 输出21
string.format("%u", 3.14)               -- 输出3
string.format("%x", 13)                 -- 输出d
string.format("%X", 13)                 -- 输出D
string.format("%e", 1000)               -- 输出1.000000e+03
string.format("%E", 1000)               -- 输出1.000000E+03
string.format("%6.3f", 13)              -- 输出13.000
string.format("%q", "One\nTwo")         -- 输出"One\
                                        --   Two"
string.format("%s", "monkey")           -- 输出monkey
string.format("%10s", "monkey")         -- 输出    monkey
string.format("%5.3s", "monkey")        -- 输出  mon

9.3 字符串转整数

-- 字符转换
-- 转换第一个字符
print(string.byte("Lua"))
-- 转换第三个字符
print(string.byte("Lua",3))
-- 转换末尾第一个字符
print(string.byte("Lua",-1))
-- 第二个字符
print(string.byte("Lua",2))
-- 转换末尾第二个字符
print(string.byte("Lua",-2))

-- 整数 ASCII 码转换为字符
print(string.char(97))


以上代码执行结果为:
76
97
97
117
117
a

9.4 匹配模式

  字符类指可以匹配一个特定字符集合内任何字符的模式项。比如,字符类 %d 匹配任意数字。所以你可以使用模式串 %d%d/%d%d/%d%d%d%d 搜索 dd/mm/yyyy 格式的日期:

s = "Deadline is 30/05/1999, firm"
date = "%d%d/%d%d/%d%d%d%d"
print(string.sub(s, string.find(s, date)))    --> 30/05/1999

点链接,看所有匹配模式:https://www.runoob.com/lua/lua-strings.html

10. 数组

一维数组

array = {}

for i= -2, 2 do
   array[i] = i *2
end

for i = -2,2 do
   print(array[i])
end

多维数组

-- 初始化数组
array = {}
for i=1,3 do
   array[i] = {}
      for j=1,3 do
         array[i][j] = i*j
      end
end

-- 访问数组
for i=1,3 do
   for j=1,3 do
      print(array[i][j])
   end
end

11.迭代器

11.1 泛型for迭代器

  泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。

泛型 for 迭代器提供了集合的 key/value 对:

for k, v in pairs(t) 
do
    print(k, v)
end
k, v为变量列表;pairs(t)为表达式列表。
array = {"Google", "Runoob"}

for key,value in ipairs(array)
do
   print(key, value)
end


输出结果为:
1  Google
2  Runoob

执行过程

  • 首先,初始化,计算 in 后面表达式的值,表达式应该返回泛型 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。
  • 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
  • 第三,将迭代函数返回的值赋给变量列表。
  • 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
  • 第五,回到第二步再次调用迭代函数

11.2 无状态迭代器

  无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
  每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
  这种无状态迭代器的典型的简单的例子是 ipairs,它遍历数组的每一个元素,元素的索引需要是数值。

实现 数字 n 的平方:

function square(iteratorMaxCount,currentNumber)
   if currentNumber<iteratorMaxCount
   then
      currentNumber = currentNumber+1
   return currentNumber, currentNumber*currentNumber
   end
end

for i,n in square,3,0
do
   print(i,n)
end


结果为:
1    1
2    4
3    9

  迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs 和迭代函数都很简单,我们在 Lua 中可以这样实现:

function iter (a, i)
    i = i + 1
    local v = a[i]
    if v then
       return i, v
    end
end
 
function ipairs (a)
    return iter, a, 0
end

当 Lua 调用 ipairs(a) 开始循环时,他获取三个值:迭代函数 iter、状态常量 a、控制变量初始值 0;然后 Lua 调用 iter(a,0) 返回 1, a[1](除非 a[1]=nil);第二次迭代调用 iter(a,1) 返回 2, a[2]……直到第一个 nil 元素。

11.3 多状态迭代器

  很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数。

array = {"Google", "Runoob"}

function elementIterator (collection)
   local index = 0
   local count = #collection
   -- 闭包函数
   return function ()
      index = index + 1
      if index <= count
      then
         --  返回迭代器的当前元素
         return collection[index]
      end
   end
end

for element in elementIterator(array)
do
   print(element)
end


结果为:
Google
Runoob

12.table(表)

  table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
  Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
  Lua table 是不固定大小的,你可以根据自己需要进行扩容。
  Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。

-- 初始化表
mytable = {}

-- 指定值
mytable[1]= "Lua"

-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存

  当我们为 table a 设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。

-- 简单的 table
mytable = {}
print("mytable 的类型是 ",type(mytable))

mytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引为 1 的元素是 ", mytable[1])
print("mytable 索引为 wow 的元素是 ", mytable["wow"])

-- alternatetable和mytable的是指同一个 table
alternatetable = mytable

print("alternatetable 索引为 1 的元素是 ", alternatetable[1])
print("mytable 索引为 wow 的元素是 ", alternatetable["wow"])

alternatetable["wow"] = "修改后"

print("mytable 索引为 wow 的元素是 ", mytable["wow"])

-- 释放变量
alternatetable = nil
print("alternatetable 是 ", alternatetable)

-- mytable 仍然可以访问
print("mytable 索引为 wow 的元素是 ", mytable["wow"])

mytable = nil
print("mytable 是 ", mytable)


结果为:
mytable 的类型是     table
mytable 索引为 1 的元素是     Lua
mytable 索引为 wow 的元素是     修改前
alternatetable 索引为 1 的元素是     Lua
mytable 索引为 wow 的元素是     修改前
mytable 索引为 wow 的元素是     修改后
alternatetable 是     nil
mytable 索引为 wow 的元素是     修改后
mytable 是     nil

12.1 table常用操作

1. table连接

table.concat (table [, sep [, start [, end]]]):
concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。

fruits = {"banana","orange","apple"}
-- 返回 table 连接后的字符串
print("连接后的字符串 ",table.concat(fruits))

-- 指定连接字符
print("连接后的字符串 ",table.concat(fruits,", "))

-- 指定索引来连接 table
print("连接后的字符串 ",table.concat(fruits,", ", 2,3))

执行以上代码输出结果为:
连接后的字符串     bananaorangeapple
连接后的字符串     banana, orange, apple
连接后的字符串     orange, apple

2. 插入和删除

插入table.insert (table, [pos,] value)
在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾.
  
删除:table.remove (table [, pos])
返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。

fruits = {"banana","orange","apple"}

-- 在末尾插入
table.insert(fruits,"mango")
print("索引为 4 的元素为 ",fruits[4])

-- 在索引为 2 的键处插入
table.insert(fruits,2,"grapes")
print("索引为 2 的元素为 ",fruits[2])

print("最后一个元素为 ",fruits[5])
table.remove(fruits)
print("移除后最后一个元素为 ",fruits[5])

结果为:
索引为 4 的元素为     mango
索引为 2 的元素为     grapes
最后一个元素为     mango
移除后最后一个元素为     nil

3. 排序

table.sort (table [, comp])
对给定的table进行升序排序。

fruits = {"banana","orange","apple","grapes"}
print("排序前")
for k,v in ipairs(fruits) do
        print(k,v)
end

table.sort(fruits)
print("排序后")
for k,v in ipairs(fruits) do
        print(k,v)
end


结果为:
排序前
1    banana
2    orange
3    apple
4    grapes
排序后
1    apple
2    banana
3    grapes
4    orange

13.模块与包

  模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
  Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。以下为创建自定义模块 module.lua,文件代码格式如下:
所以使用这种API的方法是,类似于调用table中的元素那样来操作函数调用模块中的常量或函数

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
 
-- 定义一个常量
module.constant = "这是一个常量"
 
-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end
 
local function func2()
    print("这是一个私有函数!")
end
 
function module.func3()
    func2()
end
 
return module

13.1 require函数

Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:require("<模块名>")或者require "<模块名>"

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
 
print(module.constant)
 
module.func3()

以上代码执行结果为:
这是一个常量
这是一个私有函数!

或者给加载的模块定义一个别名变量,方便调用:

test_module2.lua 文件
-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")
 
print(m.constant)
 
m.func3()

以上代码执行结果为:
这是一个常量
这是一个私有函数!

13.2 加载机制

  对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
  require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
  当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 “~/lua/” 路径加入 LUA_PATH 环境变量里:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

13.3 C包

  Lua和C是很容易结合的,使用 C 为 Lua 写包。
  与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。
  Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:

local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket")

  loadlib 函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在Lua中调用他。
  如果加载动态库或者查找初始化函数时出错,loadlib 将返回 nil 和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:

local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f()  -- 真正打开库

  一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。
  将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。

14.元表

  在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)。

  因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。

  例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。

  当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 __add 的字段,若找到,则调用对应的值。 __add 等即时字段,其对应的值(往往是一个函数或是 table)就是"元方法"。

  有两个很重要的函数来处理元表:

setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
getmetatable(table): 返回对象的元表(metatable)。
以下实例演示了如何对指定的表设置元表:

mytable = {}                          -- 普通表
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表

以上代码也可以直接写成一行:
mytable = setmetatable({},{})

以下为返回对象元表:
getmetatable(mytable)                 -- 这会返回 mymetatable

https://www.runoob.com/lua/lua-metatables.html

14.1 __index方法

  这是 metatable 最常用的键。
  当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。

  如果__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)

实例输出结果为:
value1    metatablevalue

1 mytable 表赋值为 {key1 = “value1”}。
2 mytable 设置了元表,元方法为 __index。
3 在mytable表中查找 key1,如果找到,返回该元素,找不到则继续。
4 在mytable表中查找 key2,如果找到,返回 metatablevalue,找不到则继续。
5 判断元表有没有__index方法,如果__index方法是一个函数,则调用该函数。
6 元方法中查看是否传入 “key2” 键的参数(mytable.key2已设置),如果传入 “key2” 参数返回 “metatablevalue”,否则返回 mytable 对应的键值。

我们可以将以上代码简单写成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } }) print(mytable.key1,mytable.key2)

总结

Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

  • 1.在表中查找,如果找到,返回该元素,找不到则继续
  • 2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
  • 3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

14.2 __newindex方法

  __newindex 元方法用来对表更新,__index则用来对表访问 。
  当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。

mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })

print(mytable.key1)

mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)

mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)

以上实例执行输出结果为:
value1
nil    新值2
新值1    nil

  以上实例中表设置了元方法 __newindex,在对新索引键(newkey)赋值时(mytable.newkey = “新值2”),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。

15.协同程序

  协同程序,具有独立的堆栈、局部变量、指令指针,与其他协同程序共享全局变量和其它大部分东西。

协同程序和线程的区别

多线程程序可以同时运行,而协同程序需要彼此协作运行
在任一时刻只能由一个协同程序运行,并且只有这个程序在要求被挂起时才会被 挂起
协同程序类似于同步的多线程

你可能感兴趣的:(Unity,lua,unity,开发语言)