Lua技术总结

最近的工作中,用到Lua脚本来完成网管、CLI命令的下发或回显。系统框架C代码调用Lua完成命令参数解析,在Lua中调用C码完成数据库(DB)读写等,感觉有点意思,于是稍微花点时间琢磨了一番!

Lua是一个小巧的脚本语言,由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)的一个研究小组,由Roberto IerusalimschyWaldemar Celes Luiz Henrique de Figueiredo1993年开发,设计目的是在嵌入式系统中,为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,因此,基本上在所有操作系统和平台上都可以编译、运行。

Lua脚本可以被C/C++代码调用,反过来也可以调用C/C++的函数,这使得Lua在应用程序中被广泛应用。不仅仅作为扩展脚本,亦可以作为普通的配置文件,代替XMLini等配置文件,并且更容易理解和维护。Lua并没有提供强大的库,故Lua不适合作为独立应用程序的开发语言。但这却是由它的定位决定的。一个完整的Lua解释器不过200K,在目前所有脚本引擎中,Lua的速度最快。这一切就决定了Lua是作为嵌入式脚本的最佳选择。

 

一、数据交换

Lua可以与C/C++代码相互调用,那它们是如何进行数据交换的呢?

要理解LuaC/C++交互,首先要理解Lua堆栈。LuaC/C++程序通过一个栈交换数据: struct lua_State。栈的特点是先进后出(FILO)。栈的序号可以从栈顶和栈底计数:从栈底计数,则栈底是1,向栈顶方向递增;从栈顶计数,则栈顶是-1,向栈底方向递减。一般都用从栈底计数的方式。栈的默认大小是20,可以用lua_checkstack修改,可以用lua_gettop获得栈里的元素数目。如图:

 lua的栈类似于以下的定义,它是在创建lua_State的时候创建的:

TValuestack[max_stack_len]  //可查 lstate.c stack_init函数了解详情

存入栈的数据类型包括数值、字符串、指针、table、闭包等,下面是一个栈的例子:

  依次执行下面的代码,就可以让你的lua栈呈现上图中的情况:

   lua_pushcclosure(L, func, 0) //创建并压入一个闭包

    lua_createtable(L, 0,0)        //新建并压入一个表

    lua_pushnumber(L, 123)     //压入一个数字

    lua_pushstring(L,“mystr”)   //压入一个字符串

    这里要说明的是,压入的类型有数值、字符串、表和闭包(在c中看来是不同类型的值),但是最后都是统一用TValue这种数据结构来保存,下面用图简单的说明一下这种数据结构:

     TValue结构对应于lua中的所有数据类型,是一个{,类型}结构,为了lua中动态类型的实现,它把值和类型绑在一起,用tt记录value的类型,value是一个联合体,由Value定义,可以看到这个联合体有四个域:

        gc --其他诸如tablethreadclosurestring,需要内存管理&垃圾回收的类型都存在这里

       p --可以存一个指针,实际上是lua中的light userdata结构

        n --所有的数值存在这里,可以是int,或是float

        b -- Boolean值存这里,注意:lua_pushinteger不是存在这里,而存在n中,b只存布尔值

        gc是一个指针,它指向的类型由联合体GCObject定义。从图中可以看出,有stringuserdataclosuretableprotoupvaluethread

由上可得出如下结论:

  • lua中,numberbooleanlight userdatanil四种类型的值是直接存在栈上元素里的,和垃圾回收无关。

  • lua中,stringtableclosureuserdatathread存在栈元素里的只是指针,它们均会在生命周期结束后被垃圾回收。

 

二、堆栈的操作

因为LuaC/C++是通过栈来通信,Lua提供了C API对栈进行操作。

一些常用的栈操作:

lua_State *L = luaL_newstate();        //luaL_newstate返回一个指向堆栈的指针

  lua_pushnumber(L, 123)          //压入一个数字

   lua_pushstring(L,“mystr”)       // 压入一个字符串

 

int  lua_gettop (lua_State *L);           //返回栈顶索引(即栈长度) 

void lua_settop (lua_State *L, int idx);    //               

void lua_pushvalue (lua_State *L, int idx); //idx索引上的值的副本压入栈顶 

void lua_remove (lua_State *L, int idx);  //移除idx索引上的值 

void lua_insert (lua_State *L, int idx);    //弹出栈顶元素,并插入索引idx位置 

void lua_replace (lua_State *L, int idx);   //弹出栈顶元素,并替换索引idx位置的值

… …

其中,lua_settop将栈顶设置为一个指定的位置,即修改栈中元素的数量。如果值比原栈顶高,则高的部分nil补足,如果值比原栈低,则原栈高出的部分舍弃,所以可以用lua_settop(0)来清空栈。

 

三、Lua开源软件安装

Lua开源软件安装,可参考《Lua脚本:开源软件的安装方法

 

四、C调用Lua

实现方法可参考《Lua脚本:C调用Lua实现方法

 

五、Lua调用C

实现方法可参考《Lua脚本:Lua调用C实现方法

 

六、总结 

  • LuaC/C++是通过一个虚拟栈来实现数据交换的。

  • C/C++调用Lua实际上是:由C/C++先把数据放入栈中,由Lua去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回C/C++

  • LuaC/C++也一样:先编写自己的C/C++模块,然后注册函数到Lua解释器中,然后由Lua去调用这个模块的函数。

     

最后附上:Lua语言15分钟快速入门

原文出处:http://tylerneylon.com/a/learn-lua/

 

--单行注释

--[[

    [多行注释]

--]]

 

----------

- 1.变量 &控制流

----------

num = 23  -- 数字都是双精度,类似于C中的double

str = 'aspythonstring' -- Python一样不可变

str = "aspythonuse"  -- 可以双引号

str = [[

         Python的多行注释可用于

        表示多行字符串一样

        方便

    ]]

bol = nil  --

定义;支持垃圾回收

 

--缩进只为易读性,像 Matlab一样以end结尾

while num < 50 do

    num = num + 1   -- 没有++-- +=-=*=/=等操作符号

end

 

-- IF条件开关

if num > 40 then

    print('> 40')

elseif s ~= 'aspython' then   -- ~= 表示不等于,类似于C中的!=,注意elseif中间没有空格

    io.write('s isnot aspython') --标准输入输出

else

    thisIsGlobal = 5    -- 驼峰式命名

    --显示声明局部变量(像Javascript一样)

    local line =io.read()

 

    -- ..作为字符串连接符

    print('凛冬将至'.. line)

end

 

--引用未定义变量(默认视为全局变量)将返回nil,这并非错误!

foo = anUnknownVariable    -- 等价于foo = nil

 

aBoolValue = false

--只有 nil false为逻辑假;数字0与空字串''为真!

if not aBoolValue then print('false')end

 

-- Python一样运用'or''and'

--得到 C语言中a ? b : c的效果;需注意b = falsenil的情况

ans = aBoolValue and 'yes' or 'no'

 

karlSum = 0

for i = 1, 100 do  -- Matlab的递增语法,如同数学中[1,100]

    karlSum =karlSum + i

end

 

-- Step 2递减的方式'100, 1, -2'

for j = 100, 1, -2 then print(j) end

 

--综上,范围可表示为"begin, end [, step]"

 

--另一种循环控制

num = 23

repeat

    print('不甘于现状')

    num = num - 1

until num == 0

 

 

----------

- 2.函数

----------

function fib(n)

    if n < 2 thenreturn 1 end

    return fib(n -2) + fib(n - 1)

end

 

-- Javascript一样的匿名函数与闭包

function adder(x)

    --返回一个函数

    --闭包内封存x

    return function(y) return x + y end

end

a1 = adder(9)

a2 = adder(36)

print(a1(16))  --> 25

print(a2(64))  --> 100

 

--遇到不匹配的列表长度时

--过长的变量将被赋予 nil

--过长的值将被忽略

 x, y, z = 1, 2, 3, 4   -- 4 将被忽略

function bar(a, b, c)

    print(a, b, c)

    return 4, 8, 15,16, 23, 42

end

x, y = bar('zaphod')  -->"zaphod  nil nil"

-- x = 4, y = 8,其余值被忽略

 

--函数与其他类型一样为一等公民

--同样有local/global之分

function f(x) return x * x end

f = function (x) return x * x end

 

print 'Hello World!'  -- 只有一个`字符串`参数时可省略括号

 

----------

- 3.(Table)

----------

--表是 Lua中唯一的复合类型

-- PHP中的数组或Javascript中的Object一样

--可用作list/dict/map

 --默认以字符串作为 key

t = {key1 = 'value1', key2 = false}

--Javascript一样以.取值

print(t.key1) --> "value1"

t.key3 = {}  -- 加入新的键值对

t.key2 = nil  -- 销毁一组键值对

 

--理论上任何非 nil的变量都可以作为key

u = {['@!#'] = 'qbert', [{}] = 1729,[6.28] = 'tau'}

print(u[6.28])  -->"tau"

a = u['@!#'] -- a = 'qbert'

b = u[{}]  -- b = nil,像Javascript一样{}会创建新的对象

--同字符串一样,只有一个表作为函数的参数时可以省略括号

--为了一个括号增加阅读难度,得不偿失

function h(x) print(x.key1) end

h{key1 = 'Sonmi~451'}  -->"Sonmi~451"

 

for key, val in pairs(u)do  --Python  一样的键值迭代

    print(key, val)

end

 

--Javascript一样的全局作用域_G

print(_G['_G'] == _G) --> true

 

--省略 key之后即可变身为list

--实际上是以递增自然数为 key

v = {'value1', 'value2', 1.21,'gigawatts'}

for i = 1, #v do   -- Bash一样,#v表示列表长度

    print(v[i])    -- Matlab一样,列表索引从1开始

end

 

----------

- 3.1 Metatables & metamethods

----------

--元表(metatable)就是表的表,像Javascript的原型(prototype)一样

--为表重载一些元方法(metamethods)

 f1 = {a = 1, b = 2}

f2 = {a = 2, b = 3}

-- s = f1 + f2为错

 

mm = {}

function mm.__add(x, y)

  sum = {}

  sum.a = x.a + y.a

  sum.b = x.b + y.b

  return sum

end

 

setmetatable(f1, mm)

setmetatable(f2, mm)

 

--实际调用 f1metatable中的__add(f1, f2)

--只为 f1设置元表也可以

s = f1 + f2 -- s = {a = 3, b = 5}

 

-- s2 = s + s为错,s未定义元表

 

-- __index元方法重载表中key的提取符号`.`

defaultFavs = {animal = 'gru', food ='donuts'}

myFavs = {food = 'pizza'}

setmetatable(myFavs, {__index =defaultFavs})

food = myFavs.food

 

-- Lua中的值都具有元方法,只有 Table可以重载

--所有元方法如下

 -- __add(a,b)                    for a + b

-- __sub(a,b)                    for a - b

-- __mul(a,b)                    for a * b

-- __div(a,b)                    for a / b

-- __mod(a,b)                    for a % b

-- __pow(a,b)                    for a ^ b

--__unm(a)                        for-a

-- __concat(a,b)                  fora .. b

--__len(a)                        for#a

-- __eq(a, b)                      fora == b

-- __lt(a,b)                      fora < b

-- __le(a,b)                      fora <= b

-- __index(a, b)  <fn or atable>  for a.b

-- __newindex(a, b,c)             fora.b = c

-- __call(a,...)                  fora(...)

 

----------

- 3.2类风格的 Table与继承

----------

 

--Javascript一样并没有内置Class

--但可以通过 Table`{}`实现

Dog ={}                                  -- 1.

functionDog:new()                      --2.

    newObj = {sound= 'woof'}           -- 3.

    self.__index =self                -- 4.

    returnsetmetatable(newObj, self)   -- 5.

end

functionDog:makeSound()                --6.

    print('I say '.. self.sound)

end

 

mrDog =Dog:new()                      -- 7.

mrDog:makeSound() --> "I saywoof"

 

-- 1. Dog像类但实际是 Table

-- 2. Dog:new(...) := Dog.new(self, ...)

-- 3. newObj Dog的实例

-- 4. self Lua中默认的参数,在这里self = Dog

--    继承的时候可以改变

--    self.__index self的元方法__index不是一回事

--    self ={__index = self, metatable = {__index = ...}}

-- 5. setmetatable(newObj, self)相当于setmetatable(newObj, {__index = self})

--    赋予实例所有类方法

-- 6. 2.

-- 7. mrDog = Dog.new(Dog)

 

--继承

LoudDog = Dog:new()

function LoudDog:makeSound()

    s = self.sound.. ' '

    print(s .. s ..s)

end

seymour = LoudDog:new()

seymour:makeSound() --> "woofwoof woof"

 

----------

- 4.模块

----------

--以下来自文件mod.lua

local M = {}

 

local function sayMyName()

    print('Hrunkner')

end

 

function M.sayHello()

    print('Why hellothere')

    sayMyName()

end

return M

--以上

--回到主文件

local mod = require('mod')  -- 运行mod.lua中的代码

--操作同下

local mod = (function()

    --Javascript一样

    --[[

        mod.lua中的代码

    ]]--

end)()

 

mod.sayHello() --> "Why hellothere"

mod.sayMyName() -->错!sayMyName()mod.lua中的局部变量

 

-- require返回的值将被缓存

--即使多次调用require被调用文件也只运行一次

 

-- mod2.lua包含print("mod2")

local a = require("mod2")--> "mod2"

local b = require("mod2")  -- 不输出,实际为 b = a

 

-- dofile是不缓存的版本的require

dofile("mod2") -->"mod2"

dofile("mod2") -->"mod2"

 

-- loadfile读取文件但不执行

--勘误:f =loadfile('mod2'),需加后缀名,否则找不到文件

f = loadfile('mod2.lua')

f() --> "mod2"

 

-- loadstring读取代码字符串

f = loadstring("print('Lua iscool!')")

f() --> "Lua is cool!"

 

----------

- 5.参考,略

----------

 

猜你喜欢

转载自blog.csdn.net/hanlizhong85/article/details/74906621