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关键字,引用的时候必须把模块指定给一个变量

  • 相关阅读:
    VisionPro 各控件的C#中类库 CogAcqFifoTool(2)
    VisionPro 各控件的C#中类库 CogAcqFifoTool(1)
    C# new的三种用法
    C# as用法
    C# 基类之Environment类
    C#开发的软件在Windows7中出现对路径的访问被拒绝异常
    IDEA创建springboot项目【mas-service】
    .Net Core-ObjectPool
    中介者模式的实现-MediatR
    .NET Core分布式事件总线、分布式事务解决方案:CAP
  • 原文地址:https://www.cnblogs.com/xiaoqiangink/p/12082890.html
Copyright © 2011-2022 走看看