本文档记录lua的学习流程,并摘录lua的基本语法,希望能成为后续温习lua脚本 语言的重要参考!
lua是一个小巧的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供 灵活的扩展和定制功能。
- 由标准C编写,几乎在所有操作系统和平台上都可以编译、运行
- 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能
- 脚本可以很容易的被C/C++代码调用,也可以调用C/C++的函数
- Lua没有提供强大的库,不适合开发独立应用程序
- Lua语言的各个版本不相兼容
Lua非常高效,它运行得比许多其它脚本(如Perl、Python、Ruby)都快,这点在第三方 的独立测评中得到了证实。
LuaJIT就是一个为了再榨出一些速度的尝试,它利用即时编译(Just-in Time)技术把 Lua代码编译成本地机器码后交由CPU直接执行。
LuaJIT是采用C和汇编语言编写的Lua解释器与即时编译器;被设计成全兼容标准的 Lua5.1语言,同时可选地支持Lua5.2和Lua5.3中的一些不破坏向后兼容性的有用特 性;LuaJIT支持比标准Lua 5.1语言更多的基本原语和特性,因此功能上也要更加强 大。
$ 下载lua源码包
$ tar zxvf lua-5.1.5.tar.gz
$ cd lua-5.1.5/
$ make linux
$ make install INSTALL_TOP=/home/sqlfocus/Program/lua5.1
$ vim ~/.bashrc
添加 export PATH=$PATH:/home/sqlfocus/Program/lua5.1/bin
$ source ~/.bashrc
$ lua -v 显示版本号
$ wget http://luajit.org/download/LuaJIT-2.1.0-beta2.tar.gz
$ tar -xvf LuaJIT-2.1.0-beta2.tar.gz
$ cd LuaJIT-2.1.0-beta2
$ make
$ make install PREFIX=/home/sqlfocus/Program/bin
$ vim ~/.bashrc
添加 export PATH=$PATH:/home/sqlfocus/Program/bin
$ source ~/.bashrc
$ luajit -v 显示版本号
- 变量由字母、数字、下画线构成,不以数字开头
- 避免下画线后跟大写字母的变量,为内部保留
- 变量没有类型,类型在值中(dynamically typed language)
- 变量默认声明为全局的,局部变量需前加local修饰
脚本代码可以从全局变量arg获取此脚本的调用参数
$ lua -e "sin=math.sin" script a b
$ 对应的arg变量数组为
arg[-3] = "lua"
arg[-2] = "-e"
arg[-1] = "sin=math.sin"
arg[0] = "script"
arg[1] = "a"
arg[2] = "b"
此变量代表了Lua的运行环境,包含了所有的全局变量
- nil
- 无效值,未赋值变量的默认值
- boolean
- true/false,只有nil和false为假,其余为真(比如0)
- number
- 实数
- string
- 字符串,使用 ” / “” / [[]] 定义字符串
- table
- 表
- function
- 函数
- userdata
- 自定义类型,主要用于存储由应用程序或C语言创建的新类型
- thread
- 线程
Lua支持词法定界(lexical scoping)和闭包(closure).
词法定界:When a function is written enclosed in another function, it has full access to local variables from the enclosing function.
闭包:Simply put, a closure is a function plus all it needs to access nonlocal variables correctly.
非局部变量:non-local variable, 被访问的外部函数的变量;因为它们对于内 部函数而言,即非全局变量,也非局部变量
function newCounter()
local i = 0
return function()
i = i+1
return i
end
end
c1 = newCounter()
print(c1()) -->1
print(c1()) -->2
c2 = newCounter()
print(c2()) -->1
print(c2()) -->2
迭代器是能够遍历集合元素的结构的通称;在Lua中,迭代器一般指代函数,每次 调用此函数,返回集合中的下一个元素。
迭代器需要保存中间状态,closure胜任!一般closure结构需要两个函数,closure 自身和工厂(创建closure的函数)。
function values(t)
local i = 0
return function() i = i + 1; return t[i]; end
end
tt = {10, 20, 30}
iter = values(t) --方式1, 简单模式
while true do
local elem = iter()
if not elem then break end
print(elem)
end
for elem in values(t) do --方式2, 生成器for形式
print(elem)
end
做为发生器的for,保存了迭代循环的中间信息,比如迭代器函数等,格式如下
for <var-list> in <exp-list> do <body> end
其中<var-list>为逗号分隔的变量名,<exp-list>为逗号分隔的表达式(一般情 况下仅有一个元素,即迭代器工厂函数)。
实际上,for保存了三种信息:the iterator function, an invariant state, and a control variable. 一般<var-list>的第一个变量为control variable, 当它的值为nil时,for退出。
一般,1)for语句的第一要务是执行<exp-list>,得到上述的三种信息:迭代器函 数、不变状态值、控制变量的初始值;2)随后,for会调用迭代器函数,参数为不 变状态值、控制变量,返回值赋值<var-list>;3)for执行<body>代码;4)执行2-3 直到控制变量为空,退出。
用代码表示for迭代器流程,如下
do
local _f, _s, _var = <explist>
while true do
local var_1, ... , var_n = _f(_s, _var)
_var = var_1
if _var == nil then break end
<block>
end
end
简单的示例,如下
local 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
for index,v in ipairs({'hi', 'wo', 'you'}) do
print(index, "=", v)
end
- dofile()
- 从文件加载并运行lua代码块, raise error
模拟等价实现 function dofile(filename) local f = assert(loadfile(filename)) return f() end - load()
- 终极的加载函数,一般不使用
- loadfile()
- 从文件加载代码块,只编译不运行, NOT raise error
- loadstring()
- 从字符串读取代码,只编译不运行, NOT raise error
- package.loadlib()
- 动态加载C库函数(不推荐使用)
local path = "/usr/local/lib/lua/5.1/socket.so" local f = package.loadlib(path, "luaopen_socket") - require(“xxx”)
- 动态加载模块儿,包括Lua和C(推荐方式)
Lua利用垃圾收集机制自动删除对象,使得程序使用者能更好的集中在业务处理 上;不过,再聪明的垃圾收集器都需要外界的辅助,哪怕是一点点。
Lua认为没有被引用的对象是无用的,可以被清理;因此,赋值nil给某变量将 导致其指代的对象被清理。
但,有的时候仅仅赋值nil是不够的;比如,希望有一个集合收集活动的obj,但 是obj一旦放入表中,就存在对它的引用,就永远不会被Lua回收机制释放,即使 除了收集表外再无引用。
weak table就提供了一种机制,此特定的引用不应该阻止Lua的垃圾回收;也就 是说,如果一个obj仅被weak table持有,Lua也会自动回收它。
a = {}
b = { __mode = "k"} --k/v分别指代弱引用键/值
setmetatable(a, b) --now 'a' has weak keys
key = {} --creates first key
a[key] = 1
key = {} --creates second key, 覆盖了变量key代表的对象,即第一个{}现在仅被a表索引
a[key] = 2
collectgarbage() --forces a garbage collection cycle
for k, v in pairs(a) do print(v) end ---> 2
注意,仅有obj做为weak table的键或值时才能被回收;而number、boolean等不 能被回收;string比较特殊一般也不会被回收,除非对应的值已经被回收。
- ~=
- 不等于
- ..
- 字符串拼接符
- …
- 函数变长参数,访问变长实参仍需要利用此符号
- _
- 虚变量,dummy variable,占位符,用于丢弃不需要的变量
- –
- 行注释
- –[=[
- 块注释起,中间的等号个数代表注释内[[的嵌套层数
- ]=]
- 块注释结束,等号个数必须与开始处匹配
- #
- 获取数组长度
- {}
- 构造lua的table数据结构
- assert()
- 检查第一个参数是否为true,true则简单返回第一个参数
- error()
- 显式的触发错误,停止Lua程序
- ipairs()
- table的无状态迭代器工厂
- pairs()
- table的迭代器工厂
- rawget()
- 获取变量值,绕过元表的__index方法
- rawset()
- 声明新变量,绕过元表的__newindex方法
- setfenv()
- 改变函数的环境
- type()
- 获取数据类型
- unpack()
- 解耦展开数组元素
- a[x]与a.x的区别
a.x等价于a["x"] table的统一初始化风格{["x"]=1, [1]=2, ... ,} <==> {x=1, 2} - a.x()与a:x()的区别
a:x(arg)等价于a.x(a, arg) - Lua数组
以1做为索引起始值 #arr表示数组长度 数组可包含空隙,hole,即中间存在为nil的元素 带空隙数组,#arr值不准确,应利用table.maxn(arr)获取最大正索引 - 多重返回值
Lua允许函数返回多个结果,只需在return关键字后列出所有返回值 Lua会调整返回值数量以匹配不同情形,多则默默丢弃,少则补充nil unpack(),接受数组作参数,返回数组的所有元素 - 函数没有名?
函数和其他所有值一样都是匿名的 通常所说的函数名指持有某个函数的变量 function foo() return 1; end <==> foo = function() return 1; end - 如何定义递归的局部函数?
错误的格式如下 local fact = function(n) if n==0 then return 1 else return n*fact(n-1) --错误点:编译至此时,局部的fact end --尚未定义完毕,此处引用里全局的 end --fact,而非函数自身 正确的格式 local function fact(n) ... 或 local fact fact = function(n) ... --递归时使用局部变量,虽定义未完全 --但执行时可保证正确的值 - 错误的尾递归
lua支持尾调用消除,类似于goto,不保存尾调用的栈信息,速度快 + 省内存:
正确的格式 function f(x) return g(x) end:
错误的格式 function f(x) g(x) end 默默丢弃返回值 function f(x) return g(x)+1 end 利用返回值作额外的计算 function f(x) return x or g(x) end 调整返回值并作额外计算 function f(x) return (g(x)) end 需调整返回值 - Lua如何实现动态连接?
ANSI C不支持动态连接,Lua通常不包含无法通过ANSI C实现的机制 动态连接机制是例外,因此为可移植性,Lua自身在不同平台提供了动态连接机制 功能的实现集中在package.loadlib()函数,加载指定的库,并链接入Lua
通常,Lua中的每个值都有一套预定义的操作集合;不过,可以通过元表修改其行为, 使得其对未预定义的操作执行指定的函数;在元表中自定义的方法,称为元方法。
- 元表类似于C++的操作符重载
- table和userdata可以拥有独立的元表,其他类型的值则共享其类型所属的单一元表
- 通过setmetatable/getmetatable()函数来设置/获取元表
- lua中只能设置table的元表,其他类型值(如userdata)的元表可通过C代码设置
- 设置元表的__metatable字段后,其他用户再不能设置、查看集合的元表
在lua中可修改的预定义操作有:
- __add/__sub/__mul/__div/__unum/__mod/__pow
- 算术操作符
- __concat
- 连接操作符号
.. - __eq/__lt/__le
- 关系操作符
- __tostring
- obj.tostring()方法
- __metatable
- 禁用getmetatable()/setmetatable()函数
- __index
- table访问操作符, 如local a = set[i],通过rawget()绕过
- __newindex
- table赋值操作符, 如set[i] = val,通过rawset()绕过
创建支持union的集合
Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt) --设置元表
for _,v in pairs(l) do
set[v] = true
end
return set
end
function Set.union(a, b) --实现合并操作
local res = Set.new()
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return resA
end
mt.__add = Set.union --重载 + 操作符
实验
s1 = Set.new({10, 20, 30, 40})
s2 = Set.new({30, 1})
s3 = s1 + s2 --s3结果为{1, 10, 20, 30, 40}
从用户的观点看,一个模块儿就是一个程序库,可以通过 require 函数用来加载; 加载后,就得到了一个table,就像C++的命名空间,包含了模块儿中导出的所有东西, 如函数和常量。
- 标准库是预先加载的,不需要单独加载
- require的Lua库搜索路径存放在变量package.path中,以LUA_PATH初始化
- require的C库搜索路径存放在变量package.cpath中,以LUA_CPATH初始化
- 搜索路径每项利用
;分隔 - 搜索时,require利用模块儿名替换搜索路径每项中的
?
local m = require("mod-name")
m.func()
在Lua中创建一个模块最简单的方法是:创建一个table,并将所有需要导出的函 数放入其中,最后返回这个table就可以了。
local modname = ... --读取require的参数做为模块儿名,
--避免文件名和模块儿名不匹配的情形
local M = {} --定义模块儿导出表
_G[modname] = M --设定导出的表名
package.loaded[modname] = M --相当于尾端的return M语句,写在此处
--以省略结尾的return,所有的依赖
--关系都在开头部分,更清晰
setmetatable(M, {__index=_G}) --通过元表引入原全局变量
setfenv(1, M) --占用单独的环境
local function getname() --局部函数不导出,不被外界所知
return "Lucy"
end
function greeting() --全局被导出,为外界调用
print("hello" .. getname())
end
lua没有明确提供面向对象编程的方法,不过利用table可以仿真面向对象编程。 lua没有类的概念,每个对象只能自定义行为和形态;不过要在lua中模拟类也 并不困难,可以参考基于原型的语言,如javascript等;原型也是一种常规的 对象,当其他对象遇到一个未知操作时,会查找原型对象。
能方便地利用表和动态元机制实现基于原型(prototype-based)的面向对象模型
实现原型很简单,可以通过设置元表实现;a上没有的操作就会在b上查找,
b可以称为a的类。
setmetatable(a, {__index = b})
lua可以通过元表从其它对象获取方法,这种行为就是一种继承。
仿真父类对象
Account = {balance = 0}
function Account:new(o)
o = o or {} --用户没有提供则创建
setmetatable(o, self) --设置o的元表为Account
self.__index = self --最终等价于setmetatable(o, {__index=Account})
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
function Account:withdraw(v)
if v>self.balance then
error("insufficient funds")
end
self.balance = self.balance - v
end
继承并重定义父类方法
SpecialAccount = Account:new()
function SpecialAccount:withdraw(v)
if v-self.balance >= self:getLimit() then
error("insufficient funds")
end
self.balance = self.balance - v
end
function SpecialAccount:getlimit()
return self.limit or 0
end
继承对象的实例化
s = SpecialAccount:new{limit=1000.00}
执行
s:deposit(100)
大多数人认为封装(私密性)是面向对象语言不可或缺的一部分;每个对象的状态都应 该由它自身掌握。lua在设计对象时,并没有提供封装机制;但它足够灵活,可以通过 其它方式实现访问控制。
基本思想是,通过两个table表示一个对象,一个用来保存对象的状态,另一个用来保 存对象的操作,即接口,对象本身是通过第二个表来访问的;为了实现访问控制,表 示状态的table不保存在其它的表中,而只是保存在方法的closure中。
构造对象的工厂
function newAccount(initBalance)
local self = {balance = initBalance} --状态表,在方法的closure中
local withdraw = function (v)
self.balance = self.balance -v
end
local deposit = function (v)
self.balance = self.balance + v
end
return { --接口表
withdraw = withdraw,
deposit = deposit,
}
end
- 巧用块儿注释,注释、激活代码
利用--[[、--]]包围多行代码,当需要重新激活代码块儿时,仅需要 替换为---[[、--]],这样它们将变为两个单行注释 - UNIX直接调用脚本,stand-alone interpreter
脚本第一行为 #!/usr/local/bin/lua 或 #!/usr/bin/env lua - 尽量多使用局部变量
避免污染全局变量空间 访问速度更快 作用域结束后,便于垃圾回收 如 ~local foo = foo~ 表示利用局部变量表示同名的全局变量 - do .. end控制变量作用域
利用do end包裹代码段,作用类似于c语言的{...} - 变长参数
示例1 function add(...) local a,b = ... print(a, b):
for i,v in ipairs{...} do print(i, v) end end:
示例2 function show(...) for i=1, select("#", ...) do --获取变参个数(...可以包含nil) local arg = select(i, ...) --提取第i个变参 print(arg) end end - 函数参数表达式不要过于复杂
Lua普通函数调用时,首先计算参数表达式,然后才执行函数体:
因此assert(tonumber(n), "invalid input: " .. n .. " is not a number") 不是特别合适,因为即使n是数字,调用assert()前,仍要拼接后续字符串 此语句可替换为 if not tonumber(n) then error("invalid input: " .. n .. " is not a number") end - 利用pcall()/xpcall()实现Lua的错误处理机制
此函数可以提供Lua方式的错误处理,它提供了运行于其中的代码的保护模式 local status,err = pcall(function() error({code = 121}) end) print(err.code) ---> 121:
xpcall不同于pcall,不会破坏调用栈,以便于用户收集更详细的栈信息 - 大量字符拼接,避免..
local buff = "" for line in io.lines() do buff = buff .. line .. "\n" end 对于大文件,上述示例代码会引发巨大的性能损耗,为什么? 假设每行20bytes,已经读取了2500行,此时buff为50k;继续读取下一行 时,需创建50020大小的内存,然后copy老buffer到此; 也就是说,随后 的100行,仅仅2k的数据,我们需要复制5M大小的字符串。再加上令人头痛 的垃圾回收机制,性能,哈哈哈!:
解决方案 1) 小文件一次性读取,io.read("*all") 2) 暂存每行到表,然后调用table.concat()函数组合
- debug库
- 调试库
- ffi库
- 最重要的扩展库,允许从纯lua调用c函数
- io库
- 文件操作
- math库
- 数学库
- os库
- 系统库
- string库
- 包含强大的字符操作函数
- table库
- 包含表辅助函数
人们往往认为写数据比读数据简单很多,因为写一个文件时,对写的内容拥有完全的 控制权;但读一个文件,却无从得知会读到的内容。
我们可以借助table构造式来定义数据格式,只需要在写数据时作一点额外的工作,读 数据就会变得相当容易。这项技术就是把数据做为lua代码来输出,当运行这些代码时, lua也就读取了数据。
原始数据
"lisongqing" 32 1984
"fanlin" 33 1983
现组织为文件data,格式如下:
Entry{
"lisonqging",
32,
1984,
}
Entry{
"fanlin",
33,
1983,
}
读取文件的lua代码
local count = 0
function Entry(_) count = count+1 end
dofile("data")
print("number of entries: " .. count)
注意:
1. Entry{<code>} <===> Entry({<code>})
2. 拓展本示例可以实现更加复杂的功能
采用具名参数的形式进一步改进data文件的格式,如下:
Entry{
name = "lisonqging",
age = 32,
year = 1984,
}
Entry{
name = "fanlin",
age = 33,
year = 1983,
}
改进的处理函数:
local people = {}
function Entry(arg) if arg.name then people[arg.name] = true end
dofile("data")
for name in pairs(people) do print(name) end
备注:
1. 此格式下,关键字次序不再重要
2. 每个信息块儿可以拥有不同的关键字
3. 处理更加灵活
通常需要串行化一些数据,然后才能将其存储到一个文件中,或者通过网络发送 出去;串行化后的数据可以用lua代码表示,这样当运行这些代码时,存储的数据 就可以在读取程序中得到重构了。
比如全局变量,可以串行化为 varname = <exp> ,其中varname为变量名,
<exp> 为计算变量值的语句。
串行化无环table的代码如下:
function serialize(o)
if type(o) == "number" then
io.write(o) --数字直接输出
elseif type(o) == "string" then
io.write(string.format("%q", o)) --%q选项可以用于转义
elseif type(o) == "table" then
io.write("{\n") --表输出
for k,v in pairs(o) do
io.write(" ", k, " = ")
serialize(v)
io.write(",\n")
end
io.write("}\n")
else
error("cannot serialize a " .. type(o))
end
end
无论lua程序处于应用程序代码和库代码中哪种情形,与C语言交互都是通过C API 完成的。C API是一组能使C和lua交互的函数,包括读写lua全局变量、调用lua函 数、运行lua代码、注册C函数以供lua调用等。
lua和c通信主要靠一个虚拟栈,可用于交互数据、暂存中间结果等。lua.h定义了 提供的基础函数,包括创建lua环境、调用lua函数、读写lua全局变量、以及注册 供lua调用的新函数等,它们基本都以lua_开头;luaxlib.h定义了辅助库提供的 函数,它们都以luaL_开头,本库是一个利用lua.h提供的API编写的高度抽象层, lua所有的标准库都用到了此辅助库。
保存在fact.lua文件,做为被调用的脚本
function fact (n)
if n==0 then
return 1
else
return n*fact(n-1)
end
end
保存为fact.c,做为应用程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
void error (lua_State *L, const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
lua_close(L);
exit(EXIT_FAILURE);
}
int fact(lua_State *L, int n)
{
int res;
lua_getglobal(L, "fact"); /* 待调用lua函数压栈 */
lua_pushnumber(L, n); /* 第一个参数压栈 */
if (lua_pcall(L, 1, 1, 0) != 0) { /* 调用lua,1参数,1结果 */
error(L, "error from func 'f': %s\n", lua_tostring(L, -1));
}
if (!lua_isnumber(L, -1)) { /* 检查栈顶,返回值 */
error(L, "func 'f' must return a number\n");
}
res = lua_tonumber(L, -1);
lua_pop(L, 1); /* 弹出返回值,1代表弹出的元素个数 */
return res;
}
/* 编译指令: gcc fact.c -I/home/sqlfocus/Program/include/luajit-2.1
-L/home/sqlfocus/Program/lib -lluajit-5.1
-o fact_exe
执行指令: ./fact_exe 3
*/
int main(int argc, char **argv)
{
int res;
int num;
const char *fact_file = "fact.lua";
lua_State *L = luaL_newstate(); /* 打开lua */
luaL_openlibs(L); /* 打开标准库 */
if (luaL_loadfile(L, fact_file) || lua_pcall(L, 0, 0, 0)) {
/* 加载lua脚本 */
error(L, "cannot load lua file, %s\n", fact_file);
}
num = argc > 1 ? atoi(argv[1]) : 4;
res = fact(L, num); /* 调用lua脚本 */
printf("fact of %d is %d\n", num, res);
lua_close(L); /* 关闭lua */
return 0;
}
编译执行
$ gcc ...
$ ./fact_exe 3
---> fact of 3 is 6
当lua调用C函数时,也使用一个与C语言调用lua时相同的栈;C函数从栈中获取 函数参数,并将结果压入栈中;为了在栈中将函数结果与其他值分开,C函数还 应该返回其压入栈中结果的数量。
栈不是全局性的结构,每个函数都有自己的局部私有栈;当lua调用一个C函数 时,第一个参数总是这个局部栈的索引1。
保存在文件fact.lua中
local M = require "mylib" --将mylib.so库链接到lua,并寻找
--luaopen_mylib()函数,注册此函数,
--随后调用它,以打开模块儿
local num = ... or 3 --获取命令行参数,默认值3
num = tonumber(num)
local i = 1
while i<=num do
print(M.fact(i)) --调用C动态库方法
i = i + 1
end
保存到mylib.c中
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
static int fact(lua_State *L)
{
int res = 1;
int n = lua_tonumber(L, 1); /* 获取传入的参数 */
for (int i=1; i<=n; i++) {
res *= i;
}
lua_pushnumber(L, res); /* 回传结果 */
return 1;
}
static const struct luaL_Reg mylib[] = {
{"fact", fact}, /* 定义输出的函数 */
{NULL, NULL}
};
int luaopen_mylib (lua_State *L) { /* 注册函数集到lua环境 */
luaL_register(L, "mylib", mylib);
return 1;
}
编译.c为.so库
$ gcc mylib.c -I/home/sqlfocus/Program/include/luajit-2.1
-fPIC --shared -o mylib.so
运行.lua脚本
$ export LUA_CPATH=/home/sqlfocus/work/mylib.so
$ luajit fact.lua 3
保存到fact.lua脚本
local M = require "mylib" --引入mylib库
function fact_lua(num)
local i = 1
while i<=num do
print(M.fact(i)) --调用库函数
i = i + 1
end
return 0
end
保存到fact.c文件,当作应用程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
void error (lua_State *L, const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
lua_close(L);
exit(EXIT_FAILURE);
}
static int fact(lua_State *L)
{
int res = 1;
int n = lua_tonumber(L, 1); /* 获取传入的参数 */
for (int i=1; i<=n; i++) {
res *= i;
}
lua_pushnumber(L, res); /* 回传结果 */
return 1;
}
static const struct luaL_Reg mylib[] = {
{"fact", fact},
{NULL, NULL}
};
int luaopen_mylib (lua_State *L) {
luaL_register(L, "mylib", mylib);
return 1;
}
int main(int argc, char **argv)
{
int num;
int res;
const char *fact_file = "fact.lua";
lua_State *L = luaL_newstate(); /* 打开lua */
luaL_openlibs(L); /* 打开标准库 */
luaopen_mylib(L); /* 注册函数到lua环境 */
if (luaL_loadfile(L, fact_file) || lua_pcall(L, 0, 0, 0)) {
/* 加载lua脚本 */
error(L, "cannot load lua file, %s\n", fact_file);
}
num = argc > 1 ? atoi(argv[1]) : 4;
lua_getglobal(L, "fact_lua"); /* 待调用lua函数压栈 */
lua_pushnumber(L, num); /* 第一个参数压栈 */
if (lua_pcall(L, 1, 1, 0) != 0) { /* 调用lua,1参数,1结果 */
error(L, "error from func 'f': %s\n", lua_tostring(L, -1));
}
if (!lua_isnumber(L, -1)) { /* 检查栈顶,返回值 */
error(L, "func 'f' must return a number\n");
}
res = lua_tonumber(L, -1);
lua_pop(L, 1); /* 弹出返回值,1代表弹出的元素个数 */
printf("fact_lua retval is %d\n", res);
lua_close(L); /* 关闭lua */
return 0;
}
编译C程序
$ gcc fact.c -I/home/sqlfocus/Program/include/luajit-2.1
-L/home/sqlfocus/Program/lib -lluajit-5.1 -o fact
运行
$ export LD_LIBRARY_PATH=/home/sqlfocus/Program/lib
$ ./fact
通常,C函数需要保存一些非局部的数据,这些数据的生存时间会比C函数的执行 更久,在C语言中通常利用全局变量或静态变量来达成这个目的。但为lua编写库 函数时,这不是好办法:
- 无法在C中保存普通的lua对象
- 无法用于多个lua状态
对应Lua函数三个存放非局部变量的方法,C API也有三种方式:
- 注册表 === 全局变量
- 环境 === 函数环境
- upvalue === 非局部变量
全局table,只能被C代码访问;可以用它保存模块儿间共享的数据。
- 位于伪索引LUA_REGISTRYINDEX上
- 是一个普通的lua table
- 所有C模块儿共享它,因此key值需紧慎选择,以避免冲突
- key可以使用静态变量的地址,链接器保证唯一
保存字符串 const void *key; lua_pushlightuserdata(L, (void*) &key); /* 压入键,此处利用C连接器地址的唯一性创建全局唯一的键 */ lua_pushstring(L, mystr); /* 压入值 */ lua_settable(L, LUA_REGISTRYINDEX); /* registry[&key] = mystr */
检索字符串 lua_pushlightuserdata(L, (void*) &key); /* 压入地址 */ lua_gettable(L, LUA_REGISTRYINDEX); /* 检索值 */ mystr = lua_tostring(L, -1); /* 转换成字符串 */
可用于保存某个模块儿的私有数据,可在模块儿内的函数间共享
- 从lua5.1开始支持
- 伪索引为LUA_ENVIRONINDEX
- 推荐使用
int luaopen_foo(lua_State *L)
{
lua_newtable(L); //创建模块儿环境
lua_replace(L, LUA_ENVIRONINDEX); //替换成新创建的环境
luaL_register(L, <库名>, <函数列表>); //注册函数,它们共享新环境
...
}
与特定函数关联的Lua值,类似于C语言中的静态变量机制
- 与单个函数关联
- 每个函数可与任意数量的upvalue关联
- 每个upvalue保存一个lua值
- 这种关联称为C Closure
/* 前向声明 */
static int counter(lua_State *L);
/* 工厂函数 */
int newCounter(lua_State *L)
{
lua_pushinteger(L, 0); //upvalue的初始值
lua_pushcclosure(L, &counter, 1); //每个counter关联一个upvalue
return 1;
}
/* 定义 */
static int counter(lua_State *L) {
int val = lua_tointeger(L, lua_upvalueindex(1)); //获取upvalue
lua_pushinteger(L, ++val); //更新并压栈
lua_pushvalue(L, -1); //复制更新后的值并压栈
lua_replace(L, lua_upvalueindex(1)); //更新upvalue,并pop
return 1;
}
可以通过C语言的自定义类型,扩展lua;lua提供了userdata基本类型,以支持 自定义扩展。
- userdata提供了一块原始的内存区域
- lua中没有为userdata提供任何预定义操作
- 通过元表的__gc方法,在内存回收过程中释放关键资源,比如IO fd等
通过一个简单的示例,布尔数组,来了解userdata的使用。
#include <limits.h>
#define BITS_PER_WORD (CHAR_BIT * sizeof(unsigned int))
#define I_WORD(i) ((unsigned int)(i)/BITS_PER_WORD)
#define I_BIT(i) (1<<((unsigned int)(i)%BITS_PER_WORD))
typedef struct NumArray {
int size;
unsigned int values[1]; /* 可变部分 */
};
/* 检查指定参数的元表类型是否匹配,匹配则返回参数的指针 */
#define checkarray (L) \
(NumArray *) luaL_checkudata(L, 1, "LuaBook.array")
/* 新建布尔数组 */
static int newarray(lua_State *L) {
int i,n;
size_t nbytes;
NumArray *a;
n = luaL_checkint(L, -1); /* 获取参数 */
nbytes = sizeof(NumArray) + I_WORD(n-1) * sizeof(unsigned int);
a = (NumArray *)lua_newuserdata(L, nbytes); /* 创建userdata */
a->size = n;
for (int i=0; i<I_WORD(n-1); i++) { /* 初始化 */
a->values[i] = 0;
}
luaL_getmetatable(L, "LuaBook.array"); /* 为新建数组设置元表 */
lua_setmetatable(L, -2);
return 1; /* 新的userdata已在栈上 */
}
/* 设置某个位置的BIT位 */
static int setarray(lua_State *L) {
NumArray *a = checkarray(L); /* 检测元表是否一致 */
int index = luaL_checkint(L, 2) - 1;
if (lua_toboolean(L, 3)) {
a->value[I_WORD(index)] |= I_BIT(index); /* 设置bit */
} else {
a->value[I_WORD(index)] &= ~I_BIT(index); /* 重置bit */
}
return 0;
}
/* 注册C模块儿函数 */
static const struct luaL_Reg arrayLib_f[] = { /* 普通函数 */
{"new", newarray},
{NULL, NULL}
};
static const struct luaL_Reg arrayLib_m[] = { /* 接口函数 */
{"set", setarray},
{"__newindex", setarray}, /* 使对象具有数组访问格式 */
{NULL, NULL}
};
int luaopen_array (lua_State *L) {
luaL_newmetatable(L, "LuaBook.array"); /* 设置元表 */
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index"); /* 元表.__index = 元表 */
luaL_register(L, "array", arrayLib_f); /* 通用函数注册到array表 */
luaL_register(L, NULL, arrayLib_m); /* 接口直接注册到元表 */
return 1;
}
使用
a = array.new(1000)
a.set(a, 10, false) /* 接口注册到元表,使得 */
/* a具有面向对象的风格 */
a[10] = false /* 数组风格 */