2 类型和值
Lua是一种动态类型语言。该语言中没有类型定义,每个值都有自己的类型。
Lua中有八种基本类型:nil、boolean、number、string、userdata、function、thread和table。type函数给出给定值的类型名称:
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
第一个示例展示了字符串 “Hello world” 的类型,即 string。
第二个示例演示了算术表达式 10.4*3 的结果的类型,即 number。
第三和第四个示例展示了 print 函数和 type 函数的类型,它们都是 function。
第五个示例显示了布尔值 true 的类型,即 boolean。
第六个示例指示了 nil 值的类型,即 nil。
第七个示例展示了将 type 函数应用于变量 X 的结果的类型,无论其实际值如何,结果始终是 string。
Lua中变量没有预定义的类型;任何变量都可以包含任何类型的值。通过使用变量 a 来表示不同类型的值进行演示:
print(type(a)) --> nil (`a' 未初始化)
a = 10
print(type(a)) --> number
a = "a string!!"
print(type(a)) --> string
a = print -- 是的,这是有效的!
a(type(a)) --> function
第一个示例演示了未初始化变量 a 的类型为 nil。
第二个示例将值 10 赋给 a,将其类型更改为 number。
第三个示例将字符串赋给 a,将其类型更改为 string。
第四个示例将 print 函数赋给 a,显示了Lua中函数是一等值(first-class values)的事实。
最后一行调用了存储在 a 中的函数,并以其自身的类型作为参数。
虽然在Lua中使用单一变量表示不同类型可能导致混乱的代码,但在某些情况下,明智地使用这一特性可以带来一些帮助,比如使用 nil 来区分正常返回值和异常情况。
2.1 Nil
nil 是一种具有单一值的类型,即 nil,其主要属性是与任何其他值都不同。正如我们所见,全局变量在第一次赋值之前默认具有 nil 值,而你可以将 nil 赋给一个全局变量以删除它。Lua 使用 nil 表示一种非值,用来表示缺少有用值的情况。
-- 全局变量 'x' 在未显式赋值之前默认为 nil
print(x) --> nil
-- 为 'x' 赋值
x = 42
print(x) --> 42
-- 通过赋值 nil 删除 'x' 的值
x = nil
print(x) --> nil
在这个例子中,nil 用于表示变量 x 缺少或没有特定值的情况。
2.2 Booleans
布尔类型有两个值,false 和 true,它们代表传统的布尔值。然而,它们并不是条件值的唯一代表:在 Lua 中,任何值都可以表示一个条件。
条件语句(比如在控制结构中使用的条件)将 false 和 nil 视为假,而将其他任何值视为真。请注意,与一些其他脚本语言不同,Lua 在条件测试中将零和空字符串都视为真。
2.3 Numbers
数字类型代表实数(双精度浮点数)。Lua 没有整数类型,因为它并不需要。关于浮点数运算错误有一个普遍的误解,有些人担心即使是简单的增量也可能在浮点数中出现问题。
事实是,当你使用双精度浮点数表示整数时,根本没有舍入错误(除非这个数大于 100,000,000,000,000)。具体而言,Lua 数字可以表示任何长整数而无需舍入问题。而且,大多数现代 CPU 执行浮点运算与整数运算一样快,甚至更快。
可以轻松地编译 Lua 以使用其他类型的数字,如长整数或单精度浮点数。这在没有硬件支持浮点数的平台上特别有用。请查看分发版以获取详细的说明。
我们可以使用一个可选的小数部分,加上一个可选的小数指数来编写数值常量。有效数值常量的示例包括:
4 0.4 4.57e-3 0.3e12 5e+20
这些数字表示不同形式的实数,其中 e 后面的数字表示指数形式。例如,4.57e-3 表示 4.57 乘以 10 的负 3 次方。
2.4 Strings
字符串含义:字符串在 Lua 中表示字符序列,是由字符组成的。每个字符可以是任意数值,包括嵌入的零。
Lua 是八位清洁的,意味着它能够处理包含所有 8 位二进制值(从 0 到 255)的数据。字符串可以包含任何数值,包括嵌入的零,因此可以用于存储任意二进制数据。
Lua 中的字符串是不可变的值,这意味着无法直接在字符串内部更改字符,类似于在 C 语言中所做的操作。相反,你需要创建一个具有所需修改的新字符串。
通过使用 string.gsub 函数,你可以创建一个新的字符串,对原始字符串进行修改。在例子中,将字符串 “one string” 中的 “one” 替换为 “another”,创建了新的字符串 “another string”。
a = "one string"
b = string.gsub(a, "one", "another") -- 改变字符串的部分
print(a) --> one string
print(b) --> another string
字符串和其他 Lua 对象一样,受到自动内存管理的影响。这意味着你无需担心字符串的分配和释放,Lua 会为你处理这些事情。
字符串可以包含单个字母或整本书,而 Lua 能够高效地处理长字符串。在 Lua 中,处理包含 100K 或 1M 字符的字符串的程序并不罕见。
字符串可以使用匹配的单引号或双引号进行字面表示。在编写程序时,建议使用相同类型的引号,除非字符串本身包含引号,这时可以使用另一种引号或通过反斜杠转义这些引号。
a = "a line"
b = 'another line'
此外,Lua 中的字符串支持类似于 C 语言的转义序列,用于表示特殊字符。以下是常见的转义序列及其含义:
\a:响铃(bell)
\b:退格(back space)
\f:换页(form feed)
\n:换行(newline)
\r:回车(carriage return)
\t:水平制表符(horizontal tab)
\v:垂直制表符(vertical tab)
\:反斜杠(backslash)
":双引号(double quote)
':单引号(single quote)
[:左方括号(left square bracket)
]:右方括号(right square bracket)
以下是一些示例,演示了这些转义序列的使用:
print("one line\nnext line\n\"in quotes\", 'in quotes'")
-- 输出:
-- one line
-- next line
-- "in quotes", 'in quotes'
print('a backslash inside quotes: \'\\\'')
-- 输出:
-- a backslash inside quotes: '\'
print("a simpler way: '\\'")
-- 输出:
-- a simpler way: '\'
此外,我们可以使用 \ddd 形式的转义序列,其中 ddd 是最多三个十进制数字,表示字符串中某个字符的数值编码。
例如,字符串字面值 “alo\n123"” 和 ‘\97lo\10\04923"’ 在使用 ASCII 编码的系统中具有相同的值:97 是字母 ‘a’ 的 ASCII 编码,10 是换行符的编码,而 49(在例子中为 \049)是数字 ‘1’ 的编码。
在 Lua 中,我们还可以使用匹配的双方括号 [[…]] 来表示字面字符串。这种方括号形式的字面字符串可以跨足多行,可以嵌套,并且不解释转义序列。此外,当该字符串的第一个字符是换行符时,这种形式会忽略字符串的第一个字符。这种形式在编写包含程序片段的字符串时特别方便,例如:
page = [[
<HTML>
<HEAD>
<TITLE>An HTML Page</TITLE>
</HEAD>
<BODY>
<A HREF="http://www.lua.org">Lua</A>
[[a text between double brackets]]
</BODY>
</HTML>
]]
write(page)
Lua 提供了在运行时数字和字符串之间的自动转换。对字符串应用的任何数字运算都会尝试将字符串转换为数字:
print("10" + 1) --> 11
print("10 + 1") --> 10 + 1
print("-5.3e-10"*"2") --> -1.06e-09
print("hello" + 1) -- 错误(无法转换 "hello")
在 Lua 中,我们还可以使用匹配的双方括号 [[…]] 来表示字面字符串。这种方括号形式的字面字符串可以跨足多行,可以嵌套,并且不解释转义序列。此外,当该字符串的第一个字符是换行符时,这种形式会忽略字符串的第一个字符。这种形式在编写包含程序片段的字符串时特别方便,例如:
page = [[
<HTML>
<HEAD>
<TITLE>An HTML Page</TITLE>
</HEAD>
<BODY>
<A HREF="http://www.lua.org">Lua</A>
[[a text between double brackets]]
</BODY>
</HTML>
]]
write(page)
Lua 提供了在运行时数字和字符串之间的自动转换。对字符串应用的任何数字运算都会尝试将字符串转换为数字:
print("10" + 1) --> 11
print("10 + 1") --> 10 + 1
print("-5.3e-10"*"2") --> -1.06e-09
print("hello" + 1) -- 错误(无法转换 "hello")
Lua 不仅在算术运算符中应用此类强制转换,而且还在其他期望数字的地方进行。相反,每当 Lua 在期望字符串的地方找到数字时,Lua 将数字转换为字符串:
print(10 .. 20) --> 1020
-- (.. 是 Lua 中的字符串连接运算符。当你将它紧跟在数字后面时,你必须用一个空格分隔它们,否则 Lua 会认为第一个点是小数点。)
尽管存在这些自动转换,字符串和数字是不同的东西。例如,比较 10 == “10” 总是为 false,因为 10 是一个数字而 “10” 是一个字符串。如果需要显式将字符串转换为数字,可以使用 tonumber 函数,该函数如果字符串不能表示有效的数字则返回 nil:
line = io.read() -- 读取一行
n = tonumber(line) -- 尝试将其转换为数字
if n == nil then
error(line .. " is not a valid number")
else
print(n*2)
end
要将数字转换为字符串,可以调用 tostring 函数或将数字与空字符串连接起来:
print(tostring(10) == "10") --> true
print(10 .. "" == "10") --> true
2.5 Tables
在 Lua 中,table 类型实现了关联数组。关联数组是一种可以用不仅仅是数字,还可以用字符串或语言中的任何其他值(除了 nil)作为索引的数组。此外,table 没有固定的大小;你可以动态地向 table 中添加任意数量的元素。
在 Lua 中,table 是主要(实际上是唯一)的数据结构机制,而且是一个强大的机制。我们可以使用 table 来表示普通数组、符号表、集合、记录、队列和其他数据结构,以一种简单、统一和高效的方式。Lua 还使用 table 来表示包。当我们写 io.read 时,我们的意思是“从 io 包中读取 read 条目”。对于 Lua 来说,这意味着“使用字符串 ‘read’ 作为键索引表 io”。
在 Lua 中,table 既不是值也不是变量;它们是对象。如果你熟悉 Java 或 Scheme 中的数组,那么你对我们的意思有一个相当好的了解。然而,如果你对数组的概念来自 C 或 Pascal,那么你需要打开一点思维。你可以将 table 视为一个动态分配的对象;你的程序只操作对它们的引用(或指针)。在幕后没有隐藏的复制或创建新的表。此外,你不需要在 Lua 中声明 table;实际上,没有声明 table 的方式。你通过构造表达式来创建表,最简单的形式是写为 {}:
a = {} -- 创建一个表并将其引用存储在 `a` 中
k = "x"
a[k] = 10 -- 新的条目,键为 "x",值为 10
a[20] = "great" -- 新的条目,键为 20,值为 "great"
print(a["x"]) --> 10
k = 20
print(a[k]) --> "great"
a["x"] = a["x"] + 1 -- 递增条目 "x"
print(a["x"]) --> 11
一个 table 总是匿名的。在持有 table 的变量和 table 本身之间没有固定的关系:
a = {}
a["x"] = 10
b = a -- `b` 引用与 `a` 相同的 table
print(b["x"]) --> 10
b["x"] = 20
print(a["x"]) --> 20
a = nil -- 现在只有 `b` 仍然引用该 table
b = nil -- 现在没有对该 table 的引用了
当程序不再引用 table 时,Lua 内存管理将最终删除该 table 并重新使用其内存。
每个 table 可以存储具有不同类型索引的值,而且它会根据需要动态增长以容纳新的条目:
a = {} -- 空表
-- 创建 1000 个新条目
for i=1,1000 do a[i] = i*2 end
print(a[9]) --> 18
a["x"] = 10
print(a["x"]) --> 10
print(a["y"]) --> nil
注意最后一行:与全局变量一样,如果它们未初始化,table 字段将求值为 nil。与全局变量一样,你可以将 nil 分配给 table 字段以删除它。这不是巧合:Lua 将全局变量存储在普通的 table 中。有关这个主题的更多信息,请参见第 14 章。
为了表示记录,你可以使用字段名作为索引。Lua 通过提供 a.name 这样的语法糖来支持这种表示法,它等同于 a[“name”]。因此,我们可以将上一个示例的最后几行以更清晰的方式编写:
a.x = 10 -- 与 a["x"] = 10 相同
print(a.x) -- 与 print(a["x"]) 相同
print(a.y) -- 与 print(a["y"]) 相同
注意最后一行:与全局变量一样,如果它们未初始化,table 字段将求值为 nil。与全局变量一样,你可以将 nil 分配给 table 字段以删除它。这不是巧合:Lua 将全局变量存储在普通的 table 中。有关这个主题的更多信息,请参见第 14 章。
为了表示记录,你可以使用字段名作为索引。Lua 通过提供 a.name 这样的语法糖来支持这种表示法,它等同于 a[“name”]。因此,我们可以将上一个示例的最后几行以更清晰的方式编写:
a.x = 10 -- 与 a["x"] = 10 相同
print(a.x) -- 与 print(a["x"]) 相同
print(a.y) -- 与 print(a["y"]) 相同
对于 Lua,这两种形式是等效的,可以自由混合使用;但对于人类读者来说,每种形式可能表示不同的意图。
初学者经常犯的一个常见错误是混淆 a.x 和 a[x]。第一种形式表示 a[“x”],即一个由字符串 “x” 索引的 table。第二种形式是由变量 x 的值索引的 table。看看这两者的区别:
a = {}
x = "y"
a[x] = 10 -- 将 10 存储在字段 "y" 中
print(a[x]) --> 10 -- 字段 "y" 的值
print(a.x) --> nil -- 字段 "x" 的值(未定义)
print(a.y) --> 10 -- 字段 "y" 的值
为了表示传统数组,你可以简单地使用带有整数键的 table。没有办法声明其大小;你只需初始化需要的元素:
-- 读取 10 行并将它们存储在 table 中
a = {}
for i=1,10 do
a[i] = io.read()
end
当你迭代数组的元素时,第一个未初始化的索引将导致 nil;你可以使用这个值作为表示数组末尾的标志。例如,你可以使用以下代码打印上一个示例中读取的行:
-- 打印行
for i,line in ipairs(a) do
print(line)
end
基本的 Lua 库提供了 ipairs,这是一个方便的函数,允许你按照数组元素的约定进行迭代,数组在其第一个 nil 元素处结束。
由于你可以用任何值索引 table,因此你可以用任何你喜欢的数字开始数组的索引。然而,在 Lua 中,习惯上数组的索引从 1 开始(而不是从 0 开始,如 C 中的数组),标准库也遵循这个约定。
由于我们可以用任何类型索引 table,因此在索引 table 时会出现与相等性相同的微妙之处。虽然我们可以用数字 0 和字符串 “0” 都作为 table 的索引,但这两个值是不同的(根据相等性),因此在 table 中表示不同的位置。同样,字符串 “+1”、“01” 和 “1” 都表示不同的位置。如果对索引的实际类型有疑问,请使用显式转换以确保:
i = 10; j = "10"; k = "+10"
a = {}
a[i] = "one value"
a[j] = "another value"
a[k] = "yet another value"
print(a[j]) --> another value
print(a[k]) --> yet another value
print(a[tonumber(j)]) --> one value
print(a[tonumber(k)]) --> one value
2.6 Functions 函数
在 Lua 中,函数是一等值(First-class values) 。这意味着函数可以存储在变量中,作为参数传递给其他函数,并作为结果返回。这些功能赋予了语言很大的灵活性:程序可以重新定义函数以添加新功能,或者简单地擦除函数以在运行一段不受信任的代码时创建一个安全环境(比如通过网络接收的代码)。此外,Lua 对函数式编程提供了良好的支持,包括具有正确词法作用域的嵌套函数;稍后会介绍。最后,一等值函数在 Lua 的面向对象功能中扮演了关键角色,我们将在第 16 章中看到。
Lua 可以调用用 Lua 编写的函数和用 C 编写的函数。Lua 中的所有标准库都是用 C 编写的,包括用于字符串处理、表处理、I/O、访问基本操作系统功能、数学函数和调试的函数。应用程序可以在 C 中定义其他函数。
2.7 Userdata and Threads 用户数据和线程
Userdata(用户数据):userdata 类型允许将任意的 C 数据存储在 Lua 变量中。在 Lua 中,userdata 类型没有预定义的操作,除了赋值和相等性测试。userdata 主要用于表示由应用程序或使用 C 语言编写的库创建的新类型。例如,标准 I/O 库使用 userdata 来表示文件。更多关于 userdata 的信息将在后面涉及到 Lua 的 C API 时进行讨论。
Thread(线程):thread 类型将在第 9 章详细讨论,那时我们会讨论协程。协程是一种特殊的线程,它允许在程序执行的不同部分之间进行协同工作,有助于更灵活地管理程序的执行流程。
这两种类型的详细使用和操作会在后续章节中进行解释
本文介绍了Lua语言的类型和值。Lua是动态类型语言,有八种基本类型,变量可包含不同类型值。文中详细阐述了每种类型的特点,如nil表示非值,布尔类型中零和空字符串视为真,数字为双精度浮点数,字符串不可变且支持自动转换等,还介绍了table、函数、用户数据和线程类型。
5355

被折叠的 条评论
为什么被折叠?



