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 | 表示执行的独立线路,用于执行协同程序 |
| table | Lua 中的表(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 嵌套语句 | 你可以在if 或 else if中使用一个或多个 if 或 else 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,逻辑非为 false | not(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 个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续
- 判断元表有没有
__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() --关闭数据库环境