Lua 入门

本文参考 菜鸟教程

Lua 简介

  • Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能

环境安装

Lua 安装可参考 CSDN 直接在 Lua 官网 下载,Windows 和 Linux 都有提供~

  • CentOS 7 安装可参考 CSDN

IDE 使用 VSCode,安装下列插件 (一个用于调试,一个用于代码补全):

在这里插入图片描述

  • 要想进行调试需要配置 launch.json

在这里插入图片描述

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Lua Debug",    // 名字,可以随意更改
            "type": "lua",          // 要调试的语言类型
            "request": "launch",    // launch模式,等同于使用lua.exe来执行你的代码
            "program": "${workspaceFolder}/test.lua",   // lua.exe执行的入口文件
            "cwd": "${workspaceFolder}",    // lua.exe的当前目录
            "stopOnEntry": false,   // 开始调试时是否先暂停
            "consoleCoding": "utf8",// lua的标准输出的编码
            "path": "${workspaceFolder}/?.lua", // 初始化package.path
            "cpath": "${workspaceFolder}/?.dll",    // 初始化package.cpath
            "arg": []				// lua.exe 的命令行参数
        }
    ]
}

更多配置参考:GitHub


上面的 Debug 插件在 Windows 下是好用的,但是我到 Linux 下用的时候它就是不好使… 崩溃。好在后来又找到了这个插件:

在这里插入图片描述
配置更简单,直接在设置里填写启动 Lua 解释器的命令即可:

在这里插入图片描述

这个插件也很有特点,Linux 下好用 Windows 下就是不好用。也很有可能是我操作有问题。目前作为新手就只能先在 Windows 和 Linux 下分别用这两款插件了… (菜鸡的左右横跳)

Lua 基础

运行 lua 程序

  • 可以直接使用解释器进行交互式编程
  • 也可以编写 .lua 文件进行脚本式编程
$ lua hello.lua

注释

  • 单行注释:两个减号
--
  • 多行注释: Multiple line comments start with double hyphen and two square brackets. and end with two square brackets.
--[[
	print('Hello World')
]]
  • 多行注释多加一个 - 后就相当于取消注释
---[[
	print('Hello World')
]]

关键字

  • Lua reserved words are: and, break, do, else, elseif, end, false, for, function, if, in, local, nil, not, or, repeat, return, then, true, until, while.
  • 一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量
print(_VERSION)		-- output: Lua 5.1

变量

  • Lua 变量有三种类型:全局变量局部变量表中的域
    • 默认情况下,变量总是认为是全局的,那怕是语句块或是函数里,除非用 local 显式声明为局部变量
    • 变量的默认值均为 nil

应该尽可能的使用局部变量,1. 避免命名冲突;2. 访问局部变量的速度比全局变量更快

  • 全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil
  • 如果你想删除一个全局变量,只需要将变量赋值为nil。也就是说,只要变量不等于 nil,这个变量就存在
  • Variables hold values which have types, variables don’t have types.
    • 这点跟 python 很像

赋值语句

  • Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量
a, b = 10, 2 * x       -->       a=10; b=2*x
  • 遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值
x, y = y, x                     -- swap 'x' for 'y'
  • 变量个数和值的个数不一致时,Lua 会一直以变量个数为基础采取以下策略:
    • 变量个数 > > > 值的个数: 按变量个数补足nil
    • 变量个数 < < < 值的个数: 多余的值会被忽略
a, b, c = 0, 1
print(a, b, c)             --> 0   1   nil
 
a, b = a+1, b+1, b+2     -- value of b+2 is ignored
print(a, b)               --> 1   2
 
a, b, c = 0
print(a, b, c)             --> 0   nil   nil
  • 多值赋值经常用来交换变量,或将函数调用返回给变量
a, b = f()

Lua 数据类型

  • Lua 中有 8 个基本类型:nilbooleannumberstringuserdatafunctionthreadtable

  • 我们可以使用 type 函数测试给定变量或者值的类型:
a = 1
b = "abc"
c = {}
d = print

print(type(a))	--> number
print(type(b))	--> string
print(type(c))	--> table
print(type(d))	--> function

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

nil

  • nil 类型表示一个无效值(在条件表达式中相当于false
> print(type(a))	-- 打印一个没有赋值的变量
nil
  • 给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉
tab1 = { key1 = "val1", key2 = "val2", "val3" }
for k, v in pairs(tab1) do
    print(k .. " - " .. v)
end
 
tab1.key1 = nil
for k, v in pairs(tab1) do
    print(k .. " - " .. v)
end

output:

1 - val3
key2 - val2
key1 - val1
1 - val3
key2 - val2

  • 注意:nil 作比较时应该加上双引号:
> type(X)
nil
> type(X)==nil
false
> type(X)=="nil"
true

boolean

  • boolean 包含两个值:falsetrue
  • Lua 只把 falsenil 看作 false,其余均为 true (0 也为 true)

number

  • Lua 默认只有一种 number 类型 – double(双精度)类型
    • 默认类型可以修改 luaconf.h 里的定义
-- 均输出 number
print(type(2))
print(type(2.2))
print(type(7.8263692594256e-06))

string

  • string 由一对双引号单引号来表示
> print("'Hello', \"World\"")	-- 支持单引号里嵌套双引号,但嵌套单引号需要使用转义字符
'Hello', "World"
> print('\'Hello\', "World"')	-- 支持双引号里嵌套单引号,但嵌套双引号需要使用转义字符
'Hello', "World"
  • 也可以用 2 个方括号 [[ ]] 来表示"一块"字符串, 可以跨越多行且默认加上换行符,且里面可以随便加单引号或双引号
html = [[
<html>
<head></head>
<body>
    <a href="http://www.runoob.com/">菜鸟教程</a>
</body>
</html>
]]
print(html)

字符串连接 ..

> print('Hello' .. ' World')
Hello World

stringnumber 的转换

显式类型转换

  • tostring(num)
  • tonumber(s)

隐式类型转换

  • 运行时,Lua 会自动在 stringnumber 之间自动进行类型转换:
    • 在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字
    • 在对两个 number 进行连接运算时,会自动将其结果转为string
> print("2" + 6)
8.0
> print("2" + "6")
8.0
> print("2 + 6")
2 + 6
> print("-2e2" * "6")
-1200.0
> print("error" + 1)
stdin:1: attempt to perform arithmetic on a string value
stack traceback:
        stdin:1: in main chunk
        [C]: in ?
>
-- 当在一个数字后面写 `..` 时,必须加上空格以防止被解释错
> print(111 .. 222)
111222
> print(type(111 .. 222))
string

计算字符串长度 #

实际上输出的是字符串所占字节数。对于英文来说一个字母正好是一个字节,因此输出的正好是字符串长度;但是对中文就不一定了

> len = "www.runoob.com"
> print(#len)
14
> print(#"www.runoob.com")
14
> print(#'你好')
4

字符串操作

string.upper, string.lower
  • string.upper(argument): 字符串全部转为大写字母
  • string.lower(argument)
string.reverse
  • string.reverse(arg): 字符串反转
> string.reverse("Lua")
auL
string.charstring.byte
  • string.char(arg) , string.byte(arg[,int]): char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)
> string.char(97,98,99,100)
abcd
> string.byte("ABCD",4)
68
> string.byte("ABCD")
65
string.len
  • string.len(arg): 计算字符串长度
string.len("abc")
3
string.rep
  • string.rep(string, n): 返回字符串stringn个拷贝
> string.rep("abcd",2)
abcdabcd
string.format
  • string.format(...): 返回一个类似printf的格式化字符串
> string.format("the value is:%d", 4)
the value is:4
  • 转义码:
    • %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 n n, 若后接 f (浮点数转义符, 如 %6.3f)则设定该浮点数的小数只保留 n n n 位, 若后接 s (字符串转义符, 如 %5.3s) 则设定该字符串有 5 位宽度,但只显示前 3 位.
string.sub (按位置截取)
  • string.sub(s, i [, j]) : 截取字符串
    • i 为截取开始位置。j为截取结束位置,默认为 -1,即最后一个字符
string.sub('Hello', 2, 2)	--> e
string.gsub (按照匹配模式进行替换)
  • string.gsub(mainString,findString,replaceString,num): 在字符串中替换
    mainString 为要操作的字符串, findString 为被替换的字符,replaceString 为要替换的字符,num 为替换次数(可以忽略,默认为全部替换)
> string.gsub("aaaa","a","z",3);
zzza    3

注意:替换是按照字节来替换的,如:

> print(string.gsub("hello,up-down!", "%A", ".")) -- 中文逗号,占两个字节
hello..up.down. 4
string.find (按照匹配模式查找,返回位置)
  • string.find(str, substr, [init, [end]]): 在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil
> string.find("Hello Lua user", "Lua", 1) 
7    9
string.gmatch (按照匹配模式查找,返回迭代器)
  • string.gmatch(str, pattern): 返回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil
> for word in string.gmatch("Hello Lua user", "%a+") do print(word) end
Hello
Lua
user
string.match (按照匹配模式查找,返回所有捕获结果)
  • string.match(str, pattern, init): string.match()只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为 1
    在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil

如果单个匹配模式不用小括号包含的话,最后会返回完整匹配的字符串:

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

如果 pattern中 每个匹配模式用小括号包含的话,最后会按照多返回值的方式被捕获到:

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

匹配模式

用于模式匹配函数 string.find, string.gmatch, string.gsub, string.match

  • Lua 中的匹配模式直接用常规的字符串来描述

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


下面的表列出了 Lua 支持的所有字符类

  • 单个字符(除 ^ $ ( ) % . [ ] * + - ? 外): 与该字符自身配对

  • .(点): 与任何字符配对

  • %a: 与任何字母配对

  • %c: 与任何控制符配对(例如\n)

  • %d: 与任何数字配对

  • %l: 与任何小写字母配对

  • %p: 与任何标点(punctuation)配对

  • %s: 与空白字符配对

  • %u: 与任何大写字母配对

  • %w: 与任何字母/数字配对

  • %x: 与任何十六进制数配对

  • %z: 与任何代表0的字符配对

  • %x(此处x是非字母非数字字符): 与字符x配对. 主要用来处理表达式中有功能的字符( ^ $ ( ) % . [ ] * + - ? )的配对问题, 例如%%%配对

  • [数个字符类]: 与任何[]中包含的字符类配对. 例如[%w_]与任何字母/数字, 或下划线符号(_)配对

  • [^数个字符类]: 与任何不包含在[]中的字符类配对. 例如[^%s]与任何非空白字符配对

  • 当上述的字符类用大写书写时, 表示与非此字符类的任何字符配对. 例如, %S表示与任何非空白字符配对


模式条目可以是:

  • 单个字符类匹配该类别中任意单个字符;
  • 单个字符类跟一个 ‘*’, 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串;
  • 单个字符类跟一个 ‘+’, 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串;
  • 单个字符类跟一个 ‘-’, 将匹配零或更多个该类的字符。 和 ‘*’ 不同, 这个条目总是匹配尽可能短的串;
  • 单个字符类跟一个 ‘?’, 将匹配零或一个该类的字符。 只要有可能,它会匹配一个;
  • %n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物的子串。
  • %bxy, 这里的 xy 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 xy 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x+1 ,读到一个 y-1, 最终结束处的那个 y 是第一个记数到 0 的 y
    举个例子,条目 %b() 可以匹配到括号平衡的表达式
  • %f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 匹配出的那个空串的开始和结束点的计算就看成该处有个字符 ‘\0’ 一样
    例如,
print(string.gsub('a1', '%f[%d]', 'b'))		--> ab1     1

模式

  • 模式 指一个模式条目的序列。 在模式最前面加上符号 ‘^’ 将锚定从字符串的开始处做匹配。 在模式最后面加上符号 ‘$’ 将使匹配过程锚定到字符串的结尾。 如果 ‘^’ 和 ‘$’ 出现在其它位置,它们均没有特殊含义,只表示自身

捕获

  • 模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。 当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号
    例如,对于模式 “(a*(.)%w(%s*))” , 字符串中匹配到 “a*(.)%w(%s*)” 的部分保存在第一个捕获物中 (因此是编号 1 ); 由 “.” 匹配到的字符是 2 号捕获物, 匹配到 “%s*” 的那部分是 3 号。

  • 作为一个特例,空的捕获 () 将捕获到当前字符串的位置(它是一个数字)。
    例如,如果将模式 “()aa()” 作用到字符串 “flaaap” 上,将产生两个捕获物: 3 和 5

table (表)

  • table 的创建是通过构造表达式来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
-- 创建一个空的 table
local tbl1 = {}
 
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

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

  • table 其实是一个 关联数组,数组的索引可以是数字、字符串或表类型(但不能是 nil
-- table_test.lua 
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

output:

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

output:

Key    1
Key    2
Key    3
Key    4
  • table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始化的 table 都是 nil
> a = {}
> print(a['none'])
nil
  • 使用pairs遍历打印输出时,会先按照顺序输出表的值,然后再按照键值对的键的哈希值打印
tab = {"Hello", "World", a = 1, b = 2, z = 3, x = 10, y = 20, "Good", "Bye"}
for k,v in pairs(tab) do
    print(k.."  "..v)
end

output:

1  Hello
2  World
3  Good
4  Bye
x  10
y  20
z  3
b  2
a  1

索引

t[i]
t.i                 -- 当索引为字符串类型时的一种简化写法
> site = {}
> site["key"] = "www.runoob.com"
> print(site["key"])
www.runoob.com
> print(site.key)
www.runoob.com

数组

array = {"Lua", "Tutorial"}

for i = 1, 2 do
   print(array[i])
end
  • 数组索引默认为 1,但其实你可以用任何整数值做索引:
array = {}

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

for i = -2,2 do
   print(array[i])
end
  • 通过 table 也可以灵活地建立多维数组:
-- 初始化数组
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

table 操作

table.concat 连接
table.concat(table [, sep [, start [, end]]]):
  • 参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开
fruits = {"banana","orange","apple"}
-- 返回 table 连接后的字符串
print("连接后的字符串 ",table.concat(fruits))

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

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

output:

连接后的字符串     bananaorangeapple
连接后的字符串     banana, orange, apple
连接后的字符串     orange, apple
table.insert 插入和 table.remove 移除
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])

output:

索引为 4 的元素为     mango
索引为 2 的元素为     grapes
最后一个元素为     mango
移除后最后一个元素为     nil
table.sort 排序
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

output:

排序前
1    banana
2    orange
3    apple
4    grapes
排序后
1    apple
2    banana
3    grapes
4    orange

  • 排序也支持自定义排序规则,当返回 true 时,ab 前面:
table.sort(t, function(a, b) return a.A > b.A; end) --降序

function

  • function :由 C 或 Lua 编写的函数. function 也是对象,也可以存在变量里
-- function_test.lua 
function factorial1(n)
    if n == 0 then
        return 1
    else
        return n * factorial1(n - 1)
    end
end
print(factorial1(5))		--> 120
factorial2 = factorial1
print(factorial2(5))		--> 120

thread (线程)

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

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

userdata (自定义类型)

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

迭代器

  • 迭代器用来遍历标准模板库容器中的部分或全部元素
  • 在 Lua 中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型
    • 无状态的迭代器
    • 多状态的迭代器

pairs & ipairs

  • pairs可以遍历表中所有的keyvalue (包括 nil)
  • ipairs则只能从下标为1开始遍历所有数组下标的值,也就是说如果 ipairs 在迭代过程中会直接跳过所有手动设定key值的变量。并且在遇到 nil 时直接停止
tab = {1,2,a= nil,"d", nil, "c"}
for i,v in ipairs(tab) do
    print(i,v)
end

output:

1  1
2  2
3  d

泛型 for 迭代器

  • 泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数状态常量控制变量
  • 泛型 for 迭代器提供了集合的 key/value
for k, v in pairs(t) do -- k, v 为键值对
    print(k, v)
end

下面我们看看泛型 for 的执行过程:

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

无状态的迭代器

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

output:

1    1
2    4
3    9
-- ipairs 的实现
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 元素

多状态的迭代器

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

-- `elementIterator` 内使用了闭包函数,实现计算集合大小并输出各个元素
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

output:

Google
Runoob

循环

while 循环

while(condition)
do
   statements
end

for 循环

for var=exp1,exp2,exp3 do  
    <执行体>  
end  
  • varexp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 “执行体”。exp3 是可选的,默认为 1

  • for的三个表达式在循环开始前一次性求值,以后不再进行求值
a = 3

function f(x)
    return x * 2
end

for i = 1, f(a) do
	print(i)
	a = a - 1
end

output:

1
2
3
4
5
6

泛型for循环

  • 通过一个迭代器函数来遍历所有值
--打印数组a的所有值  
a = {"one", "two", "three"}
for i, v in ipairs(a) do
    print(i, v)
end 
  • i是数组索引值
  • v是对应索引的数组元素值
  • ipairs是一个迭代器函数,用来迭代数组

repeat...until

repeat
   statements
until( condition )

break 语句

  • 跳出最内层循环的执行

goto 语句

goto Label

Label 的格式为:

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

a = a + 1
if a < 3 then
    goto label   
end

output:

--- goto label ---
--- goto label ---

可以在 lable 中设置多个语句:

i = 0
::s1:: do
  print(i)
  i = i + 1
end
if i > 3 then
  os.exit()   -- i 大于 3 时退出
end
goto s1

output:

0
1
2
3

控制结构语句

注意,只有 falsenil 才被归为 false,其他值包括 0 均为 true

if 语句

if(布尔表达式)
then
   -- statement
end

if...else 语句

if(布尔表达式)
then
   -- statement
else
   -- statement
end

if...elseif...else 语句

if( 布尔表达式 1)
then
    -- statement
elseif( 布尔表达式 2)
then
    -- statement
else 
    -- statement
end

函数

optional_function_scope function function_name( argument1, argument2..., argumentn)
    function_body
    return result_params_comma_separated
end
  • optional_function_scope: 该参数是可选的,用于指定函数是全局函数还是局部函数,默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local
  • result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开

可以直接在定义时将函数赋值给变量:

myprint = function(param)
   -- statement
end

匿名函数

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

output:

key1 = val1
key2 = val2

可变参数

固定参数必须放在变长参数之前

  • 函数参数列表中使用三点 ... 表示函数有可变的参数
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", 变长参数为 1 和 2
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 函数来访问变长参数

  • select('#', …) 返回可变参数的长度
  • select(n, …) 返回 nselect('#',…) 的参数 ( − 1 -1 1 表示最后一个参数的位置)
do  
    function foo(...)  
        for i = 1, select('#', ...) do  -->获取参数总数
            local arg = select(i, ...); -->读取参数 select 返回多个值,但 arg只接受第一个
            print("arg", arg);  
        end  
    end  
 
    foo(1, 2, 3, 4);  
end

output:

arg    1
arg    2
arg    3
arg    4

注意一下多返回值的函数在赋值时的情况,仅仅只有放在所有逗号之后的那个函数会把返回值展开:

function add()
    return 1,0
end

local b,c,d,e = add(),add()

print(b) -- 1
print(c) -- 1
print(d) -- 0
print(e) -- nil

运算符

算术运算符

+, -, *, /, %(取余), ^(乘幂)

注意:^ 是右结合的,即:x^y^z <--> x^(y^z)

关系运算符

==, ~= (不等于), >, <, >=, <=

逻辑运算符

and, or, not

其他运算符

  • Lua 中没有三元表达式,但是可以利用 andor 来实现三元表达式:
(condition and {result1} or {result2})[1]

参考:https://www.runoob.com/w3cnote/trinocular-operators-in-lua.html

  • ..: 连接两个字符串

  • #: 一元运算符,返回字符串或表的长度

这里返回的表长实际上运作的跟直观感觉不一样… 只能对照着源码实现来看
可参考:https://www.runoob.com/w3cnote/lua-table-length-analysis.html

运算符优先级

优先级从高到低
^
not , - (unary)(负号)
* , / , %
+ , -
.. (字符串连接运算)
< , > , <= , >= , ~= , ==
and
or

Lua 模块与包

  • Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 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

require 函数

  • require函数用于加载模块。例如:
require("<模块名>")
require "<模块名>"
  • 执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量
-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
 
print(module.constant)
 
module.func3()
  • 可以给加载的模块定义一个别名变量,方便调用:
-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")
 
print(m.constant)
 
m.func3()

output:

这是一个常量
这是一个私有函数!

加载机制

  • 对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块
  • require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化

  • 当然,也可以自定义设置环境变量 LUA_PATH (Linux),在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以)。文件路径以 “;” 号分隔,最后的 2 个 “;;” 表示新加的路径后面加上原来的默认路径:
#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"
  • 接着,更新环境变量参数,使之立即生效
source ~/.profile

  • 如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始化的。搜索的策略跟上面的一样,只不过现在换成搜索的是 sodll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它

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 库了

Lua 元表 (Metatable)

  • 在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作。因此 Lua 提供了元表 (Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法
    • 例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a + b. 当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表之后检查是否有一个叫 “__add” 的字段若找到,则调用对应的值。"__add“等即时字段,其对应的值(往往是一个函数或是 table)就是"元方法
  • 有两个很重要的函数来处理元表:
    • setmetatable(table,metatable): 对指定 table 设置元表,如果元表中存在 __metatable 键值,setmetatable 会失败
    • getmetatable(table): 返回对象的元表
mytable = {}                          -- 普通表
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表

-- 等价于
mytable = setmetatable({},{})
  • 以下为返回对象元表:
getmetatable(mytable)                 -- 这回返回mymetatable

__index 元方法

  • 这是 metatable 最常用的键
  • 当你通过键来访问 table 的时候,如果这个键没有值,那么 Lua 就会寻找该 table 的 metatable(假定有 metatable)中的 __index

  • 如果 __index 包含一个表格,Lua 会在表格中查找相应的键
$ lua
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil
  • 如果 __index 包含一个函数的话,Lua 就会调用那个函数,table 和键会作为参数传递给函数
mytable = setmetatable({key1 = "value1"}, {
  __index = function(mytable, key)
    if key == "key2" then
      return "metatablevalue"
    else
      return nil
    end
  end
})

-- 等价于
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })

print(mytable.key1, mytable.key2) 	--> value1    metatablevalue

__newindex 元方法

  • 当你给表的一个缺少的索引赋值,解释器就会查找 __newindex 元方法:如果存在则调用这个函数而不进行赋值操作
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })

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

mytable.key1 = "新值1"
print(mytable.key1, mymetatable.key1)		--> 新值1    nil
-- 使用 rawset 函数来更新表
mytable = setmetatable({}, {
  __newindex = function(mytable, key, value)
                	rawset(mytable, key, "\""..value.."\"")
	  			end
})

mytable.key1 = 4

print(mytable.key1)	--> "4"

为表添加操作符

  • 表中对应的操作列表如下:
元方法对应的运算符
__add+
__sub-
__mul*
__div/
__mod%
__unm-
__concat
__eq==
__lt<
__le<=
-- 计算表中最大值,即计算表的元素个数
function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        end
    end
    return mn
end

-- 两表相加操作
mytable = setmetatable({ 1, 2, 3 }, {
  __add = function(mytable, newtable)
    for i = 1, table_maxn(newtable) do
      table.insert(mytable, table_maxn(mytable)+1,newtable[i])
    end
    return mytable
  end
})

secondtable = {4,5,6}

mytable = mytable + secondtable

for k,v in ipairs(mytable) do
	print(k,v)
end

output:

1    1
2    2
3    3
4    4
5    5
6    6

__call 元方法

  • __call 元方法在 Lua 调用一个值时调用
-- 计算表中最大值,即计算表的元素个数
function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        end
    end
    return mn
end

-- 定义元方法__call
mytable = setmetatable({10}, {
  __call = function(mytable, newtable)	-- 计算表中元素的和
        sum = 0
        for i = 1, table_maxn(mytable) do
                sum = sum + mytable[i]
        end
    	for i = 1, table_maxn(newtable) do
                sum = sum + newtable[i]
        end
        return sum
  end
})
newtable = {10,20,30}
print(mytable(newtable))	--> 70

__tostring 元方法

  • __tostring 元方法用于修改表的输出行为
mytable = setmetatable({ 10, 20, 30 }, {
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
                sum = sum + v
        end
    return "表所有元素的和为 " .. sum
  end
})
print(mytable)		--> 表所有元素的和为 60

Lua 面向对象

  • 对象由属性和方法组成。LUA 中最基本的结构是 table,所以需要table 来描述对象的属性. function 可以用来表示方法。那么 LUA 中的类可以通过 table + function 模拟出来
  • 至于继承,可以通过 metetable 模拟出来
    A class is a table which has a particular metatable.
Account = {balance = 0}
function Account.withdraw(v)
    Account.balance = Account.balance - v
end

Account.withdraw(100.00)
-- 元类
Rectangle = {area = 0, length = 0, breadth = 0}

-- 派生类的方法 new
-- 冒号的作用就是:定义函数时,给函数的添加隐藏的第一个参数self;调用函数时,默认把当前调用者作为第一个参数传递进去
function Rectangle:new (o, length, breadth)
	o = o or {}
	setmetatable(o, self)	-- 把 Rectangle 设为 o 的元表
	self.__index = self
	self.length = length or 0
	self.breadth = breadth or 0
	self.area = length * breadth;
	return o
end

-- 派生类的方法 printArea
function Rectangle:printArea()
  	print("矩形面积为 ", self.area)
end

创建对象

r = Rectangle:new(nil, 10, 20)	-- Rectangle.new(self, nil, 10, 20)

访问属性

  • 我们可以使用点号(.)来访问类的属性
print(r.length)

访问成员函数

  • 我们可以使用冒号 : 来访问类的成员函数
r:printArea()
-- 元类
Shape = {area = 0}

-- 基础类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end

-- 基础类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)

myshape:printArea()		--> 面积为     100

Lua 继承

-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  side = side or 0
  self.area = side*side;
  return o
end
-- 基础类方法 printArea
function Shape:printArea ()
  print("面积为 ",self.area)
end

-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()

Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
  o = o or Shape:new(o,side)
  setmetatable(o, self)
  self.__index = self
  return o
end

-- 派生类方法 printArea
function Square:printArea ()
  print("正方形面积为 ",self.area)
end

-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()

Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
  o = o or Shape:new(o)
  setmetatable(o, self)
  self.__index = self
  self.area = length * breadth
  return o
end

-- 派生类方法 printArea
function Rectangle:printArea ()
  print("矩形面积为 ",self.area)
end

-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()

output:

面积为     100
正方形面积为     100
矩形面积为     200
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值