(接上篇)
--------------------------------------
8 一些例子
--------------------------------------
本段给出一些显示 Lua 特性的例子。它并不打算覆盖完整的语言,只是显示一有趣的使用。
-------------------
8.1 数据结构
-------------------
表是统一的数据结构。它可以实现多种数据类型,像通常的数组,记录,集合,包,和列表。
数组不用解释。在 Lua 中,索引通常从 1 开始,但这只是个惯例。数组可以以 0 ,负数,或者其它的任何值(除了 nil)作为索引。记录也像平常一样用语法糖 a.x 实现。
最好的实现集合的方法是把它的元素保存为表的索引。语句 s={} 新建一个空集合 s。语句 s[x] = 1 把 x 的值插入到集合 s。表达式 s[x] 为真如果 x 属于 s。最后,语句 s[x] = nil 把 x 从 s 中删除。
包的实现方式和集合差不多,但是用和元素相关的值作为计数器。所以,为插入一个元素,可以用下面的代码:
if s[x] then s[x] = s[x]+1
else s[x] = 1 end
删除一个元素可以用下面的代码:
if s[x] then s[x] = s[x]-1 end
if s[x] == 0 then s[x] = nil end
Lisp 风格的列表也有一个简单的实现。二个元素 x 和 y 的 "cons" 可以由代码 l = {car=x, cdr=y} 建立。表达式 l.car 取得头,l.cdr 取得尾。另一个方法是直接用 l={x,y} 新建列表,然后用l[1] 取得头,用 l[2] 取得尾。
-------------------
8.2 函数 next 和 nextvar
-------------------
这个例子显示如何使用函数 next 去遍历一个表的字段。函数 clone 接受一个 table 并且返回它的一个克隆。
function clone (t) -- t is a table
local new_t = {} -- create a new table
local i, v = next(t, nil) -- i is an index of t, v = t[i]
while i do
new_t[i] = v
i, v = next(t, i) -- get next index
end
return new_t
end
这个例子打印所有值非空的全局变量的名字
function printGlobalVariables ()
local i, v = nextvar(nil)
while i do
print(i)
i, v = nextvar(i)
end
end
-------------------
8.3 字符串操作
-------------------
这个例子去掉字符串前后的空白:
function trim(s)
local l = 1
while strsub(s,l,l) == ‘ ‘ do
l = l+1
end
local r = strlen(s)
while strsub(s,r,r) == ‘ ‘ do
r = r-1
end
return strsub(s,l,r)
end
这个例子去掉字符串所有的空白:
function remove_blanks (s)
local b = strfind(s, ‘ ‘)
while b do
s = strsub(s, 1, b-1) .. strsub(s, b+1)
b = strfind(s, ‘ ‘)
end
return s
end
-------------------
8.4 可变个数参数
-------------------
Lua 并没有提供显著的机制去实现可变个数参数。然而,你可以使用表的构造去模拟这个机制。如下面的例子,假设函数想连接它所有的参数。可以采用如下代码:
function concat (o)
local i = 1
local s = ‘‘
while o[i] do
s = s .. o[i]
i = i+1
end
return s
end
为了调用它,可以使用一个表的构造去连接所有的参数:
x = concat{"hello ", "john", " and ", "mary"}
-------------------
8.5 持久化
-------------------
由于 Lua 的自反性,持久化在 Lua 中可以用 Lua 实现。本节展示一些方法来存储和恢复 Lua 中的值,用 Lua 写成的文本文件作为存储媒介。
保存一个键值对,用下面的代码就可以了:
function store (name, value)
write(format(‘\n%s =‘, name))
write_value(value)
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, ‘q‘)
end
end
为了恢复这些值,一个 lua_dofile 就足够了。
存储表有点复杂。假定表是一棵树,所有下标均为标识符(也就是说,表被用作记录),表的值可以用表的构造函数写成。
首先,把函数 write_value 改为
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, ‘q‘)
elseif t == ‘table‘ then write_record(value)
end
end
函数 write_record 是:
function write_record(t)
local i, v = next(t, nil)
write(‘{‘) -- starts constructor
while i do
store(i, v)
write(‘, ‘)
i, v = next(t, i)
end
write(‘}‘) -- closes constructor
end
-------------------
8.6 继承
-------------------
不存在索引的回退可以用来在 Lua 中实现多种继承。如下例,下面的代码实现了一个单继承:
function Index (t,f)
if f == ‘parent‘ then -- to avoid loop
return OldIndex(t,f)
end
local p = t.parent
if type(p) == ‘table‘ then
return p[f]
else
return OldIndex(t,f)
end
end
OldIndex = setfallback("index", Index)
当 Lua 试图去获得一个表中不存在的字段时,它调用回退函数 Index 。如果表有一个 parent 字段含有表的值,Lua 会试图去从它的 parent 对象获得想要的字段。这个过程重复“向上”直到为那个字段找到一个值或者对象没有 parent。在后面的情况,前面的回退将会被调用去为相应字段提供一个值。
当需要一个更好的性能时,同样的回退可以用 C 实现,如下面的代码中所示。
#include "lua.h"
int lockedParentName; /* lock index for the string "parent" */
int lockedOldIndex; /* previous fallback function */
void callOldFallback (lua_Object table, lua_Object index)
{
lua_Object oldIndex = lua_getref(lockedOldIndex);
lua_pushobject(table);
lua_pushobject(index);
lua_callfunction(oldIndex);
}
void Index (void)
{
lua_Object table = lua_getparam(1);
lua_Object index = lua_getparam(2);
lua_Object parent;
if (lua_isstring(index) && strcmp(lua_getstring(index), "parent") == 0)
{
callOldFallback(table, index);
return;
}
lua_pushobject(table);
lua_pushref(lockedParentName);
parent = lua_getsubscript();
if (lua_istable(parent))
{
lua_pushobject(parent);
lua_pushobject(index);
/* return result from getsubscript */
lua_pushobject(lua_getsubscript());
}
else
callOldFallback(table, index);
}
这个代码必须如下注册:
lua_pushstring("parent");
lockedParentName = lua_ref(1);
lua_pushobject(lua_setfallback("index", Index));
lockedOldIndex = lua_ref(1);
注意为提升性能字符串 "parent" 是如何锁定的。
-------------------
8.7 用类编程
-------------------
在 Lua 中有多种不同的方法进行面向对象编程。这个就展示一种可能实现类的方法,用上面提到的继承机制。注意:下面的例子只在 index 的回退被根据 8.6 重定义后才可以工作。
就像你所预想的那样,一个好的表示类的方法是用表。这个表将包含类的所有实例方法,另外可能的实现变量的默认值。一个类的例子是让它的 parent 的字段指向那个类,所以它“继承”所有的方法。
Point = {x = 0, y = 0}
function Point:create (o)
o.parent = self
return o
end
function Point:move (p)
self.x = self.x + p.x
self.y = self.y + p.y
end
...
--
-- creating points
--
p1 = Point:create{x = 10, y = 20}
p2 = Point:create{x = 10} -- y will be inherited until it is set
--
-- example of a method invocation
--
p1:move(p2)
例如,一个 Point 类可以如上所示。函数 create 帮助新建一个新的 point,添加它的 parent 字段。函数 move 是一个实例方法的例子。最后,一个字类可以新建为一个新表,它的 parent 字段指向它的父类。注意一个有趣的地方是 self 在方法 create 是如何使用的是允许这个方法可以正常工作就算是它被一个子类继承了。像平常的一样,一个字类可以用它自己的方法重写所有它继承的方法。
-------------------
8.8 模块
-------------------
这里我们解释一个可以在 Lua 中模拟模块的方法。主要的想法是使用一个表去保存模拟的函数。
一个模块应该被写为一个单独的块(chunk),开始于:
if modulename then return end -- avoid loading twice the same module
modulename = {} -- create a table to represent the module
之后,函数可以直接由下面的语法定义:
function modulename.foo (...)
...
end
所有需要这些模块的代码只需要执行 dofile("filename"), filename 就是模块定义的文件。之后,任何函数都可以由下面这样调用:
modulename.foo(...)
如果一个模块方法将要被使用很多次,程序可以给它一个局部名字。
因为函数是值,所以这样写就可以:
localname = modulename.foo
最后,一个模块可以打开,给它的所有的函数直接的访问权限,如下代码所示:
function open (mod)
local n, f = next(mod, nil)
while n do
setglobal(n, f)
n, f = next(mod, n)
end
end
-------------------
8.9 一个 Cfunction
-------------------
一个 Cfunction 用来计算最大的数字参数可以写成:
void math_max (void)
{
int i=1; /* number of arguments */
double d, dmax;
lua_Object o;
/* the function must get at least one argument */
if ((o = lua_getparam(i++)) == LUA_NOOBJECT)
lua_error ("too few arguments to function `max‘");
/* and this argument must be a number */
if (!lua_isnumber(o))
lua_error ("incorrect argument to function `max‘");
dmax = lua_getnumber (o);
/* loops until there is no more arguments */
while ((o = lua_getparam(i++)) != LUA_NOOBJECT)
{
if (!lua_isnumber(o))
lua_error ("incorrect argument to function `max‘");
d = lua_getnumber (o);
if (d > dmax) dmax = d;
}
/* push the result to be returned */
lua_pushnumber (dmax);
}
使用下面的函数注册:
lua_register ("max", math_max);
这个函数就可以由 Lua 调用了,如下:
i = max(4, 5, 10, -34) -- i receives 10
-------------------
8.5 调用 Lua 函数
-------------------
这个例子显示一个 C 函数如何调用一个 8.3 节中展示的 Lua 函数 remove_blanks。
void remove_blanks (char *s)
{
lua_pushstring(s); /* prepare parameter */
lua_call("remove_blanks"); /* call Lua function */
strcpy(s, lua_getstring(lua_getresult(1))); /* copy result back to ‘s‘ */
}
--------------------------------------
鸣谢
--------------------------------------
作者要感谢 CENPES/PETROBROBAS 和 TeCGraf 一起,使用该系统的早期版本,并提出宝贵意见。作者还要感谢 Carlos Henrique Levy,为这个语言起了个名字。Lua 在葡萄牙语里是月亮的意思。
--------------------------------------
其它(略)
--------------------------------------
和之前版本的不兼容及索引。