Back to Blogs
lua

Lua 编程基础知识

Soloman
2022-06-02

Lua 编程基础知识

1 数据类型

Lua 有8种基本数据类型:nil、boolean、number、string、userdata、function、thread、table。

数据类型描述
nil只有值nil,表示一个无效值(在条件表达式中相当于false)
boolean包含两个值:false和true
number表示双精度类型的实浮点数
string字符串由一对双引号或单引号来表示
function由 C 或 Lua 编写的函数
userdata表示任意存储在Lua变量中的C数据结构
thread表示执行的独立线路,用于执行协同程序
tableLua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

可用type()函数检测变量或值的类型

--输出:string
print(type("www.soloman.vip"))

--输出:number
print(type(16384))

--输出:nil,因为soloman是一个未定义的变量
print(type(soloman))

1.1 nil

nil 除了表示一个无效值,还能删除变量,当给一个变量赋值为nil,则等同于把它删除了。nil 作类型比较时要使用双引号,因为type()函数返回的是字符串。

> type(soloman)
nil

> print(soloman == nil)
true

> type(soloman) == nil
false

> type(soloman) == "nil"
true

> type(type(soloman))
string

> type(type(soloman)) == string
false

> type(type(soloman)) == "string"
true

1.2 boolean

布尔类型只有true和false,Lua 把nil(注意:其类型仍然是nil)和false看做false,其它全都是true,0也是true

1.3 number

Lua 默认只有一种数字类型:double双精度浮点数

1.4 string

字符串可用一对单引号或双引号表示,还能用两个方括号"[[ ]]"表示一块字符串。在对数字字符串进行算术操作时,Lua 会尝试将其转换成数字。+并不能连接字符串,.. 才是Lua中的字符串连接符。使用#号放在字符串或变量前面来计算其长度,其输出值是字符串所占字节数,因此英文时等于字符数,但中文时等于2倍字符数(一个中文占用两个字节时)。

> print("2"+"2")
4

> print("2"+6)
8

> print("www." .. "soloman.vip")
www.soloman.vip

--数字也会被当做字符串来连接,Lua 在需要时会将string与number互相转换
> print(100 .. 86)
10086

> a = "Soloman"
> print(#a)
7

> print(#"welcome to soloman")
18

> print(#"你好,世界")
10

1.5 table

Lua 中的表可以用来创建不同的数据类型,如数组、字典等。Lua 中默认索引以1开始。表不会固定长度,当加入新数据时其长度自动增大,未初始化的表为nil

> tbl = {"https", "://", "www.", "soloman.vip"}

> print(tbl)
table: 00000000007c0ad0

> print(tbl[0])
nil

> print(tbl[1])
https

> print(tbl[4])
soloman.vip

--移除引用,Lua 垃圾回收会自动释放内存
tbl = nil

1.6 functiion

Lua 中函数使用function关键字定义,函数可以存在变量里

function factorial(n)
    if n == 0 then
        return 1
    else
        return n * factorial(n - 1)
    end
end

1.7 thread

Lua 中主要使用协程(coroutine)

方法描述
coroutine.create()创建 coroutine,返回 coroutine, 参数是一个函数,当和 resume 配合使用的时候就唤醒函数调用。coroutine.create 方法只要建立了一个协程 ,那么这个协程的状态默认就是suspend。使用resume方法启动后,会变成running状态;遇到yield时将状态设为suspend;如果遇到return,那么将协程的状态改为dead
coroutine.resume()重启 coroutine,和 create 配合使用。这个方法只要调用就会返回一个boolean值,coroutine.resume 方法如果调用成功,那么返回true;如果有yield方法,同时返回yield括号里的参数;如果没有yield,那么继续运行直到协程结束;直到遇到return,将协程的状态改为dead,并同时返回return的值。coroutine.resume方法如果调用失败(调用状态为dead的协程会导致失败),那么返回false,并且带上一句"cannot resume dead coroutine"
coroutine.yield()挂起 coroutine,将 coroutine 设置为挂起状态
coroutine.status()查看 coroutine 的状态 注:coroutine 的状态有三种:dead,suspended,running
coroutine.wrap()创建 coroutine,返回一个函数,一旦你调用这个函数,就进入 coroutine,和 create 功能类似
coroutine.running()返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用running的时候,就是返回一个 corouting 的线程号

1.8 userdata

一种用户自定义数据,可以将任意C/C++类型的数据存储到Lua变量中调用

2 变量

Lua 变量有3种类型:全局变量、局部变量、表中的域。在 Lua 中除了用 local 显示声明为局部变量,否则都是全局变量。局部变量的作用域从声明位置开始到所在语句块结束。变量的默认值均为 nil

3 循环

循环类型描述
while 循环在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。
for 循环重复执行指定语句,重复次数可在 for 语句中控制。
repeat...until重复执行循环,直到 指定的条件为真时为止
循环嵌套可以在循环内嵌套一个或多个循环语句(while do ... end;for ... do ... end;repeat ... until;)

3.1 while

--格式
while(condition)
do
   statements
end

--例子
a = 0
while (a < 18)
do
    print("Value of a is:", a)
    a = a + 1
end

3.2 数值 for 循环

var 从start_index到end_index,步长为step,可选,默认为1

--格式
for var=start_index, end_index, step do
    <statements>
end

--例子
for i=10, 1, -1 do
    print(i)
end

3.3 泛型 for 循环

通过一个迭代器函数来遍历所有值。pairs 能迭代所有键值对,ipairs 可以想象成 int+pairs,只会迭代键为数字的键值对。

days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}  
for i,v in ipairs(days) do
    print(i, v)
end  

3.4 repeat...until

至少会执行一次循环体,循环直到condition为true时结束

--格式
repeat
   statements
until( condition )

--例子
a = 10
repeat
    print("Value of a is:", a)
    a = a + 1
until(a > 18)

3.5 goto

goto 语句可以无条件跳转到被标记的语句处

goto label

:: label ::

--例子
local a = 16
::soloman:: print("goto label: soloman")

a = a + 1
if a < 18 then
    goto soloman
end

4 流程控制

语句描述
if 语句if 语句由一个布尔表达式作为条件判断,其后紧跟其他语句组成
if...else 语句if 语句可以与else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码
if 嵌套语句你可以在ifelse if中使用一个或多个 ifelse if 语句

4.1 if

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

--例子
local a = 20
if(a > 18) then
    print("Age larger than 18.")
end

4.2 if...else

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


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

5 函数

--函数结构
[function_scope] function func_name( argument1, argument2..., argumentn)
    function_body
    return result_params_comma_separated
end

--例子
function max(num1, num2)
    if(num1 > num2) then
        result = num1
    else
        result = num2
    end
    return result
end

--Lua 函数可以返回多个值,使用逗号分隔。Lua 函数能用可变参数,用三点...表示。
function add(...)
    local sum, count = 0, 0
    for i, v in ipairs{...} do
        sum = sum + v
        count = count + 1
    end
    return sum, count
end
--调用
s, c = add(6, 7, 8, 9)
print(s, c)

--获取可变参数的数量
select("#", ...)
--返回从起点n到结束位置的所有值
select(n, ...)

6 运算符

Lua 运算符主要有:算术运算符、关系运算符、逻辑运算符、其它运算符

6.1 算术运算符

操作符描述实例(A = 10, B = 20)
+加法A + B 输出结果 30
-减法A - B 输出结果 -10
*乘法A * B 输出结果 200
/除法B / A 输出结果 2
%取余B % A 输出结果 0
^乘幂A^2 输出结果 100
-负号-A 输出结果 -10
//整除运算符(>=lua5.3)5//2 输出结果 2

6.2 关系运算符

操作符描述实例(A = 10, B = 20)
==等于,检测两个值是否相等,相等返回 true,否则返回 false(A == B) 为 false
~=不等于,检测两个值是否相等,不相等返回 true,否则返回 false(A ~= B) 为 true
>大于,如果左边的值大于右边的值,返回 true,否则返回 false(A > B) 为 false
<小于,如果左边的值大于右边的值,返回 false,否则返回 true(A < B) 为 true
>=大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false(A >= B) 返回 false
<=小于等于,如果左边的值小于等于右边的值,返回 true,否则返回 false(A <= B) 返回 true

6.3 逻辑运算符

操作符描述实例(A = true, B = false)
and逻辑与操作符。若 A 为 false,则返回 A,否则返回 B(A and B) 为 false
or逻辑或操作符。若 A 为 true,则返回 A,否则返回 B(A or B) 为 true
not逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 falsenot(A and B) 为 true

6.4 其它运算符

操作符描述实例
..连接两个字符串a..b,其中 a 为 "Hello ",b 为 "World",输出结果为 "Hello World"
#一元运算符,返回字符串或表的长度#"Hello" 返回 5

7 数组

Lua 数组是一组相同数据类型的元素集合,数组大小不固定。

array = {"soloman.vip", "sundaydesign.top"}
for i=0, 2 do
    print(array[i])
end
--[[
索引从1开始,索引0的没有值即为nil,所以输出:
nil
soloman.vip
sundaydesign.top
--]]

--负数索引值
array = {}
for i=-2, 2 do
    array[i] = i * 2
end
for i=-2, 2 do
    print(array[i])
end

--3 X 4多维数组
array = {}
for i=1, 3 do
    array[i] = {}
    for j=1, 4 do
        array[i][j] = i * j
    end
end

8 迭代器

Lua 迭代器是一种支持指针类型的结构,可以遍历集合的每一个元素。Lua 迭代器分为无状态迭代器和多状态迭代器,常用于泛型 for 循环

for k, v in pairs(t) do
    print(k, v)
end

array = {"soloman.vip", "sundaydesign.top"}
for key, value in ipairs(array)
do
    print(key, value)
end

9 模块与包

Lua 模块把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,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()
    print("外部只能调用公有函数,公有函数可调用内部私有函数")
    func2()
end

return module

Lua 使用require函数用来加载模块

--格式
require("<模块名>")
require "<模块名>"

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

10 元表

Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。有两个很重要的函数来处理元表:

  • setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
  • getmetatable(table): 返回对象的元表(metatable)。
mytable = {}                          -- 普通表
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表

-- 以上三行合为一行
mytable = setmetatable({},{})

getmetatable(mytable)                 -- 这会返回 mymetatable

10.1 __index 元方法

Lua 查找一个表元素时就是如下 3 个步骤:

  1. 在表中查找,如果找到,返回该元素,找不到则继续
  2. 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续
  3. 判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复步骤 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值
mytable = setmetatable({key1 = "www.soloman.vip"}, {
  __index = function(mytable, key)
    if key == "key2" then
      return "www.sundaydesign.top"
    else
      return nil
    end
  end
})

print(mytable.key1, mytable.key2, mytable.key3)
--[[
输出:
www.soloman.vip
www.sundaydesign.top
nil
--]]

10.2 __newindex 元方法

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

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

print(mytable.key1)

mytable.newkey = "www.soloman.vip"
print(mytable.newkey, mymetatable.newkey)

mytable.key1 = "www.sundaydesign.top"
print(mytable.key1, mymetatable.key1)

--[[
输出:
Hello, world
nil  www.soloman.vip
www.sundaydesign.top  nil
--]]

10.3 元表中的其他元方法

元方法描述
__add对应的运算符 +
__sub对应的运算符 -
__mul对应的运算符 *
__div对应的运算符 /
__mod对应的运算符 %
__unm对应的运算符 -
__concat对应的运算符 ..
__eq对应的运算符 ==
__lt对应的运算符 <
__le对应的运算符 <=
__call在 Lua 调用一个值时调用
__tostring用于修改表的输出行为

11 文件 I/O

Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。文件打开模式如下:

模式描述
r以只读方式打开文件,该文件必须存在。
w打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
a以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
r+以可读写方式打开文件,该文件必须存在。
w+打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a+与a类似,但此文件可读可写
b二进制模式,如果文件是二进制文件,可以加上b
+号表示对文件既可以读也可以写

11.1 简单模式

-- 以只读方式打开文件
file = io.open("test.lua", "r")

-- 设置默认输入文件为 test.lua
io.input(file)

-- 输出文件第一行
print(io.read())

-- 关闭打开的文件
io.close(file)

-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")

-- 设置默认输出文件为 test.lua
io.output(file)

-- 在文件最后一行添加 Lua 注释
io.write("--  test.lua 文件末尾注释")

-- 关闭打开的文件
io.close(file)

11.2 完全模式

使用 file:function_name 来代替 io.function_name 方法

-- 以只读方式打开文件
file = io.open("test.lua", "r")

-- 输出文件第一行
print(file:read())

-- 关闭打开的文件
file:close()

-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")

-- 在文件最后一行添加 Lua 注释
file:write("--test")

-- 关闭打开的文件
file:close()

12 错误处理

程序错误一般分为语法错误和运行错误,在 Lua 中可使用 assert 和 error 来处理错误。

-- 档位false时,返回error_message,否则什么也不做
assert(true/false, "error_message")

-- 终止正在执行的函数,并返回message的内容作为错误信息
-- level=1[默认]:返回调用error位置(文件+行号)
-- level=2:指出哪个调用error的函数
-- level=0:不添加错误位置信息
error(message, level)

-- 例子,a=10,b=nil
local function add(a,b)
   assert(type(a) == "number", "a 不是一个数字")
   assert(type(b) == "number", "b 不是一个数字")
   return a+b
end
add(10)

Lua 中处理错误,还可用函数 pcall(protected call)、xpcall 来包装需要执行的代码,并结合 debug 库获取错误堆栈信息。

13 数据库操作

Lua 数据库的操作库:LuaSQL

luasql = require "luasql.mysql"

--创建环境对象
env = luasql.mysql()

--连接数据库
conn = env:connect("数据库名", "用户名", "密码", "IP地址", 端口)

--设置数据库的编码格式
conn:execute"SET NAMES UTF8"

--执行数据库操作
cur = conn:execute("select * from role")
row = cur:fetch({},"a")

--文件对象的创建
file = io.open("role.txt", "w+");

while row do
    var = string.format("%d %s\n", row.id, row.name)
    print(var)
    file:write(var)
    row = cur:fetch(row, "a")
end


file:close()  --关闭文件对象
conn:close()  --关闭数据库连接
env:close()   --关闭数据库环境

14 参考文档

Lua 官方网站