由于表构造器不能创建带有循环的或共享字表的表,所以如果要处理表示通用拓扑结构(例如带有循环或共享字表)的表,就需要采用不同的方法。我们需要引入名称来表示循环。因此,下面的函数把值外加其名称一起作为参数。另外,还必须用一个额外的表来存储以保存表的名称,以便在发现循环时对其进行复用。这个额外的表使用此前已被保存的表作为键,以表的名称作为值。
function basicSerialize(o) -- 假设'o'是一个数字或字符串 return string.format("%q", o) end function save(name, value, saved) saved = saved or {} -- 初始值 io.write(name, " = ") if type(value) == "number" or type(value) == "string" then io.write(basicSerialize(value), "\n") elseif type(value) == "table" then if saved[value] then -- 值是否已被保存? io.write(saved[value], "\n") -- 使用之前的名称 else saved[value] = name io.write("{}\n") for k, v in pairs(value) do k = basicSerialize(k) local fname = string.format("%s[%s]", name, k) save(fname, v, saved) end end else error("cannot save a " .. type(value)) end end
我们假设要序列化的表只使用字符串或数值作为键。函数 basicSerialize
用于对这些基本类型进行序列化并返回序列化后的结果,另一个函数 save
则完成具体的工作,其参数 saved
就是之前所说的用于存储已保存表的表。例如,假设要创建一个如下所示的表:
a = {x = 1, y = 2; {3, 4, 5}} a[2] = a -- 循环 a.z = a[1] -- 共享字表
调用 save("a", a)
会将其保存为:
a = {} a[1] = {} a[1][1] = 3 a[1][2] = 4 a[1][3] = 5 a[2] = a a["z"] = a[1] a["y"] = 2 a["x"] = 1
取决于表的遍历情况,这些赋值语句的实际执行顺序可能会有所不同。不过尽管如此,上述算法能够保证任何新定义节点中所用到的节点都是已经被定义过的。
如果想保存具有共享部分的几个表,那么可以在调用函数 save
时使用相同的表 saved
函数。例如,假设有如下两个表:
a = {{"one", "two"}, 3} b = {k = a[1]}
如果以独立的方式保存这些表,那么结果中不会有共同的部分。不过,如果调用 save
函数时使用同一个表 saved
,那么结果就会共享共同的部分:
local t = {} save("a", a, t) save("b", b, t)
输出:
a = {} a[1] = {} a[1][1] = "one" a[1][2] = "two" a[2] = 3 b = {} b["k"] = a[1]
在 Lua
语言中,还有其他一些比较常见的方法。例如,我们可以在保存一个值时不指定全局名称而是通过一段代码来创建一个局部值并将其返回,也可以在可能的时候使用列表的语法等等。 Lua
语言给我们提供了构建这些机制的工具。