lua确实容易上手,不容易精通,这本书看了3遍,走算有些收获,做些笔记以便日后再看
书中代码也都敲了一遍,放在了https://github.com/buck84/PrgInLua2
而且要想学好,必须在实践中学习,光看书很难真正了解
人生两大悲哀:结婚以后不再恋爱,毕业以后不再学习
lua特点:可扩展、简单、高效、平台无关
lua使用者:嵌入其它应用、独立使用、混合c使用
Chunks:Lua执行的每一块语句,比如一个文件或者交互模式下的每一行都是一个Chunk
介绍了交互模式使用方式
lua -i -l
dofile("lib1.lua")
介绍了lua保留字
单行注释 --
多行注释 --[[ ]] --[==[ ]==]
环境变量LUA_INIT为加载默认lua文件
8种类型nil、boolean、number、string、userdata、function、thread、table
false和nil为假,0为真
string8位字节,string不能修改,可以创建新的,单引号双引号都可以,[[...]]也可以,还可以包含多行、嵌套。
Userdata主要保存C数据。
Threads用来协同操作
C中^为以后,lua中为幂
.. 两个点连接运算符
构造表 {},表第一个元素index=1
数值for for var=init,end,step do end,step可省略
泛型for for i,v in ipairs(a) do print(v) end
break、return只能在block的结尾一句,如果非要在中间用,可以do return end
函数有一种情况可以没有()调用:参数只有一个并且是字符串或表构造 f{x=10} <--> f({x=10})
unpack这个特殊函数接受一个数组作为输入参数,返回数组的所有元素。
可变参数类似c,用...,参数放在表arg中,arg.n表示参数个数
_为哑元,可以忽略一些值的情况用
函数参数很多的时候可以用一个表作为参数
排序table:table.sort(tablename, function(a,b) return a.x>b.x end)
闭包:函数里面的函数,可以用来形成一个安全环境
local function 为局部函数,之所以有这种用法,是因为函数是第一类值,local function与它访问的unlocal-variable其实是处于同一层级
6.1
闭合函数就是一个函数放在另一个函数内部,可以实现很多有意思的用途,比如这一节最后提到的用法:
将一个全局系统函数保存在一个局部变量中,并重新写这个全局函数,给全局函数新的逻辑(通常是加一些限制或打印访问log)
6.2
非全局的函数非常类似类及其成员函数,递归函数定义的问题:在3种函数定义中:
local func = function() {}
local func; func = function() {}
local function func() {}
第一种因为定义的时候func还没有定义,所以递归定义会有问题,而第3种跟第2种完全一样
6.3
尾调用正确使用可以减少堆栈
但是会给调试lua代码带来一些麻烦,因为堆栈显示不是真实发生过的调用堆栈,当然lua程序员一般也不调试
7.1
迭代器并没有迭代,完成迭代功能的是for语句,应该叫生成器(generator)
这一节的2个例子说明的都是很直观的用法,每次执行表达式就是调用一个函数
迭代函数难写易用
7.2
这一节说明了lua的泛型for机制:
for var_1, ..., var_n in explist do block end 类似于:
do
local _f, _s, _var = explist
while true do
local var_1, ..., var_n = _f(_s, _var)
_var = var_1
if _var == nil then break end
block
end
end
以上就是lua的泛型for 机制
7.3
这一节说明了lua中最常用的ipairs和pairs,就是无状态迭代器
常用方for i, v in ipairs(t) do body end 这种方式使用ipairs,ipairs的实现方式如下:
function iter (a, i)
i = i+1
local v = a[i]
if v then
return i, v
end
end
function ipairs (a)
return iter, a, 0
end
pairs则返回next, t, nil
这种迭代器只保存一个恒定状态(通常是待遍历数据)和一个控制变量
7.4
有状态的迭代器是指在恒定状态中保存更多的内容
7.5
真正的迭代器将处理函数作为参数传入迭代函数中,而不是返回值等待处理。
这一章内容看书不好理解,可能实际用一下才更清楚,而且lua命令行使用很少,主要是与c结合使用
loadfile、loadstring编译文件和代码段,但是还没有定义
loadstring不关心词法范围、不会抛出错误信息而是返回错误码和错误信息(两个返回值)
dofile会抛出错误,每次都要编译,loadfile编译一次多次运行
8.1
loadfile: 从文件加载lua代码块,但是不运行,将便以结果作为一个函数返回
dofile: 相当于调用loadfile以后,调用loadfile返回的函数
require:与dofile比,require会搜索目录加载文件、会判断是否已经加载避免重复加载
8.2
lua代码是否可以直接使用动态库,等26章再研究吧
8.4 异常和错误处理使用pcall
8.5 error debug.debug debug.traceback
9.1
resume:挂起自身,目标执行,等目标执行完或碰到yield以后返回
yield:挂起自身,resume返回
9.2
看filter的例子感觉很复杂,看了很长时间才搞明白
主要是因为对这种用法不是很熟悉,其实很简单,关键是理解receive函数的作用:从prod那获取一个值
首先是4个函数定义:
receive
传入一个thread,将这个thread唤醒并返回其返回的value
producer
返回一个thread,这个thread可以在需要时唤醒并返回一个值
filter
返回一个thread,这个thread保持一个计数line,循环调用receive,receive的参数是filter的参数,receive返回后挂起并返回receive的结果
consumer
一个循环,不停调用receive,从prod返回一个值
接下来就是代码执行逻辑:
p
生产者线程,可以通过resume唤醒并返回一个值
f
返回以p为参数调用filter的线程
最后调用consumer(f)开始运行,不停调用receive,从f返回值,f也调用receive,从p返回值
9.3
首先要理解排列组合函数的原理,就是一种递归,对于长度为n的数组,将每一个都放到最后,对前面的n-1长度数组再做同样的处理
9.4
select版本加了变量connections,使用过后在某次receive的时候就会报内存错误,不知道为啥
两个很有趣也很有用的例子
lua中主要使用table,其它数据结构用的不多
1
Metatables用来定义table的一些行为,比如算术运算和关系运算
getmetatable(t) setmetatable(t, t1)
13.1 +(__add) *(__mul)-(__sub)/(__div)-(__unm)^(__pow)..(__concat)
选择Metamethods原则:如果第一个有则用第一个的Metatable,否则用第二个的,否则报错
13.2 =(__eq) <(__lt)<=(__le)
Methodmethod选择不支持混合运算,否则抛出错误
13.3 希望print能直接打印table,则元表中__tostring定义。如果自己的库不希望别人修改metatable,则可以设置__metatable。
13.4 table元方法
__index,如果table中没有待查询的字段,则查询__index,通常用来提供默认值,可以是table或函数(参数为table,key)。如果不希望查询__index,则用rawgeti(t,i)访问
__newindex与__index类似,只是用来更新,__index是用来查询,rawseti(t, i)不修改__newindex
13.4.3说了很多种具有默认值的table
第一种方法setDefault(t, d)很简单就是直接返回一个值
第二种方法把metatable放在了函数外面,这样很多table就可以设置同一个metatable了
第三种方法为table设置一个key作为默认值,并给metatable使用
跟踪table访问就是将待访问的table作为一个空table的metadata
1
1
1
1
19.2中排序函数名很简单,后面的pairsByKeys函数则hen奇妙,是closure的典型用法,如果没有经验很难想到可以这样实现这个功能
1
1
time类似mktime
date类似gmtime
1
24.1的例子直接下载lua代码建立一个工程生成静态库或动态库,然后新建测试项目即可,如果新建的是c++项目,由于lua库是作为c代码编译的,所以链接过程会提示unresolved external symbol "void __cdecl lua_close(struct lua_State *),把lua头文件放在extern "C"{}里面即可。
lua.h为lua提供的基础函数
lauxlib.h为利用lua.h中基础函数实现的更高层次的抽象
lualib.h定义了打开各种库的抽象,需要哪个库就打开哪个而不是全打开
24.2lua把字符串压栈以后会复制一份,而不是用指针。
栈的大小:LUA_MINSTACK
各种堆栈操作24.2.3最后的例子试一下就ok:
lua_gettop 堆栈元素个数
lua_settop 设置指定位置为栈顶,不足补nil
lua_pushvalue 压入指定索引一个拷贝
lua_remove 移除指定索引位置元素
lua_insert 移动栈顶元素到指定索引位置
lua_replace 栈顶弹出元素设置到指定位置
24.3介绍了写lua库函数:被lua调用的c函数注意问题:发现错误调用lua_error
lua作为普通配置文件很简单,只需要lua_getglobal, lua_tonumber即可
25.1如果配置稍微复杂点,比如颜色有3个分量,需要表操作,表操作主要是:
getxxx
lua_getglobal(L, name),获取全局变量name的值并压栈
lua_pushstring(L, key)
lua_gettable(L, -2),pop key, push name[key].
setxxx
lua_newtable(L) // create new table, push this table
lua_pushstrng(L, index);
lua_pushnumber(L, value);
lua_settable(L, -3); // table.index = value
lua_setglobal(globalname); // pop table, 将该table赋给全局变量名globalname
25.1最后说的两种方法:
变量表示颜色名:
字符串表示颜色名:错误提示,节省不常用的大量颜色的开销。缺点是获取颜色的时候需要很多处理(文中这样说,其实还好了)。
25.2 调用lua函数步骤
// 如果需要错误处理函数先入栈
lua_getglobal(L, "f");
lua_pushnumber(L, x);
lua_pushnumber(L, y);
lua_pcall(L, 2, 1, 0)
z = lua_tonumber(L, -1);
lua_pop(L, 1);
// 如果有错误处理函数则出栈
25.3如果使用这一章的方法,最后的3点注意要看
26.1 被lua调用的函数格式typedef int (*lua_CFunction)(lua_State *L)
注册方法:
lua_pushcfunction(l, func);
lua_setglobal(l, cfunc);
26.2
这一节说明了怎么在lua中调用c模块(dll,不是c函数)
lua中调用require "mylib"的时候,如果有mylib.dll,会调用其中的luaopen_mylib
所以在dll中需要导出这个函数
extern "C" __declspec(dllexport) int luaopen_mylib(lua_State *L) { ... }
在这个函数中注册lua中使用的函数即可
如果不支持动态库,用静态库写的代码,需要手动调用这个函数,或者照书中这一节最后说的方法,加到linit.c中重新编译lua库
27.1 如果index为负索引,则
lua_rawgeti(L, index, k) 类似: lua_pushnumber(L, key); lua_rawget(L, index)
lua_rawseti(L, index, k) 类似: lua_pushnumber(L, key); lua_insert(L, -2); lua_rawset(L, index);,中间的lua_insert(L, -2)是因为如果用lua_rawset是先push key再push value,而lua_rawseti相当于先push value,再push key。
27.2 c函数使用lua的字符串的话,如果字符串正在被访问不要将其出栈,永远不要修改字符串。
字符串处理函数有lua_pushlstring(L, start, len):push子串,lua_concat连接串并push,lua_pushfstring(L, fmt, ...)
27.3 在lua中全局保存c数据的方法是使用registry系统:lua_pushstring(L, "key"); lua_gettable(L, LUA_REGISTRYINDEX)
References使用方法如下,具体什么地方用呢?
int r = luaL_ref(L, LUA_REGISTRYINDEX); // 从栈中弹出reference
lua_rawgeti(L, LUA_REGISTRYINDEX, r); // 将reference对应的值入栈
luaL_unref(L, LUA_REGISTRYINDEX, r); //释放r
Upvalues实现了类似static变量的东西,这种变量只在特定函数内可见。其原理是闭包相关,c函数返回一个函数。
1