Lua是一个很小的编程语言,很多人将其与Python高级语言进行比较。
Lua有以下的特点:
(1)纯C语言实现,源码小,可以很好地与C/C++融合。可自行编译,生成静态库。
(2)语法简单,灵活,易学。
我也同时学习了Python,相比之下,Lua精简,功能简单,可用的库少,但是语言的实现写得如此精简也很不错了!与C/C++程序结合就能显示它的强大能力,适用于要求可配置性很高的C/C++程序中,比如游戏。
如果想写纯脚本程序,那就选Python吧,它是强大,库多,喜欢单干。
好,我们学习Lua吧!
Lua的常用数据类型有:
(1)Nil (空) <==> C语言中的NULL
(2)number 所有数据,包含整数小数
(3)string 字串类型
(4)boolean 布尔类型 true, false
(5)function 函数类型
(6)table 表类型
其实,Lua中的数据类型远不止上面的这几个,比如file.
table在Lua中是最重要的数据类型,Lua的很多思想都是基于表来实现的,比如模块、对象。
boolean 除Nil与false为假以为,其它都为真。
string
\ 转义字符
\[
\]
\ddd 3个十进制的数据表示一个字符,高位填0
[[ ... ]] 表示多行字符串,其中字串无转义
> a = [[ \t \n [[ ]] > print(a) \t \n [[
当字符串被参于数值运算时,Lua会试图将字串转成数值
> print("10" + 1) --> 11 > print("123" * "12") --> 1476当数值参于字串运算时,Lua也会尝试将其转换成字符串
> print(123 .. 12) -->12312 > > print(10 == "10") --> 永远都是false > -- 除非 > print(10 == ("10" + 0)) --> true > print("10" == (10 .. "")) --> true总之:操作符决定了两边操作数的转换类型。
(1)算术运算符 + - * / ^ (加减乘除幂)
(2)关系运算符 < > <= >= == ~= (不等于)
nil只与自己相等,table, usedate, function, 只比对象不比内容的值,相当于C语言中的指针比较
(3)逻辑运算符 and or not
and, or 的运算结果不是true与false
a and b -- 如果a为false,则返回a,否则b a or b -- 如果a为true,则返回a,否则b a and b or c -- 类似C中的 a ? b : cnot 结果返回true或false
(4)优先级
^ 与 .. 是右连接
> days = {"sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} > print(days[0]) --> nil > print(days[1]) --> Sun注意: Lua中,表的索引是从1开始的,不是0!
Polyline = { color = "blue", thickness = 2, npoints = 4, {x = 0, y = 0}, {x = -10, y = 0}, {x = -10, y = 10}, {x = 0, y = 10} } Polyline["color"] --> "blue" Polyline.color --> "blue" Polyline[1] --> {x = 0, y = 0} Polyline[1].x --> 0表中的元素都是由key-value组成。如果没有指定key,那就默认以1开始的整数做key,如上4个{x,y}。如果指定了名称,则以string类型为key,如color, thickness, npoints。其实,表的key还可以为table, function, file, boolean任意类型。value可以为任何Lua类型。
Person = {["name"] = "Peter", ["age"] = 28} -- 等价于 Person = {name = "Peter", age = 28} {x=0, y=0} --> {["x"]=0, ["y"]=0} {"red", "green", "blue"} --> {[1]="red", [2]="green", [3]="blue"}
a, b = 10, 'Hi' --> 等价于 a = 10; b = 'Hi'如果左边与右边个数不一致时,多的忽略,少的填Nil。
使用local创建的一个局部变量,可以避免命名冲突,而比全局变量高效。
---------------------------------- if condition1 then ... else if condition2 then ... else ... end ---------------------------------- while condition do ... end ---------------------------------- repeat ... until conditionfor 语句
for var=exp1, exp2, exp3 do ... end ---> exp1: 初始值 ---> exp2: 终止值 ---> exp3: 步进 for k in pairs(days) do print(k .. ' = ' .. days[key]) end for i, val in ipairs(days) do print(i .. ' = ' .. val) end
定义格式:
function func_name (arguments_list) ... end当函数的参数只有一个且为string或table类型时,可以不加括号。如:
dofile "text.lua" table_print {a=12, b=30}函数的返回值可以是多个:
function foo(a, b) return a+b, a-b end aa, bb = foo(32, 11) print(aa, bb) --> 43, 21
Python与Lua都有闭包的特性,我记得好像Java也有,但是C与C++是没有的。
-- 调用newCounter()返回一个function,而这个function引用了newCounter()函数内部的局部变量local i ---------------------------- function newCounter() local i = 0 return function() i = i + 1 return i end end c1 = newCounter print(c1()) --> 1 print(c2()) --> 2 newCounter = nil -- 就算我把这个函数销毁了 print(c1()) --> 3 还是生效为什么?因为在Lua中,所有的变量都是“指针”。所有的变量都是从堆里分配出来的,不像C与C++,局部变量是从栈里分配,一旦函数退出,所有局部变量都要被释放。Lua中的所有变量都是从堆里申请的。对象的释放是自动进行的,只有这个对象的引用计数为0了才会被释放掉。
为了便于理解,我写一段C++的伪代码:
class CounterFunc { public: CounterFunc(int i*) { m_pCnt = i; } int operator()() { ++i; return i; } private: int *m_pCnt; } CounterFunc* newCounter() { int *i = new int; return new CounterFunc(i); } int main() { CounterFunc &c1 = *newCounter() cout << c1() << endl; cout << c1() << endl; }熟悉C++的学友们一下就看懂了。
表中的函数实现方式:
Lib = {} Lib.foo = function(x, y) return x+y end ---------------------------------------------- Lib = { foo = function(x, y) return x+y end } ---------------------------------------------- Lib = {} function Lib.foo(x, y) return x+y end局部函数
local foo = function(x, y) return x+y end ------------------------------------------------ local function foo (x, y) return x+y end
当函数的最后返回结果是调用另一个函数,称之为尾调函数。Lua的在调用尾调函数时,先是弹出当前函数的栈空间,然后再调用尾调函数,从而降低了函数层层调动过程中的栈消耗,非常适用于函数递归调用。
当我们需要加载与运行已有文件中的Lua代码时,可以用以下几种方式:
f = loadstring "a = 12; print('a=' .. a)" f() --> a=12loadstring()本是函数,由于函数是string,所以没有加括号。loadstring()执行完之后,只是加载解析,并没有执行。返回的是一个function类型。执行该函数便可以运行。相当于:
f = loadstring "function() a = 12; print('a='..a) end"
这个函数相当于从文件里读出string,然后再调用loadstring(file_text)实现加载功能。
相当于loadfile()之后,返回一个函数,再调用这个函数。
function dofile( file_name ) local f = assert(loadfile(file_name)) f() end
这个函数是通过调用dofile()来实现的。不同的是,每次加载执行一个文件时,require()都会记录,避免重复加载。另外,如果给定的路径找不到文件,require()会到指定的路径下去找输到加载的文件。文件名称可以省去.lua后缀。
关于这个功能,我的理解是:是非抢占式任务,由任务主动放弃执行权来达到任务切换的目的。
function foo (x, y) while ture do local a = x + y local b = x - y x, y = coroutine.yield(a, b) end end ------------------------------------------ co = coroutine.create(foo) print(coroutine.resume(co, 1, 2)) --> true 3 -1 print(coroutine.resume(co, 7, 1)) --> true 8 6这里最不好理想的就是yield()与resume()参数与返回值的问题。这里分步骤详细解释一下:
(3)foo(1, 2)执行到coroutine.yield(a, b),(a, b)分别为(3,-1)。程序执行yield(3, -1)切换到主任务,resume()函数返回第一个值为bool,后面的正是yield()中传入的参数。
(4)coroutine.resume(co, 7, 1),程序跳到foo()中的coroutine.yield()行继续执行。其中resume()函数中带的参数(7, 1),在foo()函数中,x, y = coroutine.yield() 通过返回值的形式赋给了(x, y)。
(5)跳到(3),如此重复。
可见,Lua并没有实现多任务并行执行,而是任务之间通过resume()与yield()函数进行切换。我想,其主要的作用还是用于将功能以任务的形式进行隔离,便于维护。
元表与元方法,这是一个很重要的概念。可以为table设置或指定metatable,用于指定某些运算符对应的操作方法。
* setmetatable(table, meta_table) 设置元表
* getmetatable(table) 获取元表
这个元素,可以是个函数。
tb = { ----> <metatable> = { __add = function : xxx 加法操作 __sub = function : xxx 减法操作 __mul = function : xxx 乘法操作 ... } [1] = 20 [2] = 40 ... }
+ --> __add(a, b) - --> __sub(a, b) * --> __mul(a, b) / --> __div(a, b) -负 --> __unm(a) ^ --> __pow(a, b)
== --> __eq(a, b) < --> __lt(a, b) <= --> __le(a, b)只要定义上面几个就行了,不用再定义 ~=, >=, >
__tostring = function(tb)在print(t)的时候,print会调用元素中的tostring(t)来进行转换,tostring(t)就查t中的metatable中是否有__tostring域,如果有就调用默认是有的。
__index = function(tb, key) __index = tb访问索引,如果执行读取一个表中的操作,而这个表里又没有这个域,那么Lua就去查询metatable中的__index域。如果__index是表,那么就去查__index表中有没有这个域,如果有就从__index这个表里的这个域里去取值。如果__index是函数,那么就调用__index(tb, key)函数。
__newindex = function(tb, key)更新索引,在更改表域的时候,如果这个表中没有这个域,那么就会从元表的__index里去找。与__index同理。
这两个东西,在类继承里用到。
Lua环境放在全局变量 _G 中。可以用以下语句打印全局变量。
> for n in pairs(_G) do print(n) end
> a = 5 > print(_G["a"]) --> 5其实,所有的全局变量都是放在__G表里。可以对_G表里加metatable,来控制全局变量的形为。如__newindex, __index, 限制直接定义与访问一个变量。
do a = 'Hi' setfenv(1, {_G = _G}) _G.print(_G.a) --> Hi end在局部Chunk中,将_G放入一个空表中,将这个表作为全局表。