Lua中的模块与module函数详解_Lua

很快就要开始介绍Lua里的“面向对象”了,在此之前,我们先来了解一下Lua的模块。

1.编写一个简单的模块

Lua的模块是什么东西呢?通常我们可以理解为是一个table,这个table里有一些变量、一些函数…

等等,这不就是我们所熟悉的类吗?

没错,和类很像(实际上我说不出它们的区别)。
 
我们来看看一个简单的模块,新建一个文件,命名为game.lua,代码如下:

复制代码 代码如下:

game = {}
function game.play()
    print("那么,开始吧");
end
function game.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return game;

我们定义了一个table,并且给这个table加了两个字段,只不过这两个字段的值是函数而已。

至于如何使用模块,那就要用到我们之前介绍过的require了。
 
我们在main函数里这么使用:

复制代码 代码如下:

local function main()
    cc.FileUtils:getInstance():addSearchPath("src")
    game = require("game");
  
    game.play();
end

注意,我们要require其他文件的时候,要把文件路径给设置好,否则会找不到文件。
因为我使用的是Cocos Code IDE,直接调用addSearchPath函数就可以了,我的game.lua文件是在src目录下的。
 
好了,运行代码,结果如下:

复制代码 代码如下:

[LUA-print] 那么,开始吧

OK,这就是一个很简单的模块,如果我们习惯了Java、C++等面向对象语言,那也可以简单地把模块理解为类。

2.为以后的自己偷懒——避免修改每个函数中的模块名

假设我们想把刚刚的game模块改个名字,改成eatDaddyGame,那么,我们需要做以下两件事情:

1).修改game.lua的文件名
2).修改game.lua的内容,把所有的game改成eatDaddyGame
 
目前的game.lua函数还算少,就两个,实际上一个模块的函数肯定不会少的,那么,要这么去改这些函数,太烦了。

如果批量修改,又怕有哪个地方改错。

于是,我们可以这么偷懒:

复制代码 代码如下:

game = {}
local M = game;
function M.play()
    print("那么,开始吧");
end
function M.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

我们用一个局部变量M来代替了game,于是,以后我们只需要修改前面两个的game就可以了,函数部分的内容完全不需要去修改。

这个偷懒其实蛮有用的,某些情况下,修改越少,越安全~

3.更进一步的偷懒——模块名参数

实际上,我们可以更加得偷懒,以后修改模块名,只需要修改模块的文件名就可以了,文件内容可以不管,具体怎么实现?

看代码:

复制代码 代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
function M.play()
    print("那么,开始吧");
end
function M.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

留意一下,这里有一个 local modelName = …
“…”就是传递给模块的模块名,在这里其实就是“game”这个字符串。
 
接着,有点微妙了,还记得之前介绍的全局环境_G吗?我们以”game”作为字段名,添加到_G这个table里。

于是,当我们直接调用game的时候,其实就是在调用_G["game"]的内容了,而这个内容就是这里的M。
 
能逻辑过来吗?就是这么简单,在你没有忘记_G的前提下~

4.利用非全局环境制作更简洁和安全的模块

如果说,刚刚已经达到了我们作为高(ai)智(zhe)商(teng)人群的巅峰,那,你就太天真了。

巅峰就是要拿来超越的,还记得我们的非全局环境吗?就是那个setfenv函数。
 
我们来看看下面的代码:

复制代码 代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
setfenv(1, M);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

我们把game.lua这个模块里的全局环境设置为M,于是,我们直接定义函数的时候,不需要再带M前缀。

因为此时的全局环境就是M,不带前缀去定义变量,就是全局变量,这时的全局变量是保存在M里。

所以,实际上,play和quit函数仍然是在M这个table里。
 
于是,我们连前缀都不用写了,这真是懒到了一个极致,简直就是艺术~

另外,由于当前的全局环境是M,所以, 在这里不需要担心重新定义了已存在的函数名,因为外部的全局变量与这里无关了。
 
当然,如果大家现在就运行代码,肯定会报错了。

因为我们的全局环境改变了,所以print函数也找不到了。

为了解决这个问题,我们看看第5条内容吧~

5.解决原全局变量的无法找到的问题——方案1

第一个方法,就是我们之前介绍过的,使用继承,如下代码:

复制代码 代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
-- 方法1:使用继承
setmetatable(M, {__index = _G});
setfenv(1, M);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

没错,使用__index元方法就能解决这个问题了,当找不到print等函数时,就会去原来的_G里查找。

6.解决原全局变量的无法找到的问题——方案2

第二个方法更简单,使用一个局部变量把原来的_G保存起来,如下代码:

复制代码 代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
-- 方法2:使用局部变量保存_G
local _G = _G;
setfenv(1, M);
function play()
    _G.print("那么,开始吧");
end
function quit()
    _G.print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

这种方法的缺点比较明显,那就是,每次调用print等函数时,都要使用_G前缀。

7.解决原全局变量的无法找到的问题——方案3

第三个方法比较繁琐,使用局部变量把需要用到的其他模块保存起来,如下代码:

复制代码 代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
-- 方法3:保存需要使用到的模块
local print = print;
setfenv(1, M);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

这种方法的缺点更明显了,所有用到的模块都要用局部变量声明一次,烦人。
 
但,就速度而言,第三种方案比第二种方案快,第二种方法又比第一种快。
但至于快多少,我也不知道,只是理论上~我也没测试。

8.你就笑吧,但,我还想更加偷懒——module函数

本以为刚刚介绍的那些技巧已经够偷懒的吧?
但Lua似乎知道我们有多懒似的,它竟然把我们把这一切都自动完成了。
再来回忆我们刚刚为了偷懒而写的几句代码:

复制代码 代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
setmetatable(M, {__index = _G});
setfenv(1, M);

就这几句代码,其实我们可以忽略不写,因为,我们有module函数,它的功能就相当于写了这些代码。
我们修改一下game.lua的内容,如下代码:

复制代码 代码如下:

module(..., package.seeall);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end

注意,前面的几行代码都没了,只留下了一个module函数的调用。

module函数的调用已经相当于之前的那些代码了。

而package.seeall参数的作用就是让原来的_G依然生效,相当于调用了:setmetatable(M, {__index = _G});
 
再次留意一下,代码末尾的return M也不见了,因为module函数的存在,已经不需要我们主动去返回这个模块的table了。

9.结束

这篇结束的内容似乎有点多,我也写了一个多小时了。

其实我还省略不少东西,比如package.loaded,lua路径查找的规则等等。

因为这些Cocos Code IDE,或者说是Cocos2d-x lua,已经帮我们做了,我们不需要去管这些。

所以我就避重就轻了,啊不,是顾此失彼…不对~!反正,就是那个意思了~!

时间: 2024-08-21 03:53:52

Lua中的模块与module函数详解_Lua的相关文章

Lua教程(四):函数详解_Lua

一.函数:     在Lua中函数的调用方式和C语言基本相同,如:print("Hello World")和a = add(x, y).唯一的差别是,如果函数只有一个参数,并且该参数的类型为字符串常量或table的构造器,那么圆括号可以省略,如print "Hello World"和f {x = 20, y = 20}.     Lua为面对对象式的调用也提供了一种特殊的语法--冒号操作符.表达式o.foo(o,x)的另一种写法是o:foo(x).冒号操作符使调用o

Lua中强大的元方法__index详解_Lua

今天要来介绍比较好玩的内容--__index元方法 1.我是备胎,记得回头看看 咳咳,相信每一位女生都拥有或者不知不觉中拥有了一些备胎,啊,当然,又或许是成为过别人的备胎. 没有备胎的人,就不是完整的人生.(小若:停!)   我们来想象一下,如果对一个table进行取值操作,但是table根本就没有这个值呢? 比如: 复制代码 代码如下:     local t = {         name = "hehe",     }     print(t.money); 输出结果当然是:n

Lua中break语句的使用方法详解_Lua

 当循环中遇到break语句,循环立即终止,程序控制继续下一个循环语句后面. 如果您正在使用嵌套循环(即一个循环里面另一个循环),break 语句将停止最内层循环的执行并开始执行的下一行代码的程序后段.语法 Lua break语句语法如下: 复制代码 代码如下: break  例子: 复制代码 代码如下: --[ local variable definition --] a = 10--[ while loop execution --] while( a < 20 ) do    print

Lua中break语句的使用方法详解

  这篇文章主要介绍了Lua中break语句的使用方法详解,是Lua入门学习中的基础知识,需要的朋友可以参考下 当循环中遇到break语句,循环立即终止,程序控制继续下一个循环语句后面. 如果您正在使用嵌套循环(即一个循环里面另一个循环),break 语句将停止最内层循环的执行并开始执行的下一行代码的程序后段. 语法 Lua break语句语法如下: 代码如下: break 例子: 代码如下: --[ local variable definition --] a = 10--[ while l

C语言中改变目录的相关操作函数详解_C 语言

C语言fchdir()函数:改变当前工作目录头文件: #include <unistd.h> 定义函数: int fchdir(int fd); 函数说明:fchdir()用来将当前的工作目录改变成以参数fd 所指的文件描述词. 返回值:执行成功则返回 0, 失败返回-1, errno 为错误代码. 范例 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <

Go语言中的流程控制结构和函数详解_Golang

这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和无条件跳转. if if也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事. Go里面if条件判断语句中不需要括号,如下代码所示: 复制代码 代码如下: if x > 10 {     fmt.Println("x is greater than 10&

MySQL内置函数中的日期和时间函数详解

下面的例子使用了时间函数.以下询问选择了最近的 30天内所有带有date_col 值的记录: mysql> SELECT something FROM tbl_name -> WHERE DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= date_col; 注意,这个询问也能选择将来的日期记录. 用于日期值的函数通常会接受时间日期值而忽略时间部分.而用于时间值的函数通常接受时间日期值而忽略日期部分. 返回各自当前日期或时间的函数在每次询问执行开始时计算一次.这

Linux中的fork()函数详解

一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. 我们来看一个例子: [cpp] view plaincopy        /*  

JavaScript中的apply和call函数详解_jquery

第一次翻译技术文章,见笑了! 翻译原文: Function.apply and Function.call in JavaScript 第一段略. 每个JavaScript函数都会有很多附属的(attached)方法,包括toString().call()以及apply().听起来,你是否会感到奇怪,一个函数可能会有属于它自己的方法,但是记住,JavaScript中的每个函数都是一个对象.看一下 这篇文章 ,复习一下(refresher)JavaScript特性.你可能还想知道JavaScrip