Lua编程2之数据类型
<p>思考并回答以下问题:<br>1.nil 的“删除”作用怎么理解?如何删除table里的一个值?<br>2.怎么比较一个变量是否为nil?<br>3.数字零和空字符串为真吗?假有哪些?<br>4.如何表示块字符串?如何避免误解析的发生?<br>5.显式转换函数有哪些?<br>6.如何计算字符串的长度?<br>7.如何构建数组?第一个索引是0吗?table会固定长度吗?<br>8.a = {} a[1000] = 1 和table.maxn()之间有什么关系?<br>9.function和int类型一样怎么理解?C#中的delegate,class和int一样怎么理解?<br>10.lua中如何进行字符串连接?<br>11.Lua将nil作为界定数据结尾的标志会导致什么问题?</p>
Lua是一种动态类型的语言,变量本身没有类型,只有值拥有类型。Lua语言本身没有提供类型定义的语法,每个值都“携带”了它自身的类型信息。
在Lua中有8种基础类型,分别是:nil、boolean、number、string、userdata、function、thread和table。
nil | 这个最简单,只有值nil属于该类型,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同进程 |
table | Lua 中的表(table)其实是一个“关联数组”(associative arrays),数组的索引可以是数字或者是字符串。在 Lua 里,table 的创建是通过“构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。 |
我们可以通过type函数获得变量的类型信息,该类型信息将以字符串的形式返回。如:
1 | print(type("hello world")) |
nil(空)
nil是一种类型,它只有一个值nil,它的主要功能是区别其他任何值。就像之前所说的,一个全局变量在第一次赋值前的默认值的默认值就是nil,将nil赋予一个全局变量等同于删除它。Lua将nil用于表示一种“无效值”的情况,类似C#中的null。
例如打印一个没有赋值的变量,便会输出一个 nil 值:
1 | print(type(a)) -- nil |
对于全局变量和 table,nil 还有一个“删除”作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉,如下面代码:
1 | tab1 = { key1 = "val1", key2 = "val2", "val3" } |
nil 作比较时应该加上双引号 “”:
1 | type(X) --nil |
type(X)==nil 结果为 false 的原因是因为 type(type(X))==string。
boolean(布尔)
boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是“假”,其他的都为“真”,如数字零和空字符串也为真,这点需要注意。
1 | print(type(true)) |
以上代码执行结果如下:
1 | boolean |
number(数字)
Lua 默认只有一种 number 类型 – double(双精度)类型(默认类型可以修改 luaconf.h 里的定义),以下几种写法都被看作是 number 类型:
1 | print(type(2)) |
Lua中没有专门的类型表示整数。
string(字符串)
字符串由一对双引号或单引号来表示。
1 | string1 = "this is string1" |
Lua支持和C语言类似的字符转义序列,见下表:
a | 响铃 |
b | 退格 |
n | 换行 |
r | 回车 |
t | 水平Tab |
反斜杠 | |
" | 双引号 |
' | 单引号 |
也可以用 2 个方括号 “[[]]” 来表示”一块”字符串,这个时候会禁用里面的转义字符。
1 | html = [[ |
如果两个方括号中包含这样的内容:a = b[c[i]],这样将会导致Lua的误解析,因此在这种情况下,我们可以将其改为[===[ 和 ]===]的形式,从而避免了误解析的发生。
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字,这和C#中的加号会进行字符串拼接不同:
1 | print("2" + 6) --8.0 |
以上代码中”error” + 1执行报错了,** 字符串连接应该使用的是 .. **,如:
1 | print("a" .. 'b') --ab |
尽管Lua提供了这种自动转换的功能,为了避免一些不可预测的行为发生,特别是因为Lua版本升级而导致的行为不一致现象。鉴于此,还是应该尽可能使用显式的转换,如字符串转数字的函数tonumber(),或者是数字转字符串的函数tostring()。对于前者,如果函数参数不能转换为数字,该函数返回nil。如:
1 | line = "150.56" |
使用 # 来计算字符串的长度,放在字符串前面,如下实例:
1 | len = "hello world" |
table(表)
在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
1 | -- 创建一个空的 table |
Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以为任意类型(nil除外)。类似C#中的Dictionary的Key-Value结构。
1 | a = {} |
脚本执行结果为:
1 | key : value |
不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。
1 | local tbl = {"apple", "pear", "orange", "grape"} |
脚本执行结果为:
1 | Key 1 |
table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。
1 | a3 = {} |
脚本执行结果为:
1 | val |
在Lua中还提供了另外一种方法用于访问table中的值,见如下示例:
1 | a.x = 10 --等同于a["x"] = 10 |
由于数组实际上仍为一个table,所以对于数组大小的计算需要留意某些特殊的场景,如:
1 | a = {} |
在上面的示例中,数组a中索引值为1–999的元素的值均为nil。而Lua则将nil作为界定数据结尾的标志。当一个数组含有“空隙”时,即中间含有nil值,长度操作符#会认为这些nil元素就是结尾标志。当然这肯定不是我们想要的结果。因此对于这些含有“空隙”的数组,我们可以通过函数table.maxn()返回table的最大正数索引值。如:
1 | a = {} |
function(函数)
在Lua中,函数可以存储在变量中,可以通过参数传递其它函数,还可以作为其它函数的返回值。这种特性使语言具有了极大的灵活性。
1 | function (n) |
脚本执行结果为:
1 | 120 |
function 可以以匿名函数(anonymous function)的方式通过参数传递:
1 | function (tab,fun) |
脚本执行结果为:
1 | key1 = val1 |
thread(线程)
在 Lua 里,thread代表了单独线程的执行,并且用来实现lua里的协程(coroutine),类似Unity中的协程。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和命令指针,可以跟其他协同进程共享全局变量和其他大部分东西。
Lua的线程和操作系统的线程并不相关。Lua的协程支持所有的操作系统,即使那些不支持线程的操作系统也支持协程。
userdata(自定义类型)
userdata 是一种用户自定义数据,提供了将任意外部数据(通常是 struct 和 指针)存储在lua变量中的能力。一个userdata代表了一块内存数据。
有两种userdata,一种是full userdata,是lua管理的一个对象,拥有一块内存区域;还有一种叫light userdata,是一个指针。
在Lua中除了赋值和比较,没有其他针对userdata预先定义的操作,通常使用metatable对full userdata定义操作。userdata的值无法在lua中创建或者修改,只能通过C语言API,这也保证了和宿主进程数据的一致性。