跳转至

Lua 数据类型

基本数据类型

Lua 是动态类型语言, 其变量在运行时根据赋值的内容自动决定数据类型.

Lua 有以下 8 种基本数据类型:

  • nil: 表示一个空值或无效值
  • number: 表示双精度类型的浮点数
  • string: 表示字符串
  • boolean: 表示布尔值
  • table: 表示表类型
  • function: 表示函数类型
  • userdata: 表示用户自定义的数据类型
  • thread: 表示线程类型

Info

Loenn 侧的 Lua 脚本编写并不会涉及 userdatathread, 下面不做介绍.
实际上是自己也不清楚(草草

我们可以使用 type() 函数查看变量的类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- nil
print(type(nil))              -- 输出 nil

-- number
print(type(202))              -- 输出 number
print(type(3.14))             -- 输出 number

-- string
print(type("Celeste"))        -- 输出 string
print(type("mb6fbhsphdrcb"))  -- 输出 string

-- boolean
print(type(true))             -- 输出 boolean
print(type(false))            -- 输出 boolean

-- table
local numbers = {1, 2, 3}
print(type(numbers))          -- 输出 table

-- function
local function add(a, b)
    return a + b
end
print(type(add))              -- 输出 function

nil

nil 是一种特殊的类型, 只包含一个值 nil. 所有未初始化的变量都被视为 nil, 把变量赋值为 nil 即可删除这个变量:

1
2
3
4
5
print(a)        -- 输出 nil
local a = 10    -- 初始化变量 a
print(a)        -- 输出 10
a = nil         -- 删除变量 a
print(a)        -- 输出 nil

nil 在逻辑判断中被视为"假", 但其与布尔值 false 是不同的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
local value = nil
if value then
    print("Value is valid")
else
    print("Value is nil")    -- 输出 Value is nil
end

print(nil == false)    -- 输出 false
print(type(nil))       -- 输出 nil
print(type(false))     -- 输出 boolean

number

Lua 默认只有一种数字类型, 即双精度浮点数 double. 以下写法都可以表示 number 类型:

1
2
3
4
5
6
7
8
-- 输出结果都为 number
print(type(0))
print(type(2))
print(type(2.2))
print(type(0.2))
print(type(2e+1))
print(type(0.2e-1))
print(type(7.8263692594256e-06))

number 在进行浮点数运算时会出现数值精度误差, 这导致在进行计算时可能会出现预期外的结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
print(0.1 + 0.2)           -- 输出 0.3, 但实际计算结果可能是 0.30000000000000004
print(0.1 + 0.2 == 0.3)    -- 输出 false

-- 浮点数精度比较函数的可能实现
-- 添加参数 precision 判断两个浮点数的误差是否小到可以忽略
local function almostEqual(a, b, precision)
    precision = precision or 1e-10
    return math.abs(a - b) < precision
end

print(almostEqual(0.1 + 0.2, 0.3))    -- 输出 true, 忽略了微小误差

number 类型存在三种特殊值: infinite, -infinite 以及 NaN. 其中, NaN 是唯一不等于自身的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
print(type(1 / 0))     -- 输出 number
print(type(-1 / 0))    -- 输出 number
print(type(0 / 0))     -- 输出 number

print(1 / 0)          -- 输出 inf
print(math.huge)      -- 输出 inf
print(-1 / 0)         -- 输出 -inf
print(-math.huge)     -- 输出 -inf
print(0 / 0)          -- 输出 -nan(ind)

print((0 / 0) == (0 / 0))    -- 输出 false, NaN 不等于自身

Note

-nan(ind) 表示未定义结果, nan 表示这不是一个数字, 即 Not a Number. 其中 indindeterminate 的缩写, 即"不确定".
进行未定义行为的运算都会得到这个值, 例如 0 / 0, 0 ^ 0 以及 math.sqrt(-1) 等.

string

Luastring 类型用于表示文本数据或字符序列. 可以使用 ' ', " " 以及 [[ ]] 声明字符串:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local str1 = "Celeste"        -- 使用 "" 声明
local str2 = 'Everest'        -- 使用 '' 声明, "" 与 '' 并没有区别

-- 使用 [[]] 声明多行字符串, 这种声明方式会保留字符串中的换行与格式
-- print(str3) 的输出会是:  
--
-- some stuff i guess
-- another stuff                     
local str3 = [[               
some stuff i guess
another stuff
]]

print(type(str1))    -- 输出 string
print(type(str2))    -- 输出 string
print(type(str3))    -- 输出 string

可以在字符串中使用转义字符 \ 表示一些无法直接输入或有特殊含义的字符:

1
2
3
4
print("\"wow\"")             -- 输出 "wow"
print("IVXL \\\\ ECOM")      -- 输出 IVXL \\ ECOM
print("a line\nnew line")    -- 输出 a line
                             --      new line 

string 类型是不可变的, 所有修改字符串的操作本质上都是生成并返回修改后的字符串:

1
2
3
4
5
6
7
8
9
local str1 = "hi there"
print(str1)      -- 输出 hi there

-- 直接修改 string 的值会报错
-- str1[1] = "H"    报错 attempt to index a string value (local 'str1')

local str2 = "H" .. str1:sub(2)    -- 使用 .. 运算符与 sub 方法生成新的字符串 str2
print(str2)      -- 输出 Hi there
print(str1)      -- 输出 hi there, str1 的值并没有被改变

boolean

boolean 类型有两个取值: truefalse, 通常用于条件判断, 循环控制等. 可以直接声明 boolean 类型的变量为 truefalse:

1
2
3
4
5
local var1 = true
local var2 = false

print(var1)    -- 输出 boolean
print(var2)    -- 输出 boolean

nilfalse 是唯一被认为是"假"的值, 其他的值都被认为是"真":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-- nil 被认为是假
if nil then
    print("nil is true")
else
    print("nil is false")      -- 输出 nil is false
end

-- false 被认为是假
if false then
    print("false is true")
else
    print("false is false")    -- 输出 false is false
end

-- 0 被认为是真
if 0 then
    print("0 is true")         -- 输出 0 is true
else
    print("0 is false")
end

-- 空字符串 "" 被认为是真
if "" then
    print("\"\" is true")      -- 输出 "" is true
else
    print("\"\" is false")
end

-- 空表 {} 被认为是真
if {} then
    print("{} is true")        -- 输出 {} is true
else
    print("{} is false")
end

table

Lua 只有 table 一种基本数据结构, 即"表". 其可以实现数组, 字典, 堆栈, 链表等复杂数据结构. table 是关联数组, 可以使用除 nil 外任何类型的值作为数组的索引. 索引对应的值可以是任何类型, 包括 nil. table 没有预定义的大小, 可以动态扩展.

我们可以使用 {} 声明一个空表, 也可以向其填充数据以直接初始化表. 可以通过 [] 索引器访问对应的值:

Info

Lua 还提供了一种遍历表中元素的访问方式, 你可以在 Lua - 迭代器 找到.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
-- 声明一个空表
local emptyTable = {}    

-- 预先填充数据以直接初始化表, 其中每项元素使用 , 进行分隔
-- 连续索引的表
local fruitTable = {"apple", "orange", "strawberry"} 

-- 连续索引, 即数组类型的表其索引从 1 开始而不是 0
-- 索引越界会返回 nil
print(fruitTable[1])    -- 输出 apple
print(fruitTable[0])    -- 输出 nil
print(fruitTable[4])    -- 输出 nil

---------------------------------------------------------------------------------------------

local function someFunc()
    return "hi there"
end

local function noReturnValue() end

-- 非连续索引的表可以有任意类型和不连续的键
local mixedTable = {
    [1] = nil,                                -- number 作为索引, nil 作为值
    ["count"] = 202,                          -- string 作为索引, number 作为值
    result = true,                            -- string 作为索引时可以省略 [""], boolean 作为值
    [true] = "a boolean value",               -- boolean 作为索引, string 作为值  
    sayHi = someFunc(),                       -- string 作为索引, function 作为值
    [someFunc()] = "use function as index"    -- function 作为索引, string 作为值

--  不能使用没有返回值的函数作为索引, 访问时会报错 "table index is nil"
--  没有返回值的函数会返回 nil, nil 不能作为表的索引
--  [noReturnValue()] = "error"
}

-- 表内也可以嵌套表
local nestedTable = {
    [fruitTable] = "some fruit",     -- table 作为索引, string 作为值  
    inner = {                        -- string 作为索引, table 作为值
        innerIndex = "innerValue"    
    }
}

-- 通过 [] 访问索引对应的值
print(mixedTable[1])             -- 输出 nil
print(mixedTable["count"])       -- 输出 202
print(mixedTable.result)         -- 输出 true, string 作为索引可以通过 . 访问其对应的值
print(mixedTable[true])          -- 输出 a boolean value 

-- 函数作为索引时会使用其返回值作为索引, 有多个返回值则使用第一个返回值作为索引
-- 也可以直接通过函数名访问对应的值
print(mixedTable["hi there"])       -- 输出 use function as index
print(mixedTable[someFunc()])       -- 输出 use function as index

-- 函数作为值时访问会返回其返回值
print(mixedTable.sayHi)             -- 输出 hi there

print(nestedTable[fruitTable])         -- 输出 some fruit
print(nestedTable.inner.innerIndex)    -- 输出 innerValue

-- 访问不存在的索引会返回 nil
print(nestedTable.notExistIndex)       -- 输出 nil

我们也可以动态地向表中添加或删除元素:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
local someTable = {}

-- 动态添加元素
someTable.player = "Madeline"
someTable[2] = "second element"
someTable.square = function(x) return x ^ 2 end

--[[
添加完成后的表会是
someTable = {
    player = "Madeline",
    [2] = "second element",
    square = function(x) return x ^ 2 end
}
--]]

print(someTable.player)       -- 输出 Madeline
print(someTable[2])           -- 输出 second element
print(someTable.square(5))    -- 输出 25.0

-- 向索引赋值 nil 即可删除键值对
someTable.player = nil
someTable[2] = nil

--[[
删除完成后的表会是
someTable = {
    square = function(x) return x ^ 2 end
}
--]]

print(someTable.player)       -- 输出 nil
print(someTable[2])           -- 输出 nil
print(someTable.square(5))    -- 输出 25.0

function

Lua 中的函数使用 function 关键字声明.以 end 关键字结束. 函数的结构如下:

1
2
3
4
optionalFunctionScope function functionName(argument1, argument2..., argumentn)
    functionBody
    return result1, result2..., resultn
end

  • optionalFunctionScope: 可选部分, 函数的作用域. 默认为全局函数, 使用 local 关键字声明局部函数.
  • functionName: 函数名, 调用函数时所使用的标识符.
  • argument1, argument2..., argumentn: 可选部分, 传递给函数的参数. 参数的数量可以是任意多.
  • functionBody: 函数体, 函数需要执行的代码语句块.
  • result1, result2..., resultn: 可选部分, 函数的返回值. 返回值的数量可以是任意多.

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 定义局部函数 calculate, 接受两个参数 a b, 计算其相加,相减以及相乘的值并返回
local function calculate(a, b)
    local add = a + b
    local sub = a - b
    local mul = a * b
    return add, sub, mul
end

local addResult, subResult, mulResult = calculate(20, 5)
print(addResult, subResult, mulResult)                       -- 输出 25 15 100

Lua 支持可变参数, 函数可以接受可变数量的参数, 在参数列表中使用 ... 进行声明:

Note

... 是可变参数表达式, 表示一个动态的参数列表. ... 本身并不是一个表,而是一个特殊的语法结构. 必须显式地把 ... 转换成表才能访问这些参数.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local function average(...)
    local result = 0
    local args = {...}                 -- 显式转换可变参数 `...` 至表 args

    if #args == 0 then return 0 end    -- #args 返回表的长度, 即参数数量. 判断是否有参数传入

    for _, value in ipairs(args) do
        result = result + value
    end

    return result / #args   
end

print(average(1, 2, 3))           -- 输出 2.0
print(average(1, 2, 3, 5, 10))    -- 输出 4.2
print(average())                  -- 输出 0

Lua 支持函数式编程, 函数可以作为变量赋值, 参数传递以及结果返回:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-- 函数可以作为变量赋值
-- 使用匿名函数定义加法操作
local function add(a, b)
    return a + b
end

print(add(1, 2))    -- 输出 3

-- 函数可以作为参数传递
-- 定义一个接受函数作为参数的高阶函数
local function apply(func, x, y)
    return func(x, y)
end

local function sub(a, b)
    return a - b
end

-- 将 sub 函数作为参数传递给 apply 函数
print(apply(sub, 10, 3))    -- 输出 7

-- 函数可以作为返回结果
-- 定义 createMultiplier 函数, 返回一个新的函数, 该函数会将其参数与给定的 factor 相乘
local function createMultiplier(factor)
    -- 返回一个捕获 factor 的匿名函数
    return function(x)
        return x * factor
    end
end

-- 定义 double 函数, 该函数会将其参数乘2返回
local double = createMultiplier(2)
print(double(10))    -- 输出 20
上方 Lua 代码对应的 C# 代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 函数可以作为变量赋值
// 使用委托定义加法操作
Func<double, double, double> Add = (a, b) => a + b;
Console.WriteLine(Add(1, 2));    // 输出 3

// 函数可以作为参数传递
// 定义一个接受函数作为参数的高阶函数
double Apply(Func<double, double, double> func, double x, double y)
{
    return func(x, y);
}

Func<double, double, double> Sub = (a, b) => a - b;

// 将 Sub 函数作为参数传递给 Apply 函数
Console.WriteLine(Apply(Sub, 10, 3));    // 输出 7

// 函数可以作为返回结果
// 定义 CreateMultiplier 函数, 返回一个新的函数, 该函数会将其参数与给定的 factor 相乘
Func<double, double> CreateMultiplier(double factor)
{
    // 返回一个捕获 factor 的匿名函数
    return x => x * factor;
}

// 定义 DoubleFunc 函数, 该函数会将其参数乘2返回
var DoubleFunc = CreateMultiplier(2);
Console.WriteLine(DoubleFunc(10));    // 输出 20

类型转换

Lua 提供以下三种显式类型转换函数:

  • tonumber(e): 将 string 类型的 e 转换为 number, 失败会返回 nil.
  • tonumber(e, base): 将 string 类型的 e 从指定进制 base 转换为十进制 的 number, 失败会返回 nil.
  • tostring(v): 将任何类型 v 转换为 string, 函数和表会返回其内存地址.

以下是使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
local function printToNumber(e, base)
    local castedResult = tonumber(e)

    if base then
        castedResult = tonumber(e, base)
    end

    if type(castedResult) == "nil" then
        print("type: nil, cast failed")
    else
        print("type: " .. type(castedResult) .. " value: " .. castedResult)
    end
end

printToNumber("123")        -- 输出 type: number value: 123
printToNumber("123.45")     -- 输出 type: number value: 123.45
printToNumber("-123")       -- 输出 type: number value: -123
printToNumber("0")          -- 输出 type: number value: 0

printToNumber("FF", 16)     -- 输出 type: number value: 255
printToNumber("1010", 2)    -- 输出 type: number value: 10
printToNumber("1010", 3)    -- 输出 type: number value: 30
printToNumber("3", 2)       -- 输出 type: nil, cast failed

printToNumber("abc")        -- 输出 type: nil, cast failed
printToNumber("")           -- 输出 type: nil, cast failed
printToNumber(nil)          -- 输出 type: nil, cast failed
printToNumber(true)         -- 输出 type: nil, cast failed

---------------------------------------------------------------------------------------------

print(tostring(123))               -- 输出 123
print(tostring(123.45))            -- 输出 123.45
print(tostring(-123))              -- 输出 -123
print(tostring(0))                 -- 输出 0

print(tostring(nil))               -- 输出 nil
print(tostring(true))              -- 输出 true
print(tostring(false))             -- 输出 false

print(tostring(function() end))    -- 输出 function: 000002B0EBF58730, 内存地址可能不同
print(tostring({"some stuff"}))    -- 输出 table: 000002B0EBF563C0, 内存地址可能不同