MelonTeam 移动终端前沿技术的探索者

Lua 学习手记

2017-12-10
nemohou

导语 介绍 Lua 的语法和优化细节

Lua 是一种轻量小巧的脚本语言,一个完整的 Lua 解释器不过200k,用标准 C 语言编写并以源代码形式开放。但麻雀虽小五脏俱全,Lua 本身其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。在所有的脚本引擎中,Lua的速度是最快的。像《魔兽世界》的插件,手机游戏《大掌门》《神曲》《迷失之地》等都是用Lua来写的逻辑。

Lua 语法说明


1、变量和赋值

C++   
int main() {
	string a = "hello";
	int b = 2017;
	cout << a << b;
	return 0;
}


Lua  
a = 'hello';
b = 2017;
print(a .. 2017);

上例输出:Hello2017

可以看出 Lua 和 PHP 一样没有变量类型的概念,所以相对于那种生活在变量类型转换来转换去的 C++ 来说,Lua的确能提高很大的书写速度。而且 Lua 里 number 就是表示双精度类型的实浮点数,不必像 C++ 那样要区分 int、unsigned、double 等等。如果你要打印一个没有赋值的变量,会输出 nil(空类型),但不会出错。

同时 Lua 的赋值可以多变量同时赋值,如下:

a = 1;
b = 2;
c = 3;
c,b,a = a,b,c;
print(a,b,c);  
function a() 
    a = 1;
    b = 2;
    return a,b;
end
c,d = a();
print(c,d);

上例输出:

3 2 1
1 2

如果值的数量不匹配,则多出的值会被忽略,缺少的值会被赋 nil。同时从上例可以看出,Lua 中函数也可以同时返回多个变量。

2、函数

function a(b)
    return b+1;
end

c = function(d)
    if(type(d) == 'function') then
        return d(2);
    else
        return d + 2;
    end;
end

print(c(a) + c(10));

上例输出:15

从上例中我们首先可以看到 2 种函数的定义方法。其次,由于在 Lua 中 function 也是一种变量类型,故此函数 c 中我们可以把函数 a 作为变量带入,也可以直接传一个固定数值进去。c(a) 实际调用了a(2) 返回 3,c(10) 返回 12。

3、表 table 和模块

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,在 Lua 中你可以把它当成数组 array,也可以把它当成对象 object,我们从下面的例子中进行讲解:

t1 = {3,1,14,6,12};
print(#t1);
table.sort(t1);
for k, v in pairs(t1) do
	print(k,v);
end

a = function(a)
	print('hello ' .. a);
end

t2 = {3,4,['wow'] = '魔兽世界', ['lol'] = '英雄联盟',5, ['func'] = a};
print(#t2);
for k, v in pairs(t2) do
	print(k,v);
end
print(t2.wow);
print(t2['lol']);
t2.func('nemohou');
t2['func']('nemohou');

上例输出:
5
1 1
2 3
3 6
4 12
5 14
3
1 3
2 4
3 5
func function: 02C3B8B8
lol 英雄联盟
wow 魔兽世界
魔兽世界
英雄联盟
hello nemohou
hello nemohou

t1 为我们定义的第一个 table,为普通的数组类型,因此可以用 Lua 通用计算长度的方式“#”计算出长度 5;而后我们用 table.sort 进行排序;“k, v in pairs(t1)”为迭代器,可遍历出 table 的每个元素;
t2 中我们定义了一个复杂的 table,这个复杂 table 我们不仅自定义了索引,而且把函数 a 作为一个元素进行带入,这时通过 pairs 进行遍历的时候只能打印出这个函数的内存地址。Lua 中对于 table 元素的引用可以用数组的方式也可以用“.”的方式,这时我们发现,t2 实际上已经形成了类的概念。

那么有了类的概念我们就可以封装一个模块了,我们可以定义一个这样的模块文件 module.lua

module = {}

module.constant = "常量"

function module.publicFunc()
end

local function priviteFunc()
end

return module

 publicFunc为公有函数,priviteFunc为私有函数,然后通过require函数就可以调用这个模块文件进行使用

require("module")

print(module.constant)

4、协同程序 coroutine

Lua中的协同程序是非常强大的功能,但与线程有所不同,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

co = coroutine.create(
    function(type)
		print(1)
		coroutine.yield()
		print(2)
		coroutine.yield()
		print(3)
		coroutine.yield()

    end
)

coroutine.resume(co) --1
coroutine.resume(co) --2
coroutine.resume(co) --3

 coroutine.create() 方法为创建一个 coroutine,coroutine.yield() 方法为挂起,coroutine.resume() 为重启或继续执行。因此上例中我们先创建了一个协同程序 co,这时它并没有开始执行,直到运行到 resume,输出了 1 后 co 挂起,等待第二个 resume 后继续执行输出 2 然后继续挂起,等待执行到第三个 resume 后再继续挂起,程序结束。

我们再看一个互相通讯的例子:

function send()
     local i = 0
     while true do
          i = i + 1
          coroutine.yield(i)
     end
end

function receive(tid)
     while true do
          local status, value = coroutine.resume(tid)
          print(value)
     end
end

tid = coroutine.create(send)
print(tid);
receive(tid)

 上例输出:
thread: 001EC1E0
1
2
3
4
5

此例我们首先创建了一个死循环的函数 send,此函数会持续的把累加的变量 i 挂起后发送出去。然后 receive 函数负责接收指定 thread 的挂起信号然后继续执行,同时会接收 send 发送过来的变量 i 进行输出。因此此程序会一直循环的输出累加的变量 i 的值,直到手动结束程序进程为止。

Lua 语法优化

人人都说 Lua 是效率很高的一种语言,可为什么我的程序还是那么慢,下面说下基本的优化方法

我们先看一个例子:

for i = 1,100000000 do
  local x = math.sin(i)
end  

 此程序运行结果为 10.886 左右,10秒。

local sin = math.sin
for i = 1,100000000 do
  local x = sin(i)
end  

但如果我们先一步把 math.sin 保存成局部变量 sin 后运行结果为 8.203 左右 8 秒。提升了25%左右。为啥会有这样的差异呢?因为 Lua 会为每一个活动的函数都会其分配一个栈,每调用一次都会分配一次,而如果事先把函数分配好栈空间然后再去调用,自然省去不少时间,如果你的算法中有经常循环调用的地方可以用此方法优化。

继续看下一个例子:

for i = 1,10000000 do
    local a = {}
    a[1] = 1; a[2] = 2; a[3] = 3
end  

 运行时间 8.818 秒

for i = 1,10000000 do
    local a = {1,1,1}
    a[1] = 1; a[2] = 2; a[3] = 3
end  

 运行时间 4.321 秒,快了将近一倍。差异在 table a 的初始化方式上,如果你创建了一个未知大小的 table 的在遇到分配的空间已满的时候,会重新分配空间并将记录移到新的位置,这将耽误很多时间。所以如果你定义的 table 知道有多大,建议预先填充好 table 的大小。也就是说 table 初始化时尽量固定好空间的大小,减少容量的变动。

继续看下一个例子:

local s = ''
for i = 1,500000 do
    s = s .. 'a'
end  

 运行时间 29.018 秒

local s = ''
local t = {}
for i = 1,500000 do
    t[#t + 1] = 'a'
end
s = table.concat( t, '')

 运行时间 0.07 秒,快的太多了,已然不是一个量级。原因是什么呢?Lua 在进行字符串创建的时候都会进行比较的操作,而每次累加 a 时都要进行,故此很慢。而我们可以用 table 进行缓存的模拟后效率会很快

其他语言调用 Lua

test.lua

str = "hello"  
function add(a,b)  
    return a + b  
end

C++

#include   
#include   
using namespace std;  
   
extern "C"  
{  
    #include "lua.h"  
    #include "lauxlib.h"  
    #include "lualib.h"  
}  
void main()  
{  
    lua_State *L = luaL_newstate();  
   
    //加载Lua文件  
    int bRet = luaL_loadfile(L,"test.lua");  
   
    //运行Lua文件  
    bRet = lua_pcall(L,0,0,0);  
   
    //读取变量  
    lua_getglobal(L,"str");  
    string str = lua_tostring(L,-1);  
    cout<<"str = "<endl;        //str = I am so cool~  
   
    //读取函数  
    lua_getglobal(L, "add");        // 获取函数,压入栈中  
    lua_pushnumber(L, 10);          // 压入第一个参数  
    lua_pushnumber(L, 20);          // 压入第二个参数  
    int iRet= lua_pcall(L, 2, 1, 0);// 调用函数,2=参数个数,1=返回结果  

    double fValue = lua_tonumber(L, -1);  
    cout << "Result is " << fValue << endl;   
   
    //关闭state  
    lua_close(L);  
    return ;  
}

 PHP

$lua = new Lua();
$lua->include('test.lua');
var_dump($lua->call('add', array(20, 10)));

下一篇 JNI简介

说一说

目录