Lua:何时以及如何将表写入_G?

7

我正在从一本书中学习Lua,但我不是程序员。我试图使用以下函数(直接从书上复制的)将数据表保存到文件中,但当尝试从_G[resTable]获取字符串时,该函数会出现错误。为什么会这样?

function readFromFile(filename,resTable)
    local hfile = io.open(filename)
    if hfile == nil then return end
    local results = {} -why is this table here?
    local a = 1
    for line in hfile:lines() do-- debug shows this loop doesn't run (no lines in hfile?)
        _G[resTable[a]] = line
        a = a + 1
    end
end

function writeToFile(filename, resTable)
    local hfile = io.open(filename, "w")
    if hfile == nil then return end
    local i
    for i=1, #resTable do
        hfile:write(_G[resTable[i]])--bad argument #1 to 'write' (string expected, got nil)
    end
end

当尝试向_G[resTable[i]]写入时,“writeToFile”会出现错误。在这里列出的前两个函数中,我不明白为什么它们引用了_G[resTable[i]],因为我没有看到任何写入_G的代码。

因此,以下是执行顺序:

local aryTable = {
"Score",
"Lives",
"Health",
}

readFromFile("datafile", aryTable)

writeToFile("datafile", aryTable)

我遇到了一个错误:

bad argument #1 to 'write' (string expected, got nil)
stack traceback:
[C]: in function 'write'
test.lua:45: in function 'writeToFile'
test.lua:82: in main chunk

你的数据文件包含什么内容? - interjay
目前 "datafile" 没有任何内容。 - PHazer
那么你期望 readFromFile 做什么? - interjay
writeToFile 函数无法将数据写入文件,因此 readToFile 函数当然读取到了 nil。 - PHazer
您可以使用Lua Pickle Table方法:http://lua-users.org/wiki/PickleTable - hjpotter92
感谢所有的发帖者让我感觉不那么疯狂! - PHazer
3个回答

3

显然作者已经实现了一种保存全局变量列表并将其恢复的方法。

writeToFile函数需要一个文件名和全局变量名称列表(resTable)。它会打开这个文件进行写操作,并对提供的名称进行迭代:

for i=1, #resTable do
    hfile:write(_G[resTable[i]])
end

在这个循环中,resTable[i] 是第i个名称,_G[resTable[i]] 是从存储所有全局变量的表格_G中获取的相应值。如果未定义该名称的全局变量,则_G[resTable[i]]将返回nil,这是您遇到错误的原因。因此,您必须提供一个填充了现有全局变量名称的resTable,以避免此错误。
除此之外,作者的序列化策略确实很幼稚,因为它只处理具有字符串值的变量。事实上,通过像这样将变量保存到文件中,类型信息会丢失,因此具有值"100"(字符串)和另一个具有值100(数字)的变量将在磁盘上存储相同。
问题可以通过分析readFromFile函数来明显地解决。打开文件进行读取后,它逐行扫描文件,在其resTable列表中提到的每个名称创建一个新变量:
local a = 1
for line in hfile:lines() do
    _G[resTable[a]] = line
    a = a + 1
end

问题有多重:
  • 循环变量line始终具有字符串值,因此即使它们最初是数字,重新创建的全局变量也将是所有字符串;
  • 它假定变量按相同顺序重新创建,因此在保存文件时必须使用resTable中使用的相同名称;
  • 它假定值以每行一个存储,但这是一种错误的假设,因为writeToFile函数不会在每个值后写入换行符;

此外,local results = {}是无用的,在两个函数中,文件句柄hfile都没有关闭。后者是非常糟糕的实践:它可能会浪费系统资源,并且如果您的脚本失败,则部分被认为已写入的数据可能永远无法到达磁盘,因为它仍然可能卡在某个缓冲区中。文件句柄在脚本结束时会自动关闭,但仅当以合理的方式结束时才会关闭。

除非您在粘贴代码或省略了其中的重要部分,或者该书正在逐步构建某个示例,否则我敢说它相当糟糕。


如果您想要一种快速而简单的方法来保存和检索一些全局变量,您可以使用以下代码:

function writeToFile( filename, resTable )
    local hfile = io.open(filename, "w")
    if hfile == nil then return end
    for _, name in ipairs( resTable ) do
        local value = _G[name]
        if value ~= nil then
            hfile:write( name, " = ")
            local vtype = type( value )
            if vtype == 'string' then
                hfile:write( string.format( "%q", value ) )
            elseif vtype == 'number' or vtype == 'boolean' then
                hfile:write( tostring( value ) )
            else
                -- do nothing - unsupported type
            end
            hfile:write( "\n" )
        end
    end
    hfile:close()
end

readFromFile = dofile

它将全局变量保存为Lua脚本,并通过执行该脚本使用Lua dofile函数读取它们。它的主要限制是它只能保存字符串、布尔值和数字,但通常在学习过程中这已经足够了。
您可以使用以下语句进行测试:
a = 10
b = "20"
c = "hello"
d = true
print( a, b, c, d )
writeToFile( "datafile", { "a", "b", "c", "d" } )
a, b, c, d = nil
print( a, b, c, d )
readFromFile( "datafile" )
print( a, b, c, d )

如果您需要更高级的序列化技术,可以参考Lua WIKI关于表格序列化的页面

我应该提到,在他的书的下一段中,他承认,“如果要保存的数据不是字符串或数字,函数将开始失败,特别是如果变量是表格。表格的最大问题是遍历表格的深度。”然后他主张使用JSON库对表对象和JSON编码字符串之间的信息进行编码或解码。 - PHazer
此外,作者正在逐步解决问题,从最简单的函数开始解决问题,指出陷阱,然后随着章节的进展引入更全面的函数。这可能是这些函数存在问题的原因,也可能不是。他的最终解决方案是将表保存为JSON编码字符串的函数。然而,对于像我这样的新手来说,这种方法往往会让人感到困惑。 - PHazer
@PHazer 我没有看到它,所以无法做出评判。但是我讨厌把事情过于简单化的书籍:它们往往会使进一步的学习变得更加困难,因为读者,特别是初学者,在最后必须纠正错误的习惯或错误的方法。 - Lorenzo Donati support Ukraine
1
我建议你尝试一下《Lua编程》(Programming in Lua)。这本书是Lua的开发者之一编写的,可以在网上免费获取。它是针对Lua 5.0版本编写的,因此对于Lua 5.1或5.2来说并不完全相关(但仍然有用)。它可以给你一些提示,帮助你决定作者的风格是否适合你,并帮助你决定是否购买更高版本的副本。它不会从头教你编程,但它是一本非常好的中级书籍,语言清晰,内容详细。 - Lorenzo Donati support Ukraine

0

这些不是通用的“从/到任何文件读写任何表格”的函数。它们显然期望作为参数传递一个全局表的名称,而不是[对本地表的引用]。它们看起来像是一种针对非常具体问题的一次性解决方案,这种解决方案往往会出现在书籍中。:-)

您的函数不应该对_G做任何操作。我手头没有API参考,但读取循环应该执行类似于以下操作:

resTable[a] = line

而写循环将会执行

hfile:write(resTable[i])

也把那个本地的“results”表扔掉吧。:-)


是的,这些函数确实期望一个全局表的名称作为参数。这就是为什么作者在传递一个局部表作为参数时让我感到困惑的原因。 - PHazer
...而且这个神秘的“结果”表也没有给我带来任何信心。 - PHazer
1
作者对“readFromFile”的推理: 在您的应用程序中,您可能存储的变量数量对于每个应用程序都可能不同。您永远不会知道数据保存的顺序,如果更改保存数据或读取数据的顺序,则可能会将值放入错误的变量中。因此,我们要做的是使用包含数据和字段名称的表来解决此问题。这将为我们提供未来进一步扩展的灵活性。然后他展示了readFromFile函数。 - PHazer

0

这段代码从文件中读取和写入数据到全局变量中,这些变量的名称在aryTable中指定。由于您的文件是空的,readFromFile实际上没有设置变量值。然后当尝试获取变量值时,writeToFile会失败,因为它们还没有被设置。

尝试将数据放入文件中,以便变量得到设置,或在将它们写入文件之前自己设置变量值(例如Score = 10等)。


你找出了作者写作的主要问题。对我来说,readFromFile在一个空数据文件上存在问题是显而易见的,然而在整个章节中,他从未提到过除使用writeToFile之外的任何记录数据到数据文件的方法。 - PHazer
另外,当我将一些数据行直接放入数据文件中(文本编辑器),函数无论如何都会将它们全部删除。 - PHazer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接