【Lua从青铜到王者基础篇】第八篇:Lua表和模块与包

x33g5p2x  于2022-03-15 转载在 其他  
字(6.4k)|赞(0)|评价(0)|浏览(674)

系列文章目录

前言

🌲一、Lua表

  • table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
  • Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
  • Lua table 是不固定大小的,你可以根据自己需要进行扩容。
  • Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。

1.table(表)的构造

构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。可以直接初始化数组。

  1. --构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。可以直接初始化数组:
  2. --初始化表
  3. mytable1={}
  4. --指定值
  5. mytable1[1]="Lua"
  6. --移除引用
  7. mytable1=nil
  8. --lua垃圾回收会释放内存

当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。

以下实例演示了以上的描述情况:

  1. -- 简单的 table
  2. mytable = {}
  3. print("mytable 的类型是 ",type(mytable))
  4. mytable[1]= "Lua"
  5. mytable["wow"] = "修改前"
  6. print("mytable 索引为 1 的元素是 ", mytable[1])
  7. print("mytable 索引为 wow 的元素是 ", mytable["wow"])
  8. -- alternatetablemytable的是指同一个 table,所以修改的alternatetable也是这个table的内容
  9. alternatetable = mytable
  10. print("alternatetable 索引为 1 的元素是 ", alternatetable[1])
  11. print("mytable 索引为 wow 的元素是 ", alternatetable["wow"])
  12. alternatetable["wow"] = "修改后"
  13. print("mytable 索引为 wow 的元素是 ", mytable["wow"])
  14. -- 释放变量
  15. alternatetable = nil
  16. print("alternatetable 是 ", alternatetable)
  17. -- mytable 仍然可以访问
  18. print("mytable 索引为 wow 的元素是 ", mytable["wow"])
  19. mytable = nil
  20. print("mytable 是 ", mytable)

以上代码执行结果为:

  1. mytable 的类型是 table
  2. Untitled-1.lua:930
  3. mytable 索引为 1 的元素是 Lua
  4. Untitled-1.lua:934
  5. mytable 索引为 wow 的元素是 修改前
  6. Untitled-1.lua:935
  7. alternatetable 索引为 1 的元素是 Lua
  8. Untitled-1.lua:940
  9. mytable 索引为 wow 的元素是 修改前
  10. Untitled-1.lua:941
  11. mytable 索引为 wow 的元素是 修改后
  12. Untitled-1.lua:945
  13. alternatetable nil
  14. Untitled-1.lua:949
  15. mytable 索引为 wow 的元素是 修改后
  16. Untitled-1.lua:952
  17. mytable nil

2.table(表)的操作

1.table连接

我们可以使用 concat() 输出一个列表中元素连接成的字符串:

  1. fruits={"banana","orange","apple"}
  2. --返回table连接后的字符串
  3. print("连接后的字符串 ",table.concat(fruits))
  4. --指定连接字符串
  5. print("连接后的字符串 ",table.concat(fruits,","))
  6. --指定索引来连接字符串
  7. print("链接后的字符串",table.concat(fruits,",",2,3))

以上代码执行结果为:

  1. 连接后的字符串 bananaorangeapple
  2. Untitled-1.lua:962
  3. 连接后的字符串 banana,orange,apple
  4. Untitled-1.lua:965
  5. 链接后的字符串 orange,apple
  6. Untitled-1.lua:968

2.table插入和移除

以下实例演示了 table 的插入和移除操作:

  1. --table的插入
  2. --在末尾插入
  3. table.insert(fruits,"mango")
  4. print("索引为4的元素为",fruits[4])
  5. --在索引键为2的键处插入
  6. table.insert(fruits,2,"grapes")
  7. print("索引为2的元素为",fruits[2])
  8. print("最后一个元素为 ",fruits[5])
  9. table.remove(fruits)
  10. print("移除后的最后一个元素为 ",fruits[5])

以上代码执行结果为:

  1. 索引为4的元素为 mango
  2. Untitled-1.lua:974
  3. 索引为2的元素为 grapes
  4. Untitled-1.lua:978
  5. 最后一个元素为 mango
  6. Untitled-1.lua:980
  7. 移除后的最后一个元素为 nil
  8. Untitled-1.lua:98

3.table排序

以下实例演示了 sort() 方法的使用,用于对 Table 进行排序:

  1. --table的排序
  2. print("排序前")
  3. for k,v in ipairs(fruits)
  4. do
  5. print(k,v)
  6. end
  7. table.sort(fruits)
  8. print("排序后")
  9. for k,v in ipairs(fruits)
  10. do
  11. print(k,v)
  12. end

以上代码执行结果为:

  1. 排序前
  2. Untitled-1.lua:988
  3. 1 banana
  4. Untitled-1.lua:991
  5. 2 grapes
  6. Untitled-1.lua:991
  7. 3 orange
  8. Untitled-1.lua:991
  9. 4 apple
  10. Untitled-1.lua:991
  11. 排序后
  12. Untitled-1.lua:994
  13. 1 apple
  14. Untitled-1.lua:997
  15. 2 banana
  16. Untitled-1.lua:997
  17. 3 grapes
  18. 4 orange
  19. Untitled-1.lua:997

4.table最大值

table.maxn 在 Lua5.2 之后该方法已经不存在了,我们定义了 table_maxn 方法来实现。

以下实例演示了如何获取 table 中的最大值:

  1. function table_maxn(t)
  2. local mn=nil
  3. for k,v in ipairs(t)
  4. do
  5. if(mn==nil)
  6. then
  7. mn=v
  8. end
  9. if
  10. mn<v
  11. then
  12. mn=v
  13. end
  14. end
  15. return mn
  16. end
  17. tbl = {[1] = 2, [2] = 6, [3] = 34, [26] =5}
  18. print("tbl 最大值:", table_maxn(tbl))
  19. print("tbl 长度 ", #tbl)

以上代码执行结果为:

  1. tbl 最大值: 34
  2. Untitled-1.lua:1021
  3. tbl 长度 3
  4. Untitled-1.lua:1022

当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。 索引的时候会中断计数,所以换种办法可以使用以下方法来代替:

  1. function table_length(t)
  2. local length=0
  3. for k, v in pairs(t) do
  4. length=length+1
  5. end
  6. return length;
  7. end

🌳二、Lua模块与包

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。以下为创建自定义模块 module.lua,文件代码格式如下:

  1. -- 文件名为module.lua
  2. -- 定义一个名为module的模块
  3. module={}
  4. --定义一个常量
  5. module.constant="这是一个常量"
  6. --定义一个函数
  7. function module.func1()
  8. io.write("这是一个公有函数!\n")
  9. end
  10. local function func2()
  11. print("这是一个私有函数!")
  12. end
  13. function module.func3()
  14. func2()
  15. end
  16. return module

由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。

上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用.

🌲1.require函数

Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("<模块名>")

或者:

require"<模块名>"

执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。

test_module.lua文件

  1. --test_module.lua文件
  2. --module模块为上文提到的module.lua
  3. require("module")
  4. print(module.constant)
  5. module.func3()

以上代码的执行结果为:

  1. 这是一个常量
  2. 这是一个私有函数!

或者给加载的模块定义一个别名变量,方便调用:

  1. -- test_module2.lua 文件
  2. -- module 模块为上文提到到 module.lua
  3. -- 别名变量 m
  4. local m=require("module")
  5. print("m.constant")
  6. m.func3()

以上代码的执行结果为:

  1. 这是一个常量
  2. 这是一个私有函数!

🌳2.加载机制

  • 对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
  • require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
  • 当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 “~/lua/” 路径加入 LUA_PATH 环境变量里:
    #LUA_PATH
    export LUA_PATH="~/lua/?.lua;;"

文件路径以 “;” 号分隔,最后的 2 个 “;;” 表示新加的路径后面加上原来的默认路径。接着,更新环境变量参数,使之立即生效。

source ~/.profile

这时假设 package.path 的值是:

/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua

那么调用 require(“module”) 时就会尝试打开以下文件目录去搜索目标。

/Users/dengjoe/lua/module.lua;
./module.lua
/usr/local/share/lua/5.1/module.lua
/usr/local/share/lua/5.1/module/init.lua
/usr/local/lib/lua/5.1/module.lua
/usr/local/lib/lua/5.1/module/init.lua

  • 如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。
  • 搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。
  • 搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。动静态库!

🌴3.C包

  • Lua和C是很容易结合的,使用 C 为 Lua 写包。

  • 与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。

  • Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:
    local path = “/usr/local/lua/lib/libluasocket.so”
    local f = loadlib(path, “luaopen_socket”)

  • loadlib 函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在Lua中调用他。

  • 如果加载动态库或者查找初始化函数时出错,loadlib 将返回 nil 和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:
    local path = “/usr/local/lua/lib/libluasocket.so”
    – 或者 path = “C:\windows\luasocket.dll”,这是 Window 平台下
    local f = assert(loadlib(path, “luaopen_socket”))
    f() – 真正打开库

  • 一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。

  • 将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。

💬🌲🌳🌴🌵总结

以上就是今天要讲的内容,本文介绍了Lua表和Lua模块与包的使用,而表、模块、包、相关操作提供了大量能使我们快速便捷地处理数据的函数和方法,我们务必掌握。另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。

相关文章