import{_ as a,c as e,o as r,a4 as p}from"./chunks/framework.Cy9JvDAg.js";const g=JSON.parse('{"title":"6.3 – 模块","description":"","frontmatter":{"title":"6.3 – 模块","footer":true,"lastUpdated":true,"outline":"deep"},"headers":[],"relativePath":"6.3.md","filePath":"6.3.md"}'),c={name:"6.3.md"},o=p('<h2 id="_6-3-–-模块" tabindex="-1">6.3 – 模块 <a class="header-anchor" href="#_6-3-–-模块" aria-label="Permalink to &quot;6.3 – 模块&quot;">​</a></h2><p>包相关库提供了Lua中加载模块相关的基础工具。其直接将一个函数<a href="#require">require</a>导出到了全局环境中，其他都导出到了表 package 中。</p><h3 id="require" tabindex="-1">require (modname) <a class="header-anchor" href="#require" aria-label="Permalink to &quot;require (modname) {#require}&quot;">​</a></h3><p>加载给定的模块。此函数一开始会在表<a href="#package.loaded">package.loaded</a>中确认名为参数 modname 模块是否已经加载。如果是，那么 require 会返回存在 package.loaded[modname] 中的值。（这种情况下是没有第二个返回值的，以表示此次调用没有加载任何模块。）否则将尝试查找模块的<em>加载器 loader</em> 。</p><p><a href="#package.searchers">package.searchers</a>会引导查找加载器。该表中的每一个元素都是一个查找函数，通过其各自的方式查找模块。所以通过改变该表我们就可以改变 require 查找模块的方式。下边解释了默认配置下的 package.searchers 。</p><p>首先 require 会查询 package.preload[modname]。如果找到了，那么其对应的值（一定是个函数）就是加载器。否则 require 会使用另一个加载器，这个加载器使用存在 <a href="#package.path">package.path</a> 的路径查找模块。如果又失败了，则会使用一个C加载器，其会在 <a href="#package.cpath">package.cpath</a> 中寻找。如果还是失败了，则会尝试使用 <em>all-in-one</em> 加载器（参见<a href="#package.searchers">package.searchers</a>）。</p><p>对于每个找到的加载器， require 都会使用两个参数调用它：modname 和一个扩展值（是一个 <em>加载器数据 loader data</em>，也是由查找器返回的） 。这个加载器数据是模块所用到的一个任意值；对于默认的查找器，它是加载器被查找到的位置。（例如，如果是一个来自文件中的加载器，这个值就是文件路径。）如果加载器返回了任何非空的值，require 会将其赋值给 package.loaded[modname] 。如果加载器没有返回有效值并没有任何 package.loaded[modname] 的赋值，那么 require 会将该条目置为<em>true</em>。任何情况下，require 都会返回 package.loaded[modname] 最终的值。除这个值以外，require 还会将查找器返回的加载器数据作为第二个结果返回，其表明了 require 是如何找到的模块。</p><p>如果在加载或运行模块的过程中发生了任何错误，或者没有找到任何加载器，那么 require 都会抛出一个错误。</p><h3 id="package.config" tabindex="-1">package.config <a class="header-anchor" href="#package.config" aria-label="Permalink to &quot;package.config  {#package.config}&quot;">​</a></h3><p>该字符串描述了关于包的一些编译时设置。此字符串由这些行构成：</p><ul><li>第一行是目录的分割符。Windows下默认是 &#39;&#39; ，其他系统默认是 &#39;/&#39; 。</li><li>第二行是各个路径的分割符。默认为 &#39;;&#39; 。</li><li>第三行是标记匹配替换的符号。默认为 &#39;?&#39; 。</li><li>第四行是表示在Windows中会被替换为可执行文件所在的目录的符号。默认为 &#39;!&#39; 。</li><li>第五行是一个标记，会使得在构建 luaopen_ 函数名时忽略其后边的文本。默认为 &#39;-&#39; 。</li></ul><h3 id="package.cpath" tabindex="-1">package.cpath <a class="header-anchor" href="#package.cpath" aria-label="Permalink to &quot;package.cpath  {#package.cpath}&quot;">​</a></h3><p>该字符串是用于<a href="#reqire">reqire</a>中查找 C 加载器的路径。</p><p>Lua初始化<a href="#package.cpath">package.cpath</a>的方式和<a href="#package.path">package.path</a>是一样的，使用环境变量 LUA_CPATH_5_4 ，或使用环境变量 LUA_CPATH，或者使用定义在 luaconf.h 文件中的默认路径。</p><h3 id="package.loaded" tabindex="-1">package.loaded <a class="header-anchor" href="#package.loaded" aria-label="Permalink to &quot;package.loaded  {#package.loaded}&quot;">​</a></h3><p>用于<a href="#require">require</a>的一个表，可以用来确认哪些模块已经加载了。当你需要一个名为 modname 的模块时，如果 package.loaded[modname] 存在，那么<a href="#require">require</a>就会之间返回存在此处的值。</p><p>这个全局变量只是真正的 loaded 表的引用；给这个变量赋值并不会改变<a href="#require">require</a>的行为。真正的 loaded 表是存在C注册表中的（参见<a href="/lua5.4-manual-zh/4.3.html">4.3</a>），索引为 LUA_LOADED_TABLE， 一个预定义的字符串。</p><h3 id="package.loadlib" tabindex="-1">package.loadlib (libname, funcname) <a class="header-anchor" href="#package.loadlib" aria-label="Permalink to &quot;package.loadlib (libname, funcname) {#package.loadlib}&quot;">​</a></h3><p>将一个名为 libname 的C库动态链接到宿主程序中。</p><p>如果 funcname 为 &quot;*&quot; ，那么只会链接这个库，将其符号导出给其他的动态链接库。否则，会在这个库中查找名为 funcname 的函数并找到的函数作为Lua定义的C函数形式返回。所以， funcname 函数必须符合<a href="/lua5.4-manual-zh/4.6.html#lua_CFunction">lua_CFunction</a>的约定类型。</p><p>这是一个底层接口。其完全绕开了包和模块系统。与<a href="#require">require</a>不同，其不会执行任何路径查找和紫红添加扩展名。参数 libname 必须是C库的完整文件名，其包括包括必要的路径和扩展名。参数 funcname 必须是C库中已经导出的确切名称（可能取决于所使用的C编译器和链接器）。</p><p>ISO标准C并不支持此函数。因为函数的功能只在部分支持动态链接的操作系统上可用（Windows、Linux、Mac OS X、Solaris、BSD以及其他支持 dlfcn 的类Unix系统）。</p><p>此函数本质上是不安全的，因为其允许Lua在系统中的任何可读动态库中调用任何函数。（Lua说的函数都是假设符合约定类型并遵守调用约定的，可以参见<a href="/lua5.4-manual-zh/4.6.html#lua_CFunction">lua_CFunction</a>。因此在任意库中调用任意函数通常会引起访问冲突。）</p><h3 id="package.path" tabindex="-1">package.path <a class="header-anchor" href="#package.path" aria-label="Permalink to &quot;package.path  {#package.path}&quot;">​</a></h3><p>该字符串变量存储了供<a href="#require">require</a>查找加载器的路径。</p><p>在启动时Lua会初始化这个变量的值，其值来自于环境变量 LUA_PATH_5_4 或是 LUA_PATH ，如果没有找到这些环境变量，则使用定义在 luaconf.h 文件中的默认路径。环境变量中的 &quot;;;&quot; 会被替换为默认路径。</p><h3 id="package.preload" tabindex="-1">package.preload <a class="header-anchor" href="#package.preload" aria-label="Permalink to &quot;package.preload  {#package.preload}&quot;">​</a></h3><p>存储了一些特殊模块的加载器的表（参见<a href="#require">require</a>）。</p><p>该变量只是引用了真正的表；给这个变量赋值不会改变<a href="#require">require</a>的行为。真正的表是存在C注册表中的（参见<a href="/lua5.4-manual-zh/4.3.html">4.3</a>），索引是一个预定义的字符串 LUA_LOADED_TABLE 。</p><h3 id="package.searchers" tabindex="-1">package.searchers <a class="header-anchor" href="#package.searchers" aria-label="Permalink to &quot;package.searchers  {#package.searchers}&quot;">​</a></h3><p>控制了<a href="#require">require</a>如何查找模块的表。</p><p>该表中的每个条目都是一个<em>查找函数</em>。每当查找一个模块，<a href="#require">require</a>会先后调用其中的每个查找函数，并将模块名（由传进<a href="#require">require</a>的参数而来）作为唯一参数传入。查找器如果找到了模块，则返回另一个函数，即该模块的<em>加载器</em>，以及一个额外的值，即<em>加载数据</em>，将会传入加载器并作为<a href="#require">require</a>的第二个参数。如果没找到模块，会返回一个字符串以解释原因（或是返回<strong>nil</strong>以表示无话可说）。</p><p>Lua会使用四个函数来初始化这个表。</p><p>第一个函数会直接在<a href="#package.preload">package.preload</a>表中查找加载器。</p><p>第二个函数会使用<a href="#package.path">package.path</a>中的路径来查找有没有相应的Lua库。搜索方式可以参照<a href="#package.searchpath">package.searchpath</a>中的描述。</p><p>第三个函数会使用<a href="#package.cpath">package.cpath</a>中所给出的路径来查找有没有相应的C库。同样，其搜索方式可以参照<a href="#package.searchpath">package.searchpath</a>中的描述。例如，如果有一个如下的C路径：</p><pre>&quot;./?.so;./?.dll;/usr/local/?/init.so&quot;</pre><p>对于名为 foo 的模块，查找器将会先后尝试打开这些文件：./foo.so、./foo.dll、以及 /usr/local/foo/init.so 。一旦找到了C库，查找器就会使用动态链接工具将这个库链接到程序中。然后在其中搜索一个C函数作为加载器。这个C函数的名称是一个以 &quot;luaopen_&quot; 开头、后跟模块名的字符串，其中模块名中的 &#39;.&#39; 要替换成下划线。此外，如果模块名中含有连字符 &#39;-&#39; ，那么第一个连字符本身以及其后的内容都要被移除，如果有模块名为 a.b.c-v2.1 ，那么函数名应当为 luaopen_a_b_c 。</p><p>第四个函数会尝试使用 <em>all-in-one</em> 加载器。其搜索给定模块的根名称的C库。例如导入 a.b.c 库是，其将会搜索名为 a 的库。如果找到了，会在其中搜索子模块的 open 函数；在本例中，这个 open 函数会是 luaopen_a_b_c 。通过这种方式，一个包可以将多个C子模块打包到一个库中，其中每个子模块都有各自的 open 函数。</p><p>除了第一个查找函数之外，所有的查找函数都会返回一个记录了模块文件路径的额外字符串，由<a href="#package.searchpath">package.searchpath</a>返回。第一个查找函数始终返回字符串 &quot;:preload:&quot; 。</p><p>查找函数应该不会抛出错误并且对Lua没有副作用。（可能会因为将库连接到了程序，从而对C代码产生副作用。）</p><h3 id="package.searchpath" tabindex="-1">package.searchpath (name, path [, sep [, rep]]) <a class="header-anchor" href="#package.searchpath" aria-label="Permalink to &quot;package.searchpath (name, path [, sep [, rep]]) {#package.searchpath}&quot;">​</a></h3><p>在给定路径 path 中查找名称 name 。</p><p>路径是一个包含了多个<em>模板 templates</em> 的字符串，各模板之间使用分号隔开。对于每个模板，此函数会将每个模板中的问号替换由参数 name 得来的文件路径，其中会将 name 中的每个遇到的 sep（默认是 &#39;.&#39;）替换为 rep （默认为系统中的目录分隔符），然后再尝试打开最后得到的各个文件路径。</p><p>例如一个路径为如下的字符串：</p><pre>&quot;./?.lua;./?.lc;/usr/local/?/init.lua&quot;</pre><p>如果在其中查找名称 foo.a ，则会先后尝试打开这些文件： ./foo/a.lua、./foo/a.lc、以及 /usr/local/foo/a/init.lua 。</p><p>如果成功了，则返回第一个遇到的可以用读模式打开的文件名（在此之后会关闭文件）。否则返回<strong>fail</strong>加上一个错误消息。（这个错误消息列出了每个文件打不开的原因。）</p>',48),h=[o];function l(t,i,u,n,d,s){return r(),e("div",null,h)}const m=a(c,[["render",l]]);export{g as __pageData,m as default};
