Lua的思想
Lua是一个具有过程式(procedural)编程和数据描述能力的嵌入式编程语言。作为一个嵌入语言,Lua没有“main”函数,只能嵌入到宿主程序(host)运行。Lua作为一个库文件,链接到宿主程序中。宿主程序可以调用接口执行lua代码获取或修改lua的变量,并且可以注册C函数给Lua代码调用。
因此我们可以通过一个共享的语法框架,创建一个定制化的编程语言,将Lua扩展后就可以处理不同领域的事情(Beckman 1991)。
这部分主要简单描述了Lua的主要概念。代码案例及语法定义可在《Lua参考手册》(Ierusalimschy-Figueiredo-Celes1994)上面找到。
语法Syntax
前面提到过,我们确定将Lua设计为一个简洁,熟悉的语法。因此Lua支持一个常见的语句,含蓄但明确的语句块终止结构。
传统的语句包括简单的assignment,例如控制结构while-do-end,repeat-until,if-then-elseif-else-end以及function
非传统的语句包括 multiple assignment:局部变量声明可以放在一个语句块的任意位置,table的建立,可以包含用户要求的validationfunctions。
此外,Lua的函数可以接受可变参数,也可以返回多个值,这样当需要返回多个结果时,可以不需要通过函数参数返回。
环境与模块Environment and modules
Lua里的所有可执行语句都保存在一个全局环境里(globalenvironment)。宿主程序开始时初始化全局环境,并持续到宿主程序结束。全局环境保存了所有的全局变量(global variables),函数。Lua代码可以修改全局环境,宿主程序也可以修改。
Lua的执行单元(unit)叫做模块(module)。一个模块可以包含多个语句和函数定义,可以存储在一个文件里,也可以存储在宿主程序的一个字符串里。
当要执行一个模块时,首先所有的函数和语句都会被编译,同时函数也会加入到全局环境,然后按顺序执行语句。一个模块对全局环境的修改是永久的,即使这个模块执行结束了。这些修改包括全局变量和新的函数。实际上一个函数定义就是一个assignment to a global variable。
数据类型和变量Data types and variables
Lua是动态类型语言,变量没有类型,只有变量的值有类型。所有的值都有他们自己的类型信息。因此在Lua里没有类型定义。
Lua有垃圾回收机制(garbage collection),它能够保留那些在使用的值,丢弃那些没有在使用的值。这样就不需要内存分配与管理。Lua里有7种基本数据类型:
- nil 一个叫做nil的类型,这个类型只有一个值nil
- number 浮点数数字
- string 字符数组
- function 用户定义的函数
- Cfunction 宿主程序提供的函数
- userdata 指向宿主程序的数据空间
- table 关联数组
Lua提供运行时字符串到数字的自动转换。任何对字符串的数学运算操作都会尝试用一般的转换规则把这个字符串转换成一个数字。相反,无论何时,一个数字需要作为字符串来使用时,数字都会以合理的格式转换为字符串。这样的特性非常有用,因为它编程简单,避免显示的转换函数。
全局变量(Global variables)不需要声明定义,只有局部变量(local variables)需要声明定义。如果一个变量没有显示的声明为local,这个变量等同于全局变量。局部变量可以放在语句块的任意位置。
由于给变量赋值前,变量的值是nil,因此Lua中没有未初始化的变量。因此对nil的有效操作就是赋值和相等测试。nil的这种特性不同于其他任何的值,因此如果在一个上下文中需要一个实际的数字进行数学运算时,使用了未赋值的变量,将引起执行错误警告:这个变量没有初始化。所以自动初始化变量为nil也是不被鼓励。
function在Lua里是first-class values ,Lua函数体被编译后,函数名作为变量名存储在全局变量里。Lua可以调用Lua写的函数,也可以调用C写的函数。C实现的函数有个专有类型Cfunction.
userdata类型允许任意类型的C指针存储在Lua变量里,对其有效操作就是赋值和相等测试
table类型用关联数组实现,关联数组的值可以通过数字和字符串2种方式进行索引。因此这个类型不仅可以用于表达一般的数组,还是可以表示记录集合。为了表达一个记录,Lua使用域名(fieldname)作为索引。Lua支持像a.name这样的语法糖(syntactic sugar)表示a["name"]。
关联数组是一个强大的语言创新。很多算法由于这个特性变得简单,因为语言本身已经提供搜索的算法和结构(Aho-Kerninghan-Weinberger 1988,Bentley 1988)。
例如 table[word] = table[word] + 1 不需要在列表里搜索word,就可以实现遍历计数功能。 但是现实工作中常需要一个按字母顺序排列(alphabetically)的报告,而通常在lua中table的目录是无序存放的。
有很多方法创建table,最简单的方法是类似于普通的数组: t = @()
这样的表达式将创建一个空的table,在上面的表达式中table的大小是可选项,也可以给一个初始值。 Lua中的table都是随需动态扩展的,与是否初始化大小并无关系。因此t[200]和t["day"]都是有效的。
有2种方式创建带数据内容的table,一个是列表方式@[],一个是记录方式@{}。
t=@["red","green","blue",3]
与之等价的语句如下
t = @()
t[1] = "red"
t[2] = "green"
t[3] = "blue"
t[4] = 3
另外在创建列表和记录时,还可以table作为函数,如
t = @colors["red","green","blue","yellow"]
t =@employee{name="huangyuxi",age=32}
在table创建后,colors和employee就是2个用户函数了,我们可以调用它们。如上面的employee域的赋值可以这样实现
t = @()
t.name = huangyuxi
t.age =32
employee(t)
这样的表达式在lua里非常强大。
预定义函数和库Predefined functions and libraries
在Lua里预先定义了一些函数,这些函数很少,但是作用却不小。预定义函数具有如下功能:
- 执行一个文件或字符串里的lua模块
- 枚举table里的所有成员
- 枚举所有全局变量
- 类型查询和转换
数据固化Persistence
在lua里枚举函数可以用于全局环境的固化。也可以写lua代码写lua代码,通过执行,存储所有的全局变量。
我们现在展示一些方法来存储,恢复在lua里使用的变量。
为了通过name存储数值,下面的代码就可以
function store(name,value)
write(name,..'=') -- write is a library function for output,'..'is the string concatenation operator
write_value(value) -- outputs a suitablerepresentation of a value based on its type
end
function write_value(value)
local t = type(value)
if t = 'nil' then write nil
elseif t = 'number' then write(value)
elseif t = 'string' then write('"'..value..'"')
end
end
保存一个table要稍微复杂一些。首先write_value要增加一个elseif t = 'table' then write_record(value),假设table是一个records,table的值可以直接通过table的结构进行直接保存。
function write_record(t)
local i,v = next(t,nil) -- next enumerates the fields of t
write('@{') -- starts constructor
while i do
store(i,v)
i, v = next(t,i)
if i then write(', ') end
end
write('}') -- closes constructor
end
通过对这部分的理解,我了解了:
1 lua的设计思想
2 lua的流程控制while-do-end,repeat-until,if-then-elseif-else-end
3 lua函数参数为可变参数,返回值可以同时返回多个
4 lua执行一个模块,会改变全局环境,是永久性的改变
5 lua是动态类型语言,变量的类型至于变量的值有关系。
6 lua本身提供垃圾回收机制,编程不需要考虑内存分配
7 lua具有7个基本数据类型
8 lua有预定义函数,一个例子参见 Lua1.0使用与研究- globals.lua
9 lua库,可以自己扩展,本身提供的库有字符串操作,算术函数,io库,可以按需使用连接到宿主程序
10 文中提到一些代码后面我会做些验证,测试,重点table的使用;提到的垃圾回收也是测试验证的重点。