Skip to content

Latest commit

 

History

History
1095 lines (925 loc) · 36.8 KB

File metadata and controls

1095 lines (925 loc) · 36.8 KB

lua简介

本文档记录lua的学习流程,并摘录lua的基本语法,希望能成为后续温习lua脚本 语言的重要参考!

lua简介

lua是一个小巧的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供 灵活的扩展和定制功能。

  • 由标准C编写,几乎在所有操作系统和平台上都可以编译、运行
  • 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能
  • 脚本可以很容易的被C/C++代码调用,也可以调用C/C++的函数
  • Lua没有提供强大的库,不适合开发独立应用程序
  • Lua语言的各个版本不相兼容

luaJIT

Lua非常高效,它运行得比许多其它脚本(如Perl、Python、Ruby)都快,这点在第三方 的独立测评中得到了证实。

LuaJIT就是一个为了再榨出一些速度的尝试,它利用即时编译(Just-in Time)技术把 Lua代码编译成本地机器码后交由CPU直接执行。

LuaJIT是采用C和汇编语言编写的Lua解释器与即时编译器;被设计成全兼容标准的 Lua5.1语言,同时可选地支持Lua5.2和Lua5.3中的一些不破坏向后兼容性的有用特 性;LuaJIT支持比标准Lua 5.1语言更多的基本原语和特性,因此功能上也要更加强 大。

安装lua

$ 下载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      显示版本号

安装luaJIT

$ 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      显示版本号

lua语法

变量

  • 变量由字母、数字、下画线构成,不以数字开头
  • 避免下画线后跟大写字母的变量,为内部保留
  • 变量没有类型,类型在值中(dynamically typed language)
  • 变量默认声明为全局的,局部变量需前加local修饰

arg

脚本代码可以从全局变量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"

- _G

此变量代表了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,保存了迭代循环的中间信息,比如迭代器函数等,格式如下

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(推荐方式)

弱表,weak table

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字段后,其他用户再不能设置、查看集合的元表

table的预定义操作

在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

与C交互的C API

无论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所有的标准库都用到了此辅助库。

示例,C调用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

当lua调用C函数时,也使用一个与C语言调用lua时相同的栈;C函数从栈中获取 函数参数,并将结果压入栈中;为了在栈中将函数结果与其他值分开,C函数还 应该返回其压入栈中结果的数量。

栈不是全局性的结构,每个函数都有自己的局部私有栈;当lua调用一个C函数 时,第一个参数总是这个局部栈的索引1。

lua调用C库

保存在文件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

lua调用C主程序的函数

保存到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函数的执行 更久,在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, <库名>, <函数列表>);    //注册函数,它们共享新环境
    ...
}

upvalue

与特定函数关联的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                                      /* 数组风格 */

参考