第一篇 语言
第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