Lua学习笔记:Lua高级编程

Lua基础语法上一篇已经介绍过了,这一篇介绍高级概念

模块

模块更像是一个类,但是因为lua中没有类的概念,模块的实现是依赖于table。公共或者私有的变量+函数组成了模块的主体,最后一个return module完成了基本的构造,其实这个return就是返回了这个table。

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
local DemoClass = {}  
  
local gameName = "Game1"  
gameParentName = "Game"  
DemoClass.gameChildName = "GameChild"  
function ()  
    return gameName;  
end  
return DemoClass  
  
-- 对DemoClass的调用  
gameParentName = "haha"  
require("DemoClass") -- 导入模块  
gameParentName = "haha1"  
print(DemoClass.GetGameName) -- Game1  
print(DemoClass.gameName) -- nil  
print(gameParentName) -- Game  
print(DemoClass.gameChildName) -- GameChild  

—|—

  • 解析一下代码,从例子中可以总结,模块中的以模块名+.形成的变量(DemoClass.gameChildName)是全局变量,而且即使在模块内部调用用也要用模块名+.的形式使用
  • 没有加模块名的变量其实也是全局变量,外部代码也可以直接访问。注意代码中在require的前后分别定义了一个和模块中名字相同的变量,这里有一个很有意思的事情,就是如果是之前定义的,那么在print时是模块中的值,如果在之后定义那么就是本地定义的值。这说明require这种过程实际上等于在本地定义了一些变量。所以最终输出的值可以按照后定义的输出。
  • 显示标记为local的变量是本地变量,在模块内部使用是没有问题的,但是外部代码无法访问。

加载机制

  • 基本上遵循C的加载机制,也就是先找同一目录下的文件,然后会找全局变量中path里面定义的文件。
  • 通常我们在编写代码的时候肯定是会有物理的文件夹结构的,此时如果我们不去改package.path的值,那么可以在引用时加速文件夹名字,比如我把class和student放到了Module文件夹下,那么代码中写require(“Module/DemoClass”)即可正常调用。

协程

Lua中的线程是真的多线程,并非unity的那种主线程内部的线程。
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
以下用网上的栗子来说明一下

  • coroutine.create() 创建 coroutine,返回 coroutine, 参数是一个函数,当和 resume 配合使用的时候就唤醒函数调用
  • coroutine.resume() 重启 coroutine,和 create 配合使用
  • coroutine.yield() 挂起 coroutine
  • coroutine.status() 查看 coroutine 的状态,coroutine 的状态有三种:dead,suspended,running,具体什么时候有这样的状态请参考下面的程序
  • coroutine.wrap()创建 coroutine,返回一个函数,一旦你调用这个函数,就进入 coroutine,和 create 功能重复
  • coroutine.running() 返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用running的时候,就是返回一个 corouting 的线程号

     1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45   46   47   48   49   50   51   52   53  
    
    co = coroutine.create(  
    function(i)  
        print(i);  
    end  
)  
coroutine.resume(co, 1)   -- 1  
print(coroutine.status(co))  -- dead  
  
print("----------")  
  
co = coroutine.wrap(  
    function(i)  
        print(i);  
    end  
)  
co(1)  
  
print("----------")  
  
co2 = coroutine.create(  
    function()  
        for i=1,10 do  
            print(i)  
            if i == 3 then  
                print(coroutine.status(co2))  --running  
                print(coroutine.running()) --thread:XXXXXX  
            end  
            coroutine.yield()  
        end  
    end  
)  
   
coroutine.resume(co2) --1  
coroutine.resume(co2) --2  
coroutine.resume(co2) --3  
print(coroutine.status(co2))   -- suspended  
print(coroutine.running())  
print("----------")  
  
-- 结果  
1  
dead  
----------  
1  
----------  
1  
2  
3  
running  
thread: 0x7fb801c05868    false  
suspended  
thread: 0x7fb801c04c88    true  
----------  

—|—

coroutine.running就可以看出来,coroutine在底层实现就是一个线程。当create一个coroutine的时候就是在新线程中注册了一个事件。当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。接下来我们分析一个更详细的实例:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
function foo (a)  
    print("foo 函数输出", a)  
    return coroutine.yield(2 * a) -- 返回  2*a 的值  
end  
   
co = coroutine.create(function (a , b)  
    print("第一次协同程序执行输出", a, b) -- co-body 1 10  
    local r = foo(a + 1)  
       
    print("第二次协同程序执行输出", r)  
    local r, s = coroutine.yield(a + b, a - b)  -- a,b的值为第一次调用协同程序时传入  
       
    print("第三次协同程序执行输出", r, s)  
    return b, "结束协同程序"                   -- b的值为第二次调用协同程序时传入  
end)  
          
print("main", coroutine.resume(co, 1, 10)) -- true, 4  
print("--分割线----")  
print("main", coroutine.resume(co, "r")) -- true 11 -9  
print("---分割线---")  
print("main", coroutine.resume(co, "x", "y")) -- true 10 end  
print("---分割线---")  
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine  
print("---分割线---")  
  
-- 运行结果  
第一次协同程序执行输出    1    10  
foo 函数输出    2  
main    true    4  
--分割线----  
第二次协同程序执行输出    r  
main    true    11    -9  
---分割线---  
第三次协同程序执行输出    x    y  
main    true    10    结束协同程序  
---分割线---  
main    false    cannot resume dead coroutine  
---分割线---  

—|—

示例解析:

  • 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;
  • 协同程序运行;
  • 运行到yield语句;
  • yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数)
  • 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数)
  • yield返回;
  • 协同程序继续运行;
  • 如果使用的协同程序继续运行完成后继续调用 resume方法则输出:cannot resume dead coroutine resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。

元表

参考:深入理解Lua中的元表(setmetatable)

面向对象

  • lua的oop是基于table的,table中可以设置全局或局部的变量、方法等
  • 继承通过子类调用父类的构造函数(其实lua没有构造函数一说,只是我们人为的在代码中创造出来了一个创建对象的函数)来创建,然后在扩展,从而实现了继承。
  • 从本质上说,lua的继承和C的继承是一样的,依赖的是table嵌套table,而C中是结构体嵌套结构体。

糖果

糖果
LUA教程

如果不小心安装错 SQL Server 为 Evaluation 的版本,要小心当超过 180 天之后,系统就会无法正常使用了 这几天遇到一个蛮特别的案例,原本收到的问题是 “维护计划” 忽然无法使用,即便是里面没有任何的Task,都无法顺利地执行。但从对方所提供的错误消...… Continue reading

PLUM NIZ静电容键盘怎么样?

Published on September 25, 2020

程序员如何选择合适的机械键盘

Published on September 18, 2020