《Lua程序设计》(Programming in Lua)阅读笔记

官网:http://www.lua.org/

手册:http://www.lua.org/manual/5.2/manual.html

社区:http://lua-users.org/

论坛:http://www.luaer.cn/

在线中文手册:http://manual.luaer.cnhttp://www.codingnow.com/2000/download/lua_manual.html

花了三天多时间看这本书,基本算入门了吧,有些东西因为时间关系没细看,以后再逐个击破。

摘要(2014.4.24):

1.table可用c中的数组跟python中的dict混合组成,作为c数组的那部分索引从1开始

2.对于table A,#A拿到的是A中作为c数组那部分元素的个数(有坑,详见lua随手记item10);ipairs()迭代的也是作为c数组那部分的元素;pairs()迭代的全部

3.默认情况下,用不存在的key直接访问table对象(myobj[mykey])时返回nil。Python dict的话会报错,需要用has_key来判断key是否存在。C++ map的话会在map中新增一个跟key对应的条目,把默认value返回来(如果value是int类型就返回0)。为什么说默认情况下呢,因为lua的table可以用metatable的__index 来定制访问不存在key时的行为,Python的dict默认的__getitem__()貌似(试了一下不行,没深究)做了保护而不可改变,不过继承dict的话应该是可以定制__getitem__()的。

4.默认形参不直接支持,可用一个table参数的成员模拟多个参数来实现

5.多变量赋值时,变量个数大于值个数时多余变量用nil赋值,变量个数小于值个数时多余值丢弃;对函数传参也是如此;函数返回值也是如此

6.做是否为真的判断时false和nil以外的都是真(跟Python不一样,None/False/零数值/空串/空元组/空列表/空字典都是假(自定义类型据__nonzero__()or__len__()而定),其他为真)

[第一章] 起点

Chunk

Chunk 语句块,一个语句/一系列语句的组合/函数(类似于Python中block的概念,不同的是Python的交互模式是一个__main__ module)。e.g. 一个文件,交互模式下的每一行(所以在交互模式下输入一行"local i=1",该局部变量可能只在本行有效,除非该行被do..end等block包含)

分号(;) 可选,一行多语句时最好用分号隔开(更清晰)

退出交互模式(跟Python一致),ctrl-D (for Unix) / ctrl-Z (for dos/windows) / os.exit()

lua -i -la -lb 在一个Chunk中先执行a然后执行b,最后进入交互模式(Python也有这个功能,只是没用过),-l等效于require

dofile("lib1.lua") 加载文件并执行(Python中的import lib1)

-i / dofile 用于调试和测试Lua代码很方便

全局变量

给一个变量赋值即创建了该全局变量,当前仅当一个变量不等于nil时,这个变量存在

词法

标示符:字符下或下划线开头,字母下划线数字组成 (避免使用"下划线+大写字母开头的标示符")

保留字:and break do else else elseif end false for function if in local nil not or repeat return then true until while

Lua大小写敏感

单行注释 --

多行注释 --[[ --]]

命令行方式

-e 执行语句

-l 加载文件

-i 交互模式

_PROMPT 内置变量,存储交互模式提示符

LUA_INIT 环境变量,值为@filename则Lua会加载指定文件,值不以@开头则Lua把其值作为代码块执行(待验证 markbyxds)

arg 全局变量,存放Lua的cli参数,脚本名的index为0,脚本的参数从1开始增加,脚本前面的参数从-1开始减少

[第二章]类型和值

基本类型:nil boolen number string userdata function thread table

Lua允许我们将任何对象当做table的key

type(xxx)测试给定变量或值的类型,Lua是动态性语言,变量不要类型定义(Python也有类似函数,内建type())

Nil 特殊类型,只有一个值(nil);一个全局变量被赋值之前默认值就是nil;给全局变量赋值nil可以删除该变量

Booleans 两个取值false/true; Lua中所有值都可以作为条件;控制结构中除false和nil为假以外其他都为真(0和空串都是真)

Numbers 实数(Lua中没有整数),可以处理任何长整数不用担心误差

Strings 字符串

不可修改(同Python);单引号/双引号都行;用\转义;和其他对象一样自动分配/释放内存;

[[..]]可以包含多行,可嵌套且不会解释转义序列,第一个字符如果是换行符则自动忽略 (类似于Python中用三个单引号包含多行的字符串)

string-numbers自动类型转换:当字符串使用算数操作符时string会被转成数字;Lua期望string而碰到numbers时会将number转换成string

.. 字符串连接,数字后面写..时必须加上空格防止被解释错

10 == “10” ---> false

tonumber(xx) 显式的把string转换成number,如果xx不能转成number就返回nil

Functions 

函数是第一类值(和其他变量相同;第一类值(first-class values)指,一个对象(广义概念上)在运行期间被创建,可以当做一个参数被传递)

函数可以被存储在变量中,可以作为函数的参数和返回值

Lua可以调用lua或者c实现的函数,Lua的所有标准库(string table io os 算术 debug)都是用c实现的

Userdata and Threads userdata 用于描述应用程序或c库创建的新类型

[第三章]表达式

关系运算符 < > <= >= == ~=

==和~=,如果类型不同Lua认为两者不同

nil只和自己相等;

通过引用比较tables、userdata、functions(也就是说当前仅当两者表示同一对象时相等)(可改写metatabl的__eq方法,类似于Python中的__eq__)

数字按照传统数字大小比较

字符串按照字母顺序进行,但是字母顺序依赖于本地环境(不太明白 markbyxds)

不能用大于小于比较数字和字符串(以及不同类型变量)

逻辑运算符 and or not

and 优先级高于 or

false和nil是假(false),其他(包括0)是真(true)

and/or的运算结果不是true/false:a and b, 如果a为false则返回a,否则返回b; a or b, 如果a为true则返回a,否则返回b

not的运算结果是true/false

x = x or v 如果x为false/nil则给x赋初值v(if not x then x = v end)

"(a and b) or c" 等效于c语言的三元运算符 "a?b:c" (WARNING:小心b的类型,如果b的类型是布尔值是false,那么就无法达到预期效果了

> print(true and false or true)
true
> print(true and false or "a") 
a
> print(false and false or "a")    
a

连接运算符 .. 字符串连接;如果操作数字,Lua将数字转成字符串(前面的数字跟连接符之间需要有空格)

优先级从高到低: 

^
not	-(unary)
*	/
+	-
..
<	>	<=	>=	~=	==
and
or
除^和..外所有二元运算符都是左连接的(左连接?markbyxds)

表的构造

a.x = nil --> remove field 'x'
list: a = {"Sunday", "Monday"} (索引从1开始)	<===>	a = {[1]="Sunday",[2]="Monday"}
record: a = {x=0,y='hello'}	<===>	a = {}; a.x = 0; a.y = 'hello'	<===>	a = {['x']=0,['y']='hello'}
linked list: 
	> for line in io.lines() do l = {next = l, value = line} end
	hi
	hello
	why?
	> print(l)
	table: 0x167e530
	> while l do print(l.value); l = l.next; end
	why?
	hello
	hi
混合list和record初始化

v = {color="blue", week="Monday", {x=0,y=0}, {x=1,y=1}}
一般初始化(list风格初始化和record风格初始化是一般初始化的特例)
opnames = {["+"] = "add", ["-"] = "sub",
		["*"] = "mul", ["/"] = "div"}
i = 20; s = "-"
a = {[i+0] = s, [i+1] = s..s, [i+2] = s..s..s}
print(opnames[s])    --> sub
print(a[22])         --> ---

[第四章]基本语法

赋值语句

a, b = 10, 2*x	<===>	a = 10; b = 2*x
x, y = y, x				-- swap 'x' for 'y' (Lua先计算右边所有的值然后再进行赋值操作)
a[i], a[j] = a[j], a[i]	-- swap 'a[i]' for 'a[j]'
变量个数与值的个数不一致时:变量个数>值的个数,则按变量个数补足nil; 变量个数<值的个数,则多余的值会被忽略( Python会报错)

局部变量和代码块(block)

local 创建局部变量,只在被声明的代码块内有效

block 代码块,一个控制结构/函数体/chunk(变量被声明的那个文件或文本串)

x = 10
local i = 1              -- local to the chunk
while i<=x do
	local x = i*2        -- local to the while body
	print(x)             --> 2, 4, 6, 8, ...
	i = i + 1
end
if i > 20 then
	local x              -- local to the "then" body
	x = 20
	print(x + 2)
else
	print(x)             --> 10  (the global one)
end
print(x)                 --> 10  (the global one)

应尽可能使用局部变量,好处:避免命名冲突;访问局部变量的速度比全局变量快

用do..end可以给block划定明确的界限以控制局部变量的作用范围,等效于c/c++中的{}

控制结构语句

if .. then .. end;
if .. then .. else .. end;
if .. then .. elseif .. then .. else .. end;
while .. do .. end;
repeat .. until ..;

数值for循环:for var=exp1,exp2,exp3 do .. end;

exp1初始值 exp2终止值 exp3是每次循环对v的增量,缺省为1;

三个表达式只在循环开始前计算一次

控制变量var是只在循环内有效的被自动声明的局部变量

循环过程中不要改变控制变量var的值,结果不可预知;可用break退出循环(没有continue,空语句即可)

泛型for循环:

for i,v in ipairs(a) do print(v) end-- print all values of array 'a'

for k in pairs(t) do print(k) end-- print all keys of table 't'

(pairs()函数基本和ipairs()函数用法相同, 区别在于ipairs只返回从1开始的连续数字索引部分)

控制变量var是只在循环内有效的被自动声明的局部变量;

循环过程中不要改变控制变量var的值,结果不可预知;可用break退出循环

break & return

break退出当前循环(for,repeat,while),循环外部不可用break

return从函数返回结果,函数自然结束时候结尾会有默认的return

break/return只能出现在block的结尾一句(作为chunk的最后一句/end之前/else之前/until之前),为了调试而需特殊使用时可加上do..end来实现

[第五章]函数

语法

function func_name (arguments-list)
	statements-list;
end;

当函数只有一个参数且参数是字符串或表构造时,调用时可省略():print "hello,world"; type {}; f {x=10, y=20};

实参和形参的匹配跟赋值语句类似,忽略多余部分,nil补足缺少部分 (Python会报错)

圆括号强制性的使函数调的返回变成一个值(不再是多个)

调用可变参数的可变函数:f(unpack(a)) (unpack,接受一个数组作为输入参数,返回数组的所有元素)

可变参数(...)在名为arg的表中,arg中有个名为n的域记录参数个数

今天(2014.6.5)在项目中遇到一个错误attempt to index global 'arg' (a nil value)

原因:将变参自动存储到arg这个table,这是lua 4.x版的默认行为。而在lua 5.x系列,这个特性被改成了可配置特性。如果打开它,行为和lua 4.x一致,仍然可以使用arg来访问,但代价是每次函数调用,虚拟机都要自动构造arg这个table,影响效率。所以如果要提高效率,可以通过配置关闭这个特性,即只能用...的方式访问变参。可以用local args = {...}把参数转过来。

哑元(dummy variable,下划线) _

文本格式化的函数string.format(类似C语言的sprintf函数)

命名参数的实现:将所有的参数放在一个表中,把表作为函数的唯一参数

返回值个数跟接受结果的变量个数不一致时,赋值规则跟传参规则一样(Python规则不一样:如果是多返回值给一个变量,那么会作为tuple赋值;如果是多返回值给多个变量而个数不一致就会报错)

[第六章]再论函数

Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。

第一类值指:在Lua中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。

词法定界指:嵌套的函数可以访问他外部函数中的变量(在嵌套函数内这个变量被叫做“外部的局部变量”/"upvalue")。(Python也有这个,闭包!)

Lua中关于函数稍微难以理解的是函数也可以没有名字,匿名的。(Python也有这个,lambda!)

function foo (x) return 2*x end		<===>	foo = function (x) return 2*x end
局部函数像局部变量一样在一定范围内有效

尾调用(尾递归),函数的最后一个动作是调用另外一个函数。处理尾调用时不需要额外的栈(层次无限制、不会栈溢出)就表示语言(Lua)支持正确的尾调用

[第七章]迭代器与泛型for

在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素

要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)。

范性for:

for <var-list> in <exp-list> do
	<body>
end

泛型for执行过程:(什么泛型、无状态什么玩意儿的把人都搞晕了,反正写自定义遍历和迭代照着下面这个流程写就是了!)

首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。

第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。

第三,将迭代函数返回的值赋给变量列表。

第四,如果返回的第一个值(即,控制变量)为nil循环结束,否则执行循环体。

第五,回到第二步再次调用迭代函数。

如果我们的迭代函数是f,状态常量是s,控制变量的初始值是a0,那么控制变量将循环:a1=f(s,a0)、a2=f(s,a1)、……,直到ai=nil。

无状态的迭代器

无状态的迭代器是不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价

每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素

迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量)

当Lua调用ipairs(a)开始循环时,他获取三个值:迭代函数iter、状态常量a、控制变量初始值0;然后Lua调用iter(a,0)返回1,a[1](除非a[1]=nil);第二次迭代调用iter(a,1)返回2,a[2]……直到第一个非nil元素。

多状态的迭代器(两种方式)

1.闭包

2.将所有状态信息封装到table内,将table作为迭代器的状态常量

[第八章]编译·运行·错误信息

Lua会首先把代码预编译成中间码然后再执行(Python会先把代码编译成字节码然后执行)

loadfile 编译代码成中间码并返回编译后的chunk作为一个函数,loadfile不抛出错误而是返回错误码

运行多次的时候loadfile只编译一次,但可多次运行;dofile需要每次都编译(loadfile和dofile的关系见下面一小段代码)

loadstring类似于loadfile,只是从字符串中读入;loadstring总是在全局环境中编译他的串

function dofile (filename)
	local f = assert(loadfile(filename))
	return f()
end

require函数

require跟dofile的区别:

1.require会搜索目录加载文件

2.require会判断是否文件已经加载避免重复加载同一文件

_LOADED 加载过的虚文件名

_REQUIREDNAME 保存被required的虚文件的文件名

C Packages

print(loadlib())检测该版本lua是否支持动态链接机制

loadlib(库的绝对路径,初始化函数)

错误

error(err_str, framenum)显式抛出错误,第一个参数是要抛出的错误信息,第二个参数是错误发生的层级

assert(exp, err_str) 检测第一个参数,若没问题不做任何事情;否则以第二个参数作为错误信息抛出

处理异常是返回错误代码还是抛出错误,原则是:对于程序逻辑上能避免的异常以抛出错误方式处理,否则返回错误代码

pcall(func, arg_for_func)在保护模式(protected mode)下执行函数内容,同时捕获所有的异常和错误。若一切正常,pcall返回true以及“被执行函数”的返回值;否则返回nil和错误信息(错误信息不一定仅为字符串,传递给error的任何信息都会被pcall返回)

pcall返回错误信息时已经释放了保存错误发生情况的栈信息

xpcall(function,error_function),发生错误时在释放栈信息之前调用error_function。这样就有机会使用debug库收集错误信息:debug.debug() debug.traceback()

function func()
	assert(false)
end

a,b = pcall(func)
print(a,b)	--false   E:\lua-study\pcall.lua:2: assertion failed!

a,b = xpcall(func, "error")
print(a,b)  --false   error in error handling

[第九章]协同程序 时间关系暂时略过 貌似跟Python的stackless的微进程之类的比较类似 markbyxds


[第十一章]数据结构

数组

通过整数下标访问table中元素,即是数组。并且数组大小不固定,可动态增长

习惯上,Lua的数组下标从1开始。Lua的标准库遵循此惯例,因此你的数组下标必须也是从1开始,才可以使用标准库的函数

矩阵

两种实现:数组的数组;行列组合作为索引

稀疏举证,矩阵的大部分元素都为空或者0的矩阵(用table实现的数据本身天生的就具有稀疏的特性)

链表

每一个节点是一个table,指针是这个表的一个域(field),并且指向另一个节点(table)

队列和双向队列

其实就是把创建table作为队列的函数、插入数据的函数、弹出数据的函数都放到另一个table里面(不放也不要紧啊,只是为了避免全局命名空间的污染),然后调用这个table的属性来完成相关操作

说白了就是把数据和两个额外的标记('first','last')存到table中,用1、2、3..做数据索引操作数据,用'first'/'last'作索引存放队列头尾的索引值

集合和包

Lua中表示集合的方法,将所有集合中的元素作为下标存放在一个table里,只需要测试看对于给定的元素,表的对应下标的元素值是否为nil就知道是否存在于集合中了

字符串缓冲

Lua使用真正的垃圾收集算法,但他发现程序使用太多的内存他就会遍历他所有的数据结构去释放垃圾数据

当Lua执行".."字符串连接时,它创建一个新的字符串,并且从老串中把数据拷贝到新串中。老的字符串变成了垃圾数据,垃圾数据占的内存达到阀值时Lua会进行垃圾收集

table.concat函数可以将一个列表的所有串合并(table.contact是用C语言实现的,处理速度很快)

[第十二章]数据文件与持久化 (lua居然没有专门的序列化功能库,其他脚本大多会有,如Python的pickle/cpickle/msgpack)

相对CSV和其他紧缩格式,自描述数据格式更容易阅读和理解

数据描述是Lua的主要应用之一,从Lua发明以来,我们花了很多心血使他能够更快的编译和运行大的chunks

为了以安全的方式引用任意的字符串,string标准库提供了格式化函数专门提供"%q"选项。它可以使用双引号表示字符串并且可以正确的处理包含引号和换行等特殊字符的字符串

[第十三章]Metatables and Metamethods (元表Metatable跟Python的元类还有些区别,Python中的元类是创建类的类,不过跟Python中的各magic methods有些异曲同工之妙)

说白了,一个对象的元表就记录着这个对象某些特定操作需要调用的函数

算数运算的Metatables

两个table相加: a+b, 检查a或b是否有Metatable以及其Metatable是否有__add域,如找到则调用该__add函数(所谓的Metamethod)去计算结果.(类似于Python的__add__(http://docs.python.org/2/reference/datamodel.html#object.__add__))

Lua默认创建一个不带metatable的新表

getmetatable(tablename)

setmetatable(tablename, metatable_name)

任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)

对于每一个算术运算符,metatable都有对应的域名与其对应,除了__add、__mul外,还有__sub(减)、__div(除)、__unm(负)、__pow(幂),我们也可以定义__concat定义连接行为

对于加法,Lua选择metamethod的规则:

检测第一个参数是否存在带有__add域的metatable,是则使用该函数做运算

检测第二个参数是否存在带有__add域的metatable,是则使用该函数做运算

报错

关系运算的Metatables

__eq(等于),__lt(小于),和__le(小于等于)(对剩下的三个关系运算符没有专门的metamethod,因为Lua将a ~= b转换为not (a == b);a > b转换为b < a;a >= b转换为 b <= a)

关系元算的metamethods不支持混合类型运算:比较不同类型的对象,Lua将抛出错误;比较两个带有不同metamethods的对象,Lua也将抛出错误

相等比较从来不会抛出错误,如果两个对象有不同的metamethod,比较的结果为false,甚至可能不会调用metamethod;仅当两个有共同的metamethod的对象进行相等比较的时候,Lua才会调用对应的metamethod

库定义的Metamethods

print函数总是调用tostring来格式化它的输出,然而当格式化一个对象的时候,tostring会首先检查对象是否存在一个带有__tostring域的metatable。如果存在则以对象作为参数调用对应的函数来完成格式化,返回的结果即为tostring的结果。

setmetatable/getmetatable函数调用也会使用对象的metafield(metatable中的域__metatable)。假定你想保护你的集合使其使用者既看不到也不能修改metatables。如果你对metatable设置了__metatable的值,getmetatable将返回这个域的值,而调用setmetatable 将会出错.

__index metamethod (类似于Python中的__getattr__(http://docs.python.org/2/reference/datamodel.html#object.__getattr__))

print(w.width),当Lua发现w不存在域width时,但是有一个metatable带有__index域,Lua使用w(the table)和width(缺少的值)来调用__index metamethod并把结果返回

简写升级:__index metamethod不需要非是一个函数,他也可以是一个表。当它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数;当它是一个表的时候,Lua将在这个表中看是否有缺少的域。

rawget(t,i)绕过__index metathod直接访问表

__newindex Metamethod

__newindex metamethod用来对表更新,__index则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod:如果存在则调用这个函数而不进行赋值操作.

__newindex metamethod可以跟__index一样也是个table,这样就直接对该table做赋值操作

rawset(t,k,v)不调用任何metamethod直接对表t的k域赋值为v

有默认值的表

如何让带有不同默认值的表mytable可以重用相同的元表mymt:

a.把默认值赋给mytable的一个唯一key(可用新建的table作为key来保证唯一),并对mytable的元表mymt设置__index域,该域对应的函数从mytable上获取唯一key对应的value

b.使用一个分开的表来处理,在这个特殊的表中索引是表,对应的值为默认值(需要weak table)

c.memoize metatables(需要weak table)

监控表

如果我们想监控一个表的所有访问情况,我们应该为真实的表创建一个代理。这个代理是一个空表,并且带有__index和__newindex metamethods,由这两个方法负责跟踪表的所有访问情况并将其指向原始的表

只读表

采用代理的思想很容易实现一个只读表。我们需要做得只是当我们监控到企图修改表时候抛出错误

这种用法要求每一个只读代理有一个单独的新的metatable,使用__index指向原始表 (为了重用metatable还是可以像监控表的代理那样用__index来对应一个函数而不是table啊!)


[第十四章]环境
    全局变量_G记录所有全局变量(_G._G等于_G)(类似于Python的globals(),前者用table记录所有全局符号跟全局对象之间的对应关系,后者用dict记录;插述一句,Lua对table的依赖和使用密集度跟Python对dict一样样的!正因为这个原因,Python的dict选用了高效的hash table做存储,Lua的话还不清楚。)
使用动态名字访问全局变量
    操纵一个名字被存储在另一个变量中的全局变量,或者需要在运行时才能知道的全局变量:

loadstring("value = " .. varname)()
value = loadstring("return " .. varname)()
    更高效更简洁的方式:
        value = _G[varname]
声明全局变量
    全局变量不需要声明
    因为Lua所有的全局变量都保存在一个普通的表中,我们可以使用metatables来改变访问全局变量的行为
    rawset(_G, name, initval or false)
    rawget(_G, var)
    用下面这个函数绕过元表控制调用rawset操作全局表,这个函数必须在为全局表设置元表之前声明,这个是为什么呢?因为这个函数是全局函数!声明会在_G中添加新条目!
function declare (name, initval)
	rawset(_G, name, initval or false)
end
非全局的环境
    setfenv(1, {}) 设置当前函数的环境(table)
    setfenv(2, {}) 设置调用当前函数的函数的环境
    setfenv(f, {}) 设置函数f的环境
    getfenv() 获取当前函数的环境(table)
    代码示例,继承旧的环境而不担心误操作修改全局变量表:
function func()
	a = 1
	local newgt = {}     -- create new environment
	setmetatable(newgt, {__index = _G})
	setfenv(1, newgt)    -- set it
	print(a)             --> 1 _G.a
	a = 10
	print(a)      --> 10 当前环境的a
	print(_G.a)   --> 1 _G.a (如果没对newgt的metatable做__index=_G设置的话,这里是找不到名为"_G"的全局变量的)
	_G.a = 20
	print(_G.a)   --> 20 _G.a
	local t = getfenv()
	for k,v in pairs(t) do --> {a=10}
		print(k,v)
	end
end
func()

[第十五章]Packages
    组织全局变量的命名的机制(避免不同库中命名冲突的问题):Modula的modules;Java和Perl的packages;C++的namespaces;( Python的package-module-class?)
    Lua使用表来描述package
基本方法
    定义一个全局名,为其赋值一个table,其他的定义都放在这个table里
    最后用return语句返回全局名:package打开的时候返回本身是一个很好的习惯。额外的返回语句并不会花费什么代价,并且提供了另一种操作package的可选方式
私有成员(Privacy)
    私有成员,是指只有package本身才能访问的对象
    在Lua中一个传统的方法是将私有部分定义为局部变量来实现
    将package内的所有函数都声明为局部的,最后将公有的函数放在最终的表中
包与文件
    把代码写到一个独立的文件中,执行文件即会加载package。但是package的文件名跟package名(在文件中自己定义的一个存储全局table对象名)没有必然关系。
    如何让文件名跟package名关联起来:(为什么我require老是报错?LUA_PATH和_REQUIREDNAME也总是nil? markbyxds )
local P = {}      -- package
if _REQUIREDNAME == nil then
	complex = P
else
	_G[_REQUIREDNAME] = P
end
使用全局表
    把当前文件主chunk的环境设置成package(package是一个table对象)
local P = {}      -- package
if _REQUIREDNAME == nil then
	complex = P	-->package名是complex
else
	_G[_REQUIREDNAME] = P	-->package名是_G[_REQUIREDNAME],也就是变量_REQUIREDNAME的值
end
setfenv(1, P)
    要在package内部访问以前的全局变量的话,可以使用继承:必须在调用setfenv之前调用setmetatable,否则在当前环境会找不到setmetatable函数!
        local P = {}      -- package
        setmetatable(P, {__index = _G})
        setfenv(1, P)
    或者声明一个局部变量来保存老的环境(速度比上面的方式更快一些):
local P = {}
pack = P
local _G = _G
setfenv(1, P)
    或者只把需要的函数或者packages声明为local:
local P = {}
pack = P
-- Import Section:
-- declare everything this package needs from outside
local sqrt = math.sqrt
local io = io
-- no more external access after this point
setfenv(1, P)

其他一些技巧(Other Facilities)
    不需要将package的所有公有成员的定义放在一起,私有成员必须限制在一个文件之内
    可以在同一个文件之内定义多个packages,我们需要做的只是将每一个package放在一个do代码块内,这样local变量才能被限制在那个代码块中
    在package外部,如果我们需要经常使用某个函数,我们可以给他们定义一个局部变量名
    如果我们不想一遍又一遍的重写package名,我们用一个短的局部变量表示package
    写一个函数拆开package也是很容易的,将package中所有的名字放到全局命名空间即可
    由于packages本身也是表,我们甚至可以在packages中嵌套packages;也就是说我们在一个package内还可以创建package,然后很少有必要这么做
    
[第十六章]面向对象程序设计
    Lua的面向对象,说白了是利用元表(metatable)的__index域这个特性实现的
    self参数的使用是很多面向对象语言的要点。大多数OO语言将这种机制隐藏起来,这样程序员不必声明这个参数(虽然仍然可以在方法内使用这个参数)。Lua也提供了通过使用冒号操作符来隐藏这个参数的声明。
     冒号的效果相当于在函数定义和函数调用的时候,增加一个额外的隐藏参数作为第一个参数(self;调用时把调用者作为第一个参数用"self"传入)。这种方式只是提供了一种方便的语法,实际上并没有什么新的内容。我们可以使用dot语法定义函数而用冒号语法调用函数,反之亦然,只要我们正确的处理好额外的参数

    Lua不存在类的概念,每个对象定义他自己的行为并拥有自己的形态(shape)
     在Lua中仿效类的概念:创建一个对象,作为其它对象的原型(prototype)即可(原型对象为类,其它对象为类的instance)。类与prototype的工作机制相同,都是定义了特定对象的行为
Account = {}
function Account:PrintSex() --等价于 function Account.PrintSex(Account) print Account.sex end
	print(self.sex)
end
function Account:new(o)
	o = o or {}
	setmetatable(o, self)	-->o的元表metatable设置为Account
	self.__index = self		-->访问o不存在的域时从Account的域里面取
	return o
end
a = Account.new(Account, {sex='male'})	-->Account作为a的prototype <==> Account是class a是Account的instance
a:PrintSex()				-->a没有PrintSex的域(方法),从元表Account中获取该域
b = Account:new({sex='female'})
b.PrintSex(a)
继承
    通常面向对象语言中,继承使得类可以访问其他类的方法
Account = {balance = 0}
function Account:new (o)
	o = o or {}
	setmetatable(o, self)
	self.__index = self
	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()			---> class(prototype):Account			instance:SpecialAccount
s = SpecialAccount:new{limit=1000.00}	---> class(prototype):SpecialAccount	instance:s
s:deposit(100.00)						---> Account.deposit(s, 100.00)

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
function s:getLimit ()					---> 修改特定对象s的方法
	return self.balance * 0.10
end
s:getLimit()							-->s.getLimit(s)
s:withdraw(200.00)						--> SpecialAccount.withdraw(s,200.00) (withdraw内部调用getLimit的时候调的是s.getLimit(s)不是SpecialAcount.getLimit)
多重继承
     说白了把子类的metatable['__index']设置成在多个父类的属性(域)中寻找要访问的方法
-- look up for `k' in list of tables 'plist'
local function search (k, plist)
	for i=1, table.getn(plist) do
	   local v = plist[i][k]    -- try 'i'-th superclass
	   if v then return v end
	end
end

function createClass (...)
	local c = {}      -- new class
	-- class will search for each method in the list of its
	-- parents (`arg' is the list of parents)
	setmetatable(c, {__index = function (t, k) return search(k, arg) end})
	
	--optimized for method search
	--[[
	setmetatable(c, {__index = function (t, k)
									local v = search(k, arg)
									t[k] = v      -- save for next access
									return v
								end
					}
				)
	--]]

	-- prepare `c' to be the metatable of its instances
	c.__index = c

	-- define a new constructor for this new class
	function c:new (o)
		o = o or {}
		setmetatable(o, c)
		return o
	end

	-- return new class
	return c
end
私有性(privacy)
    Lua没有打算被用来进行大型的程序设计,相反,Lua目标定于小型到中型的程序设计,通常是作为大型系统的一部分。典型的,被一个或者很少几个程序员开发,甚至被非程序员使用。所以,Lua避免太冗余和太多的人为限制。如果你不想访问一个对象内的一些东西就不要访问(If you do not want to access something inside an object, just do not do it.)。
    设计思想:每个对象用两个表来表示:一个描述状态;另一个描述操作(或者叫接口).对象本身通过第二个表来访问,也就是说,通过接口来访问对象.为了避免未授权的访问,表示状态的表中不涉及到操作;表示操作的表也不涉及到状态,取而代之的是,状态被保存在方法的闭包内.
     说白了,用闭包隐藏数据而只暴露操作数据的接口(方法)
function newAccount (initialBalance)
	local self = {		-->私有数据 私有方法都可以放到这个table里面
					balance = initialBalance,
					LIM = 10000.00,
				}
				
	function self.extra()	-->私有方法
	   if self.balance > self.LIM then
		   return self.balance*0.10
	   else
		   return 0
	   end
	end
	
	local withdraw = function (v) self.balance = self.balance - v end
	local deposit = function (v) self.balance = self.balance + v end
	local getBalance = function () return self.balance + self.extra() end
	return {
	   withdraw = withdraw,
	   deposit = deposit,
	   getBalance = getBalance
	}
end

acc1 = newAccount(10010.00)
acc1.withdraw(40.00)
print(acc1.getBalance())
   
Single-Method的对象实现方法
    一个保存状态的迭代子函数就是一个single-method对象(对象只有一个单一的方法)
function newObject (value)
	return function (action, v)
	   if action == "get" then return value
	   elseif action == "set" then value = v
	   else error("invalid action")
	   end
	end
end

d = newObject(0)
print(d("get"))      --> 0
d("set", 10)
print(d("get"))      --> 10
       
[第十七章]Weak表
    和其他的( 比如Python?)不同,Lua的垃圾收集器不存在循环的问题。
    Weak表是一种用来告诉Lua一个引用不应该防止对象被回收的机制。一个weak引用是指一个不被Lua认为是垃圾的对象的引用。如果一个对象所有的引用指向都是weak,对象将被收集,而那些weak引用将会被删除。Lua通过weak tables来实现weak引用:一个weak tables是指所有引用都是weak的table。这意味着,如果一个对象只存在于weak tables中,Lua将会最终将它收集。
    三种类型的weak tables:weak keys组成的tables;weak values组成的tables;以及纯weak tables类型,他们的keys和values都是weak的。与table本身的类型无关,当一个keys或者vaule被收集时,整个的入口(entry)都将从这个table中消失。
    表的weak性由他的metatable的__mode域来指定的。在这个域存在的时候,必须是个字符串:如果这个字符串包含小写字母‘k’,这个table中的keys就是weak的;如果这个字符串包含小写字母‘v’,这个table中的vaules就是weak的。
a = {}
b = {}
setmetatable(a, b)
b.__mode = "k"     -- now 'a' has weak keys
key = {}             -- creates first key
a[key] = 1
key = {}             -- creates second key
a[key] = 2
collectgarbage()     -- forces a garbage collection cycle
for k, v in pairs(a) do print(v) end	--> 2
    要注意,只有对象才可以从一个weak table中被收集。比如数字和布尔值类型的值,都是不会被收集的。例如,如果我们在table中插入了一个数值型的key(在前面那个例子中),它将永远不会被收集器从table中移除。当然,如果对应于这个数值型key的vaule被收集,那么它的整个入口将会从weak table中被移除。
    关于字符串的一些细微差别:从上面的实现来看,尽管字符串是可以被收集的,他们仍然跟其他可收集对象有所区别。一个字符串不会从weak tables中被移除(除非它所关联的vaule被收集)
     说白了,对于在其他地方没被引用、只被table的key或者value引用的对象,如果这个table的keys是weak的或者values是weak的或者都是weak的,那么collectgarbage()之后对象会被收集,对象条目从table中移除
记忆函数
    如果表中有weak值,每次的垃圾收集循环都会移除当前时间内所有未被使用的结果(防止缓存把内存挤爆)
local results = {}
setmetatable(results, {__mode = "v"})   -- make values weak
function mem_loadstring (s)
	if results[s] then       -- result available?
	   return results[s]    -- reuse it
	else
	   local res = loadstring(s)   -- compute new result
	   results[s] = res            -- save for later reuse
	   return res
	end
end
关联对象属性
    如果我们使用一个普通的table来关联函数和名字,那么所有的这些函数将永远不会被收集。正如你所想的那样,我们可以通过使用weak table来解决这个问题。这一次,我们需要weak keys。一旦没有其他地方的引用,weak keys并不会阻止任何的key被收集。从另一方面说,这个table不会存在weak vaules;否则,活动对象的属性就可能被收集了。
    Lua本身使用这种技术来保存数组的大小。table库提供了一个函数来设定数组的大小,另一个函数来读取数组的大小。当你设定了一个数组的大小,Lua 将这个尺寸保存在一个私有的weak table,索引就是数组本身,而value就是它的尺寸
重述带有默认值的表
    用weak table来将默认vaules和对应的每一个table相联系:
local defaults = {}
setmetatable(defaults, {__mode = "k"})

local mt = {__index = function (t) return defaults[t] end}
function setDefault (t, d)
	defaults[t] = d
	setmetatable(t, mt)
end

t = {1,2,3,4,5}
setDefault(t, 100)
for k,v in pairs(t) do
	print(k,v)
end
print("t['x']", t['x'])
    使用不同的metatables来保存不同的默认值,但当我们重复使用一个默认值的时候,重用同一个相同的metatable:
local metas = {}
setmetatable(metas, {__mode = "v"})

function setDefault (t, d)
	local mt = metas[d]
	if mt == nil then
	   mt = {__index = function () return d end}
	   metas[d] = mt     -- memoize
	end
	setmetatable(t, mt)
end

t = {1,2,3,4,5}
setDefault(t, 1000)
for k,v in pairs(t) do
	print(k,v)
end
print("t['x']",t['x'])
       
[第十八章]数学库
    math.random用来产生伪随机数,有三种调用方式:
        第一:不带参数,将产生 [0,1)范围内的随机数.
        第二:带一个参数n,将产生1 <= x <= n范围内的随机数x.
        第三:带两个参数a和b,将产生a <= x <= b范围内的随机数x.
    用randomseed设置随机数发生器的种子,只能接受一个数字参数: math.randomseed(os.time())
    
[第十九章]Table库
数组大小
    我们经常假定array在最后一个非nil元素处结束
    getn,返回array的大小;setn,设置array的大小
    5.1版本已经没有setn函数了,table的n域也没了,哦也!
插入/删除
    table库提供了从一个list的任意位置插入和删除元素的函数。table.insert函数在array指定位置插入一个元素,并将后面所有其他的元素后移。另外,insert改变array的大小(using setn)。例如,如果a是一个数组{10,20,30},调用table.insert(a,1,15)后,a变为{15,10,20,30}。经常使用的一个特殊情况是,我们 不带位置参数调用insert,将会在array最后位置插入元素(所以不需要元素移动)。
    table.remove函数删除数组中指定位置的元素,并返回这个元素,所有后面的元素前移,并且数组的大小改变。 不带位置参数调用remove的时候,他删除array的最后一个元素
排序
    table.sort,有两个参数:存放元素的array(注意,是array!数字连续递增索引哦!)和排序函数。排序函数有两个参数并且如果在array中排序后第一个参数在第二个参数前面,排序函数必须返回true。如果未提供排序函数,sort使用默认的小于操作符进行比较
     用ipairs和pairs遍历数组的区别:前者使用key的顺序1、2、……,后者表的自然存储顺序(pairs()对于table的index从1递增的部分应该也是正常顺序遍历,程序验证已通过,具体应该看看源码)
    
[第二十章]String库
    string.len(s) string.rep(s,n) string.lower(s) string.upper(s) string.sub(s,i,j)(1第一个,-1倒数第一个)
    记住:Lua中的字符串是恒定不变的。String.sub函数以及Lua中其他的字符串操作函数都不会改变字符串的值,而是返回一个新的字符串。( 同Python,Python中string是变长不可变对象)
    string.char(i1,i2,i3) string.byte(s, index=1)
    string.format(formatstr, arglist) 按照c语言的printf规则来使用(调用标准C的printf函数来实现)
模式匹配函数
    string.find() 字符串查找
    string.gsub() 全局字符串替换
    string.gfind() 全局字符串查找
    Lua并不使用POSIX规范的正则表达式(也写作regexp)来进行模式匹配
模式
    字符类
        .      任意字符
        %a     字母
        %c     控制字符
        %d     数字
        %l     小写字母
        %p     标点字符
        %s     空白符
        %u     大写字母
        %w     字母和数字
        %x     十六进制数字
        %z     代表0的字符
    上面字符类的大写形式表示小写所代表的集合的补集。例如,'%A' 非字母的字符
    模式修饰符
        +      匹配前一字符1次或多次
        *      匹配前一字符0次或多次
        -      匹配前一字符0次或多次
        ?      匹配前一字符0次或1次
    '%' 用作特殊字符(( ) . % + - * ? [ ^ $)的转义字符,因此 '%.' 匹配点;'%%' 匹配字符 '%'。转义字符 '%'不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。当对一个字符有疑问的时候,为安全起见请使用转义字符转义他。
    '\' 用作转义引号
    [] 将字符类或者字符括起来创建自己的字符类,比如[%w_] [01]
    在char-set中可以使用范围表示字符的集合,第一个字符和最后一个字符之间用连字符连接表示这两个字符之间范围内的字符集合
    以 '^' 开头的模式只匹配目标串的开始部分,相似的,以 '$' 结尾的模式只匹配目标串的结尾部分
捕获(Captures)
    Capture是这样一种机制:可以使用模式串的一部分匹配目标串的一部分。将你想捕获的模式用圆括号括起来,就指定了一个capture。
    实际使用的时候再去看官方文档吧 太多了!

[第二十一章]IO库
简单I/O模式
    简单模式的所有操作都是在两个当前文件之上。I/O库将当前输入文件作为标准输入(stdin),将当前输出文件作为标准输出(stdout)。 我们可以使用 io.inputio.output函数来改变当前文件
    这样当我们执行io.read,就是在标准输入中读取一行。我们可以使用io.input和io.output函数来改变当前文件。
     在编写代码时应当避免像io.write(a..b..c);这样的书写,这同io.write(a,b,c)的效果是一样的。但是后者因为避免了串联操作,而消耗较少的资源
    io.write和print的区别:
        write不附加任何额外的字符到输出中去,例如制表符,换行符等等
        write函数是使用当前输出文件,而print始终使用标准输出
        print函数会自动调用参数的tostring方法,所以可以显示出表(tables)函数(functions)和nil
    io.read的参数:
        "*all"        读取整个文件
        "*line"        读取下一行
        "*number"    从串中转换出一个数值
        num            读取num个字符到串
     io.read("*line")函数返回当前输入文件的下一行(不包含最后的换行符)。当到达文件末尾,返回值为nil(表示没有下一行可返回)。该读取方式是read函数的默认方式,所以可以简写为io.read()。
     特别的,io.read(0)函数的可以用来测试是否到达了文件末尾。如果不是返回一个空串,如果已是文件末尾返回nil
完全I/O 模式
    io.open:它模仿C语言中的fopen函数,同样需要打开文件的文件名参数,打开模式的字符串参数。模式字符串可以是 "r"(读模式),"w"(写模式,对数据进行覆盖),或者是 "a"(附加模式)。并且字符 "b" 可附加在后面表示以二进制形式打开文件。正常情况下open函数返回一个文件的句柄。如果发生错误,则返回nil,以及一个错误信息和错误代码。
    文件打开后就可以用read和write方法对他们进行读写操作。它们和io表的read/write函数类似,但是调用方法上不同,必须使用冒号字符,作为文件句柄的方法来调用
    同C语言中的流(stream)设定类似,I/O库提供三种预定义的句柄:io.stdin、io.stdout和io.stderr
    实际使用的时候再去看官方文档吧 太多了!
    
[第二十三章]Debug库
自省(Introspective)
    debug.getinfo
        source,标明函数被定义的地方。如果函数在一个字符串内被定义(通过loadstring),source就是那个字符串。如果函数在一个文件中定义,source是@加上文件名
        short_src,source的简短版本(最多60个字符),记录一些有用的错误信息
        linedefined,source中函数被定义之处的行号
        what,标明函数类型。如果foo是一个普通得Lua函数,结果为 "Lua";如果是一个C函数,结果为 "C";如果是一个Lua的主chunk,结果为 "main"
        name,函数的合理名称
        namewhat,上一个字段代表的含义。这个字段的取值可能为:W"global"、"local"、"method"、"field",或者 ""(空字符串)。空字符串意味着Lua没有找到这个函数名。
        nups,函数的upvalues的个数
        func,函数本身       
'n' selects fields name and namewhat
'f' selects field func
'S' selects fields source, short_src, what, and linedefined
'l' selects field currentline
'u' selects field nup
    debug.getlocal
    debug.setlocal
    debug.getupvalue
Hooks
    debug库中的hook:注册一个函数,用来在程序运行中某一事件到达时被调用
    触发hook的四种事件:call return line count
    debug.sethook 注册hook:debug.sethook('crl')注册 debug.sethook()取消
Profiles
local Counters = {}
local Names = {}

local function hook ()
	local f = debug.getinfo(2, "f").func
	if Counters[f] == nil then  -- first time `f' is called?
	   Counters[f] = 1
	   Names[f] = debug.getinfo(2, "Sn")
	else   -- only increment the counter
	   Counters[f] = Counters[f] + 1
	end
end

function getname (func)
	local n = Names[func]
	if n.what == "C" then
		return n.name
	end
	local loc = string.format("[%s]:%s",
		   n.short_src, n.linedefined)
	if n.namewhat ~= "" then
	   return string.format("%s (%s)", loc, n.name)
	else
	   return string.format("%s", loc)
	end
end

local f = assert(loadfile("e:\\lua-study\\weaktable_function.lua"))
debug.sethook(hook, "c")    -- turn on the hook
f()    -- run the main program
debug.sethook()   -- turn off the hook
for func, count in pairs(Counters) do
	print(getname(func), count)
end
   
[第二十四章]C API纵览
    Lua解释器是一个使用Lua标准库(math/table/string/io/os/debug)实现的独立的解释器,它是一个很小的应用(总共不超过500行的代码)
    C扩展Lua和Lua扩展C,两种方式里C语言都使用相同的API与Lua通信,C和Lua交互这部分被称为C API
    C API组成:读写Lua全局变量的函数,调用Lua函数的函数,运行Lua代码片断的函数,注册C函数然后可以在Lua中被调用的函数, etc.
第一个示例程序
    lua.h定义基础函数,以lua_为前缀
    lauxlib.h定义辅助库auxlib的函数,以luaL_为前缀

    Lua库没有定义任何全局变量,它所有状态保存在动态结构lua_State中,其指针作为所有Lua函数的参数

#include <stdio.h>
#include <string>

#ifdef __cplusplus
extern "C" {
#endif

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#ifdef __cplusplus
}
#endif

int main (void)
{
	char buff[256];
	int error;
	lua_State *L = lua_open();  /* opens Lua */
	luaopen_base(L);         /* opens the basic library */
	luaopen_table(L);        /* opens the table library */
//	luaopen_io(L);           /* opens the I/O library */ //PANIC: unprotected error in call to Lua API (no calling environment)
	luaL_openlibs(L);		//instead of last line: luaopen_io(L);
	luaopen_string(L);       /* opens the string lib. */
	luaopen_math(L);         /* opens the math lib. */
	while (fgets(buff, sizeof(buff), stdin) != NULL) {
		error = luaL_loadbuffer(L, buff, strlen(buff),"line") || lua_pcall(L, 0, 0, 0);
		if (error) {
			fprintf(stderr, "%s", lua_tostring(L, -1));
			lua_pop(L, 1);/* pop error message from the stack */
		}
	}
	lua_close(L);
	getchar();
	return 0;
}

堆栈

    Lua和C之间交换数据时面临着两个问题:动态与静态类型系统的不匹配和自动与手动内存管理的不一致( Python面临的是引用计数到底加还是减的问题)
    Lua用一个抽象的栈在Lua与C之间交换值。( 貌似跟Python解释器中PyFrameObject对象内部的"运行时栈"有些类似,不过后者是解释器内部的,没有从API暴漏出来给用户操作)
    Lua以一个严格的LIFO规则(后进先出;也就是说,始终存取栈顶)来操作栈。当你调用Lua时,它只会改变栈顶部分。你的C代码却有更多的自由;更明确的来讲,你可以查询栈上的任何元素,甚至是在任何一个位置插入和删除元素。
    压栈函数:
void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t length); //函数返回后可随意修改和释放s,Lua内部已做拷贝等处理
void lua_pushstring (lua_State *L, const char *s); //同上
int lua_checkstack (lua_State *L, int sz);
    查询元素:
        1栈底(第一个压入的元素) -1栈顶(最后一个被压入的元素)
int lua_is...(lua_State *L, int index) //lua_istable..,检查元素类型 lua_isnumber,lua_isstring检查元素能否被转换成指定类型
lua_type函数返回栈中元素的类型
int           lua_toboolean (lua_State *L, int index);	//类型不匹配时返回0
double        lua_tonumber (lua_State *L, int index);	//类型不匹配时返回0
const char *  lua_tostring (lua_State *L, int index);	//类型不匹配时返回NULL
size_t        lua_strlen (lua_State *L, int index);		//类型不匹配时返回0
lua_tostring函数返回一个指向字符串的内部拷贝的指针(const char*)。只要这个指针对应的值还在栈内,Lua会保证这个指针一直有效。当一个C函数返回后,Lua会清理他的栈,所以,有一个原则:永远不要将指向Lua字符串的指针保存到访问他们的外部函数中(有点晕 markbyxds )
lua_tostring返回的字符串结尾总会有一个字符结束标志0,但是字符串中间也可能包含0,lua_strlen返回字符串的实际长度
其他堆栈操作
    其他函数
int  lua_gettop (lua_State *L);	//返回堆栈中的元素个数,即栈顶元素的索引
void lua_settop (lua_State *L, int index);	//重置栈中元素个数,缩小就抛弃栈顶元素,变大就用nil填充栈顶位置
void lua_pushvalue (lua_State *L, int index);	//copy栈上index对应的元素到栈顶
void lua_remove (lua_State *L, int index);	//移除index对应的元素,并把栈顶元素下移
void lua_insert (lua_State *L, int index);	//把栈顶元素移动到index对应的位置,其他元素上移
void lua_replace (lua_State *L, int index);	//弹出栈顶元素,并用它覆盖index对应的元素

    示例:

#include <stdio.h>
#include <string>

#ifdef __cplusplus
extern "C" {
#endif

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#ifdef __cplusplus
}
#endif

static void stackDump (lua_State *L) 
{
	int i;
	int top = lua_gettop(L);
	for (i = 1; i <= top; i++) {  /* repeat for each level */
		int t = lua_type(L, i);
		switch (t) {
		case LUA_TSTRING:  /* strings */
			printf("`%s'", lua_tostring(L, i));
			break;
		case LUA_TBOOLEAN:  /* booleans */
			printf(lua_toboolean(L, i) ? "true" : "false");
			break;
		case LUA_TNUMBER:  /* numbers */
			printf("%g", lua_tonumber(L, i));
			break;
		default:  /* other values */
			printf("%s", lua_typename(L, t));
			break;
		}
		printf("  ");  /* put a separator */
	}
	printf("\n");     /* end the listing */
}

void stack_test()
{
	lua_State *L = lua_open();
	lua_pushboolean(L, 1); 
	lua_pushnumber(L, 10);
	lua_pushnil(L); 
	lua_pushstring(L, "hello");
	stackDump(L); /* true  10  nil  `hello'  */
	lua_pushvalue(L, -4); stackDump(L); /* true  10  nil  `hello'  true  */
	lua_replace(L, 3); stackDump(L);/* true  10  true  `hello'  */
	lua_settop(L, 6); stackDump(L); /* true  10  true  `hello'  nil  nil  */
	lua_remove(L, -3); stackDump(L); /* true  10  true  nil  nil  */
	lua_settop(L, -5); stackDump(L); /* true  */
	lua_close(L);
	getchar();
}

int main (void)
{
	//helloworld();
	stack_test();
	return 0;
}

C API的错误处理(时间问题暂不深究)

    setjmp( http://www.cplusplus.com/reference/csetjmp/setjmp/?kw=setjmp)
    longjmp( http://www.cplusplus.com/reference/csetjmp/longjmp/?kw=longjmp)
    lua_atpanic
    lua_pcall
    lua_cpcall
    lua_error
    luaL_error
    

[第二十五章]扩展你的程序

#include <stdio.h>
#include <string>

#ifdef __cplusplus
extern "C" {
#endif

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#ifdef __cplusplus
}
#endif

void load (char *filename, int *width, int *height) {
	lua_State *L = lua_open();
	luaopen_base(L);
	luaL_openlibs(L);
	luaopen_string(L);
	luaopen_math(L);
	if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
		luaL_error(L, "cannot run configuration file: %s", lua_tostring(L, -1));
	lua_getglobal(L, "width");
	lua_getglobal(L, "height");
	if (!lua_isnumber(L, -2))
		luaL_error(L, "`width' should be a number\n");
	if (!lua_isnumber(L, -1))
		luaL_error(L, "`height' should be a number\n");
	*width = (int)lua_tonumber(L, -2);
	*height = (int)lua_tonumber(L, -1);
	lua_close(L);
}

int main (void)
{
	//helloworld();
	//stack_test();
	int a, b;
	load("config.lua", &a, &b);
	printf("a=%d,b=%d", a, b);
	getchar();
	return 0;
}
表操作

    LUA_API void  (lua_settable) (lua_State *L, int idx); //将栈顶的key(-2)和value(-1)出栈,用这两个值设置栈中以inx为索引存储的table

    具体看书中的例子
调用Lua函数
    使用API调用函数的方法是很简单的:首先,将被调用的函数入栈;第二,依次将所有参数入栈;第三,使用lua_pcall调用函数;最后,从栈中获取函数执行返回的结果
    具体看书中的例子
通用的函数调用
    call_va("f", "dd>d", x, y, &z);
    字符串 "dd>d" 表示函数有两个double类型的参数,一个double类型的返回结果。我们使用字母 'd' 表示double;'i' 表示integer,'s' 表示strings;'>' 作为参数和结果的分隔符。如果函数没有返回结果,'>' 是可选的。
    具体看书中的例子

[第二十六章]调用C函数
    当Lua调用C函数的时候,使用和C调用Lua相同类型的栈来交互。C函数从栈中获取她的参数,调用结束后将返回结果放到栈中。为了区分返回结果和栈中的其他的值,每个C函数还会返回结果的个数(the function returns (in C) the number of results it is leaving on the stack.)。这儿有一个重要的概念:用来交互的栈不是全局变量,每一个函数都有他自己的私有栈。当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置。甚至当一个C函数调用Lua代码(Lua代码调用同一个C函数或者其他的C函数),每一个C函数都有自己的独立的私有栈,并且第一个参数在index=1的位置
    待续... (要忙别的事儿了,回头继续看 markbyxds )

总结:
    Lua的核心概念:table 闭包(local) 元表(__index)
    数据结构以及几乎所有复杂对象都是基于table实现的
    迭代器、数据隐藏、私有数据都是通过闭包来实现的
    类、继承、自定义table行为都是通过元表(以及使用最频繁的__index)来实现的

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值