简介

从Lua5.1版本开始,就对模块/包添加了新的支持,可是使用require函数和package函数来加载模块,使用table模拟module来定义模块。
函数require用于加载模块,module用于创建模块。

传统模式下的模块机制module

1.什么是module

对开发来说,使用module可以有效分隔代码,实现代码共享,便于代码管理。
对于用户来说,一个module相当于一个Xnix中的共享库so或者windows中的动态库dll。

2.如何编写module

lua是通过table来实现模块的,典型的写法如下。

定义一个lua模块文件 – moduleA.lua

-- moduleA.lua
-- 通常是加local的,加了local是局部变量,需要return一下。
-- 如果不加,则M默认注册到_G中,require后,即使不return也可以直接使用M。local M = {}    -- 通过table来实现模块M.work = function(...)print("function working")for i, v in ipairs{...} doprint(i, v)end-- do some job.
endreturn M

这里定义了一个具有变长参数的函数–其函数参数个数可变。参数的定义形式为...,在内部访问时,也使用{...},和使用table的方式一样。


3.使用module

要使用定义好的module,使用require函数,加载定义的module,然后就可以使用。

-- test.lualocal m = require "moduleA"m.work('a','b',1,2,3)

module加载位置

要加载一个模块,就必须的知道这个模块在哪里。知道了这个模块在哪里以后,才能进行正确的加载。当我们写下require “mod”这样的代码以后,Lua是如何找这个mod的呢?

在搜索一个文件时,在windows上,很多都是根据windows的环境变量path来搜索,而require所使用的路径与传统的路径不同,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名的方式。require会用模块名来替换每个“?”,然后根据替换的结果来检查是否存在这样一个文件,如果不存在,就会尝试下一项。路径中的每一项都是以分号隔开。而且lua模块包括lua模块,以及c实现的模块。其对于require用于搜索的Lua文件的路径存放在变量package.path和package.cpath中。我们可以在输出看看。

检查一下lua系统中的环境,执行命令:

$ lua
Lua 5.2.4  Copyright (C) 1994-2015 Lua.org, PUC-Rio
stdin:1: unexpected symbol near char(228)
> print(package.path)
/usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;/usr/local/lib/lua/5.2/?.lua;/usr/local/lib/lua/5.2/?/init.lua;./?.lua
> print(package.cpath)
/usr/local/lib/lua/5.2/?.so;/usr/local/lib/lua/5.2/loadall.so;./?.so

可以再看看各种函数和对象的类型

> print(print)
function: 0x104cb304d
> print(require)
function: 0x7fdc3b403db0
> print(package)
table: 0x7fdc3b403740
> print(_G)
table: 0x7fdc3b4028d0
> print(module)
function: 0x7fdc3b403d60
> 

require机制

1.require实现原理:

function require(name)if not packge.loaded[name] then          ---- 避免重复加载local loader = findloader(name)      ---- 如果是so,就以`loadlib`方式加载文件,如果是lua文件,就以`loadfile`方式加载文件。if loader == nil thenerror("unable to load module " .. name)endpackage.loaded[name] = true -- 将模块标记为以加载,我们有时候会看到require返回true的现象,是由于被调用的模块,没有显示的执行package.loaded[modname] = M-- 或者给出return M这样的返回值。local res = loader(name)        -- require会以name作为入参来执行该文件,如果有返回结果,就将返回结果保存在package.loaded[name]中,-- 如果没有返回结果,就直接返回package.loaded[name]。如果我们在被调用的文件中直接写明return 1。-- 则调用者的require的返回结果就是1。但是只要我们显示的在require文件中写明了_G[modname] = M,-- 我们仍然可以在require之后,直接使用M作为名字来调用,是由于将M加入到了_G中。if res ~= nil thenpackage.loaded[name] = resendendreturn package.loaded[name]
end

2.require解析:

  • 传参: require会将模块名作为参数传递给模块

  • 返回值:如果一个模块没有返回值的话,require就会返回package.loaded[modulename]作为返回值。

3. package.loaded是系统使用的table

require会将返回值存储到package.loaded–一个table– 中;如果加载器没有返回值,require就会返回table package.loaded中的值。可以看到,我们上面的代码中,模块没有返回值,而是直接将模块名赋值给table package.loaded了。这说明package.loaded这个table中保存了已经加载的所有模块。

现在我们就可以看看require到底是如何加载的呢?

1.先判断package.loaded这个table中有没有对应模块的信息;
2.如果有,就直接返回对应的模块,不再进行第二次加载;
3.如果没有,就加载,返回加载后的模块。

环境

lua用_G一张表保存了全局数据(变量,函数和表等)。

我们在lua中定义一个module,如果不加local,则它是一个注册在全局下的表。我们通过加local避免了它在污染全局表空间,只在本文件生效。如果我们没有将其注册到_G下,在其他文件是无法直接通过他的原始名字来访问的。这里有一个不便利的地方,每个函数前面都要带MM的下的函数相互访问也要带M头。

解决方法:通过setfenv

local modname = ...local M = {}_G[modname] = Mpackage.loaded[modname] = Msetfenv(1, M)

后续的函数直接定义名字,因为他们的环境空间已经由_G改为了M

如果要使用全局函数,则可以本地额外增加一条local _G = _G或者setmetatable(M, {__index = G})

更好的方法是在setfenv之前将需要的函数都保存起来,local sqrt = math.sqrt

定义module – 使用module函数

在定义一个模块时,前面的几句代码都是一样的,就分为以下几步:

1.从require传入的参数中获取模块名;
2.建立一个空table;
3.在全局环境_G中添加模块名对应的字段,将空table赋值给这个字段;
4.在已经加载table中设置该模块;
5.设置环境变量。

就是这几步,在每一个模块的定义之前都需要加上,有点麻烦,在Lua5.1中提供了一个新函数module,它替代我们完成以上这些步骤完成的功能。

在编写一个模块时,可以直接用以下代码来取代前面的设置代码:

local moduleName = ...local M = {}            -- 局部的变量
_G[moduleName] = M      -- 将这个局部变量最终赋值给模块名package.loaded[moduleName] = Mlocal sqrt = math.sqrt  -- 在我们自己的模块中需要用到math.sqrt这个函数,所以就先保存下来
local io = io           -- 需要用到io库,也保存下来
setfenv(1, M)           -- 设置完成以后,就不能再使用_G table中的内容了

等同于

module(modname)。

默认的情况下,module不提供外部的访问的,也就是说,你无法访问前一个环境了。如果要访问外部变量,两种方法:

  • 1.在声明module之前,local 变量 = 外部变量

  • 2.使用module(modname, package.seeall), 等价于setmetatable(M, __index = _G)

在使用module时是这样解决的:

module(..., package.seeall)

其功能就好比之前的功能再加上了setmetatable(M, {__index = _G})。有了这一句代码,基本上就可以说万事不愁了。

使用module函数实现模块化

网上一个小示例:

先定义一个module:

mypack.lua

--mypack.lua
module(..., package.seeall) --定义module
ver = "0.1 alpha"
function aFunInMyPack()print("Hello!")
end
_G.aFuncFromMyPack = aFunInMyPack

测试代码test.lua:

--testP.lua:
pack = require "mypack" --导入module
print(ver or "No ver defined!")
print(pack.ver)
print(aFunInMyPack or "No aFunInMyPack defined!")
pack.aFunInMyPack()
print(aFuncFromMyPack or "No aFuncFromMyPack defined!")
aFuncFromMyPack()

执行结果:

$lua test.lua
No ver defined!
0.1 alpha
No aFunInMyPack defined!
Hello!
function: 0x7fe1e1501390
Hello!

目录层次

如果要更好地组织代码,可以把不同的模块放到不同的目录层次。
比如

ls -l -R
total 8
drwxr-xr-x  3 david  staff   96  3 23 12:07 sub
-rw-r--r--@ 1 david  staff  288  3 23 12:50 test.lua./sub:
total 8
-rw-r--r--@ 1 david  staff  174  3 23 12:11 mypack.lua

在工作执行目录下,包括了test.lua,以及sub目录,其下放置了mypack.lua – 对应mypack模块。

则test.lua中require时,需要包括模块目录形式,require "sub.mypack"
注意这里使用的是.,而不是\

Lua中的模块和使用相关推荐

  1. Lua中的模块与module函数详解

    很快就要开始介绍Lua里的"面向对象"了,在此之前,我们先来了解一下Lua的模块. 1.编写一个简单的模块 Lua的模块是什么东西呢?通常我们可以理解为是一个table,这个tab ...

  2. 利用lua中的string.gsub来巧妙实现json中字段的正则替换

    业务需求 工作中需要对某个请求的json响应中的某个字段进行替换, 通常想到的方法是,先使用lua的cjson模块解析该json响应, 取出该json字段再该改写它的值. 在这样实现的过程中,遇到一些 ...

  3. lua中的require、dofile、loadfile

    简介 lua文件是以字符串和块的方式存在的. 而在lua中,加载代码文件,通常会见到require.dofile.loadfile等函数. require 在加载一个.lua文件时,require会先 ...

  4. lua调用c 模块linux,Lua 调用自定义C模块

    这是<Lua程序设计>中提到的,但是想成功执行,对于初学Lua的确没那么简单.这里涉及如何如何生成一个动态链接库so文件:Lua5.2中导出函数从LuaL_register变成了LuaL_ ...

  5. Lua 中的 function、closure、upvalue

    Lua 中的 function.closure.upvalue function,local,upvalue,closure 参考: Lua基础 语句 lua学习笔记之Lua的function.clo ...

  6. Lua中的require与package.loaded

    require (modname) 加载一个模块. 这个函数首先查找 package.loaded 表, 检测 modname 是否被加载过. 如果被加载过,require 返回 package.lo ...

  7. Lua中的面向对象实现探讨

    Lua中,面向对向是用元表这种机制来实现的.元表是个很"道家"的机制,很深遂,很强大,里面有一些基本概念比较难理解透彻.不过,只有完全理解了元表,才能对Lua的面向对象使用自如,才 ...

  8. 关于lua中userdata的理解

    @[TOC] 关于lua中userdata的理解 关于Userdata的理解 Userdata数据结构 userdata是用来存放用户自定义数据结构的实列,userdata的类型有两种类型,分别是li ...

  9. Lua中的userdata

    userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct .指针和类)存储到 Lua 变量中 ...

最新文章

  1. 参数 相等_ANSYS DesignXplorer 参数化优化在水冷板流道设计中的应用
  2. 基于MySQL和DynamoDB的强一致性分布式事务实践
  3. 调试JavaScript/VB Script脚本程序(ASP篇)
  4. 基础省选+NOI-第2部分 数据结构进阶(II)
  5. html5简历阅读,HTML5 移动简历模板
  6. bzoj1013 [JSOI2008]球形空间产生器sphere
  7. python接口自动化(六)--发送get请求接口(详解)
  8. 第一次用PHP做电影站 用thinkphp开发的!!
  9. 关于在for循环中绑定事件打印变量i是最后一次。
  10. python文本字符串比对_[Python] 利用HTML页面查看字符串差异
  11. macbook excel导入html,mac版本怎么把网页数据导入Excel
  12. 基于大数据的NBA球员数据分析及预测系统
  13. Flink实战(八十五):flink-sql使用(十二)Flink 与 hive 结合使用(四)Hive Read Write
  14. 在微软Word中插入代码并保持代码样式
  15. 记录注册邓白氏编码过程
  16. 强化学习相关论文阅读笔记之稀疏奖励解决方法
  17. 你真的了解Linux(Deepin)的软件商店吗?(内附极力推荐的软件)
  18. idc具体是啥 idc服务器是什么意思?
  19. gitlab 安装以及卸载
  20. SLUB和SLAB的区别

热门文章

  1. Linux下面makefile编写
  2. IOC--IOC+AOP--热插拔的系统架构实现演化
  3. [2015-10-28]Angularjs-----数据获取,关联
  4. Waiting 180 more seconds for 1 worker threads to finish
  5. 什么是VB.NET的结构化异常处理
  6. 利用Bing翻译API简单的实现一个翻译工具
  7. Windows性能分析器概述(三)
  8. Spring源码:IOC容器
  9. Oracle+BEA后的ESB
  10. qq安全保护进程更改计算机,分享win10电脑系统关闭qq安全防护进程的步骤