(转载)【笨木头Lua专栏】基础补充18:Lua的模块编写与module函数

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

 

笨木头花心贡献,哈?花心?不,是用心~函数

转载请注明,原文地址:http://www.benmutou.com/archives/1786测试

文章来源:笨木头与游戏开发ui

 

1.编写一个简单的模块

Lua的模块是什么东西呢?一般咱们能够理解为是一个table,这个table里有一些变量、一些函数…lua

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

没错,和类很像(实际上我说不出它们的区别)。code

 

咱们来看看一个简单的模块,新建一个文件,命名为game.lua,代码以下:对象

1
2
3
4
5
6
7
8
9
10
11
game  {}

function game.play()
    print("那么,开始吧");
end

function game.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end

return game;

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

至于如何使用模块,那就要用到咱们以前介绍过的require了。游戏

 

咱们在main函数里这么使用:

1
2
3
4
5
6
7
8
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函数还算少,就两个,实际上一个模块的函数确定不会少的,那么,要这么去改这些函数,太烦了。

若是批量修改,又怕有哪一个地方改错。

因而,咱们能够这么偷懒:

1
2
3
4
5
6
7
8
9
10
11
12
13
game  {}

local M = game;

function M.play()
    print("那么,开始吧");
end

function M.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end

return M;

咱们用一个局部变量M来代替了game,因而,之后咱们只须要修改前面两个的game就能够了,函数部分的内容彻底不须要去修改。

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

 

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

实际上,咱们能够更加得偷懒,之后修改模块名,只须要修改模块的文件名就能够了,文件内容能够无论,具体怎么实现?

看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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函数。

 

咱们来看看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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保存起来,以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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彷佛知道咱们有多懒似的,它居然把咱们把这一切都自动完成了。

再来回忆咱们刚刚为了偷懒而写的几句代码:

1
2
3
4
5
6
local M {};

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

就这几句代码,其实咱们能够忽略不写,由于,咱们有module函数,它的功能就至关于写了这些代码。

咱们修改一下game.lua的内容,以下代码:

1
2
3
4
5
6
7
8
9
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,已经帮咱们作了,咱们不须要去管这些。

因此我就拈轻怕重了,啊不,是顾此失彼…不对~!反正,就是那个意思了~!

 

原文地址:http://www.benmutou.com/archives/1786

相关文章
相关标签/搜索