为什么80%的码农都做不了架构师?>>>
(接上篇)
--------------------------------------
5 API
--------------------------------------
这节描述 Lua 的 API, 也就是宿主程序和 Lua 交互的一组 C 函数。所有的 API 函数和相关联的类型及常量都在文件 lua.h 中声明。
所有的 API 提供的功能都可以由宏来替代,即使当我们使用术语函数(``function'')。因为这样的宏只使用它的参数一次,所以不会产生隐蔽的副作用。
-------------------
5.1 状态(State)
-------------------
Lua 库是完全可重入的:它没有使用任何全局变量。Lua 解释器的完整状态(全局变量,栈,标签方法,等)保存在一个动态分配的 lua_State 类型中。这个状态必须作为第一个参数传递给库中的每一个函数(除了下面说的 lua_open)。
在调用任何 API 函数之前,你必须新建一个状态,通过调用
lua_State *lua_open (int stacksize);
这个函数唯一的参数是解释器的栈尺寸。(每个函数调用的每个参数需要一个栈位置,局部变量,临时值,再加一个用做标记的位置。栈必须有额外的 20 个可用位置。对于很小的实现,没有递归函数,栈的尺寸为 100 可能就足够了。)如果 stacksize 是 0 ,那么默认的尺寸 1024 会被使用。
为释放一个由 lua_open 创建的状态,调用
void lua_close (lua_State *L);
这个函数销毁给定的 Lua 环境中的所有的对象(调用相应的垃圾回收标签方法,如果有的话)并且释放所有的这个状态使用的动态内存。通常,你不必调用这个函数,因为当你的程序结束时所有的资源会被自然释放。另一方面,长时间运行的程序,如守护程序或一个 web 服务器程序,可能需要在状态不需要的时候释放它,以避免内存增长过大。
除了 lua_open,所有的 Lua API 中的函数都需要一个状态作为它们的第一个参数。
-------------------
5.2 栈和索引
-------------------
Lua 用一个栈和 C 之间传递数据。栈中的每一个元素代表一个 Lua 值(nil,数值,字符串,等)。
为了方便起见,大多数 API 中的 query 操作并不需严格遵守栈的使用规则。反而,它们可以通过使用一个索引来引用任何栈中的元素。一个正数索引代表一个绝对的栈位置(始于 1 ,不像 C 中从 0 开始)。一个负数索引代表一个从栈顶开始的偏移量。更具体地说,如果栈有 n 个元素,索引 1 代表第一个元素(即,第一个压栈的元素),索引 n 代表最后一个元素;索引 -1 同样代表最后一个元素(即,栈顶的元素),索引 -n 代表第一个元素。我们说一个索引是有效的如果它位于 1 到栈顶之间(即,如果 1 <= abs(index) <= top)。
在任何时间,你可以获得栈顶元素的索引通过调用
int lua_gettop (lua_State *L);
因为索引开始于 1,lua_gettop 的结果等于栈中元素的个数(所以 0 代表一个空栈)。
当你和 Lua API 交互的时候,你有责任去控制栈的溢出。
函数
int lua_stackspace (lua_State *L);
返回栈中依然可用的位置个数。每当 Lua 调用 C,它保证最少有 LUA_MINSTACK 个位置可用。LUA_MINSTACK 在 lua.h 中定义,最小为 16,所以你需要担心栈的空间仅当你的代码中有循环压栈的时候。
大部分 query 函数接受任何位于可用栈空间中的值为索引。这样的索引被称为可接受索引。更正式的讲,我们可以定义一个可接受的索引如下
(index < 0 && abs(index) <= top) || (index > 0 && index <= top + stackspace)
注意 0 不是一个可接受的索引。
-------------------
5.3 栈的操纵
-------------------
API 为基本的栈的操纵提供了下列函数
void lua_settop (lua_State *L, int index);
void lua_pushvalue (lua_State *L, int index);
void lua_remove (lua_State *L, int index);
void lua_insert (lua_State *L, int index);
lua_settop 接受任何可接受的索引,或者 0,并且设置栈顶为该索引。如果新的栈顶比老的大,那么新的元素被填充为 nil。如果索引为 0,那么移除所有栈的元素。一个有用 API 中的宏定义是
#define lua_pop(L,n) lua_settop(L, -(n)-1)
它把栈顶的 n 个元素出栈。
lua_pushvalue 压栈给定索引处的元素的拷贝。lua_remove 移除给定位置的元素,下移所有的该位置以上的元素去填充空白。lua_insert 把栈顶的元素移动到给定的位置,并把给定位置之上的位置上移以开辟空间。这些函数只接受有效的索引。作为一个例子,如果栈开始时是 10 20 30 40 50 (从栈底到栈顶),那么
lua_pushvalue(L, 3) --> 10 20 30 40 50 30
lua_pushvalue(L, -1) --> 10 20 30 40 50 30 30
lua_remove(L, -3) --> 10 20 30 40 30 30
lua_remove(L, 6) --> 10 20 30 40 30
lua_insert(L, 1) --> 30 10 20 30 40
lua_insert(L, -1) --> 30 10 20 30 40 (no effect)
lua_settop(L, -3) --> 30 10 20
lua_settop(L, 6) --> 30 10 20 nil nil nil
-------------------
5.4 查询栈
-------------------
为了检查一个栈元素的类型,可以使用下面的函数:
int lua_type (lua_State *L, int index);
int lua_tag (lua_State *L, int index);
int lua_isnil (lua_State *L, int index);
int lua_isnumber (lua_State *L, int index);
int lua_isstring (lua_State *L, int index);
int lua_istable (lua_State *L, int index);
int lua_isfunction (lua_State *L, int index);
int lua_iscfunction (lua_State *L, int index);
int lua_isuserdata (lua_State *L, int index);
这些函数可以使用任何可接受的索引调用。
lua_type 根据给定对象的类型返回下面的常量之一:LUA_TNIL, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA。如果索引是无效的(即,如果栈的位置是空),lua_type 返回 LUA_TNONE。这个常量可以转换成字符串用
const char *lua_typename (lua_State *L, int t);
这里 t 是一个 lua_type 返回的类型。lua_typename 返回的字符串是:"nil", "number", "string", "table", "function", "userdata", 和 "no value"。
lua_tag 返回值的标签,或者 LUA_NOTAG 当索引无效时。
函数 lua_is* 返回 1 如果对象的类型的给定的类型兼容的话,否则 0 。对于无效的索引它们总是返回 0 。lua_isnumber 接受数值和数值的字符串, lua_isstring 接受字符串和数字(参见 4.2 节),lua_isfunction 接受 Lua 函数和 C 函数。为了区分 Lua 函数和 C 函数,你应该使用 lua_iscfunction。为了区分数值和数值的字符串,你可以使用 lua_type。
API 也有函数用来比较栈上的两个值:
int lua_equal (lua_State *L, int index1, int index2);
int lua_lessthan (lua_State *L, int index1, int index2);
这些函数和它们在 Lua 中相对应的函数是等价的。特别地,lua_lessthan 等价于 lt_event 描述位于 4.8 节。两个函数都返回 0 如果任意一个索引是无效的话。
为把栈上的一个值转换为一个指定的 C 类型,你可以使用下面的转换函数:
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_CFunction lua_tocfunction (lua_State *L, int index);
void *lua_touserdata (lua_State *L, int index);
这些函数可以用任何可接受的索引调用。当用一个无效的索引调用时,它们的行为像是给定的值有一个错误的类型一样。
lua_tonumber 把给定索引处的值转换为一个浮点数字。这个值必须是一个数值或者是可以转换为数值的字符串(参见 4.2 节);否则,返回 0。
lua_tostring 把一个 Lua 值转换为一个字符串(const char*).这个值必须是一个字符串或者一个数值;否则,函数返回 NULL。这个函数返回一个指向 Lua 环境中的字符串的指针。这些字符串总是有一个 0 ('\0')在最后一个字符的后面(像 C 中那样),但是可能包含其它的 0 在它内部。如果你不知道一个字符串是否包含 0,你可以使用 lua_strlen 去取得它的实际长度。因为 Lua 有垃圾回收,没有保证由 lua_tostring 返回的指针是有效的在各自的值在从栈上移除之后。
lua_tocfunction 把栈上的值转换为 C 函数。这个值必须是一个 C 函数;否则,lua_tocfunction 返回 NULL。类型 lua_CFunction 在 5.13 节解释。
lua_touserdata 把一个值转换为 void*。这个值的必须有类型 userdata;否则,返回 NULL。
-------------------
5.5 压栈
-------------------
API 有下面的函数用来把 C 的值压栈:
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t len);
void lua_pushstring (lua_State *L, const char *s);
void lua_pushusertag (lua_State *L, void *u, int tag);
void lua_pushnil (lua_State *L);
void lua_pushcfunction (lua_State *L, lua_CFunction f);
函数接受一个 C 值,把它转换为相应的 Lua 值,并且把结果压栈。特别地,lua_pushlstring 和 lua_pushstring 生成一个给定字符串的内部拷贝。lua_pushstring 只能用于压栈合适的 C 字符串(即,字符串由 0 结尾且内容没有 0 );否则你应该使用更通用的 lua_pushlstring,它接受一个明确指定的尺寸。
-------------------
5.6 垃圾回收
-------------------
Lua 用两个数值来控制它的垃圾回收。一个数值计算 Lua 使用的多少字节的动态内存,另一个是一个临界值。(这个 Lua 保持的内部的字节计数器并不完全准确;他只是一个下限,通常有 10% 的是正确的值[这半句不知道如何译])当字节值达到临界值,Lua 执行一次垃圾回收,它回收所有的无用的对象的内存(即,Lua 中访问不到的对象)。字节计数器被校正,并且临界值被重置为字节计数器的 2 倍。
你可以使用这两个值的当前值通过使用下面的函数:
int lua_getgccount (lua_State *L);
int lua_getgcthreshold (lua_State *L);
都分别返回它们的值,单元为千字节。你可以改变临界值用
void lua_setgcthreshold (lua_State *L, int newthreshold);
再次的,给定 newthreshold 值的单元为千字节。当你调用这个函数时,Lua 设置新的临界值并再次检查它和字节计数器。如果临界值小于字节计数器,Lua 将立刻执行垃圾回收;垃圾回收之后,一个新的临界值会被设置根据前面的规则。
如果你想改变垃圾回收的这种可适应行为,你可以用 nil 使用垃圾回收的标签方法并设置你自己的临界值(标签方法会在 Lua 重置临界值之后调用)。
-------------------
5.7 用户自定义数据和标签
-------------------
因为 userdata 是对象,函数 lua_pushusertag 可能新建一个 userdata。如果 Lua 有一个给定值(void*)和标签的 userdata,那么 userdata 被压栈。否则,一个新的 userdata 被创建,有给定的值和标签。如果这个函数通过标签 LUA_ANYTAG 来调用,那么 Lua 将试图去寻找任何有给定值的 userdata,不管它的标签。如果没有 userdata 有那个值,那么一个新的被创建,标签为 0 。
userdata 可以有不同的标签,它的语义只有宿主程序知道。标签由下面的函数创建:
int lua_newtag (lua_State *L);
函数 lua_settag 改变栈顶对象的标签(并不弹出它):
void lua_settag (lua_State *L, int tag);
对象必须是一个 userdata 或者是一个表;给定的标签必须是由 lua_newtag 创建的值。
(未完待续)