Program in Lua

第一篇 语言

第0章 序言

Lua仅让你用少量的代码解决关键问题。

Lua所提供的机制是C不擅长的:高级语言,动态结构,简洁,易于测试和调试等。

Lua的独有特征:

(1)可扩展性

(2)简单

(3)高效率

(4)与平台无关

第1章 起点

1.1 Chunks

Lua执行的每一块语句,比如一个文件或者交互模式下的每一行都是一个Chunk

交互模式下  可调用os.exit()退出

另一个连接外部chunk的方式是使用dofile函数,dofile函数加载文件并执行它

loadfile(): 只编译,不运行。

dofile(): 执行。

require(): 只执行一次,会保存已经加载的文件。

当且仅当一个变量不等于nil时,这个变量存在。

在运行参数之前,Lua会查找环境变量LUA_INIT的值,若存在且值为@filename,Lua将加载指定文件,若不以@开头,假定为Lua代码并执行它。

第2章 类型和值

Lua认为0和空串都是真

尽管字符串和数字可以自动转换,但两者不同,10 == “10”是错的。

tonumber()将string转成数字

tostring()将number转成字符串

函数是第一类值(和其他变量相同)

标准库包括:string库,math库,debug库,os库,table库,io库

第3章 表达式

第6章 再论函数

6.3 正确的尾调用 Proper tail calls

Lua可以正确地处理尾调用。

尾调用:当函数最后一个动作是调用另外一个函数时,称这种调用为尾调用。

可以在尾部以较低代价进行递归。

第7章 迭代器与泛型for

7.1 迭代器与闭包

Lua中使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。闭包机制可以很容易实现该任务。

8. 编译,运行,调试

dofile

loadfile:编译代码为中间码并且返回编译后的chunk作为一个函数,而不执行代码

loadfile返回nil和错误信息,使用起来较dofile更自由

loadstring与loadfile相似

都不抛出错误,只会返回nil加上错误信息

Lua中的函数定义发生在运行时的赋值而不是发生在编译时。

loadstring(s)()//快速运行

loadstring不关心词法范围,总是在全局环境中编译他的串

8.1 require函数

require与dofile完成同样的功能但有两点不同

(1)require会搜索目录加载文件

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

require的路径是一个模式列表

require关注的问题只有分号(模式之间的分隔符)和问号

Lua首先检查全局变量LUA_PATH是否为一个字符串,若是,则此串为路径,否则require使用固定的路径

8.2 C Packages

Lua在一个叫loadlib的函数内提供了所有的动态链接的功能

用法:

local path = "/usr/local/lua/lib/libXXX.so"

local f = assert(loadlib(path, "luaopen_socket"))

loadlib函数加载指定的库并连接到Lua,然而并不打开库(也就是说没有调用初始化函数)

8.3 错误

Lua中调用error("invalid input")抛出错误

n = assert (io.read("*number"), "invalid input")也可抛出错误

当函数遇到异常有两个基本的动作,返回错误代码或者抛出错误

file = assert(io.open("no-file", "r"))

io.open返回的第二个结果(错误信息)作为assert的第二个参数

8.4 异常和错误处理

如果在Lua中需要处理错误,需要使用pcall函数封装你的代码

if pcall(foo) then

-- no errors while running 'foo'

else

-- foo raised an error

end

8.5 错误信息和回跟踪(tracebacks)

当pcall返回错误信息的时候他已经释放了保存错误发生情况的栈的信息。如果想得到traceback,必须在pcall返回以前获取。xpcall接受两个参数,调用函数和错误处理函数,当错误发生时,Lua会在栈释放以前调用错误处理函数,因此可以使用debug库收集错误相关的信息。

常用的debug处理函数debug与debug.traceback,前者给出Lua的提示符,可以动手查看错误发生时的情况,后者通过traceback创建更多的错误信息,后者是控制台解释器用来构建错误信息的函数

print(debug.traceback)  --  随时查看当前运行的traceback信息

第9章 协同程序

9.1

9.2 管道与过滤器

协同是一种非抢占式的多线程。

协同模式下,任务间的切换代价较小,与函数调用相当,因此读写可以很好的协同处理

9.3 用作迭代器的协同

9.4 非抢占式多线程

不需要同步机制

第10章 完整示例

10.1 Lua作为数据描述语言使用

entry{a='a', b ='b'}

第12章 数据文件与持久化

实现一个健壮的读取数据文件的程序是很困难的

数据描述是Lua的主要应用之一

12.1 序列化

第13章 Metatables and Metamethods

(在lua代码中的普通表,不能作为userdata的metatable。必须使用luaL_newmetatable创建的表才能作为userdata的metatable。)

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

t= {}

print(getmetatable(t)) -- print nil

t1 = {}

setmetatable(t, t1)

assert(getmetatable(t) == t1)

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

13.1 算术运算的Metamethods

13.4 表相关的Metamethods

13.4.1 the __index Metamethod

当访问一个表中不存在的域,会触发lua解释器去查找__index Metamethod,如果不存在,返回nil,如果存在则由__index metamethod 返回结果

__index也可以是一个表,若是函数,Lua将table和缺少的域作为参数调用这个函数

一个函数的代价虽然稍微高点,但提供了更多的灵活性:可以实现多继承,隐藏,和其他一些变异的机制。

13.4.2 the __newindex Metamethod

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

还有一个raw函数可以绕过Metamethod: 如调用rawset(t,k,v)不调用任何Metamethod对表t的k域赋值为v。

13.4.3 有默认值的表

在一个普通的表中任何域的默认值都是nil,很容易通过metatables来改变默认值

访问情况的唯一方法就是保持表为空。如果我们想监控一个表的所有访问情况,我们应该为真实的表创建一个代理。

该设计不允许遍历表

如果想监控多张表,

13.4.4 监控表

捕获对一个表的所有

13.4.5 只读表

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

第14章 环境

Lua将环境本身存储在一个全局变量_G中,_G._G等于_G

14.1 使用动态名字访问全局变量

元编程meta-programming

value = _G[varname]

14.2 声明全局变量

14.3 非全局的环境

可以使用setfenv函数来改变一个函数的环境,该函数接受函数和新的环境作为参数。除了使用函数本身,还可以指定一个数字表示栈顶的活动函数,数字1表示当前函数,数字2代表调用当前函数的函数

第15章 Packages

Lua并没有提供明确的机制来实现packages,主要思想是:像标准库一样,使用表来描述package。

15.1 基本方法

都加在package表中

这种使用表来实现的包和真正的包的功能并不完全相同。

首先,我们对每一个函数定义都必须显式地在前面加上包的名称。第二,同一包内的函数相互调用必须在被调用的函数前指定包名。

改进方法:

可以使用固定的局部变量名,然后将这个局部变量赋值给最终的包。

这样至少可以不再依赖于固定的包名。

15.2 私有成员

缺点:当修改函数的状态,必须修改函数的调用方式

解决方案:可以将package内的所有函数都声明为局部的,最后将他们放在最终的表中。

15.3 包与文件

15.4 使用全局表

15.5 其他一些技巧

第16章 面向对象程序设计

16.1 类

如果有a和b两个对象,想让b作为a的prototype只需要

setmetatable(a, {__index = b})

function Account:new (o)

o = o or {}

setmetatable(o, self)

self.__index = self

return o

end

getmetatable(a).__index.deposit(a, 100)

Account.deposit(a, 100)

16.2 继承

16.3 多重继承

实现关键:将函数用作__index,当一个表的metatable存在一个__index函数时。如果Lua调用一个原始表中不存在的函数,Lua将调用这个__index指定的函数,这样可以用__index实现在多个父类中查找子类不存在的域。

16.4 私有性

Lua没有打算被用来进行大型的程序设计,其目标定于小型到中型的程序设计,通常作为大型系统的一部分。

Lua的另一个目标是灵活性,提供程序员元机制(meta-mechanisms),通过他可以实现很多不同的机制。

设计思想为:每个对象用两个表来表示:一个描述状态,另一个描述操作(或者叫接口),对象本身通过第二个表来访问,也就是说,通过接口来访问对象。为了避免未授权的访问,表示状态的表中不涉及到操作;表示操作的表中也不涉及到状态,取而代之的是状态被保存在方法的闭包内,

16.5 Single-method

第20章 String库

20.1 模式匹配函数

20.2 模式

字符类指可以匹配一个特定字符集合内任何字符的模式项。

例:字符类%d匹配任意数字

模式串

字符类的补集

第四篇 C API

第24章 C API纵览

C API 是一个C代码和Lua进行交互的函数集。由以下几部分组成:

读写Lua全局变量的函数

调用Lua函数的函数

运行Lua代码片段的函数

注册C函数然后可以在Lua中被调用的函数

等等

C API遵循C语言的语法形式。API中的大部分函数并不检查他们参数的正确性。

API重点放在了灵活性和简洁性方面,有时候以牺牲方便实用为代价的。

在C和Lua通信关键内容在于一个虚拟的栈。

栈的使用解决了C和Lua之间两个不协调的问题。

24.1 第一个示例程序

lua.h中定义了Lua提供的基础函数,其中包括

创建一个新的Lua环境的函数如lua_open

调用Lua函数的函数如lua_pcall

读取/写入Lua环境的全局变量的函数。

注册可以被Lua代码调用的新函数的函数

等等

所有在lua.h中被定义的都有一个lua_前缀

luaxlib.h定义了辅助库提供的函数,其中所有函数都以luaL_打头。辅助库利用lua.h中提供的基础函数提供了更高层次上的抽象;所有Lua标准库都使用了auxlib。

24.2 堆栈

Lua和C之间交换数据时面临两个问题:动态与静态类型系统的不匹配和自动与手动内存管理的不一致。

Lua API没有定义任何类似lua_Value的类型,替代的方案是:用一个抽象的栈在lua与C之间交换值。栈中的每一条记录都可以保存任何lua值

Lua以严格的LIFO规则来操作栈。当调用Lua时,只会改变栈顶部分。C代码有更多自由,可以查询栈上的任何元素,甚至在任何一个位置插入和删除元素。

24.2.1 压入元素

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)//lua字符串

void lua_pushstring (lua_State* L, const char* s)//以/0结尾

Lua从来不保持一个指向外部字符串的指针,对于它保持的所有字符串,Lua要么做一份内部的拷贝要么重新利用已经存在的字符串。故函数返回后,可以自由修改或是释放C中缓冲区

无论何时压入元素到栈上,有责任确保在栈上有空间来做这件事情。

Lua调用C的时候,则至少有20个空闲的记录。lua.h中的LUA_MINSTACK宏定义了该常量。

int lua_checkstack ( lua_State* L, int sz);

检测栈上是否有足够需要的空间。

24.2.2 查询元素

栈中第一个元素索引为1,-1为栈顶元素

int lua_is... (lua_State* L, int index)

lua_isnumber和lua_isstring函数不检查这个值是否是指定的类型,而是看它是否能被转换成指定的那种类型。

为了从栈中获得值,使用lua_to*函数

int lua_toboolean (lua_State* L, int index);

double lua_tonumber (lua_State* L, int index);

const char* lua_toString (lua_State* L, int index);

size_t lua_strlen (lua_State* L, int index);

lua_tostring函数返回一个指向字符串的内部拷贝的指针。不能修改,只要该指针对应的值在栈内,Lua会保证这个指针一直有效;

原则:永远不要将指向Lua字符串的指针保存到访问他们的外部函数中。

24.2.3 其他堆栈操作

int lua_gettop (lua_State* L);//返回堆栈中元素个数。

void lua_settop(L, int index);//lua_settop(L, 0)清空堆栈

基于上面函数,提供

#define lua_pop(L, n) lua_settop(L, -(n) - 1)

void lua_pushvalue (L, int index); // 压入堆栈上指定索引的一个拷贝到栈顶

void lua_remove(L, int index);//

void lua_insert(L, int index);

void lua_replace (L, int index);

24.3 C API的错误处理

Lua中所有结构都是动态的,按需增长,当可能时又会缩减。因此,内存分配失败的可能性在Lua中是普遍的。

24.3.1 应用程序中的错误处理

运行在非保护模式下,Lua遇错误后,调用panic函数并退出应用,可使用lua_atpanic函数设置自己的panic函数。

若不想应用退出,必须在保护模式下运行代码。

所有Lua代码使用lua_pcall()来运行,即使错误,也返回一个错误代码。

如果也想保护所有与Lua交互的C代码,可以使用lua_cpcall

24.3.2 类库中的错误处理

C库函数发现错误只要简单调用lua_error,该函数会清理所有在Lua中需要被清理的,然后和错误信息一起回到最初的执行lua_pcall的地方。

第25章 扩展你的程序

作为配置语言是Lua的一个重要应用。

25.1 表操作

将key与value压入栈中,同时table位于idx处,调用lua_settable(L, idx)

25.2 调用Lua函数

(1)将被调用的函数入栈

(2)依次将所有参数入栈

(3)使用lua_pcall调用函数

(4)从栈中获取函数执行返回的结果

lua_pcall()

25.3 通用的函数调用

使用C的vararg来封装对Lua函数的调用,

第26章 调用C函数

扩展Lua的基本方法之一:为应用程序注册新的C函数到Lua中去

Lua调用C函数时,用来交互的栈不是全局变量,每一个函数都有他自己的私有栈。当Lua调用C函数时,第一个参数总是在这个私有栈的index=1的位置。

26.1 C函数

在Lua中注册的函数必须符合原型

typedef int (*lua_CFunction) (lua_State* L);

返回一个表示返回值个数的数字

函数在将返回值入栈之前不需要清理栈,函数返回之后,Lua自动的清除栈中返回结果下面的所有内容。

lua_pushcfunction():获取指向C函数的指针,并在Lua中创建一个function类型的值来表示这个函数。

lua_pushcfunction(l, l_sin);

lua_setglobal(l, "mysin");

26.2 C 函数库

通常C库都有一个外部的用来打开库的函数。

luaL_openlib函数接受一个C函数的列表和他们对应的函数名,并且作为一个库在一个table中注册所有这些函数。

(1)定义库函数

(2)声明一个luaL_reg数组,保存所有的函数和他们对应的名字,且以{NULL,NULL}结尾

(3)使用luaL_openlib声明主函数

luaL_openlib(L, "mylib", mylib, 0)

还可以为库中所有函数注册公共的upvalues。不需要时,最后一个参数为0.

luaL_openlib返回的时候,将保存库的表放到栈内。

完成库的代码编写之后,必须将它链接到Lua解释器。最常用的方式是使用动态链接库。

可在lua中直接使用loadlib加载你刚才定义的函数库。

mylib = loadlib("fullname-of-your-library", "luaopen_mylib")

然后定义mylib(),将运行luaopen_mylib()打开定义的函数库。

当打开一个新的状态时,必须打开这个新定义的函数库。

当解释器创建新的状态的时候会调用这个宏

#define LUA_EXTRALIBS {"mylib", XXXXX}

第27章 撰写C函数的技巧

27.1 数组操作

lua_settable              lua_gettable

出于性能考虑的数组操作

void lua_rawgeti (lua_State * L, int index, int key)

void lua_rawseti (lua_State * L, int index, int key)

27.2 字符串处理

当C函数接受一个来自lua的字符串作为参数时,有两个规则必须遵守:当字符串正在被访问的时候不要将其出栈,永远不要修改字符串。

27.3 在C函数中保存状态

用于C语言保留一些非局部的数据

Lua提供了一个独立地被称为registry的表,C代码可以自由使用,但Lua代码不能访问。

27.3.1 the registry

registry位于一个由LUA_REGISTRYINDEX定义的值所对应的假索引的位置。一个假索引除了他对应的值不在栈中之外,其他都类似于栈中的索引。

第28章 User-defined Types in C

使用C函数来扩展Lua功能。

如何使用C中新类型来扩展Lua。

28.1 Userdata

void * lua_newuserdata (lua_State* L, size_t size);

按照指定大小分配一块内存,将对应的userdatum放到栈内,并返回内存块的地址。

luaL_checkint()

luaL_argcheck()

28.2 Metatables

推荐阅读更多精彩内容