Lua


2.1 数据类型


文档摘要

2.1 数据类型 Lua 数据类型详解:代码实践与深度解析 Lua 是一种轻量级、高效、可嵌入的脚本语言,以其简洁性和灵活性而闻名。理解 Lua 的数据类型是掌握这门语言的关键一步。Lua 是一门动态类型语言,这意味着变量没有固定的类型;你可以将任何类型的值赋给任何变量。Lua 会在运行时自动管理数据类型。 Lua 中的八种基本数据类型: nil (空) boolean (布尔) number (数字) string (字符串) table (表) function (函数) userdata (用户数据) thread (线程) 接下来,我们将逐一详细介绍每种数据类型。 nil (空) 类型只有一个值,即 。它表示无效值或空值,类似于其他语言中的 或 。

2.1 数据类型

Lua 数据类型详解:代码实践与深度解析

Lua 是一种轻量级、高效、可嵌入的脚本语言,以其简洁性和灵活性而闻名。理解 Lua 的数据类型是掌握这门语言的关键一步。Lua 是一门动态类型语言,这意味着变量没有固定的类型;你可以将任何类型的值赋给任何变量。Lua 会在运行时自动管理数据类型。

Lua 中的八种基本数据类型:

  1. nil (空)

  2. boolean (布尔)

  3. number (数字)

  4. string (字符串)

  5. table (表)

  6. function (函数)

  7. userdata (用户数据)

  8. thread (线程)

接下来,我们将逐一详细介绍每种数据类型。

1. nil (空)

nil 类型只有一个值,即 nil。它表示无效值空值,类似于其他语言中的 nullNone。在 Lua 中,未被赋值的变量默认值为 nil

代码实践:

-- 变量声明但未赋值,默认值为 nil local myVariable print(myVariable) -- 输出:nil print(type(myVariable)) -- 输出:nil -- 显式赋值为 nil myVariable = nil print(myVariable == nil) -- 输出:true -- nil 常用于表示函数没有返回值或操作失败 local function divide(a, b) if b == 0 then return nil, "Error: Division by zero" -- 返回 nil 和错误信息 else return a / b end end local result, errorMessage = divide(10, 2) if errorMessage then print(errorMessage) else print("Result:", result) -- 输出:Result: 5.0 end local result, errorMessage = divide(10, 0) if errorMessage then print(errorMessage) -- 输出:Error: Division by zero else print("Result:", result) end -- nil 也用于删除表中的键值对 local myTable = {key1 = "value1", key2 = "value2"} print(myTable.key1) -- 输出:value1 myTable.key1 = nil -- 删除 key1 print(myTable.key1) -- 输出:nil print(myTable) -- 输出:table: 0x... (内存地址,内容可能只剩 key2)

内容详解:

  • nil 在 Lua 中扮演着重要的角色,用于表示变量的空状态或操作的失败。

  • 检查变量是否为 nil 是常见的编程实践,可以使用 variable == nil 进行判断。

  • 函数可以使用 nil 作为返回值来指示操作失败或没有有效结果。同时可以返回第二个参数作为错误信息,这是一种常见的 Lua 错误处理模式。

  • 将表中的键值对赋值为 nil 可以有效地删除该键值对,而不是将其值设置为 nil。这与一些其他语言的行为有所不同,需要注意。

2. boolean (布尔)

boolean 类型只有两个值:truefalse,用于表示逻辑真和逻辑假。Lua 中的布尔值与其他语言类似,常用于条件判断和逻辑运算。

代码实践:

local isTrue = true local isFalse = false print(isTrue) -- 输出:true print(isFalse) -- 输出:false print(type(isTrue)) -- 输出:boolean -- 逻辑运算 print(isTrue and isFalse) -- 输出:false (与) print(isTrue or isFalse) -- 输出:true (或) print(not isTrue) -- 输出:false (非) -- 条件判断 if isTrue then print("This is true") -- 输出:This is true else print("This is false") end if isFalse then print("This is true") else print("This is false") -- 输出:This is false end -- Lua 中的假值:false 和 nil,其他所有值都为真 print(not nil) -- 输出:true (nil 为假) print(not false) -- 输出:true (false 为假) print(not 0) -- 输出:false (0 为真) print(not "") -- 输出:false (空字符串为真) print(not {}) -- 输出:false (空表为真) -- 需要注意,在 Lua 中,数字 0,空字符串 "",空表 {} 等都被视为真值。 -- 只有 `nil` 和 `false` 被认为是假值。

内容详解:

  • boolean 类型是程序逻辑控制的基础,用于表示真假状态。

  • Lua 中的逻辑运算符 and (与), or (或), not (非) 与其他语言类似。

  • 在条件判断语句(如 if, while, repeat)中,Lua 会将条件表达式的结果解释为布尔值。

  • 重要概念:Lua 中的假值只有 nilfalse,其他所有值(包括数字 0,空字符串 "",空表 {} 等)都被视为真值。 这与其他一些语言(如 JavaScript 或 C++)中 0 或空字符串被视为假值的情况有所不同,需要特别注意。

3. number (数字)

number 类型用于表示数值。在 Lua 5.3 之前,number 类型只有一种,即双精度浮点数。从 Lua 5.3 开始,number 类型引入了两个子类型:integer (整型)float (浮点型)。Lua 会根据实际情况自动选择使用整型或浮点型,程序员通常无需显式指定。

代码实践:

-- 整型 local integerNumber = 10 print(integerNumber) -- 输出:10 print(type(integerNumber)) -- 输出:number -- 浮点型 local floatNumber = 3.14 print(floatNumber) -- 输出:3.14 print(type(floatNumber)) -- 输出:number -- 科学计数法 local scientificNumber = 2.5e3 -- 2.5 * 10^3 = 2500 print(scientificNumber) -- 输出:2500.0 -- 数值运算 local sum = integerNumber + floatNumber print(sum) -- 输出:13.14 local difference = integerNumber - floatNumber print(difference) -- 输出:6.86 local product = integerNumber * floatNumber print(product) -- 输出:31.4 local quotient = integerNumber / floatNumber print(quotient) -- 输出:3.1847133757961785 local power = integerNumber ^ 2 -- 幂运算 print(power) -- 输出:100.0 local modulo = integerNumber % 3 -- 取模运算 print(modulo) -- 输出:1.0 -- 数学函数 (需要 math 库) print(math.floor(floatNumber)) -- 向下取整:3 print(math.ceil(floatNumber)) -- 向上取整:4 print(math.abs(-5)) -- 绝对值:5 print(math.sqrt(16)) -- 平方根:4.0 print(math.random()) -- 生成 0 到 1 之间的随机数 print(math.random(10)) -- 生成 1 到 10 之间的随机整数 print(math.pi) -- 圆周率:3.1415926535898 -- 类型判断 (Lua 5.3+) print(math.type(integerNumber)) -- 输出:integer print(math.type(floatNumber)) -- 输出:float print(math.isinteger(integerNumber)) -- 输出:true print(math.isinteger(floatNumber)) -- 输出:false

内容详解:

  • number 类型是 Lua 中用于数值计算的基础。Lua 能够处理整型和浮点型数值,并自动进行类型转换。

  • Lua 支持常见的算术运算符:+ (加), - (减), * (乘), / (除), ^ (幂), % (取模)。

  • Lua 提供了 math 库,包含了丰富的数学函数,如取整、绝对值、平方根、随机数、三角函数、对数等。使用 math 库需要先 require("math"),但在很多情况下,math 库是预加载的,可以直接使用。

  • 从 Lua 5.3 开始,可以使用 math.type()math.isinteger() 函数来判断数值类型是整型还是浮点型。

  • Lua 的数值运算通常使用浮点数进行,即使操作数是整数,结果也可能是浮点数。例如,10 / 2 的结果是 5.0,而不是 5。如果需要整数除法,可以使用 math.floor(a / b)

  • 在进行数值比较时,需要注意浮点数精度问题。直接比较两个浮点数是否相等可能不可靠。可以考虑使用一个小的误差范围进行比较,例如 math.abs(a - b) < 0.00001

4. string (字符串)

string 类型用于表示文本数据,即字符序列。Lua 中的字符串是不可变的,这意味着一旦创建,就不能直接修改字符串的内容。任何字符串操作都会返回一个新的字符串。

代码实践:

-- 字符串字面量 local singleQuoteString = 'Hello, Lua!' local doubleQuoteString = "Hello, Lua!" local longString = [[ This is a multi-line string. ]] print(singleQuoteString) -- 输出:Hello, Lua! print(doubleQuoteString) -- 输出:Hello, Lua! print(longString) -- 输出: -- This is a -- multi-line string. print(type(singleQuoteString)) -- 输出:string -- 字符串连接 local greeting = "Hello" local name = "World" local message = greeting .. ", " .. name .. "!" -- 使用 .. 运算符连接字符串 print(message) -- 输出:Hello, World! -- 字符串长度 print(#message) -- 输出:13 (使用 # 运算符获取字符串长度) -- 字符串格式化 (string.format) local age = 30 local formattedString = string.format("My name is %s and I am %d years old.", name, age) print(formattedString) -- 输出:My name is World and I am 30 years old. -- 字符串查找 (string.find) local position = string.find(message, "World") print(position) -- 输出:7 11 (返回起始和结束位置) local notFound = string.find(message, "Lua") print(notFound) -- 输出:nil -- 字符串截取 (string.sub) local subString = string.sub(message, 1, 5) -- 从索引 1 到 5 截取 print(subString) -- 输出:Hello -- 字符串转换大小写 (string.lower, string.upper) print(string.lower(message)) -- 输出:hello, world! print(string.upper(message)) -- 输出:HELLO, WORLD! -- 其他字符串函数 (string 库) print(string.reverse("olleh")) -- 输出:hello (反转字符串) print(string.rep("Lua", 3)) -- 输出:LuaLuaLua (重复字符串) print(string.gsub("Hello World", "World", "Lua")) -- 输出:Hello Lua 1 (替换字符串)

内容详解:

  • string 类型在 Lua 中用于处理文本数据。Lua 提供了多种方式来定义字符串字面量,包括单引号 '...',双引号 "...",以及长括号 [[...]][=[...]=] 等,长括号可以用于定义多行字符串,并且可以避免转义字符的困扰。

  • Lua 使用 .. 运算符进行字符串连接。

  • 使用 # 运算符可以获取字符串的长度。

  • string.format 函数用于格式化字符串,类似于 C 语言的 sprintf 或 Python 的 f-string

  • string 库提供了丰富的字符串处理函数,包括查找子串 (string.find),截取子串 (string.sub),转换大小写 (string.lower, string.upper),反转字符串 (string.reverse),重复字符串 (string.rep),替换子串 (string.gsub) 等。使用 string 库函数需要先 require("string"),但在很多情况下,string 库是预加载的,可以直接使用。

  • Lua 字符串的索引从 1 开始,而不是从 0 开始,这与其他一些语言(如 C, Python, JavaScript)不同,需要注意。

  • 由于 Lua 字符串是不可变的,频繁的字符串连接操作可能会导致性能问题。在需要高效构建字符串的场景下,可以考虑使用 table.concat 函数,先将字符串片段放入表中,然后一次性连接起来。

5. table (表)

table 类型是 Lua 中最重要也是唯一的数据结构,它是一种关联数组,可以用来表示数组、字典、集合、对象等复杂的数据结构。table动态的,可以随时添加或删除键值对。table对象,这意味着变量实际上存储的是对 table 的引用。

代码实践:

-- 创建空表 local emptyTable = {} print(type(emptyTable)) -- 输出:table -- 数组形式的表 (索引从 1 开始) local arrayTable = {"apple", "banana", "cherry"} print(arrayTable[1]) -- 输出:apple print(arrayTable[2]) -- 输出:banana print(arrayTable[3]) -- 输出:cherry print(#arrayTable) -- 输出:3 (使用 # 获取数组长度) -- 字典形式的表 (键值对) local dictTable = { name = "Alice", age = 30, city = "New York" } print(dictTable.name) -- 输出:Alice (使用点号访问) print(dictTable["age"]) -- 输出:30 (使用方括号访问) print(dictTable.city) -- 输出:New York -- 混合形式的表 local mixedTable = { "first", second = "second value", [10] = "tenth value", third = "third value" } print(mixedTable[1]) -- 输出:first print(mixedTable.second) -- 输出:second value print(mixedTable[10]) -- 输出:tenth value print(mixedTable.third) -- 输出:third value -- 表的嵌套 local nestedTable = { person = { name = "Bob", age = 25 }, items = {"book", "pen", "pencil"} } print(nestedTable.person.name) -- 输出:Bob print(nestedTable.items[2]) -- 输出:pen -- 遍历表 (pairs 和 ipairs) print("遍历字典形式的表 (pairs):") for key, value in pairs(dictTable) do print(key, value) end -- 输出: -- city New York -- age 30 -- name Alice print("\n遍历数组形式的表 (ipairs):") for index, value in ipairs(arrayTable) do print(index, value) end -- 输出: -- 1 apple -- 2 banana -- 3 cherry -- 添加和删除键值对 dictTable.gender = "female" -- 添加键值对 print(dictTable.gender) -- 输出:female dictTable.age = nil -- 删除键为 "age" 的键值对 print(dictTable.age) -- 输出:nil -- 表作为对象和方法 (面向对象编程基础) local rectangle = { width = 10, height = 5, area = function(self) -- 方法 return self.width * self.height end } print(rectangle.area(rectangle)) -- 输出:50 -- 可以使用冒号语法简化方法定义和调用 function rectangle:perimeter() return 2 * (self.width + self.height) end print(rectangle:perimeter()) -- 输出:30 (冒号调用,self 自动传入) -- 表的引用 local table1 = {a = 1} local table2 = table1 -- table2 引用 table1 table2.a = 100 print(table1.a) -- 输出:100 (table1 也被修改了) local table3 = {a = 1} local table4 = {} for k, v in pairs(table3) do table4[k] = v -- 复制 table3 的内容到 table4 end table4.a = 200 print(table3.a) -- 输出:1 (table3 没有被修改) print(table4.a) -- 输出:200

内容详解:

  • table 是 Lua 中功能最强大的数据类型,它实现了关联数组的功能,可以存储任意类型的值,并使用任意类型的值作为键(除了 nil)。

  • table 可以用作数组(通过数字索引),也可以用作字典(通过字符串或其他类型的值作为键)。

  • Lua 表的索引从 1 开始,而不是从 0 开始。

  • 使用 # 运算符可以获取数组形式表的长度,但对于字典形式的表,# 运算符的行为未定义,不应使用。

  • 使用 pairs 迭代器可以遍历表中的所有键值对,遍历顺序是不确定的。

  • 使用 ipairs 迭代器可以遍历数组形式表中的键值对,遍历顺序是索引递增的顺序。ipairs 只遍历到第一个非正整数索引或 nil 值为止。

  • 表可以嵌套,形成复杂的数据结构。

  • 表在 Lua 中是对象,变量存储的是对表的引用。这意味着多个变量可以引用同一个表,修改其中一个变量引用的表会影响到其他变量。如果需要复制表,需要进行深拷贝或浅拷贝,而不是直接赋值。

  • 表可以作为面向对象编程的基础,通过将函数存储在表中作为方法,可以实现简单的类和对象。冒号语法 object:method()object.method(object) 的语法糖,用于简化方法定义和调用,并自动将对象自身作为第一个参数 self 传入方法。

6. function (函数)

function 类型表示函数。在 Lua 中,函数是第一类公民,这意味着函数可以像其他值(如数字、字符串)一样被赋值给变量、作为参数传递给其他函数、作为返回值返回。Lua 支持匿名函数和闭包。

代码实践:

-- 函数定义 function add(a, b) return a + b end -- 函数调用 local sum = add(5, 3) print(sum) -- 输出:8 -- 匿名函数 local multiply = function(a, b) return a * b end local product = multiply(4, 6) print(product) -- 输出:24 -- 函数作为参数传递 local function calculate(func, x, y) return func(x, y) end local result1 = calculate(add, 10, 5) -- 将 add 函数作为参数传递 print(result1) -- 输出:15 local result2 = calculate(multiply, 2, 7) -- 将 multiply 函数作为参数传递 print(result2) -- 输出:14 -- 函数作为返回值返回 local function createCounter() local count = 0 return function() -- 返回一个匿名函数 (闭包) count = count + 1 return count end end local counter1 = createCounter() print(counter1()) -- 输出:1 print(counter1()) -- 输出:2 print(counter1()) -- 输出:3 local counter2 = createCounter() -- 创建新的计数器 print(counter2()) -- 输出:1 (独立计数) print(counter1()) -- 输出:4 (counter1 继续计数) -- 可变参数函数 local function varargFunc(...) print("参数个数:", select("#", ...)) -- 使用 select("#", ...) 获取参数个数 for i = 1, select("#", ...) do print("参数", i, ":", select(i, ...)) -- 使用 select(i, ...) 获取第 i 个参数 end end varargFunc(1, 2, "hello", true) -- 输出: -- 参数个数: 4 -- 参数 1 : 1 -- 参数 2 : 2 -- 参数 3 : hello -- 参数 4 : true -- 闭包 (closure) -- 内部函数可以访问外部函数的局部变量,即使外部函数已经执行完毕 local function outerFunction(x) local y = 10 return function(z) -- 返回一个闭包 return x + y + z -- 闭包捕获了外部函数的 x 和 y end end local innerFunction = outerFunction(5) -- 创建闭包,x=5, y=10 被捕获 print(innerFunction(20)) -- 输出:35 (5 + 10 + 20)

内容详解:

  • function 类型是 Lua 中用于定义和表示函数的数据类型。函数是 Lua 的核心组成部分,用于封装代码逻辑和实现模块化。

  • Lua 支持两种函数定义方式:具名函数(使用 function functionName(...) ... end)和匿名函数(使用 function(...) ... end)。

  • 函数可以作为值赋值给变量,可以作为参数传递给其他函数,也可以作为返回值返回。这种特性使得 Lua 具有很高的灵活性和表达力。

  • Lua 支持可变参数函数,使用 ... 表示可变参数列表。在函数体内部,可以使用 select("#", ...) 获取参数个数,使用 select(i, ...) 获取第 i 个参数。

  • 闭包是 Lua 函数的重要特性。闭包是指一个函数可以访问并操作其词法作用域之外的变量。当一个函数返回另一个函数时,返回的函数(闭包)会记住并访问其创建时所在作用域的变量,即使创建闭包的外部函数已经执行完毕。闭包在 Lua 中被广泛应用于实现状态保持、回调函数、模块化等。

7. userdata (用户数据)

userdata 类型允许 Lua 代码调用 C/C++ 编写的库。它本质上是一个指向 C/C++ 数据的指针,Lua 自身无法直接操作 userdata 的内部数据,只能通过 C/C++ 接口函数来访问和操作。userdata 主要用于扩展 Lua 的功能,使其能够与底层系统或外部库进行交互。

代码实践 (需要 C/C++ 扩展模块配合,以下为概念性示例):

-- 假设有一个 C 扩展库,提供了创建 userdata 和操作 userdata 的函数 -- 加载 C 扩展库 (假设库名为 mylib) local mylib = require("mylib") -- 创建 userdata 对象 (假设 C 库提供了 mylib.create_object() 函数) local myObject = mylib.create_object() print(type(myObject)) -- 输出:userdata -- 调用 C 库提供的函数操作 userdata (假设 C 库提供了 mylib.set_value(obj, value) 和 mylib.get_value(obj) 函数) mylib.set_value(myObject, 123) local value = mylib.get_value(myObject) print(value) -- 输出:123 -- userdata 通常与 metatable 结合使用,实现面向对象编程 (在 C/C++ 层面实现类和对象) -- 假设 C 库为 userdata 关联了 metatable,并提供了 mylib.object_method(obj) 方法 local result = mylib.object_method(myObject) -- 调用 userdata 的方法 print(result)

内容详解:

  • userdata 类型是 Lua 与 C/C++ 扩展库交互的桥梁。它允许 Lua 代码访问和操作 C/C++ 库中的数据和功能。

  • userdata 本身是一个不透明的指针,Lua 无法直接解释其内部结构。对 userdata 的操作必须通过 C/C++ 扩展库提供的接口函数进行。

  • userdata 通常与 元表 (metatable) 结合使用,实现面向对象编程。C/C++ 扩展库可以为 userdata 设置元表,定义 userdata 的行为,例如重载运算符、定义方法等。

  • 使用 userdata 可以将性能敏感的操作或需要访问底层系统资源的功能用 C/C++ 实现,然后在 Lua 中调用,从而兼顾性能和灵活性。

  • userdata 的生命周期由 Lua 的垃圾回收器管理。当 userdata 对象不再被 Lua 代码引用时,垃圾回收器会自动回收 userdata 对象及其关联的 C/C++ 资源(通常需要在 C/C++ 扩展库中实现垃圾回收逻辑)。

8. thread (线程)

thread 类型表示 Lua 中的协程 (coroutine)。协程是一种轻量级的线程,可以在单线程环境中实现并发执行。与操作系统的线程不同,协程的切换是由程序员显式控制的,开销较小。Lua 的协程机制主要用于实现协作式多任务处理。


发布者: 作者: 转发
评论区 (0)
U