lua里的一些特殊方法和变量

32 篇文章 1 订阅
6 篇文章 0 订阅

1.pcall (f, arg1, ···):pcall在保护模式(protected mode)下执行函数内容,同时捕获所有的异常和错误。若一切正常,pcall返回true以及“被执行函数”的返回值;否则返回false和错误信息(打印出来即可)。Lua 代码可以显式的调用 error 函数来产生一条错误。成功后的多个返回值要用多个变量接收, f 后面的都为参数。:不中断程序

Calls function f with the given arguments in protected mode. This means that any error inside f is not propagated; instead, pcall catches the error and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In such case, pcall also returns all results from the call, after this first result. In case of any error, pcall returns false plus the error message.

2.xpcall 接受两个参数:调用函数、错误处理函数。xpcall (f, err) :不中断程序

his function is similar to pcall, except that you can set a new error handler.

xpcall calls function f in protected mode, using err as the error handler. Any error inside f is not propagated; instead, xpcall catches the error, calls the err function with the original error object, and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In this case, xpcall also returns all results from the call, after this first result. In case of any error, xpcall returns false plus the result from err.

当错误发生时,Lua会在栈释放以前调用错误处理函数,因此可以使用debug库收集错误相关信息。常用的debug处理函数:debug.debug()和debug.traceback(),前者给出Lua的提示符,你可以自己动手察看错误发生时的情况;后者通过traceback创建更多的错误信息,也是控制台解释器用来构建错误信息的函数。你可以在任何时候调用debug.traceback获取当前运行的traceback信息。
3._G 存储全局变量的table

在Lua中,要声明全局变量很简单,那就是定义变量的时候,前面不要加上local。这个神秘的全局变量,其实本质上也是一个table,它把我们创建的全局变量都保存到一个table里了。而这个table的名字是:_G

 

A global variable (not a function) that holds the global environment (that is, _G._G = _G). Lua itself does not use this variable; changing its value does not affect any environment, nor vice-versa. (Use setfenv to change environments.)

 -- 定义一个全局变量

 number = "1";

 -- 用三种方式输出变量的值

 print(number );

print(_G["number "]);

print(_G.number );

输出结果如下:

1
1
1

4.改变函数的全局变量环境——setfenv函数

setfenv函数就是用来改变某个函数范围里的全局环境的,通俗地说,就是把某个函数范围内的_G给弄没了。

setfenv函数两个参数分别代表:

1). 第一个参数,可以是即将要改变环境的函数,也可以是一个数字。数字1代表当前函数,数字2代表调用当前函数的函数,后面以此类推。

2).第二个参数,新的全局环境table

  -- 定义一个全局变量

  number= "123";

 -- 将当前全局环境重新设置为新的table

 setfenv(1, {});

  -- 输出值

 print(number);

如果现在运行代码,输出结果将会是这样的:

attempt to call global ‘print’ (a nil value)

为什么?很出乎意料的脸print函数都无法找到了?

这是因为我们已经把当前函数范围内的全局变量环境改变了,全局变量默认是保存在_G中的,而现在的全局变量是在一个新的table里。目前这个table是空的,所以不存在任何全局变量。

setfenv函数就是用来改变某个函数范围里的全局环境的,通俗地说,就是把某个函数范围内的_G给弄没了。

setfenv函数两个参数分别代表:

1). 第一个参数,可以是即将要改变环境的函数,也可以是一个数字。数字1代表当前函数,数字2代表调用当前函数的函数,后面以此类推。

2).第二个参数,新的全局环境table。

5.保留原来的_G

现在连print函数都无法使用了,对于测试很不方便,我们可以做个小动作,把原来的_G保留起来。

如下代码:

 -- 定义一个全局变量

 number= "123";   

-- 将当前全局环境重新设置为新的table

 setfenv(1, {g = _G})

-- 输出值

 g.print(number);

   -- 再次定义一个全局变量

  gName = "456";

   -- 再次输出值

 g.print(gName );

  -- 输出原来的值

  g.print(g.number);

只要在定义新的环境时,把_G作为一个字段放到新的table里,就可以调用原来的全局变量了。

那么,输出结果如下:

nil
456
123

三次调用g.print函数的输出结果都是不一样的:

a.第一次,此时刚刚重新设置了全局环境,这时候当前函数的全局变量只有一个,那就是g,所以number的值是nil。

b.第二次,我们再一次对number进行赋值,此时,已经在新的环境中了,所以接下来输出的number值是存在的。

c.第三次,这次输出的是g.number的值,通过g调用的number值是原先的全局环境里的值,所以number的值仍然是最初的“123”。

6.最后使用__index元方法保留原来的_G

这里还有一个小技巧分享一下,刚刚举例保留_G,但是调用print等函数时还需要形如g.print的方式,有点碍事。

我们可以利用__index来解决这个问题,如下代码:

  -- 定义一个全局变量

 number = "123";

 -- 一个table,即将成为新的环境 

 local newG = {};

  setmetatable(newG, {__index = _G});

 -- 将当前全局环境重新设置为新的table

  setfenv(1, newG);   

  number = "456!"; 

 -- 输出值

  print(number );

  print(_G.number );

我们给新的table设置一个元表,这个元表的__index元方法就是_G。

于是,当新的环境里找不到print字段时,就会去_G里寻找。

输出结果如下

456
123

第一次输出的是新环境里的number 值,第二次输出的是原来环境里的number 值,互不影响。

7.lua的require函数有两个特性:1.require函数会搜索目录加载文件.2.require会判断是否文件已经加载避免重复加载同一文件。所以,这个函数只能加载一次文件,当我们加载的lua文件动态改变后,需要重复加载时,只需要在记载前调用下package.loaded[“文件名.lua”] = nil就ok了,然后再required(‘文件名.lua’)。

8.package.path和 package.cpath

package.path用于搜索自己写的库文件或者第三方的库文件。package.path    = "./script/?.lua;" .. package.path

package.cpath用于搜索自己写的so库文件或者第三方的so库文件或者dll文件。 package.cpath    = "./?.dll;./script/?.dll;" .. package.cpath

对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。

require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。

当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以)。

当我们调用 require("module") 时就会尝试打开文件目录去搜索目标。如果找到目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。

用法:

1 只加载想要的目录
package.path = "../myLuaTest/myLuaCode/?.lua;" 

2 增加目录
package.path = "../myLuaTest/myLuaCode/?.lua;"..package.path  在原有的目录上加上自己的。 

还有一种方法(并没有试过)

例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。

接着,更新环境变量参数,使之立即生效。

source ~/.profile

 8.module模块:module用来来定义模块

module(...) 操作就相当于一下操作。

local modname = ... 

local M = {}  

_G[modname] = M  

package.loaded[modname] = M     这步是为了消除末尾的return语句,将模块table直接赋值给package.loaded

--[[       和普通Lua程序块一样声明外部函数。       --]]  

setfenv(1,M)  

有一点容易被忽略掉,module 指令运行完后,整个环境被压栈,所以前面全局的东西再看不见了。比如定义了一个 test 模块,使用

module("test")

后,下面不再看的见前面的全局环境。如果在这个模块里想调用 print 输出调试信息怎么办呢?一个简单的方法是

local print=print
module("test")

这样 print 是一个 local 变量,下面也是可见的。或者可以用

local _G=_G
module("test")

那么 _G.print 也是可以用的。

注意:关于module( ... , package.seeall)。 一般在一个Lua文件内以module函数开始定义一个包。module同时定义了一个新的包的函数环境,以使在此包中定义的全局变量都在这个环境中,而非使用包的函数的环境中,也就是说出了这个模块这个模块中定义的全局变量不能用。理解这一点非常关键。 “module(..., package.seeall)”的意思是定义一个包,包的名字与定义包的文件的名字相同,并且在包的函数环境里可以访问使用包的函数环境。使用方式:一般用require函数来导入一个包,要导入的包必须被置于包路径(packagepath)上。包路径可以通过package.path或者环境变量来设定。一般来说,当前工作路径总是在包路径中。

再调用module函数时,多传入一个package.seeall的参数,相当于 setmetatable(M, {__index = _G}) 这样全局的_G就不会消失。

9. collectgarbage:用来控制自动内存管理,当设置了setstepmul和setpause,Lua便会开启自动垃圾回收。

Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。

垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。

垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。

如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。

  • collectgarbage("collect"): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:

  • collectgarbage("count"): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。

  • collectgarbage("restart"): 重启垃圾收集器的自动运行。

  • collectgarbage("setpause"): 将 arg 设为收集器的 间歇率 。 返回 间歇率 的前一个值。

  • collectgarbage("setstepmul"): 返回 步进倍率 的前一个值。

  • collectgarbage("step"): 单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。

  • collectgarbage("stop"): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。

比如下面:

collectgarbage("collect");
collectgarbage("setpause", 100);
collectgarbage("setstepmul", 500);

https://blog.csdn.net/ChinarCSDN/article/details/78667262  sublime3和lua的安装

10.math.random和math.randomseed

1.math.randomseed接收一个整数 n 作为随机序列种子。对于相同的随机种子, 生成的随即序列一定是相同的。所以程序每次运行, 赋予不同的种子很重要。很自然想到使用系统时间作为随机种子,可以看到前两次运行的随机数都是一样的。究其原因,就是 os.time() 返回的时间是秒级的, 不够精确, 而 random() 还有个毛病就是如果 seed 很小或者seed 变化很小,产生的随机序列仍然很相似,math.randomseed(tostring(os.time()):reverse():sub(1, 6)),就是把 time返回的数值字串倒过来(低位变高位), 再取高位6位。 这样, 即使 time变化很小, 但是因为低位变了高位, 种子数值变化却很大,就可以使伪随机序列生成的更好一些。https://blog.csdn.net/zhangxaochen/article/details/8095007 
这里需要注意的是,如果随机时给的随机范围不同,序列时不一样的。并且在给定了随机种子的前提下,我们即使不给范围随机48次,当第49和50次都给随机范围随机的结果跟我们50次都给范围随机的结果时一样的。

 math.random([n [, m]]) 有三种用法: 无参调用, 产生 [0,1) 之间的浮点随机数; 只有参数 n, 产生 [1,n] 之间的整数; 有两个参数 [n,m], 产生 [n,m]之间的随机整数.

种子数只是随机算法的起源数字,和生成的随机数的区间没有任何关系。

10. 项目中用到loadstring,感觉这个东西挺有用的。例如我们游戏开启的时候 获取服务器中版本号和其他信息,这个信息是一个table,如果你去解析这个字符串的话,比较麻烦。直接用loadstring,该函数的返回值是返回一个function,如果load失败,则返回nil

local info = 'local map = {a = 1,b = 2};for _,v in pairs(map) do print(v) end'
local fun = loadstring(info )
print(fun())

结果:1 2

另外比如我们存储一些配置在本地,在我们写脚本的时候先在脚本加载的时候初始化一个本地table,在用io读取cfg配置,然后利用loadstring方法返回这个字符串的函数,然后调用给本地的table赋值。这样就完成了客户端本地的初始化。

9. assert :当Lua遇到不期望的情况时就会抛出错误,比如:两个非数字进行相加;调用一个非函数的变量;访问表中不存在的值等。你也可以通过调用error函数显示的抛出错误,error的参数是要抛出的错误信息。assert(a,b) a是要检查是否有错误的一个参数,b是a错误时抛出的信息。第二个参数b是可选的。中断程序
n = io.read()      assert(tonumber(n), "invalid input:" .. n .. "is not a number")

10.unpack  

unpack它接受一个数组(table)作为参数,并默认从下标1开始返回数组的所有元素。

local info={1,2,3,4,5,6}
local a,b,c,d,e,f=unpack(info)
print(a,b,c,d,e,f)
输出结果:1   2   3   4   5   6
11.可变参数

function fwrite(fmt, ...)  ---> 固定的参数fmt
    return io.write(string.format(fmt, ...))     
end

fwrite("runoob\n")       --->fmt = "runoob", 没有变长参数。  
fwrite("%d%d\n", 1, 2)   --->fmt = "%d%d", 变长参数为 1 和 2

输出结果为 :runoob 12

通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select('#', …)或者 select(n, …)

调用select时,必须传入一个固定实参selector(选择开关)和一系列变长参数。如果selector为数字n,那么select返回它的第n个可变实参,否则只能为字符串"#",这样select会返回变长参数的总数。例子代码:

实例

do  
    function foo(...)  
        for i = 1, select('#', ...) do  -->获取参数总数
            local arg = select(i, ...); -->读取参数
            print("arg", arg);  
        end  
    end  
  
    foo(1, 2, 3, 4);  
end输出结果为:

arg    1
arg    2
arg    3
arg    4

我们知道,其实do ... end就是执行了一个语句块,并没有什么特殊的含义,它基本上等同于C/C++中的{},需要注意的是在这个{}之间的局部变量,在这个区域之后的位置是没有办法引用的,在lua中也是一样的,只不过在lua中可以随意的定义全局变量,所以在do ... end之间的定义的全局变量,在语句块之后也可以引用。

do                                                            
    function ss()
        print("printss")
    end
end

print(_G["ss"])  这个会打印出function 因为语句块里定义的是全局变量。

local ss 

do 
    function ss()
        print("printss")
    end
end

print(_G["ss"]) 这个打印为nil  因为function ss()是语法糖 等同于 ss=function() 所以相当于在语句块里给local ss变量赋值,

而打印的是全局table里的数据。

local ss = 1

do 
    ss = 2
    local aa = 1
end

print(ss) --2  语句块里改变了ss的值,
print(aa) -- nil     语句块里创建的变量在语句块外部无效。

12.index和newindex  https://www.jb51.net/article/55155.htm

文章我们介绍了__index元方法,总结来说,__index元方法是用于处理调用table中不存在的字段。

注意,【调用】这个词,只是调用,而不是赋值。
 
如果,我们要对table中某个不存在的字段赋值呢?(小若:就,直接赋值啊!)

没错,我们直接就能赋值了,不会报错的。

问题是,如果我想监控这个操作呢?如果有人想对table不存在的字段进行赋值的时候,我想进行一些额外的处理呢?

这时候就要用到__newindex。
 
大家要记住这句话:__index用于查询,__newindex用于更新。

等会不要混乱了, 初次接触的话,有可能会混乱。

2.看看普通的赋值情况

我们先来看看正常情况下的赋值,如代码:


    local smartMan = {
        name = "none",
        money = 9000000,
      
        sayHello = function()
            print("大家好,我是聪明的豪。");
        end
    }
  
    local t1 = {};
  
    local mt = {
        __index = smartMan,
    }
  
    setmetatable(t1, mt);
  
    t1.sayHello = function()
        print("en");
    end;
  
    t1.sayHello();

 

这是上一篇用过的例子,一个模仿继承结构的例子。
来分析一下,mt作为t1的元表,设置__index为smartMan。
于是,当我们调用t1中不存在的字段时,就会自动去smartMan中查找。
比如我们调用了t1.sayHello(),自然能找到对应的函数。
 
先来看看输出结果:
[LUA-print] en


我们调用t1的sayHello字段,t1并不存在这个字段(虽然可以通过__index的方式来找到smartMan的sayHello字段)。

 

但这不影响,给这个字段赋值,然后再调用t1.sayHello(),发现是成功的。

这和我们以往的做法一样,对table做正常的赋值操作,不管table本身是否存在这个字段。

监控赋值

好了,普通情况我们已经试过了,如果我们想监控table的赋值操作呢?
对于不存在的字段,我们不需要被赋值呢?想要制作一个只读的table呢?
 
如果你有这些想法,那么欢迎拨打屏幕下方的号码,前10位打进的还赠送价值..(小若:停!)
那么,如果你有这些想法,请看看下面的代码:


    local smartMan = {
        name = "none",
        money = 9000000,
      
        sayHello = function()
            print("大家好,我是聪明的豪。");
        end
    }
  
    local t1 = {};
  
    local mt = {
        __index = smartMan,
        __newindex = function(table, key, value)
            print(key .. "字段是不存在的,不要试图给它赋值!");
        end
    }
  
    setmetatable(t1, mt);
  
    t1.sayHello = function()
        print("en");
    end;
    t1.sayHello();

 

留意mt元表,我们给它加了一个__newindex。
运行代码,输出结果如下:
[LUA-print] sayHello字段是不存在的,不要试图给它赋值!
[LUA-print] 大家好,我是聪明的豪。
很显然,sayHello字段赋值失败,因为给sayHello字段赋值的时候,调用了__newindex元方法,代替了赋值操作。

(小若:为什么?sayHello字段不是存在的么?为什么会说不存在呢?)
 
这里有一个地方要注意的,t1中确实是不存在sayHello字段的,它只是因为有元表存在,而元表里的__index元方法的值是smartMan这个table。

从而,可以在t1找不到sayHello字段的时候,去smartMan中寻找。

但,实际上,t1确实是不存在sayHello字段的,不知道大家能绕明白不?
 
因此,当试图给t1的sayHello字段赋值时,Lua判定sayHello字段是不存在的,所以会去调用元表里的__newindex元方法。

__newindex元方法被调用的时候会传入3个参数:table本身、字段名、想要赋予的值。

隔山打牛,通过给一个table给另一个table的字段赋值

和__index一样,__newindex元方法也可以赋予一个table值。
这种情况下就有点意思了,先看看代码:
    local smartMan = {
        name = "none",
    }
  
    local other = {
        name = "大家好,我是很无辜的table"
    }
  
    local t1 = {};
  
    local mt = {
        __index = smartMan,
        __newindex = other
    }
  
    setmetatable(t1, mt);
    
    print("other的名字,赋值前:" .. other.name);
    t1.name = "小偷";
    print("other的名字,赋值后:" .. other.name);
    print("t1的名字:" .. t1.name);

这次的代码和刚刚差不多,但是我们新加了一个other的table,然后把other作为__newindex的值。

于是,当给t1的name字段赋值时,就会发生一些奇怪的事情…

先来看看输出结果:
[LUA-print] other的名字,赋值前:大家好,我是很无辜的table
[LUA-print] other的名字,赋值后:小偷
[LUA-print] t1的名字:none
当给t1的name字段赋值后,other的name字段反而被赋值了,而t1的name字段仍然没有发生变化。

(实际上t1的name字段还是不存在的,它只是通过__index找到了smartMan的name字段,这个就不唠叨了。)

于是,我们给t1的name赋值的时候,实际上是给other的name赋值了。

好吧,可怜的other。

总结规则

这就是__newindex的规则:

a.如果__newindex是一个函数,则在给table不存在的字段赋值时,会调用这个函数。
b.如果__newindex是一个table,则在给table不存在的字段赋值时,会直接给__newindex的table赋值。

像__index一样,如果metamethod是一个表,解释器对指定的那个表,而不是原始的表进行赋值操作,__index会将table和键作为参数传递进去,_newindex会将mytable, key, value作为参数传递。

13.reset和reget :两者的作用都是绕过元表。有的时候,我们就不想从__index对应的元方法中查询值,我们也不想更新table时,也不想执行__newindex对应的方法,或者__newindex对应的table。那怎么办?在Lua中,当我们查询table中的值,或者更新table中的值时,不想理那该死的元表,我们可以使用rawget函数,调用rawget(tb, i)就是对table tb进行了一次“原始的(raw)”访问,也就是一次不考虑元表的简单访问;你可能会想,一次原始的访问,没有访问__index对应的元方法,可能有性能的提升,其实一次原始访问并不会加速代码执行的速度。对于__newindex元方法,可以调用rawset(t, k, v)函数,它可以不涉及任何元方法而直接设置table t中与key k相关联的value v

14. os.time 和os.date

os.time:如果没有任何参数,就会返回当前时间。如果参数一个table,并且table的域必须有 year, month, day, 可有也可以没有 hour, min, sec, isdst,则会返回table所代表日期的时间,如果未定义后几项,默认时间为当天正午(12:00:00)。 返回值是一个 number ,其值取决于你的系统。返回值通常被用于 os.date 和 os.difftime。os.time(arg)  。作用就是返回指定日期或者当前的时间戳。注意:返回的值是一个数字,其含义取决于您的系统。在POSIX、Windows和其他一些系统中,这个数字统计自某个给定的开始时间(“epoch”)以来的秒数。在其他系统中,没有指定含义,时间返回的数字只能用作date和difftime的参数。所以在炫目中需要用于服务器一直的时间戳来显示时间。

os.date:第一个参数是时间的格式化参数,如果设置了则会按照固定的格式来格式化时间。若设置time参数,则按time指定的时间格式化,否则按当前时间格式化。如果format 以 ! 开始, date 会被格式化成协调世界时(Coordinated Universal Time) 。 *t 将返一个带year(4位), month(1-12), day (1--31), hour (0-23), min (0-59), sec (0-61), wday (星期几, 星期天为1), yday (年内天数), isdst (不包括)(是否为日光节约时间true/false)的表。若没有*t则返回一个按C的strftime函数格式化的字符串  若不带参数,则按当前系统的设置返回格式化的字符串 .协调世界时为世界统一时间。os.data(format, time)

local time3 = os.date("!*t", os.time())
print(os.time(time3)) 得到的是世界统一时间戳

os.difftime():原型:os.difftime (t2, t1) 解释:返回以秒计算的时刻t1到 t2 的差值。 在Windows,和其它一些系统中,这个值就等于 t2-t1.

C语言中‘\0’是判定字符数组结束的标识,表示这串字符到结尾了;例如定义charc[6]=“hello”,而在内存中字符数组c则是"hello\0。lua里的string.len()方法计算字符串长度的时候是吧‘\0’计算在内的,比如"a\000bc\000" 长度是5,而abc长度是3.

15.os.clock  返回该程序使用的CPU时间(以秒为单位)的近似值。

经过Google搜索一番,发现lua的os.clock()会调用c的 clock 函数,该函数返回值依赖于操作系统,但依然返回一个从启动到现在的cpu执行时间。 查看 os.clock 的源码也验证了这一点。

clock 函数有3个关键信息需要关心:

  1. clock 返回cpu滴答次数而非秒数
  2. clock_t clock (void) 返回类型为 clock_t ,该类型在32位系统中是4字节,64位系统中是8字节
  3. CLOCKS_PER_SEC 表示每秒钟的时钟滴答次数

在mac上测试得到到 CLOCKS_PER_SEC 为1000000,也就是说 clock()/CLOCKS_PER_SEC 为执行的秒数,如果在32位系统中有效时间最大为1小时10分钟。

Android系统都是32位,而且玩家挂机时很容易就能超过1小时的运行时间,所以问题就出在了clock溢出了。 所以只需要将上面的代码从 os.clock 修改为 os.time 就能解决当前时间小于过去时间的问题了。

再谈os.clock

上面使用 os.time 替换了 os.clock 解决了时间倒退的问题,但是丢失了时间的精度,因为 os.time 最小精度为1秒。或者使用local socket = require("socket") socket.gettime() 

有没有什么地方需要时间精度少于1秒呢,当然是有的,比如掉血后的血条过渡效果。

当显示血量从100掉到50时,血条需要花费1秒时间从100过渡到50,而不是立刻到达50。这时再用 os.time 将不能达到流畅的过渡效果,所以还得使用 os.clock 

既然 os.clock 在32位系统有溢出问题,导致当前时间小于过去的时间,显然不能不做处理就直接使用。 由于需要度量的时间很小,不会超过1小时(os.clock在32位系统的最大时间),那么可以在检查到溢出时修正当前时间就好。

总结

os.clock 特点:

  • 可以度量小于1秒的时间
  • 在32位系统中有溢出风险,最大可度量的时间为 4294.967296,约1小时10分钟
  • 多线程/多进程中不同平台的返回时间不同 (在两个clock调用之间加入Sleep(1000),那么Sleep的时间是算入clock的(Windows平台+VS2008)等)

虽然os.clock有一些限制,但是在游戏中的一些短时间应用还是没有问题的。

local function clock(old)     if not old then  return os.clock() end   local now = os.clock()    if now < old then

-- 4294.967296 = math.pow(2, 32)/CLOCKS_PER_SEC

now = now + 4294.967296 end return now end

-- 使用 local begin = clock() local function delatTime() return clock() - begin end

 

15. lua实现包裹一层的只读表 

function readOnly(t)    
    local proxy = {}
    local mt = 
    { -- create metatable
        __index = t,
        __newindex = function (t,k,v)
            error("attempt to update a read-only table", 2)
        end        
    }

    setmetatable(proxy, mt)
    return proxy
end

days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}

print(days[1])

--days[2] = "Noday"

 

多层table只读
-- 用法 local cfg_proxy = read_only(cfg)  retur cfg_proxy
-- 增加了防重置设置read_only的机制
-- lua5.3支持 1)table库支持调用元方法,所以table.remove table.insert 也会抛出错误,
--               2)不用定义__ipairs 5.3 ipairs迭代器支持访问元方法__index,pairs迭代器next不支持故需要元方法__pairs
-- 低版本lua此函数不能完全按照预期工作

  function read_only(inputTable)
    local travelled_tables = {}

    local function __read_only(tbl)
        if not travelled_tables[tbl] then
            local tbl_mt = getmetatable(tbl)
            if not tbl_mt then
                tbl_mt = {}
                setmetatable(tbl, tbl_mt)
            end

            local proxy = tbl_mt.__read_only_proxy
            if not proxy then
                proxy = {}
                tbl_mt.__read_only_proxy = proxy
               local proxy_mt = {
                     __index = tbl,
                     __newindex = function (t, k, v) error("error write to a read-only table with key = " .. tostring(k)) end,
                     __pairs = function (t) return pairs(tbl) end,
                     -- __ipairs = function (t) return ipairs(tbl) end,   5.3版本不需要此方法
                    __len = function (t) return #tbl end,
                    __read_only_proxy = proxy
                 }
               setmetatable(proxy, proxy_mt)
            end

               travelled_tables[tbl] = proxy

            for k, v in pairs(tbl) do
                 if type(v) == "table" then
                    tbl[k] = __read_only(v)
                end
            end
        end

        return travelled_tables[tbl]
    end

    return __read_only(inputTable)
end

local t0 = {k = 1}

local t2 = {
     fdsf = {456}
}

local t1 = {a = {456, 89},   b = {456,ddss = 9, t2 = t2}, d = 45, e = "string",  }

t1.c=t1

local t3 = read_only(t1)

print(t1.a[1])

上面 __pairs = function (t) return pairs(tbl) end, 这句话的意思是重写 __pairs元方法,因为返回的table表proxy是,而却通过

tbl[k] = __read_only(v)  ,已经把原始数据的key指向了新的proxy,而proxy是空的,所以遍历的时候非table的元素可以遍历出来,table值的元素是遍历不出来的,所以要重写遍历方法来实现遍历我们原数据的表。重写就是在元表里定义__pairs元方法后再setmetatable 一下。

16. 语法糖

local t = { x= 1}
t.add = function(self)
    self.x = self.x + 1
end

t.add(t)

print(t.x)                --2

local t = { x= 1}

function t:add()
   self.x = self.x + 1
end

t:add()
print(t.x)            --2     下面的为语法糖格式,隐式的增加了一个self作为第一个参数 调用从 ‘。’变为“:”

17 闭包函数   闭包绑定局部变量时是引用绑定,多个闭包可以共享同一个局部变量

function get_closure()
    local x = 1
    local add = function() x = x + 1  end
    local get = function() return x  end
    --add 和 get 使用的是同一个x
    return get, add
end

local get, add = get_closure()
print(get()) --1
add()
add()
print(get()) --3

18.在调用lua函数时,也需要将对应的参数放在一对圆括号中,即使调用函数时没有参数,也必须写出一对空括号。对于这个规则只有一种特殊的例外情况:一个函数若只有一个参数,并且此参数是一个字符串或table构造式,那么圆括号便可以省略掉。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值