lua 高级特性
这一篇来记录下 lua 的某些高级特性,以便在实际应用中得心应手。
模块和包
为了方便代码复用和扩展,可以使用 table 实现模块 module,在模块中封装通用代码。把同类型的函数放在一个文件中,然后在其它脚本中调用。
module = {}
module.version = "V0.1"
module.author = "litreily"
function module.func1 ()
function-body
end
function module.func2 ()
function-body
end
return module
然后在其它文件通过 require 导入。
require ("module")
-- or
require "module"
print(module.version)
module.func1()
-- or
local m = require ("module")
print(m.version)
m.func1()
举例说明,为了复用一些通用方法比如文件读写,异常处理等功能,可以将其放在一起。
-- utils functions
utils = {}
function utils.errorhandler (err)
print("ERROR: ", err)
end
function utils.xpcall (statements)
return xpcall (function () return statements end, utils.errorhandler)
end
-- cat file
function utils.catfile (file)
local f = assert(io.open(file, "r"))
local content = f:read("*all")
f:close()
return content
end
-- @mode: w a r+ w+ a+ b
-- @content: strings
function utils.savefile (file, mode, content)
local f = assert(io.open(file, mode))
f:write(content)
f:close()
end
return utils
上面对文件读写函数以及异常处理函数进行了封装,在其它文件中就可以通过 require 调用了。
require "utils"
-- cat demo.lua and write into test.txt
utils.savefile("test.txt", "w", utils.catfile("demo.lua"))
xpcall(function () print(utils.catfile("utils.lua")) end, utils.errorhandler)
print(utils.xpcall(os.execute("ls /")))
闭包
function 变量
首先要知道的是,在 Lua 中,function
本身就是基本的数据类型之一,也可以和普通的变量一样定义和赋值。
function foo(x)
print(x)
end
与
foo = function (x) print(x) end
是等价的。因此,函数也可以当做普通变量一样被返回、被赋值。下面再来看闭包。
闭包概念
闭包 (Closure) 的概念并不是 Lua 特有的,许多其它语言也有,比如java, js等。在 Lua 中,闭包表现为匿名函数或者函数嵌套,是由一个函数和一个非局部变量(upvalue)组成的。看个例子就清楚了。
function foo(x)
local function func1()
print(x)
end
return func1
end
func = foo("hello")
func()
内部函数也可以不定义函数名,直接return。
function foo(x)
return function ()
print(x)
end
end
func = foo("hello")
func()
以上的func就是创建的闭包。通过闭包函数,可以实现多个内部函数之间的资源共享。
function Produce(n)
local function func1()
print(n)
end
local function func2()
n = n + 1
end
return func1, func2
end
f1, f2 = Produce(2020)
f1() -- /* print 2020 */
f2()
f1() -- /* print 2021 */
f2()
f1() -- /* print 2022 */
上面两个闭包 f1, f2 通过 n 实现资源共享。
闭包应用
- 闭包作为高阶函数的参数
- 回调函数
- 创建安全的运行环境,类似沙盒
- 迭代器
迭代器
Lua 中的迭代器就可以通过闭包实现的。以下代码就是迭代函数 ipairs
的实现方式。
function iter (a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function ipairs (a)
return iter, a, 0
end
ipairs
的应用如下:
data = {"hello", "world", "!"}
for i, v in ipairs(data)
do
print(i, v)
end
面向对象
Lua 是使用C语言编写的,但其不仅支持面向过程,同时也支持面向对象。而面向对象也是通过万能的 table 实现的。
构造函数
Animal = {name = "", height = 0, weight = 0}
-- The constructor function
function Animal:new (object, name, height, weight)
object = object or {}
setmetatable(object, self)
self.__index = self
self.name = name or ""
self.height = height or 0
self.weight = weight or 0
return object
end
-- The member function
function Animal:printHeight()
print("the height of " .. self.name .. " is " .. self.height)
end
创建对象
a = Animal:new(nil, "cat", 0.2, 15)
访问属性
print(a.name)
访问成员函数
a:printHeight()
继承
在Animal类的基础上继承,派生出人类 human.
Human = Animal:new("human", 0, 0)
function Human:new (object, name, height, weight, sex)
object = object or Animal:new("name", height, weight)
setmetatable(object, self)
self.__index = self
self.sex = sex or "male"
return object
end
function Human:printSex()
print("the sex of " .. self.name .. " is " .. self.sex)
end
boy = Hunman:new("mike", 1.7, 65, "male")
print(boy.name)
boy:printSex()
错误处理
assert and error
assert 是很多语言都会用到的处理函数,在Python、C++等大部分高级语言中都会集成。通过它可以预先判断是否会有错误产生,如果有则直接处理,而不用等待真正执行到错误处才报错。比如下面的类型检查,可以规避输入参数不合法导致的错误问题,提前退出。
local function add(a,b)
assert(type(a) == "number", "a is not a number")
assert(type(b) == "number", "b is not a number")
return a+b
end
add(10)
执行会有错误提示
lua: test.lua:3: b is not a number
stack traceback:
[C]: in function 'assert'
test.lua:3: in local 'add'
test.lua:6: in main chunk
[C]: in ?
error
函数可以用来打印log, 而且可以指定level,控制输出包含哪些信息。
error (messag [, level])
- level=1 (default) : 输出调用error位置(文件,行号)
- level=2 : 指出调用 error 的函数
- level=0 : 不添加位置信息
pcall and xpcall
pcall (protected call) 有点类似 Python中的 try...catch, 尝试捕获错误,不过其 接收的是一个函数,而不是表达式 .
if pcall(function_name, ….) then
-- no error
else
-- some error
end
举例如下:
> =pcall(function(i) print(i) end, 10)
10
true
> =pcall(function(i) print(i) error('error message') end, 10)
10
false stdin:1: error message
当然第二个例子中error是人为故意添加的,倒不是说真的有错误。
理论上pcall 可以捕获函数执行过程的任意错误,但是它主要是返回错误的位置,把部分调用栈信息丢失了。此时就是 xpcall 的出场时刻了。
xpcall
第二个参数是一个错误处理函数 errorhandler, 可以将发生错误时的栈信息打印出来。本文头部讲述模块的时候已经用到了。
function foo (n)
n = n/nil
end
function errorhandler( err )
print( "ERROR:", err )
end
status = xpcall(foo, errorhandler, 10)
print(status)
执行后输出以下错误。
ERROR: stdin:1: attempt to perform arithmetic on local 'n' (a nil value)
C 与 Lua 相互调用
在C与Lua之间相互调用,需要安装有lua库,通常在编译安装时就会生成所需的头文件和库文件
- lua.h
- lualib.h
- lauxlib.h
- liblua.so or liblua.a
C 调用 Lua 脚本
如果单单只是要执行 Lua 脚本,非常简单。直接在C代码中通过 system 调用即可。
system("lua demo.lua");
但是对于嵌入式设备而言,可能没有将lua编译到设备中,此时可以通过lua的库函数 luaL_dofile
执行脚本。
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int main(int argc, char *argv[])
{
lua_State* L;
L = luaL_newstate(); /* 创建lua状态机 */
luaL_openlibs(L); /* 打开Lua状态机中所有Lua标准库 */
luaL_dofile(L, "demo.lua"); /*加载lua脚本*/
lua_close(L); /*清除Lua*/
return 0;
}
C 调用 Lua 函数
如果要在C代码中调用Lua脚本中定义的函数,以最简单的加法为例,在lua中写一个 add 函数,然后在 C 代码中调用。
-- add.lua
function add(x,y)
return x+y
end
在 C 代码中调用 lua 相关库函数。
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
lua_State* L;
int luaadd(int x, int y)
{
int sum;
lua_getglobal(L,"add");/*函数名*/
lua_pushnumber(L, x); /*参数入栈*/
lua_pushnumber(L, y); /*参数入栈*/
lua_call(L, 2, 1); /*开始调用函数,有2个参数,1个返回值*/
sum = (int)lua_tonumber(L, -1); /*取出返回值*/
lua_pop(L,1); /*清除返回值的栈*/
return sum;
}
int main(int argc, char *argv[])
{
int sum;
L = luaL_newstate(); /* 创建lua状态机 */
luaL_openlibs(L); /* 打开Lua状态机中所有Lua标准库 */
luaL_dofile(L, "add.lua"); /*加载lua脚本*/
sum = luaadd(1000, 24); /*调用C函数,这个里面会调用lua函数*/
printf("The sum is %d \n",sum);
lua_close(L); /*清除Lua*/
return 0;
}
注意包含头文件,并确保头文件所在目录包含在环境变量 $PATH
中,否则编译会出错。
下面进行编译,编译的时候一定要链接好对应的库,否则同样会编译出错。
- liblua.a (对应安装lua时安装的 liblua.a 静态库)。
- libm.so
- libdl.so
$ gcc test.c -o test -llua -lm -ldl
$ ./test
The sum is 1024
如果不添加 -llua
, 会提示以下错误。
/usr/bin/ld: /tmp/ccQVKqF9.o: in function `luaadd':
test.c:(.text+0x24): undefined reference to `lua_getglobal'
/usr/bin/ld: test.c:(.text+0x38): undefined reference to `lua_pushnumber'
/usr/bin/ld: test.c:(.text+0x4c): undefined reference to `lua_pushnumber'
/usr/bin/ld: test.c:(.text+0x70): undefined reference to `lua_callk'
/usr/bin/ld: test.c:(.text+0x89): undefined reference to `lua_tonumberx'
/usr/bin/ld: test.c:(.text+0xa4): undefined reference to `lua_settop'
/usr/bin/ld: /tmp/ccQVKqF9.o: in function `main':
test.c:(.text+0xc1): undefined reference to `luaL_newstate'
/usr/bin/ld: test.c:(.text+0xd7): undefined reference to `luaL_openlibs'
/usr/bin/ld: test.c:(.text+0xf2): undefined reference to `luaL_loadfilex'
/usr/bin/ld: test.c:(.text+0x120): undefined reference to `lua_pcallk'
/usr/bin/ld: test.c:(.text+0x159): undefined reference to `lua_close'
collect2: error: ld returned 1 exit status
如果不添加 -lm
, 会提示以下错误。
/usr/bin/ld: /usr/local/lib/liblua.a(lobject.o): in function `numarith.isra.0':
lobject.c:(.text+0x1fb): undefined reference to `fmod'
/usr/bin/ld: lobject.c:(.text+0x221): undefined reference to `pow'
/usr/bin/ld: /usr/local/lib/liblua.a(lvm.o): in function `luaV_execute':
lvm.c:(.text+0x2145): undefined reference to `pow'
/usr/bin/ld: lvm.c:(.text+0x24e0): undefined reference to `fmod'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_log10':
lmathlib.c:(.text+0xa3): undefined reference to `log10'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_pow':
lmathlib.c:(.text+0x1b8): undefined reference to `pow'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_tanh':
lmathlib.c:(.text+0x1e3): undefined reference to `tanh'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_sinh':
lmathlib.c:(.text+0x213): undefined reference to `sinh'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_cosh':
lmathlib.c:(.text+0x243): undefined reference to `cosh'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_tan':
lmathlib.c:(.text+0x273): undefined reference to `tan'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_sqrt':
lmathlib.c:(.text+0x2d6): undefined reference to `sqrt'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_sin':
lmathlib.c:(.text+0x303): undefined reference to `sin'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_log':
lmathlib.c:(.text+0x649): undefined reference to `log10'
/usr/bin/ld: lmathlib.c:(.text+0x656): undefined reference to `log'
/usr/bin/ld: lmathlib.c:(.text+0x678): undefined reference to `log2'
/usr/bin/ld: lmathlib.c:(.text+0x68c): undefined reference to `log'
/usr/bin/ld: lmathlib.c:(.text+0x6a0): undefined reference to `log'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_exp':
lmathlib.c:(.text+0x723): undefined reference to `exp'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_cos':
lmathlib.c:(.text+0x753): undefined reference to `cos'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_atan':
lmathlib.c:(.text+0x7b0): undefined reference to `atan2'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_asin':
lmathlib.c:(.text+0x7e3): undefined reference to `asin'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_acos':
lmathlib.c:(.text+0x813): undefined reference to `acos'
/usr/bin/ld: /usr/local/lib/liblua.a(lmathlib.o): in function `math_fmod':
lmathlib.c:(.text+0xca3): undefined reference to `fmod'
collect2: error: ld returned 1 exit status
如果不添加 -ldl
, 会提示以下错误。
/usr/bin/ld: /usr/local/lib/liblua.a(loadlib.o): in function `lookforfunc':
loadlib.c:(.text+0x565): undefined reference to `dlsym'
/usr/bin/ld: loadlib.c:(.text+0x5c6): undefined reference to `dlopen'
/usr/bin/ld: loadlib.c:(.text+0x649): undefined reference to `dlerror'
/usr/bin/ld: loadlib.c:(.text+0x671): undefined reference to `dlerror'
/usr/bin/ld: /usr/local/lib/liblua.a(loadlib.o): in function `gctm':
loadlib.c:(.text+0x831): undefined reference to `dlclose'
collect2: error: ld returned 1 exit status
所以编译时要根据错误提示添加指定的库。
Lua 调用 C 函数
要在 Lua 中调用 C 函数,主要有以下两种方式。
- C 中注册函数给 Lua
- C 编译出动态链接库,在 Lua 中使用 require
先来看看注册函数的方式。
#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
static int lua_SayHello(lua_State *L)
{
const char *d = luaL_checkstring(L, 1); /* 获取参数,字符串类型 */
char str[100] = "hello ";
strcat(str, d);
lua_pushstring(L, str); /* 返回给lua的值压栈 */
return 1; /* 返回值个数 */
}
int main(int argc, char *argv[])
{
lua_State *L = luaL_newstate(); /* 创建lua状态机 */
luaL_openlibs(L);
lua_register(L, "SayHello", lua_SayHello); /*注册C函数到lua */
const char* testfunc = "print(SayHello('world'))"; /*lua中调用c函数 */
if(luaL_dostring(L, testfunc)) /* 执行Lua命令。*/
printf("Failed to invoke.\n");
lua_close(L); /*清除Lua*/
return 0;
}
这里虽然没有看到脚本,但实际上也是用lua的语法写的指令 print(SayHello('world'))
, 然后通过 luaL_dostring
函数执行 lua 指令。
需要注意的是,注册的函数
l_SayHello
return 的是返回值个数,这里1代表只有一个返回值,也就是压栈的字符串。
接下来看另外一种,通过动态链接库的方式调用C函数。
/* mylualib.c
* use to compile mylualib.so */
#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
static int add(lua_State* L)
{
double op1 = luaL_checknumber(L,1);
double op2 = luaL_checknumber(L,2);
lua_pushnumber(L,op1 + op2);
return 1;
}
static int sub(lua_State* L)
{
double op1 = luaL_checknumber(L,1);
double op2 = luaL_checknumber(L,2);
lua_pushnumber(L,op1 - op2);
return 1;
}
/* 第一个字段用于Lua调用,第二个字段为C的函数指针
* 结构体数组中的最后一个元素的两个字段均为NULL,用于提示Lua注册函数已经到达数组的末尾。*/
static const struct luaL_Reg mylibs[] = {
{"add", add},
{"sub", sub},
{NULL, NULL}
};
/* 函数名必须为luaopen_xxx,xxx表示lib名称。Lua代码 require "xxx"需要与之对应。*/
extern int luaopen_mylualib(lua_State* L)
{
luaL_newlib(L, mylibs);
return 1;
}
以上注册了简单加法运算函数,编写后编译成动态链接库。
gcc mylualib.c -shared -fPIC -o mylualib.so
这里要注意的是,编译以上动态链接库需要
liblua.so
, 但是默认编译安装lua的时候只会生成静态库 liblua.a, 并不会生成这个文件,所以需要修改lua的 Makefile,添加 liblua.so 相关配置,最后重新编译安装lua。
然后写个简单的lua脚本 my.lua
.
-- my.lua
local mylib = require "mylualib"
print(mylib.add(12,33))
print(mylib.sub(33,22))
执行下看看效果。
$ lua my.lua
45.0
11.0
完美执行。
示例
最后来看个Lua的示例程序。
getopt
Lua 没有官方的getopt库,但是 GitHub 上有很多库用于替代它的自定义库。比如下面这个,是相对更符合shell习惯的。
--- /*@from: https://github.com/skeeto/getopt-lua
--- getopt(3)-like functionality for Lua 5.1 and later
-- This is free and unencumbered software released into the public domain.
--- getopt(argv, optstring [, nonoptions])
--
-- Returns a closure suitable for "for ... in" loops. On each call the
-- closure returns the next (option, optarg). For unknown options, it
-- returns ('?', option). When a required optarg is missing, it returns
-- (':', option). It is reasonable to continue parsing after errors.
-- Returns nil when done.
--
-- The optstring follows the same format as POSIX getopt(3). However,
-- this function will never print output on its own.
--
-- Non-option arguments are accumulated, in order, in the optional
-- "nonoptions" table. If a "--" argument is encountered, appends the
-- remaining arguments to the nonoptions table and returns nil.
--
-- The input argv table is left unmodified.*/
local function getopt(argv, optstring, nonoptions)
local optind = 1
local optpos = 2
nonoptions = nonoptions or {}
return function()
while true do
local arg = argv[optind]
if arg == nil then
return nil
elseif arg == '--' then
for i = optind + 1, #argv do
table.insert(nonoptions, argv[i])
end
return nil
elseif arg:sub(1, 1) == '-' then
local opt = arg:sub(optpos, optpos)
local start, stop = optstring:find(opt .. ':?')
if not start then
optind = optind + 1
optpos = 2
return '?', opt
elseif stop > start and #arg > optpos then
local optarg = arg:sub(optpos + 1)
optind = optind + 1
optpos = 2
return opt, optarg
elseif stop > start then
local optarg = argv[optind + 1]
optind = optind + 2
optpos = 2
if optarg == nil then
return ':', opt
end
return opt, optarg
else
optpos = optpos + 1
if optpos > #arg then
optind = optind + 1
optpos = 2
end
return opt, nil
end
else
optind = optind + 1
table.insert(nonoptions, arg)
end
end
end
end
return getopt
--[[ /*Examples:
getopt = require('getopt')
local append = false
local binary = false
local color = 'white'
local nonoptions = {}
local infile = io.input()
for opt, arg in getopt(arg, 'abc:h', nonoptions) do
if opt == 'a' then
append = true
elseif opt == 'b' then
binary = true
elseif opt == 'c' then
color = arg
elseif opt == 'h' then
usage()
os.exit(0)
elseif opt == '?' then
print('error: unknown option: ' .. arg)
os.exit(1)
elseif opt == ':' then
print('error: missing argument: ' .. arg)
os.exit(1)
end
end
if #nonoptions == 1 then
infile = io.open(nonoptions[1], 'r')
elseif #nonoptions > 1 then
print('error: wrong number of arguments: ' .. #nonoptions)
os.exit(1)
end
*/]]
参考
版权声明:本博客所有文章除特殊声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明出处 litreily的博客!