zoukankan      html  css  js  c++  java
  • Lua 学习之基础篇七<Lua Module,Package介绍>

    Lua 之Module介绍

    包管理库提供了从 Lua 中加载模块的基础库。 只有一个导出函数直接放在全局环境中: [require]。 所有其它的部分都导出在表 package 中。

    require (modname)

    加载一个模块。 这个函数首先查找 [package.loaded] 表, 检测 modname 是否被加载过。 如果被加载过,require 返回 package.loaded[modname] 中保存的值。 否则,它会为模块寻找加载器。

    require 遵循 [package.searchers] 序列的指引来查找加载器。如果改变这个序列,我们可以改变 require 如何查找一个模块。 下列说明基于 [package.searchers]的默认配置。

    首先 require 查找 package.preload[modname] 。 如果这里有一个值,这个值(必须是一个函数)就是那个加载器。 否则 require 使用 Lua 加载器去查找 [package.path]的路径。 如果查找失败,接着使用 C 加载器去查找 [package.cpath]的路径。 如果都失败了,再尝试一体化 加载器 。

    每次找到一个加载器,require 都用两个参数调用加载器: modname 和一个在获取加载器过程中得到的参数。 (如果通过查找文件得到的加载器,这个额外参数是文件名。) 如果加载器返回非空值, require 将这个值赋给 package.loaded[modname]。 如果加载器没能返回一个非空值用于赋给 package.loaded[modname]require 会在那里设入 true 。 无论是什么情况,require 都会返回 package.loaded[modname] 的最终值。

    如果在加载或运行模块时有错误, 或是无法为模块找到加载器, require 都会抛出错误。

    我们先看一下在lua文件中不显示require,lua运行环境会默认加载哪些, 可以通过遍历package.loaded数组来查看。

    print("Before the require function , packages in the package.loaded :")
            for k ,v in pairs(package.loaded) do 
                print(k,v)
            end
    
    Before the require function , packages in the package.loaded :
    os	table: 0x7ffc52403f00
    table	table: 0x7ffc524038e0
    math	table: 0x7ffc524054a0
    package	table: 0x7ffc524034a0
    _G	table: 0x7ffc524029b0
    coroutine	table: 0x7ffc52403fe0
    bit32	table: 0x7ffc52403d60
    utf8	table: 0x7ffc52405980
    string	table: 0x7ffc524051f0
    debug	table: 0x7ffc52404db0
    io	table: 0x7ffc52404490
    

    如何通过require 来呼叫外部lua 文件

    首先,创建一个moduleB.lua,内容如下

    Jason={}
    function Jason.Sum(max)
    sum=0
    for i=0,max,2 do  --这个for循环用法是->i 以2的增长方式递增到max
    sum=sum+i
    end
    return sum
    end
    

    其次,创建moduleA.lua

    -- package.path = "/Users/jason/Desktop/reqtest/moduleB.lua"
    package.path = "./moduleB.lua"
    
    require"moduleB.lua"
    
    for k,v in pairs (package.loaded) do
        print (k,v)
    end
    
    print (package.loaded["moduleB.lua"])
    print(Jason.Sum(100))
    print(package.path)
    print(package.cpath)
    
    输出为:
    debug	table: 0x7fd9bec04db0
    io	table: 0x7fd9bec04490
    string	table: 0x7fd9bec051f0
    moduleB.lua	true
    math	table: 0x7fd9bec054a0
    bit32	table: 0x7fd9bec03d60
    package	table: 0x7fd9bec034a0
    coroutine	table: 0x7fd9bec03fe0
    table	table: 0x7fd9bec038e0
    _G	table: 0x7fd9bec029b0
    utf8	table: 0x7fd9bec05980
    os	table: 0x7fd9bec03f00
    true
    2550
    ./moduleB.lua
    /usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so
    

    可以看到,在require相应的module后,package load会将其加载进来 并存储为true,我们可以利用这一点做文件load的check

    dofile()

    按参数filename提供的文件名打开一个文件并将其内容作为一个Lua程序块执行,当省略参数fielname时,函数默认把标准输入的内容作为程序块执行,执行结束后函数会把程序块返回的所有值作为函数的返回值返回,如果执行过程中发生了错误,函数会将错误向上跑出给它的调用者(当函数dofile()不是运行在保护模式的状态下)。

    用法是直接呼叫文件名,注意路径位置

    dofile("./hellow.lua")
    

    package

    包是一种组织代码的方式。

    使用表实现packages的明显的好处是:我们可以像其他表一样使用packages,并且可以使用语言提供的所有的功能,带来很多便利。大多数语言中,packages不是第一类值(first-class values)(也就是说,他们不能存储在变量里,不能作为函数参数。。。)因此,这些语言需要特殊的方法和技巧才能实现类似的功能。Lua中,虽然我们一直都用表来实现package,但也有其他不同的方法可以实现package.

    例一

    vector3d = {}  -- 包名  
    function vector3d.function1()  
    ......  
    end  
    function vector3d.function2()  
    ......  
          if (vector3d.function1()) then  
          ......  
          end  
    end  
    return vector3d 
    

    这样定义的就是一个vector3d包,使用require语言打开这个包后,就可以使用 vector3d.function1和vector3d.function2这两个函数了。

    这是最直接最好理解的一种Package定义方式,但是有一定的弊端。这个弊端主要体现在Package的实现过程中。可以看到,即使在

    vector3d.function2()中使用function1()函数,也必须完整的加上vector3d包名,否则无法进行函数调用。

    特别的注意最后的 return vector3d 语句,有了这句后调用者可以按照如下方式重命名包:

    MyPackage =  require "vector3d"  
    MyPackage.function2() 
    

    例二:使用局部函数定义所有的Package内函数,然后在Package的结尾处将需要公开的函数直接放入Package中。代码像这样:

    vector3d = {}  -- 包名  
    local function function1()  
    ......  
    end  
     
    local function function2()  
    ......  
          if (function1()) then  
          ......  
          end  
    end  
    vector3d = {function1 = functoin1,   
    function2function2 = function2  
    }  
    return vector3d 
    

    最后给包中赋值的部分就是将需要的接口公开的部分。这样做的好处:不需要公开的函数可以完全隐藏起来(都是local函数);Package内部的各个函数相互之间调用的时候不再需要加Package名称进行区分; 可以按照需要随意的重命名Package公开的接口名称。

    可以用local N = {}来保存数据和定义私有变量和函数。能明确的区分出接口和私有的定义,公开接口的名称还可以随意改变,这就意味着可以随意替换内部实现而不需要影响外部调用者。

    package 相关函数介绍

    1. package.config

      一个描述有一些为包管理准备的编译期配置信息的串。 这个字符串由一系列行构成:

    • 第一行是目录分割串。 对于 Windows 默认是 '' ,对于其它系统是 '/' 。
    • 第二行是用于路径中的分割符。默认值是 ';' 。
    • 第三行是用于标记模板替换点的字符串。 默认是 '?' 。
    • 第四行是在 Windows 中将被替换成执行程序所在目录的路径的字符串。 默认是 '!' 。
    • 第五行是一个记号,该记号之后的所有文本将在构建 luaopen_ 函数名时被忽略掉。 默认是 '-'。
    1. package.cpath

      这个路径被 [require] 在 C 加载器中做搜索时用到。

      Lua 用和初始化 Lua 路径 [package.path]相同的方式初始化 C 路径 [package.cpath] 。 它会使用环境变量 LUA_CPATH_5_3 或 环境变量 LUA_CPATH 初始化。 要么就采用 luaconf.h 中定义的默认路径。

    2. package.loaded

      用于 [require] 控制哪些模块已经被加载的表。 当你请求一个 modname 模块,且 package.loaded[modname] 不为假时, [require]简单返回储存在内的值。

      这个变量仅仅是对真正那张表的引用; 改变这个值并不会改变 [require 使用的表。

    3. package.loadlib(libname,funcname)

      让宿主程序动态链接 C 库 libname

      funcname 为 "*", 它仅仅连接该库,让库中的符号都导出给其它动态链接库使用。 否则,它查找库中的函数 funcname ,以 C 函数的形式返回这个函数。 因此,funcname 必须遵循原型 [lua_CFunction]

      这是一个低阶函数。 它完全绕过了包模块系统。 和 [require]不同, 它不会做任何路径查询,也不会自动加扩展名。 libname 必须是一个 C 库需要的完整的文件名,如果有必要,需要提供路径和扩展名。 funcname 必须是 C 库需要的准确名字 (这取决于使用的 C 编译器和链接器)。

      这个函数在标准 C 中不支持。 因此,它只在部分平台有效 ( Windows ,Linux ,Mac OS X, Solaris, BSD, 加上支持 dlfcn 标准的 Unix 系统)。

    4. package.path

      这个路径被 [require] 在 Lua 加载器中做搜索时用到。

      在启动时,Lua 用环境变量 LUA_PATH_5_3 或环境变量 LUA_PATH 来初始化这个变量。 或采用 luaconf.h 中的默认路径。 环境变量中出现的所有 ";;" 都会被替换成默认路径。

    5. package.preload

      保存有一些特殊模块的加载器,这个变量仅仅是对真正那张表的引用; 改变这个值并不会改变 [require] 使用的表

    6. package.serachers

      用于 [require]控制如何加载模块的表。

      这张表内的每一项都是一个 查找器函数。 当查找一个模块时, [require]按次序调用这些查找器, 并传入模块名([require]的参数)作为唯一的一个参数。 此函数可以返回另一个函数(模块的 加载器)加上另一个将传递给这个加载器的参数。 或是返回一个描述为何没有找到这个模块的字符串 (或是返回 nil 什么也不想说)。

      Lua 用四个查找器函数初始化这张表。

      第一个查找器就是简单的在 [package.preload]表中查找加载器。

      第二个查找器用于查找 Lua 库的加载库。 它使用储存在 [package.path] 中的路径来做查找工作。 查找过程和函数 [package.searchpath描述的一致。

      第三个查找器用于查找 C 库的加载库。 它使用储存在 [package.cpath]中的路径来做查找工作。 同样, 查找过程和函数 [package.searchpath]描述的一致。 例如,如果 C 路径是这样一个字符串

           "./?.so;./?.dll;/usr/local/?/init.so"
      

      查找器查找模块 foo 会依次尝试打开文件 ./foo.so./foo.dll, 以及 /usr/local/foo/init.so。 一旦它找到一个 C 库, 查找器首先使用动态链接机制连接该库。 然后尝试在该库中找到可以用作加载器的 C 函数。 这个 C 函数的名字是 "luaopen_" 紧接模块名的字符串, 其中字符串中所有的下划线都会被替换成点。 此外,如果模块名中有横线, 横线后面的部分(包括横线)都被去掉。 例如,如果模块名为 a.b.c-v2.1, 函数名就是 luaopen_a_b_c

      第四个搜索器是 一体化加载器。 它从 C 路径中查找指定模块的根名字。 例如,当请求 a.b.c 时, 它将查找 a 这个 C 库。 如果找得到,它会在里面找子模块的加载函数。 在我们的例子中,就是找 luaopen_a_b_c。 利用这个机制,可以把若干 C 子模块打包进单个库。 每个子模块都可以有原本的加载函数名。

      除了第一个(预加载)搜索器外,每个搜索器都会返回 它找到的模块的文件名。 这和 [package.searchpath的返回值一样。 第一个搜索器没有返回值。

    7. package.searchpath (name, path [, sep [, rep]])

      在指定 path 中搜索指定的 name

      路径是一个包含有一系列以分号分割的 模板 构成的字符串。 对于每个模板,都会用 name 替换其中的每个问号(如果有的话)。 且将其中的 sep (默认是点)替换为 rep (默认是系统的目录分割符)。 然后尝试打开这个文件名。

      例如,如果路径是字符串

           "./?.lua;./?.lc;/usr/local/?/init.lua"
      

      搜索 foo.a 这个名字将 依次尝试打开文件 ./foo/a.lua , ./foo/a.lc ,以及 /usr/local/foo/a/init.lua

      返回第一个可以用读模式打开(并马上关闭该文件)的文件的名字。 如果不存在这样的文件,返回 nil 加上错误消息。 (这条错误消息列出了所有尝试打开的文件名。)

    定义Module的方式

    定义module有两种方式,旧的方式,适用于Lua 5.0以及早期的5.1版本,新的方式现在均支持。

    旧的方式:

    通过module("...", package.seeall)来显示声明一个包

    --定义:
    
    -- oldmodule.lua
    module("oldmodule", package.seeall)
    function foo()
      print("oldmodule.foo called")
    end
    
    --使用:
    
    require "oldmodule"
    oldmodule.foo()
    
    • 1.module() 第一个参数就是模块名,如果不设置,缺省使用文件名。

    • 2.第二个参数package.seeall,默认在定义了一个module()之后,前面定义的全局变量就都不可用了,包括print函数等,如果要让之前的全局变量可见,必须在定义module的时候加上参数package.seeall。 具体参考云风这篇文章

    • package.seeall(module)功能:为module设置一个元表,此元表的__index字段的值为全局环境_G。所以module可以访问全局环境.

    之所以不再推荐module("...", package.seeall)这种方式,官方给出了两个原因。

    • 1.package.seeall这种方式破坏了模块的高内聚,原本引入oldmodule只想调用它的foo()函数,但是它却可以读写全局属性,例如oldmodule.os.

    • 2.第二个缺陷是module函数的side-effect引起的,它会污染全局环境变量。module("hello.world")会创建一个hello的table,并将这个table注入全局环境变量中,这样使得不想引用它的模块也能调用hello模块的方法。

    新的方式: 通过return table来实现一个模块

    --newmodule.lua
    local newmodule = {}
    function newmodule.foo()
      print("newmodule.foo called")
    end
    return newmodule
    

    使用

    local new = require "newmodule"
    new.foo()
    

    因为没有了全局变量和module关键字,引用的时候必须把模块指定给一个变量

  • 相关阅读:
    Leetcode Reverse Words in a String
    topcoder SRM 619 DIV2 GoodCompanyDivTwo
    topcoder SRM 618 DIV2 MovingRooksDiv2
    topcoder SRM 618 DIV2 WritingWords
    topcoder SRM 618 DIV2 LongWordsDiv2
    Zepto Code Rush 2014 A. Feed with Candy
    Zepto Code Rush 2014 B
    Codeforces Round #245 (Div. 2) B
    Codeforces Round #245 (Div. 2) A
    Codeforces Round #247 (Div. 2) B
  • 原文地址:https://www.cnblogs.com/xiaoqiangink/p/12082890.html
Copyright © 2011-2022 走看看