现在的位置: 首页 > 综合 > 正文

Lua程序设计笔记

2018年05月03日 ⁄ 综合 ⁄ 共 6272字 ⁄ 字号 评论关闭

学习历程

lua确实容易上手,不容易精通,这本书看了3遍,走算有些收获,做些笔记以便日后再看

书中代码也都敲了一遍,放在了https://github.com/buck84/PrgInLua2

而且要想学好,必须在实践中学习,光看书很难真正了解

人生两大悲哀:结婚以后不再恋爱,毕业以后不再学习

译序

lua以简单优雅为本,作为c/c++的扩展使用,处理c不擅长的任务:高级语言、动态结构、简洁、易于测试和调试、自动内存管理、简便的字符串处理功能。

第一篇 语言

0 序言

lua特点:可扩展、简单、高效、平台无关

lua使用者:嵌入其它应用、独立使用、混合c使用

1 起点

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

介绍了交互模式使用方式

lua -i -l

dofile("lib1.lua")

介绍了lua保留字

单行注释 --

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

环境变量LUA_INIT为加载默认lua文件

2 类型和值

8种类型nil、boolean、number、string、userdata、function、thread、table

false和nil为假,0为真

string8位字节,string不能修改,可以创建新的,单引号双引号都可以,[[...]]也可以,还可以包含多行、嵌套。

Userdata主要保存C数据。

Threads用来协同操作

3 表达式

C中^为以后,lua中为幂

.. 两个点连接运算符

构造表 {},表第一个元素index=1

4 基本语法

数值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

5 函数

函数有一种情况可以没有()调用:参数只有一个并且是字符串或表构造   f{x=10}  <--> f({x=10})

unpack这个特殊函数接受一个数组作为输入参数,返回数组的所有元素。

可变参数类似c,用...,参数放在表arg中,arg.n表示参数个数

_为哑元,可以忽略一些值的情况用

函数参数很多的时候可以用一个表作为参数

6 再论函数

排序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 迭代器和泛型

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

真正的迭代器将处理函数作为参数传入迭代函数中,而不是返回值等待处理。

8 编译*运行*错误信息

这一章内容看书不好理解,可能实际用一下才更清楚,而且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 协同程序

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的时候就会报内存错误,不知道为啥

10 完整示例

两个很有趣也很有用的例子

第二篇 tables与objects

11 数据结构

lua中主要使用table,其它数据结构用的不多

12 数据文件与持久化

1

13 Metatables 和 Metamethods

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

14 环境

1

15 Packages

1

16 面向对象程序设计

":"用来隐式实现self。

17 Weak表

1

第三篇 标准库

18 数学库

1

19 Table库

19.2中排序函数名很简单,后面的pairsByKeys函数则hen奇妙,是closure的典型用法,如果没有经验很难想到可以这样实现这个功能

20 String库

1

21 IO库

1

22 操作系统库

time类似mktime

date类似gmtime

23 Debug库

1

第四篇 C API

24 C API纵览

文中说的不到400行的lua解释器就是lua.c,luac.c则可以将lua文件进行预编译

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

25 扩展你的程序

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 调用C函数

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 撰写C函数的技巧

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函数返回一个函数。

28 User-Defined Types in C

1

29 资源管理

迭代函数难写易用

抱歉!评论已关闭.