├── .DS_Store
├── .gitignore
├── 00.md
├── 01.md
├── 02.md
├── 03.md
├── 04.md
├── 05.md
├── 06.md
├── 07.md
├── 08.md
├── 09.md
├── 10.md
├── 11.md
├── 12.md
├── LICENSE
├── README.md
└── img
├── img0001.jpg
└── img0002.jpg
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andycai/luaprimer/138cb58366880051e414bb71346050ba845bc1b0/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Object files
2 | *.o
3 |
4 | # Libraries
5 | *.lib
6 | *.a
7 |
8 | # Shared objects (inc. Windows DLLs)
9 | *.dll
10 | *.so
11 | *.so.*
12 | *.dylib
13 |
14 | # Executables
15 | *.exe
16 | *.out
17 | *.app
18 |
--------------------------------------------------------------------------------
/00.md:
--------------------------------------------------------------------------------
1 | # 目录 Table Of Contents
2 |
3 | 1. [Lua 基础知识](01.md)
4 | 2. [环境与模块](02.md)
5 | 3. [函数与面向对象](03.md)
6 | 4. [标准库](04.md)
7 | 5. [协程 Coroutine](05.md)
8 | 6. [Table 数据结构](06.md)
9 | 7. [常用的 C API](07.md)
10 | 8. [Lua 与 C/C++ 交互](08.md)
11 | 9. [编译 Lua 字节码](09.md)
12 | 10. [LuaJIT 介绍](10.md)
13 | 11. [附录一 Lua 5.1 程序接口](11.md)
14 | 12. [附录二 Lua 5.2 程序接口](12.md)
--------------------------------------------------------------------------------
/01.md:
--------------------------------------------------------------------------------
1 | # Lua - 基础知识
2 |
3 | ## (1) 变量
4 |
5 | ### 赋值
6 |
7 | 赋值是改变一个变量的值和改变表域的最基本的方法。Lua 中的变量没有类型,只管赋值即可。比如在 Lua 命令行下输入:
8 |
9 | end_of_world = "death"
10 | print(end_of_world)
11 | end_of_world = 2012
12 | print(end_of_world)
13 |
14 | 上面这四行代码 Lua 不会报错,而会输出:
15 |
16 | death
17 | 2012
18 |
19 | ### 局部变量
20 |
21 | 使用 local 创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效
22 |
23 | x = 10
24 | local i = 1 -- 局部变量
25 |
26 | while i<=x do
27 | local x = i*2 -- while 中的局部变量
28 | print(x) --> 2, 4, 6, 8, ...
29 | i = i + 1
30 | end
31 |
32 | 应该尽可能的使用局部变量,有两个好处:
33 |
34 | 1. 避免命名冲突
35 | 2. 访问局部变量的速度比全局变量更快
36 |
37 |
38 |
39 | ### 代码块(block)
40 |
41 | 代码块指一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串)。
42 |
43 | 我们给block划定一个明确的界限:do..end内的部分。当你想更好的控制局部变量的作用范围的时候这是很有用的。
44 |
45 | do
46 | local a2 = 2*a
47 | local d = sqrt(b^2 - 4*a*c)
48 | x1 = (-b + d)/a2
49 | x2 = (-b - d)/a2
50 | end -- scope of 'a2' and 'd' ends here
51 | print(x1, x2)
52 |
53 | ## (2) 类型
54 |
55 | 虽说变量没有类型,但并不是说数据不分类型。Lua 基本数据类型共有八个:nil、boolean、number、string、function、userdata、thread、table。
56 |
57 | - Nil Lua中特殊的类型,他只有一个值:nil;一个全局变量没有被赋值以前默认值为nil;给全局变量赋nil可以删除该变量。
58 | - Booleans 两个取值false和true。但要注意Lua中所有的值都可以作为条件。在控制结构的条件中除了false和nil为假,其他值都为真。所以Lua认为0和空串都是真。
59 | - Numbers 即实数,Lua 中的所有数都用双精度浮点数表示。
60 | - Strings 字符串类型,指字符的序列,Lua中字符串是不可以修改的,你可以创建一个新的变量存放你要的字符串。
61 | - Table 是很强大的数据结构,也是 Lua 中唯一的数据结构。可以看作是数组或者字典。
62 | - Function 函数是第一类值(和其他变量相同),意味着函数可以存储在变量中,可以作为函数的参数,也可以作为函数的返回值。
63 | - Userdata userdata可以将C数据存放在Lua变量中,userdata在Lua中除了赋值和相等比较外没有预定义的操作。userdata用来描述应用程序或者使用C实现的库创建的新类型。例如:用标准I/O库来描述文件。
64 | - Thread 线程会在其它章节来介绍。
65 |
66 | 可以用 **type 函数**取得表达式的数据类型:
67 |
68 | print(type(undefined_var))
69 | print(type(true))
70 | print(type(3.14))
71 | print(type('Hello World'))
72 | print(type(type))
73 | print(type({}))
74 |
75 | ## (3) 表达式
76 |
77 | ### 操作符
78 |
79 | 1. 算术运算符:+ - * / ^ (加减乘除幂)
80 | 2. 关系运算符:< > <= >= == ~=
81 | 3. 逻辑运算符:and or not
82 | 4. 连接运算符:..
83 |
84 | 有几个操作符跟C语言不一样的:
85 |
86 | - a ~= b 即 a 不等于 b
87 | - a ^ b 即 a 的 b 次方
88 | - a .. b 将 a 和 b 作为字符串连接
89 |
90 | ### 优先级:
91 |
92 | 1. ^
93 | 2. not -(负号)
94 | 3. \* /
95 | 4. \+ -
96 | 5. ..
97 | 6. < > <= >= ~= ==
98 | 7. and
99 | 8. or
100 |
101 | ### 表的构造:
102 |
103 | 最简单的构造函数是{},用来创建一个空表。可以直接初始化数组:
104 |
105 | days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
106 |
107 | Lua将"Sunday"初始化days[1](第一个元素索引为1),不推荐数组下标以0开始,否则很多标准库不能使用。
108 |
109 | 在同一个构造函数中可以数组风格和字典风格进行初始化:
110 |
111 | polyline = {color="blue", thickness=2, npoints=4,
112 | {x=0, y=0},
113 | {x=-10, y=0},
114 | {x=-10, y=1},
115 | {x=0, y=1}
116 | }
117 |
118 | ### 多重赋值和多返回值
119 |
120 | 另外 Lua 还支持多重赋值(还支持函数返回多个值)。也就是说:等号右边的值依次赋值给等号左边的变量。比如:
121 |
122 | year, month, day = 2011, 3, 12
123 | print(year, month, day)
124 | return year, month, day -- 多返回值
125 | a, b = f()
126 |
127 | 于是,交换两个变量值的操作也变得非常简单:
128 |
129 | a, b = b, a
130 |
131 | ## (4) 控制流
132 |
133 | ### if
134 |
135 | name = "peach"
136 | if name == "apple" then
137 | -- body
138 | elseif name == "banana" then
139 | -- body
140 | else
141 | -- body
142 | end
143 |
144 | ### for
145 |
146 | -- 初始值, 终止值, 步长
147 | for i=1, 10, 2 do
148 | print(i)
149 | end
150 |
151 | -- 数组
152 | for k, v in ipairs(table) do
153 | print(k, v)
154 | end
155 |
156 | -- 字典
157 | for k, v in pairs(table) do
158 | print(k, v)
159 | end
160 |
161 | 反向表构造实例:
162 |
163 | revDays = {}
164 | for i,v in ipairs(days) do
165 | revDays[v] = i
166 | end
167 |
168 | ### while
169 |
170 | while i<10 do
171 | print(i)
172 | i = i + 1
173 | end
174 |
175 | ### repeat-until
176 |
177 | repeat
178 | print(i)
179 | i = i + 1
180 | until i < 10
181 |
182 | ### break 和 return
183 |
184 | break 语句可用来退出当前循环(for, repeat, while),循环外部不可以使用。
185 |
186 | return 用来从函数返回结果,当一个函数自然结束,结尾会有一个默认的return。
187 |
188 | Lua语法要求break和return只能出现在block的结尾一句(也就是说:作为chunk的最后一句,或者在end之前,或者else前,或者until前):
189 |
190 | local i = 1
191 | while a[i] do
192 | if a[i] == v then break end
193 | i = i + 1
194 | end
195 |
196 | ## (5) C/C++ 中的 Lua
197 |
198 | 首先是最简单的 Lua 为 C/C++ 程序变量赋值,类似史前的 INI 配置文件。
199 |
200 | width = 640
201 | height = 480
202 |
203 | 这样的赋值即设置全局变量,本质上就是在全局表中添加字段。
204 |
205 | 在 C/C++ 中,Lua 其实并不是直接去改变变量的值,而是宿主程序通过「读取脚本中设置的全局变量到栈、类型检查、从栈上取值」几步去主动查询。
206 |
207 | int w, h;
208 | if (luaL_loadfile(L, fname) || // 读取文件,将内容作为一个函数压栈
209 | lua_pcall(L, 0, 0, 0)) // 执行栈顶函数,0个参数、0个返回值、无出错处理函数(出错时直接把错误信息压栈)
210 | error();
211 |
212 | lua_getglobal(L, "width"); // 将全局变量 width 压栈
213 | lua_getglobal(L, "height"); // 将全局变量 height 压栈
214 | if (!lua_isnumber(L, -2)) // 自顶向下第二个元素是否为数字
215 | error();
216 | if (!lua_isnumber(L, -1)) // 自顶向下第一个元素是否为数字
217 | error();
218 | w = lua_tointeger(L, -2); // 自顶向下第二个元素转为整型返回
219 | h = lua_tointeger(L, -1); // 自顶向下第一个元素转为整型返回
220 |
221 | 读取表的字段的操作也是类似,只不过细节上比较麻烦,有点让我想起在汇编里调戏各种寄存器:
222 |
223 | score = { chinese=80, english=85 }
224 |
225 | int chinese, english;
226 | if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))
227 | error();
228 |
229 | lua_getglobal(L, "score"); // 全局变量 score 压栈
230 |
231 | lua_pushstring(L, "chinese"); // 字符串 math 压栈
232 | lua_gettable(L, -2); // 以自顶向下第二个元素为表、第一个元素为索引取值,弹栈,将该值压栈
233 | if (!lua_isnumber(L, -1)) // 栈顶元素是否为数字
234 | error();
235 | chinese = lua_tointeger(L, -2);
236 | lua_pop(L, 1); // 弹出一个元素 (此时栈顶为 score 变量)
237 |
238 | lua_getfield(L, -1, "english"); // Lua5.1开始提供该函数简化七八两行
239 | if (!lua_isnumber(L, -1))
240 | error();
241 | english = lua_tointeger(L, -2);
242 | lua_pop(L, 1); // 如果就此结束,这一行弹不弹都无所谓了
243 |
244 | 前面说过,设置全局变量本质就是在全局表中添加字段,所以 lua_getglobal 函数本质是从全局表中读取字段。没错,lua_getglobal 本身就是一个宏:
245 |
246 | #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, s)
247 |
248 | 宏 LUA_GLOBALSINDEX 指明的就是全局表的索引。
249 |
250 | ## 导航
251 | * [目录](00.md)
252 | * 下一章:[环境与模块](02.md)
253 |
--------------------------------------------------------------------------------
/02.md:
--------------------------------------------------------------------------------
1 | # Lua - 环境与模块
2 |
3 | ## (1) 全局变量与环境
4 |
5 | lua 中真正存储全局变量的地方不是在 \_G 里面,而是在setfenv(i,table)的table中,所有当前的全局变量都在这里面找,只不过在程序开始时lua会默认先设置一个变量 \_G= 这个里面的table而已。所以在新设置环境后,如果还想找到之前的全局变量,通常需要附加上为新的table设置元表{\_index=\_G}
6 |
7 | 下面的几个例子:
8 |
9 | a=1
10 | print(a)
11 | print(_G.a)
12 | --正常情况,输出1,1
13 |
14 | a=1
15 | setfenv(1,{})
16 | print(a)
17 | print(_G.a)
18 | --这时会出错说找不到print,因为当前的全局变量表示空的,啥也找不到的
19 |
20 | a=1
21 | setfenv(1,{_G=_G})
22 | _G.print(_G.a)
23 |
24 | print(a)
25 | --这时_G.print(_G.a)可以正常吗,因为可以在新的table中找到一个叫_G的表,这个_G有之前的奈尔全局变量,但是下面的print(a)则找不到print,因为当前的table{_G=_G}没有一个叫print的东西
26 |
27 | local mt={__index=_G}
28 | local t={}
29 | setmetatable(t,mt)
30 | setfenv(1,t)
31 | print(a)
32 | print(_G.a)
33 | --这是正确输出,因为新的全局表采用之前的表做找不到时的索引,原先的表里面存在print 、_G、 a这些东西
34 |
35 | **setfenv的第一个参数可以是当前的堆栈层次,如1代表当前代码块,2表调用当前的上一层,也可以是具体的那个函数名,表示在那个函数里。**
36 |
37 | 每个新创建的函数都将继承创建它的那个函数的全局环境。
38 |
39 | 从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
40 |
41 | ### (3) 创建模块
42 |
43 | 其实 Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。格式如下:
44 |
45 | -- 定义一个名为 module 的模块
46 | module = {}
47 |
48 | -- 定义一个常量
49 | module.constant = "this is a constant"
50 |
51 | -- 定义一个函数
52 | function module.func1()
53 | io.write("this is a public function!\n")
54 | end
55 |
56 | local function func2()
57 | print("this is a private function!")
58 | end
59 |
60 | function module.func3()
61 | func2()
62 | end
63 |
64 | return module
65 |
66 | 由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。不过上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的共有函数来调用。
67 |
68 | 最后,把上面的模块代码保存为跟模块名一样的 lua 文件里(例如上面是 module.lua),那么一个自定义的模块就创建成功。
69 |
70 | ### (4) 加载模块
71 |
72 | Lua 提供一个名为 require 的函数来加载模块,使用也很简单,它只有一个参数,这个参数就是要指定加载的模块名,例如:
73 |
74 | require("<模块名>")
75 | -- 或者是
76 | -- require "<模块名>"
77 |
78 | 然后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
79 |
80 | 或者给加载的模块定义一个别名变量,方便调用:
81 |
82 | local m = require("module")
83 |
84 | print(m.constant)
85 |
86 | m.func3()
87 |
88 | require 的意义就是导入一堆可用的名称,这些名称(非local的)都包含在一个table中,这个table再被包含在当前的全局表(“通常的那个_G”)中,这样访问一个模块中的变量就可以使用_G.table.**了,初学者可能会认为模块里的名称在导入后直接就是在 _G 中了。
89 |
90 | m=require module
91 |
92 | 的 m 取决于这个导入的文件的返回值,没有返回值时true,所以在标准的情况下模块的结尾应该 return 这个模块的名字,这样 m 就是这个模块的table了。
93 |
94 | ### (5) 加载机制
95 |
96 | 对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
97 |
98 | require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
99 |
100 | 当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:
101 |
102 | #LUA_PATH
103 | export LUA_PATH="~/lua/?.lua;;"
104 |
105 | 文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。
106 |
107 | 接着,更新环境变量参数,使之立即生效:
108 |
109 | source ~/.profile
110 |
111 | 这时假设 package.path 的值是:
112 |
113 | /Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua
114 |
115 | 那么调用 require("module") 时就会尝试打开以下文件目录去搜索目标
116 |
117 | /Users/dengjoe/lua/module.lua;
118 | ./module.lua
119 | /usr/local/share/lua/5.1/module.lua
120 | /usr/local/share/lua/5.1/module/init.lua
121 | /usr/local/lib/lua/5.1/module.lua
122 | /usr/local/lib/lua/5.1/module/init.lua
123 |
124 | 如果找过目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。搜索的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。搜索的策略跟上面的一样,只不过现在换成搜索的是 so 或 dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。
125 |
126 | ## 导航
127 | * [目录](00.md)
128 | * 上一章:[Lua 基础知识](01.md)
129 | * 下一章:[函数与面向对象](03.md)
--------------------------------------------------------------------------------
/03.md:
--------------------------------------------------------------------------------
1 | # Lua - 函数与面向对象
2 |
3 | ### 变量声明与 C 语言的不同
4 |
5 | Lua 中有一个常见的用法,不论变量、函数都可以用下面这种方法保存到局部变量中(同时加快访问速度):
6 |
7 | local foo = foo
8 |
9 | 书里加了个括号来解释这种写法:
10 |
11 | > The local foo becomes visible only after its declaration.
12 |
13 | 这一点需要瞎扯的是 C 语言里相应的东西。
14 |
15 | int foo = 12;
16 | int bar = 6;
17 |
18 | void foobar(void)
19 | {
20 | int foo = foo;
21 | int bar[bar];
22 | }
23 |
24 | 与 Lua 不同,在 C 语言中初始赋值是声明之后的事情。所以这里函数 foobar 中的 foo 会被初始化为自己(而不是全局的 foo,所以值不确定),bar 却被合法地定义为一个含有 6 个元素的数组。
25 |
26 | ### 看似多余的限制
27 |
28 | 另一个有趣的现象是在 4.4 节中说到:
29 |
30 | > For syntactic reasons, a break or return can appear only as the last statement of a block; in other words, as the last statement in your chunk or just before an end, an else, or an until.
31 |
32 | 乍一看觉得加上这个限制真是麻烦,但想想这不正是 break/return 的正确用法么?因为其后的语句都永远不会被执行到,所以如果不是在块的最后写 break/return 是毫无意义的(调试除外)。虽然看上去是挺多余的一段话,但也算是说出了事物的本源。
33 |
34 | ### 函数的本质
35 |
36 | 第六章 More About Functions 中说到我们平时在 Lua 中写的函数声明
37 |
38 | function foo (x) return 2*x end
39 |
40 | 其实是一种语法糖,本质上我们可以把它写成如下代码:
41 |
42 | foo = function (x) return 2*x end
43 |
44 | 于是也就可以说
45 |
46 | - Lua 中的所有函数都是匿名函数,之前所谓「具名函数」只是保存了某个匿名函数的变量罢了。
47 | - Lua 中的函数声明其实只是一个语句而已。
48 |
49 | ### 终于有用的知识
50 |
51 | 在第 47 页看到了一段令人泪流满面的代码和运行结果:
52 |
53 | function derivative (f, delta)
54 | delta = delta or 1e-4
55 | return function (x)
56 | return (f(x + delta) - f(x))/delta
57 | end
58 | end
59 |
60 | c = derivative(math.sin)
61 | print(math.cos(10), c(10))
62 | --> -0.83907152907645 -0.83904432662041
63 |
64 | 最初我并不知道 derivative 是什么意思,但看了示例代码和运行结果,顿时恍然大悟:这货不就是导数吗?
65 |
66 | ## 沙盒
67 |
68 | ### 背景知识
69 |
70 | Lua 给我的感觉是:各种内置函数和标准库的存在感都是比较强的。如果执行这句:
71 |
72 | for name in pairs(_G) do print(name) end
73 |
74 | 就会把各种环境中已存在名称的打印出来:
75 |
76 | - 全局变量:比如字符串 _VERSION。
77 | - 内置函数:比如 print、tonumber、dofile 之类。
78 | - 模块名称:比如 string、io、coroutine 之类。
79 |
80 | 这里的全局变量 _G 就是存放环境的表(于是会有 _G 中存在着 _G._G 的递归)。
81 |
82 | 于是,平时对于全局变量的访问就可以等同于对 _G 表进行索引:
83 |
84 | value = _G[varname] --> value = varname
85 | _G[varname] = value --> varname = value
86 |
87 | ### 改变函数的环境
88 |
89 | 函数的上下文环境可以通过 setfenv(f, table) 函数改变,其中 table 是新的环境表,f 表示需要被改变环境的函数。如果 f 是数字,则将其视为堆栈层级(Stack Level),从而指明函数(1 为当前函数,2 为上一级函数):
90 |
91 | a = 3 -- 全局变量 a
92 | setfenv(1, {}) -- 将当前函数的环境表改为空表
93 | print(a) -- 出错,因为当前环境表中 print 已经不存在了
94 |
95 | 没错,不仅是 a 不存在,连 print 都一块儿不存在了。如果需要引用以前的 print 则需要在新的环境表中放入线索:
96 |
97 | a = 3
98 | setfenv(1, { g = _G })
99 | g.print(a) -- 输出 nil
100 | g.print(g.a) -- 输出 3
101 |
102 | ### 沙盒
103 |
104 | 于是,出于安全或者改变一些内置函数行为的目的,需要在执行 Lua 代码时改变其环境时便可以使用 setfenv 函数。仅将你认为安全的函数或者新的实现加入新环境表中:
105 |
106 | local env = {} -- 沙盒环境表,按需要添入允许的函数
107 |
108 | function run_sandbox(code)
109 | local func, message = loadstring(code)
110 | if not func then return nil, message end -- 传入代码本身错误
111 | setfenv(func, env)
112 | return pcall(func)
113 | end
114 |
115 | ### Lua 5.2 的 _ENV 变量
116 |
117 | Lua 5.2 中所有对全局变量 var 的访问都会在语法上翻译为 _ENV.var。而 _ENV 本身被认为是处于当前块外的一个局部变量。(于是只要你自己定义一个名为 _ENV 的变量,就自动成为了其后代码所处的「环境」(enviroment)。另有一个「全局环境」(global enviroment)的概念,指初始的 _G 表。)
118 |
119 | Lua 的作者之一 Roberto Ierusalimschy 同志在介绍 Lua 5.2 时说:
120 |
121 | > the new scheme, with _ENV, allows the main benefit of setfenv with a little more than syntactic sugar.
122 |
123 | 就我的理解来说,优点就是原先虚无缥缈只能通过 setfenv、getfenv 访问的所谓「环境」终于实体化为一个始终存在的变量 _ENV 了。
124 |
125 | 于是以下两个函数内容大致是一样的:
126 |
127 | -- Lua 5.1
128 | function foobar()
129 | setfenv(1, {})
130 | -- code here
131 | end
132 |
133 | -- Lua 5.2
134 | function foobar()
135 | local _ENV = {}
136 | -- code here
137 | end
138 |
139 | 而更进一步的是,5.2 中对 load 函数作出了修改。(包括但不限于 :))合并了 loadstring 功能,并可以在参数中指定所使用的环境表:
140 |
141 | local func, message = load(code, nil, "t", env)
142 |
143 | ## 面向对象
144 |
145 | 没错,Lua 中只存在表(Table)这么唯一一种数据结构,但依旧可以玩出面向对象的概念。
146 |
147 | ### 添加成员函数
148 |
149 | 好吧,如果熟悉 C++ 还是很好理解类似的进化过程的:如果说 struct 里可以添加函数是从 C 过渡到 C++ 的第一认识的话,为 Table 添加函数也可以算是认识 Lua 是如何面向对象的第一步吧。
150 |
151 | player = { health = 200 } --> 一个普通的 player 表,这里看作是一个对象
152 | function takeDamage(self, amount)
153 | self.health = self.health - amount
154 | end
155 |
156 | takeDamage(player, 20) --> 调用
157 |
158 | 如何将独立的 takeDamage 塞进 player 中咧?答案是直接定义进去:
159 |
160 | player = { health = 200 }
161 | function player.takeDamage(self, amount)
162 | self.health = self.health - amount
163 | end
164 |
165 | player.takeDamage(player, 20) --> 调用
166 |
167 | 这样就相当于在 player 表中添加了一个叫做 takeDamage 的字段,和下面的代码是一样的:
168 |
169 | player = {
170 | health = 200,
171 | takeDamage = function(self, amount) --> Lua 中的函数是 first-class value
172 | self.health = self.health - amount
173 | end
174 | }
175 |
176 | player.takeDamage(player, 20) --> 调用
177 |
178 | 调用时的 player.takeDamage(player, 20) 稍显不和谐(据说用术语叫做 DRY),于是就要出动「冒号操作符」这个专门为此而生的语法糖了:
179 |
180 | player:takeDamage(20) --> 等同于 player.takeDamage(player, 20)
181 | function player:takeDamage(amount) --> 等同于 function player.takeDamage(self, amount)
182 |
183 | ### 从对象升华到类
184 |
185 | 类的意义在于提取一类对象的共同点从而实现量产(我瞎扯的 >_<)。同样木有 Class 概念的 Javascript 使用 prototype 实现面向对象,Lua 则通过 Metatable 实现与 prototype 类似的功能。
186 |
187 | Player = {}
188 |
189 | function Player:create(o) --> 参数 o 可以暂时不管
190 | o = o or { health = 200 } --> Lua 的 or 与一般的 || 不同,如果非 nil 则返回该非 nil 值
191 | setmetatable(o, self)
192 | self.__index = self
193 | return o
194 | end
195 |
196 | function Player:takeDamage(amount)
197 | self.health = self.health - amount
198 | end
199 |
200 | playerA = Player:create() --> 参数 o 为 nil
201 | playerB = Player:create()
202 |
203 | playerA:takeDamage(20)
204 | playerB:takeDamage(40)
205 |
206 | 顾名思义 Metatable 也是一个 Table,可以通过在其中存放一些函数(称作 metamethod)从而修改一些默认的求值行为(如何显示为字符串、如何相加、如何连接、如何进行索引)。Metatable 的 __index 域设置了「如何进行索引」的方法。例如调用 foo.bar 时,如果在 foo 中没有找到名为 bar 的域时,则会调用 Metatable:__index(foo, bar)。于是:
207 |
208 | playerA:takeDamage(20)
209 |
210 | 因为在 playerA 中并不存在 takeDamge 函数,于是求助于 Metatable:
211 |
212 | getmetatable(playerA).__index.takeDamage(playerA, 20)
213 |
214 | 带入 Metatable 后:
215 |
216 | Player.__index.takeDamage(playerA, 20)
217 |
218 | 因为 Player 的 __index 在 create 时被指定为 self,所以最终变为:
219 |
220 | Player.takeDamage(playerA, 20)
221 |
222 | 于是 takeDamage 的 self 得到了正确的对象 playerA。
223 |
224 | ### 继承
225 |
226 | 继承是面向对象的一大特性,明白了如何创建「类」,那么继承也就比较明了了,还记得大明湖畔的参数 o 么?
227 |
228 | RMBPlayer = Player:create()
229 | function RMBPlayer:broadcast(message) --> 为子类添加新的方法
230 | print(message)
231 | end
232 | function RMBPlayer:takeDamage(amount) --> 子类重载父类方法
233 | self.health = self.health - amount / (self.money / 100)
234 | end
235 |
236 | vip = RMBPlayer:create { money = 200 } --> 子类添加新成员(单个 Table 作为参数可以省略括号)
237 |
238 | vip:takeDamage(20)
239 | vip:broadcast("F*ck")
240 |
241 | 以上便是 Lua 中实现面向对象的基本方法。
242 |
243 | ## 导航
244 | * [目录](00.md)
245 | * 上一章:[环境与模块](02.md)
246 | * 下一章:[标准库](04.md)
247 |
--------------------------------------------------------------------------------
/04.md:
--------------------------------------------------------------------------------
1 | # Lua - 标准库
2 |
3 | ## String
4 |
5 | string.byte
6 | string.char
7 | string.dump
8 | string.find
9 | string.format
10 | string.gmatch
11 | string.gsub
12 | string.len
13 | string.lower
14 | string.match
15 | string.rep
16 | string.reverse
17 | string.sub
18 | string.upper
19 |
20 | > 在string库中功能最强大的函数是:string.find(字符串查找),string.gsub(全局字符串替换),and string.gfind(全局字符串查找)。这些函数都是基于模式匹配的。
21 |
22 | > 与其他脚本语言不同的是,Lua并不使用POSIX规范的正则表达式(也写作regexp)来进行模式匹配。主要的原因出于程序大小方面的考虑:实现一个典型的符合POSIX标准的regexp大概需要4000行代码,这比整个Lua标准库加在一起都大。权衡之下,Lua中的模式匹配的实现只用了500行代码,当然这意味着不可能实现POSIX所规范的所有更能。然而,Lua中的模式匹配功能是很强大的,并且包含了一些使用标准POSIX模式匹配不容易实现的功能。
23 |
24 | ### (1) pattern 模式
25 |
26 | 下面的表列出了Lua支持的所有字符类:
27 |
28 | . 任意字符
29 | %a 字母
30 | %c 控制字符
31 | %d 数字
32 | %l 小写字母
33 | %p 标点字符
34 | %s 空白符
35 | %u 大写字母
36 | %w 字母和数字
37 | %x 十六进制数字
38 | %z 代表0的字符
39 |
40 | 可以使用修饰符来修饰模式增强模式的表达能力,Lua中的模式修饰符有四个:
41 |
42 | + 匹配前一字符1次或多次
43 | * 匹配前一字符0次或多次
44 | - 匹配前一字符0次或多次
45 | ? 匹配前一字符0次或1次
46 |
47 | '%b' 用来匹配对称的字符。常写为 '%bxy' ,x和y是任意两个不同的字符;x作为匹配的开始,y作为匹配的结束。比如,'%b()' 匹配以 '(' 开始,以 ')' 结束的字符串:
48 |
49 | print(string.gsub("a (enclosed (in) parentheses) line", "%b()", "")) --> a line
50 |
51 | 常用的这种模式有:'%b()' ,'%b[]','%b%{%}' 和 '%b<>'。你也可以使用任何字符作为分隔符。
52 |
53 | ### (2) capture 捕获
54 |
55 | Capture是这样一种机制:可以使用模式串的一部分匹配目标串的一部分。将你想捕获的模式用圆括号括起来,就指定了一个capture。
56 |
57 | pair = "name = Anna"
58 | _, _, key, value = string.find(pair, "(%a+)%s*=%s*(%a+)")
59 | print(key, value) --> name Anna
60 |
61 | ### (3) string.find 字符串查找
62 |
63 | string.find 的基本应用就是用来在目标串(subject string)内搜索匹配指定的模式的串,函数返回两个值:匹配串开始索引和结束索引。
64 |
65 | s = "hello world"
66 | i, j = string.find(s, "hello")
67 | print(i, j) --> 1 5
68 | print(string.sub(s, i, j)) --> hello
69 | print(string.find(s, "world")) --> 7 11
70 | i, j = string.find(s, "l")
71 | print(i, j) --> 3 3
72 | print(string.find(s, "lll")) --> nil
73 |
74 | string.find函数第三个参数是可选的:标示目标串中搜索的起始位置。
75 |
76 | 在string.find使用captures的时候,函数会返回捕获的值作为额外的结果:
77 |
78 | pair = "name = Anna"
79 | _, _, key, value = string.find(pair, "(%a+)%s*=%s*(%a+)")
80 | print(key, value) --> name Anna
81 |
82 | 看个例子,假定你想查找一个字符串中单引号或者双引号引起来的子串,你可能使用模式 '["'].-["']',但是这个模式对处理类似字符串 "it's all right" 会出问题。为了解决这个问题,可以使用向前引用,使用捕获的第一个引号来表示第二个引号:
83 |
84 | s = [[then he said: "it's all right"!]]
85 | a, b, c, quotedPart = string.find(s, "(["'])(.-)%1")
86 | print(quotedPart) --> it's all right
87 | print(c) --> "
88 |
89 | ### (4) string.gmatch 全局字符串查找
90 |
91 | string.gfind 函数比较适合用于范性 for 循环。他可以遍历一个字符串内所有匹配模式的子串。
92 |
93 | words = {}
94 | for w in string.gmatch("nick takes a stroll", "%a+") do
95 | table.insert(words, w)
96 | end
97 |
98 | **URL解码**
99 |
100 | function unescape(s)
101 | s = string.gsub(s, "+", " ")
102 | s = string.gsub(s, "%%(%x%x)", function(h)
103 | return string.char(tonumber(h, 16))
104 | end)
105 | return s
106 | end
107 |
108 | print(unescape("a%2Bb+%3D+c")) -- a+b = c
109 |
110 | 对于name=value对,我们使用gfind解码,因为names和values都不能包含 '&' 和 '='我们可以用模式 '[^&=]+' 匹配他们:
111 |
112 | cgi = {}
113 | function decode (s)
114 | for name, value in string.gmatch(s, "([^&=]+)=([^&=]+)") do
115 | name = unescape(name)
116 | value = unescape(value)
117 | cgi[name] = value
118 | end
119 | end
120 |
121 | **URL编码**
122 |
123 | 这个函数将所有的特殊字符转换成 '%' 后跟字符对应的ASCII码转换成两位的16进制数字(不足两位,前面补0),然后将空白转换为 '+':
124 |
125 | function escape(s)
126 | s = string.gsub(s, "([&=+%c])", function(c)
127 | return string.format("%%%02X", string.byte(c))
128 | end)
129 | s = string.gsub(s, " ", "+")
130 | return s
131 | end
132 |
133 | function encode(t)
134 | local s = ""
135 | for k, v in pairs(t) do
136 | s = s .. "&" .. escape(k) .. "=" .. escape(v)
137 | end
138 | return string.sub(s, 2) -- remove first '&'
139 | end
140 | t = {name = "al", query = "a+b = c", q = "yes or no"}
141 |
142 | print(encode(t)) --> q=yes+or+no&query=a%2Bb+%3D+c&name=al
143 |
144 | ### (5) string.gsub 全局字符串替换
145 |
146 | string.gsub 函数有三个参数:目标串,模式串,替换串,第四个参数是可选的,用来限制替换的数量。
147 |
148 | print(string.gsub("nck eats fish", "fish", "chips")) --> nick eats chips 1
149 |
150 | string.gsub 的第二个返回值表示他进行替换操作的次数:
151 |
152 | print(string.gsub("fish eats fish", "fish", "chips")) --> chips eats chips 2
153 |
154 | 使用模式:
155 |
156 | print(string.gsub("nick eats fish", "[AEIOUaeiou]", ".")) --> n.ck ..ts f.sh 4
157 |
158 | 使用捕获:
159 |
160 | print(string.gsub("nick eats fish", "([AEIOUaeiou])", "(%1)")) --> n(i)ck (e)(a)ts f(i)sh 4
161 |
162 | 使用替换函数:
163 |
164 | function f(s)
165 | print("found " .. s)
166 | end
167 |
168 | string.gsub("Nick is taking a walk today", "%a+", f)
169 |
170 | 输出:
171 | found Nick
172 | found is
173 | found taking
174 | found a
175 | found walk
176 | found today
177 |
178 | ### (6) string.sub, string.byte, string.format
179 |
180 | s = "[in brackets]"
181 | print(string.sub(s, 2, -2)) --> in brackets
182 |
183 | string.char 函数和 string.byte 函数用来将字符在字符和数字之间转换,string.char 获取0个或多个整数,将每一个数字转换成字符,然后返回一个所有这些字符连接起来的字符串。string.byte(s, i) 将字符串s的第i个字符的转换成整数。
184 |
185 | print(string.char(97)) --> a
186 | i = 99; print(string.char(i, i+1, i+2)) --> cde
187 | print(string.byte("abc")) --> 97
188 | print(string.byte("abc", 2)) --> 98
189 | print(string.byte("abc", -1)) --> 99
190 |
191 | string.format 和 C 语言的 printf 函数几乎一模一样,你完全可以照 C 语言的 printf 来使用这个函数,第一个参数为格式化串:由指示符和控制格式的字符组成。指示符后的控制格式的字符可以为:十进制'd';十六进制'x';八进制'o';浮点数'f';字符串's'。
192 |
193 | print(string.format("pi = %.4f", PI)) --> pi = 3.1416
194 | d = 5; m = 11; y = 1990
195 | print(string.format("%02d/%02d/%04d", d, m, y)) --> 05/11/1990
196 | tag, title = "h1", "a title"
197 | print(string.format("<%s>%s%s>", tag, title, tag)) -->
a title
198 |
199 | ## Table
200 |
201 | table.concat
202 | table.insert
203 | table.maxn
204 | table.remove
205 | table.sort
206 |
207 | ### (1) table.getn
208 |
209 | print(table.getn{10,2,4}) --> 3
210 | print(table.getn{10,2,nil}) --> 2
211 | print(table.getn{10,2,nil; n=3}) --> 3
212 | print(table.getn{n=1000}) --> 1000
213 |
214 | a = {}
215 | print(table.getn(a)) --> 0
216 | table.setn(a, 10000)
217 | print(table.getn(a)) --> 10000
218 |
219 | a = {n=10}
220 | print(table.getn(a)) --> 10
221 | table.setn(a, 10000)
222 | print(table.getn(a)) --> 10000
223 |
224 | ### (2) table.insert, table.remove
225 |
226 | table.isnert(table, value, position)
227 | table.remove(table, position)
228 |
229 | table库提供了从一个list的任意位置插入和删除元素的函数。table.insert函数在array指定位置插入一个元素,并将后面所有其他的元素后移。
230 |
231 | a = {}
232 | for line in io.lines() do
233 | table.insert(a, line)
234 | end
235 | print(table.getn(a)) --> (number of lines read)
236 |
237 | table.remove 函数删除数组中指定位置的元素,并返回这个元素,所有后面的元素前移,并且数组的大小改变。不带位置参数调用的时候,他删除array的最后一个元素。
238 |
239 | ### (3) table.sort
240 |
241 | table.sort 有两个参数,存放元素的array和排序函数,排序函数有两个参数并且如果在array中排序后第一个参数在第二个参数前面,排序函数必须返回true。如果未提供排序函数,sort使用默认的小于操作符进行比较。
242 |
243 | lines = {
244 | luaH_set = 10,
245 | luaH_get = 24,
246 | luaH_present = 48,
247 | }
248 |
249 | function pairsByKeys (t, f)
250 | local a = {}
251 | for n in pairs(t) do table.insert(a, n) end
252 | table.sort(a, f)
253 | local i = 0 -- iterator variable
254 | local iter = function () -- iterator function
255 | i = i + 1
256 | if a[i] == nil then return nil
257 | else return a[i], t[a[i]]
258 | end
259 | end
260 | return iter
261 | end
262 |
263 | for name, line in pairsByKeys(lines) do
264 | print(name, line)
265 | end
266 |
267 | 打印结果:
268 |
269 | luaH_get 24
270 | luaH_present 48
271 | luaH_set 10
272 |
273 | ## Coroutine
274 |
275 | coroutine.create
276 | coroutine.resume
277 | coroutine.running
278 | coroutine.status
279 | coroutine.wrap
280 | coroutine.yield
281 |
282 | ## Math
283 |
284 | math.abs
285 | math.acos
286 | math.asin
287 | math.atan
288 | math.atan2
289 | math.ceil
290 | math.cos
291 | math.cosh
292 | math.deg
293 | math.exp
294 | math.floor
295 | math.fmod
296 | math.frexp
297 | math.huge
298 | math.ldexp
299 | math.log
300 | math.log10
301 | math.max
302 | math.min
303 | math.modf
304 | math.pi
305 | math.pow
306 | math.rad
307 | math.random
308 | math.randomseed
309 | math.sin
310 | math.sinh
311 | math.sqrt
312 | math.tan
313 | math.tanh
314 |
315 | ## IO
316 |
317 | io.close
318 | io.flush
319 | io.input
320 | io.lines
321 | io.open
322 | io.output
323 | io.popen
324 | io.read
325 | io.stderr
326 | io.stdin
327 | io.stdout
328 | io.tmpfile
329 | io.type
330 | io.write
331 |
332 | ## OS
333 |
334 | os.clock
335 | os.date
336 | os.difftime
337 | os.execute
338 | os.exit
339 | os.getenv
340 | os.remove
341 | os.rename
342 | os.setlocale
343 | os.time
344 | os.tmpname
345 |
346 | ## File
347 |
348 | file:close
349 | file:flush
350 | file:lines
351 | file:read
352 | file:seek
353 | file:setvbuf
354 | file:write
355 |
356 | ## Debug
357 |
358 | debug.debug
359 | debug.getfenv
360 | debug.gethook
361 | debug.getinfo
362 | debug.getlocal
363 | debug.getmetatable
364 | debug.getregistry
365 | debug.getupvalue
366 | debug.setfenv
367 | debug.sethook
368 | debug.setlocal
369 | debug.setmetatable
370 | debug.setupvalue
371 | debug.traceback
372 |
373 | ## 导航
374 | * [目录](00.md)
375 | * 上一章:[函数与面向对象](03.md)
376 | * 下一章:[协程 Coroutine](05.md)
--------------------------------------------------------------------------------
/05.md:
--------------------------------------------------------------------------------
1 | # Lua - 协程 Coroutine
2 |
3 | 协程(coroutine)并不是 Lua 独有的概念,如果让我用一句话概括,那么大概就是:一种能够在运行途中主动中断,并且能够从中断处恢复运行的特殊函数。(嗯,其实不是函数。)
4 |
5 | ### 举个最原始的例子:
6 |
7 | 下面给出一个最简单的 Lua 中 coroutine 的用法演示:
8 |
9 | function greet()
10 | print "hello world"
11 | end
12 |
13 | co = coroutine.create(greet) -- 创建 coroutine
14 |
15 | print(coroutine.status(co)) -- 输出 suspended
16 | print(coroutine.resume(co)) -- 输出 hello world
17 | -- 输出 true (resume 的返回值)
18 | print(coroutine.status(co)) -- 输出 dead
19 | print(coroutine.resume(co)) -- 输出 false cannot resume dead coroutine (resume 的返回值)
20 | print(type(co)) -- 输出 thread
21 |
22 | 协程在创建时,需要把协程体函数传递给创建函数 create。新创建的协程处于 suspended 状态,可以使用 resume 让其运行,全部执行完成后协程处于 dead 状态。如果尝试 resume 一个 dead 状态的,则可以从 resume 返回值上看出执行失败。另外你还可以注意到 Lua 中协程(coroutine)的变量类型其实叫做「thread」Orz...
23 |
24 | 乍一看可能感觉和线程没什么两样,但需要注意的是 resume 函数只有在 greet 函数「返回」后才会返回(所以说协程像函数)。
25 |
26 | ### 函数执行的中断与再开
27 |
28 | 单从上面这个例子,我们似乎可以得出结论:协程果然就是某种坑爹的函数调用方式啊。然而,协程的真正魅力来自于 resume 和 yield 这对好基友之间的羁绊。
29 |
30 | ### 函数 coroutine.resume(co[, val1, ...])
31 |
32 | 开始或恢复执行协程 co。
33 |
34 | 如果是开始执行,val1 及之后的值都作为参数传递给协程体函数;如果是恢复执行,val1 及之后的值都作为 yield 的返回值传递。
35 |
36 | 第一个返回值(还记得 Lua 可以返回多个值吗?)为表示执行成功与否的布尔值。如果成功,之后的返回值是 yield 的参数;如果失败,第二个返回值为失败的原因(Lua 的很多函数都采用这种错误处理方式)。
37 |
38 | 当然,如果是协程体函数执行完毕 return 而不是 yield,那么 resume 第一个返回值后跟着的就是其返回值。
39 |
40 | ### 函数 coroutine.yield(...)
41 |
42 | 中断协程的执行,使得开启该协程的 coroutine.resume 返回。再度调用 coroutine.resume 时,会从该 yield 处恢复执行。
43 |
44 | 当然,yield 的所有参数都会作为 resume 第一个返回值后的返回值返回。
45 |
46 | OK,总结一下:当 co = coroutine.create(f) 时,yield 和 resume 的关系如下图:
47 |
48 |
49 |
50 | ### How coroutine makes life easier
51 |
52 | 如果要求给某个怪写一个 AI:先向右走 30 帧,然后只要玩家进入视野就往反方向逃 15 帧。该怎么写?
53 |
54 | #### 传统做法
55 |
56 | 经典的纯状态机做法。
57 |
58 | -- 每帧的逻辑
59 | function Monster:frame()
60 | self:state_func()
61 | self.state_frame_count = self.state_frame_count + 1
62 | end
63 |
64 | -- 切换状态
65 | function Monster:set_next_state(state)
66 | self.state_func = state
67 | self.state_frame_count = 0
68 | end
69 |
70 | -- 首先向右走 30 帧
71 | function Monster:state_walk_1()
72 | local frame = self.state_frame_count
73 | self:walk(DIRECTION_RIGHT)
74 | if frame > 30 then
75 | self:set_next_state(state_wait_for_player)
76 | end
77 | end
78 |
79 | -- 等待玩家进入视野
80 | function Monster:state_wait_for_player()
81 | if self:get_distance(player) < self.range then
82 | self.direction = -self:get_direction_to(player)
83 | self:set_next_state(state_walk_2)
84 | end
85 | end
86 |
87 | -- 向反方向走 15 帧
88 | function Monster:state_walk_2()
89 | local frame = self.state_frame_count;
90 | self:walk(self.direction)
91 | if frame > 15 then
92 | self:set_next_state(state_wait_for_player)
93 | end
94 | end
95 |
96 | #### 协程做法
97 |
98 | -- 每帧的逻辑
99 | function Monster:frame()
100 | -- 首先向右走 30 帧
101 | for i = 1, 30 do
102 | self:walk(DIRECTION_RIGHT)
103 | self:wait()
104 | end
105 |
106 | while true do
107 | -- 等待玩家进入视野
108 | while self:get_distance(player) >= self.range do
109 | self:wait()
110 | end
111 |
112 | -- 向反方向走 15 帧
113 | self.direction = -self:get_direction_to(player)
114 | for i = 1, 15 do
115 | self:walk(self.direction)
116 | self:wait()
117 | end
118 | end
119 | end
120 |
121 | -- 该帧结束
122 | function Monster:wait()
123 | coroutine.yield()
124 | end
125 |
126 | 额外说一句,从 wait 函数可以看出,Lua 的协程并不要求一定要从协程体函数中调用 yield,这是和 Python 的一个区别。
127 |
128 | 协同程序(coroutine,这里简称协程)是一种类似于线程(thread)的东西,它拥有自己独立的栈、局部变量和指令指针,可以跟其他协程共享全局变量和其他一些数据,并且具有一种挂起(yield)中断协程主函数运行,下一次激活恢复协程会在上一次中断的地方继续执行(resume)协程主函数的控制机制。
129 |
130 | Lua 把关于协程的所有函数放在一个名为 “coroutine” 的 table 里,coroutine 里具有以下几个内置函数:
131 |
132 | -coroutine-yield [function: builtin#34]
133 | | -wrap [function: builtin#37]
134 | | -status [function: builtin#31]
135 | | -resume [function: builtin#35]
136 | | -running [function: builtin#32]
137 | | -create [function: builtin#33]
138 |
139 | ### coroutine.create - 创建协程
140 |
141 | 函数 coroutine.create 用于创建一个新的协程,它只有一个以函数形式传入的参数,该函数是协程的主函数,它的代码是协程所需执行的内容
142 |
143 | co = coroutine.create(function()
144 | io.write("coroutine create!\n")
145 | end)
146 | print(co)
147 |
148 | 当创建完一个协程后,会返回一个类型为 thread 的对象,但并不会马上启动运行协程主函数,协程的初始状态是处于挂起状态
149 |
150 | ### coroutine.status - 查看协程状态
151 |
152 | 协程有 4 种状态,分别是:挂起(suspended)、运行(running)、死亡(dead)和正常(normal),可以通过 coroutine.status 来输出查看协程当前的状态。
153 |
154 | print(coroutine.status(co))
155 |
156 | ### coroutine.resume - 执行协程
157 |
158 | 函数 coroutine.resume 用于启动或再次启动一个协程的执行
159 |
160 | coroutine.resume(co)
161 |
162 | 协程被调用执行后,其状态会由挂起(suspended)改为运行(running)。不过当协程主函数全部运行完之后,它就变为死亡(dead)状态。
163 |
164 | 传递给 resume 的额外参数都被看作是协程主函数的参数
165 |
166 | co = coroutine.create(function(a, b, c)
167 | print("co", a, b, c)
168 | end)
169 | coroutine.resume(co, 1, 2, 3)
170 |
171 | 协程主函数执行完时,它的主函数所返回的值都将作为对应 resume 的返回值
172 |
173 | co = coroutine.create(function()
174 | return 3, 4
175 | end)
176 | print(coroutine.resume(co))
177 |
178 | ### coroutine.yield - 中断协程运行
179 |
180 | coroutine.yield 函数可以让一个运行中的协程中断挂起
181 |
182 | co = coroutine.create(function()
183 | for i = 1, 3 do
184 | print("before coroutine yield", i)
185 | coroutine.yield()
186 | print("after coroutine yield", i)
187 | end
188 | end)
189 | coroutine.resume(co)
190 |
191 | coroutine.resume(co)
192 | 上面第一个 resume 唤醒执行协程主函数代码,直到第一个 yield。第二个 resume 激活被挂起的协程,并从上一次协程被中断 yield 的位置继续执行协程主函数代码,直到再次遇到 yield 或程序结束。
193 |
194 | resume 执行完协程主函数或者中途被挂起(yield)时,会有返回值返回,第一个值是 true,表示执行没有错误。如果是被 yield 挂起暂停,yield 函数有参数传入的话,这些参数会接着第一个值后面一并返回
195 |
196 | co = coroutine.create(function(a, b, c)
197 | coroutine.yield(a, b, c)
198 | end)
199 | print(coroutine.resume(co, 1, 2, 3))
200 |
201 | ### 以 coroutine.wrap 的方式创建协程
202 |
203 | 跟 coroutine.create 一样,函数 coroutine.wrap 也是创建一个协程,但是它并不返回一个类型为 thread 的对象,而是返回一个函数。每当调用这个返回函数,都会执行协程主函数运行。所有传入这个函数的参数等同于传入 coroutine.resume 的参数。 coroutine.wrap 会返回所有应该由除第一个(错误代码的那个布尔量) 之外的由 coroutine.resume 返回的值。 和 coroutine.resume 不同之处在于, coroutine.wrap 不会返回错误代码,无法检测出运行时的错误,也无法检查 wrap 所创建的协程的状态
204 |
205 | function wrap(param)
206 | print("Before yield", param)
207 | obtain = coroutine.yield()
208 | print("After yield", obtain)
209 | return 3
210 | end
211 | resumer = coroutine.wrap(wrap)
212 |
213 | print(resumer(1))
214 |
215 | print(resumer(2))
216 |
217 | ### coroutine.running - 返回正在运行中的协程
218 |
219 | 函数 coroutine.running 用于返回正在运行中的协程,如果没有协程运行,则返回 nil
220 |
221 | print(coroutine.running())
222 |
223 | co = coroutine.create(function()
224 | print(coroutine.running())
225 | print(coroutine.running() == co)
226 | end)
227 | coroutine.resume(co)
228 |
229 | print(coroutine.running())
230 |
231 | ### resume-yield 交互
232 |
233 | 下面代码放在一个 lua 文件里运行,随便输入一些字符后按回车,则会返回输出刚才输入的内容
234 |
235 | function receive(prod)
236 | local status, value = coroutine.resume(prod)
237 | return value
238 | end
239 |
240 | function send(x)
241 | coroutine.yield(x)
242 | end
243 |
244 | function producer()
245 | return coroutine.create(function()
246 | while true do
247 | local x = io.read()
248 | send(x)
249 | end
250 | end)
251 | end
252 |
253 | function filter(prod)
254 | return coroutine.create(function()
255 | -- for line = 1, math.huge do
256 | for line = 1, 5 do
257 | local x = receive(prod)
258 | x = string.format("%5d Enter is %s", line, x)
259 | send(x)
260 | end
261 | end)
262 | end
263 |
264 | function consumer(prod)
265 | -- repeat
266 | -- local x = receive(prod)
267 | -- print(type(x))
268 | -- if x then
269 | -- io.write(x, "\n")
270 | -- end
271 | -- until x == nil
272 | while true do
273 | local obtain = receive(prod)
274 | if obtain then
275 | io.write(obtain, "\n\n")
276 | else
277 | break
278 | end
279 | end
280 | end
281 |
282 | p = producer()
283 | f = filter(p)
284 | consumer(f)
285 |
286 | ## 导航
287 | * [目录](00.md)
288 | * 上一章:[标准库](04.md)
289 | * 下一章:[Table 数据结构](06.md)
--------------------------------------------------------------------------------
/06.md:
--------------------------------------------------------------------------------
1 | # Lua - Table 数据结构
2 |
3 | Lua中的table不是一种简单的数据结构,它可以作为其它数据结构的基础。如数组、记录、线性表、队列和集合等,在Lua中都可以通过table来表示。
4 |
5 | ### (1) 数组:
6 |
7 | 使用整数来索引table即可在Lua中实现数组。因此,Lua中的数组没有固定的大小,如:
8 |
9 | a = {}
10 | for i = 1, 1000 do
11 | a[i] = 0
12 | end
13 | print("The length of array 'a' is " .. #a)
14 | --The length of array 'a' is 1000
15 |
16 | 在Lua中,可以让任何数作为数组的起始索引,但通常而言,都会使用1作为其起始索引值。而且很多Lua的内置功能和函数都依赖这一特征,因此在没有充分理由的前提下,尽量保证这一规则。下面的方法是通过table的构造器来创建并初始化一个数组的,如:
17 |
18 | squares = {1, 4, 9, 16, 25}
19 |
20 | ### (2) 二维数组:
21 |
22 | 在Lua中我们可以通过两种方式来利用table构造多维数组。其中,第一种方式通过“数组的数组”的方式来实现多维数组的,即在一维数组上的每个元素也同样为table对象,如:
23 |
24 | mt = {}
25 | for i = 1, N do
26 | mt[i] = {}
27 | for j = 1, M do
28 | mt[i][j] = i * j
29 | end
30 | end
31 |
32 | 第二种方式是将二维数组的索引展开,并以固定的常量作为第二维度的步长,如:
33 |
34 | mt = {}
35 | for i = 1, N do
36 | for j = 1, M do
37 | mt[(i - 1) * M + j] = i * j
38 | end
39 | end
40 |
41 | ### (3) 链表:
42 |
43 | 由于table是动态的实体,所以在Lua中实现链表是很方便的。其中,每个结点均以table来表示,一个“链接”只是结点中的一个字段,该字段包含对其它table的引用,如:
44 |
45 | list = nil
46 | for i = 1, 10 do
47 | list = { next = list, value = i}
48 | end
49 |
50 | local l = list
51 | while l do
52 | print(l.value)
53 | l = l.next
54 | end
55 |
56 |
57 | ### (4) 队列与双向队列:
58 |
59 | 在Lua中实现队列的简单方法是使用table库函数insert和remove。但是由于这种方法会导致后续元素的移动,因此当队列的数据量较大时,不建议使用该方法。下面的代码是一种更高效的实现方式,如:
60 |
61 | List = {}
62 |
63 | function List.new()
64 | return {first = 0, last = -1}
65 | end
66 |
67 | function List.pushFront(list, value)
68 | local first = list.first - 1
69 | list.first = first
70 | list[first] = value
71 | end
72 |
73 | function List.pushBack(list, value)
74 | local last = list.last + 1
75 | list.last = last
76 | list[last] = value
77 | end
78 |
79 | function List.popFront(list)
80 | local first = list.first
81 | if first > list.last then
82 | error("List is empty")
83 | end
84 | local value = list[first]
85 | list[first] = nil
86 | list.first = first + 1
87 | return value
88 | end
89 |
90 | function List.popBack(list)
91 | local last = list.last
92 | if list.first > last then
93 | error("List is empty")
94 | end
95 | local value = list[last]
96 | list[last] = nil
97 | list.last = last - 1
98 | return value
99 | end
100 |
101 |
102 | ### (5) 集合和包(Bag):
103 |
104 | 在Lua中用table实现集合是非常简单的,见如下代码:
105 |
106 | reserved = { ["while"] = true, ["end"] = true, ["function"] = true, }
107 | if not reserved["while"] then
108 | --do something
109 | end
110 |
111 | 在Lua中我们可以将包(Bag)看成MultiSet,与普通集合不同的是该容器中允许key相同的元素在容器中多次出现。下面的代码通过为table中的元素添加计数器的方式来模拟实现该数据结构,如:
112 |
113 | function insert(bag, element)
114 | bag[element] = (bag[element] or 0) + 1
115 | end
116 |
117 | function remove(bag, element)
118 | local count = bag[element]
119 | bag[element] = (count and count > 1) and count - 1 or nil
120 | end
121 |
122 | #### (6) StringBuilder:
123 |
124 | 如果想在Lua中将多个字符串连接成为一个大字符串的话,可以通过如下方式实现,如:
125 |
126 | local buff = ""
127 | for line in io.lines() do
128 | buff = buff .. line .. "\n"
129 | end
130 |
131 | 上面的代码确实可以正常的完成工作,然而当行数较多时,这种方法将会导致大量的内存重新分配和内存间的数据拷贝,由此而带来的性能开销也是相当可观的。事实上,在很多编程语言中String都是不可变对象,如Java,因此如果通过该方式多次连接较大字符串时,均会导致同样的性能问题。为了解决该问题,Java中提供了StringBuilder类,而Lua中则可以利用table的concat方法来解决这一问题,见如下代码:
132 |
133 | local t = {}
134 | for line in io.lines() do
135 | t[#t + 1] = line .. "\n"
136 | end
137 | local s = table.concat(t)
138 |
139 | --concat方法可以接受两个参数,因此上面的方式还可以改为:
140 | local t = {}
141 | for line in io.lines() do
142 | t[#t + 1] = line
143 | end
144 | local s = table.concat(t,"\n")
145 |
146 | ## 导航
147 | * [目录](00.md)
148 | * 上一章:[协程 Coroutine](05.md)
149 | * 下一章:[常用的 C API](07.md)
--------------------------------------------------------------------------------
/07.md:
--------------------------------------------------------------------------------
1 | # Lua - 常用的 C API
2 |
3 | ## 基础概念
4 |
5 | ### states
6 |
7 | Lua连接库是完全可重入的,因为它没有全局变量。Lua解释器的整个state(如全局变量、堆栈等)都存储在一个结构类型为Lua_State动态分配的对象里。指向这一对象的指针必须作为第一个参数传递给所有连接库的API,除了用来生成一个Lua state的函数——lua_open。在调用所有的API函数之前,你必须先用lua_open以生成一个state:
8 |
9 | lua_State* lua_open(void);
10 |
11 | 可以通过调用lua_close来释放一个通过lua_open生成的state:
12 |
13 | void lua_close (lua_State *L);
14 |
15 | 这一函数销毁给定的Lua_State中的所有对象并释放state所占用的动态内存(如果有必要的话将通过调用对应的垃圾收集元方法来完成),在某些平台上,你不必调用这个函数,因为当宿主程序退出时会释放所有的资源,换句话说,长期运行的程序,如守护进程或web服务器,应尽快释放state所占的资源,以避免其过于庞大。
16 |
17 | ### 堆栈与索引
18 |
19 | Lua使用虚拟堆栈机制和C程序互相传值,所有的堆栈中的元素都可以看作一个Lua值(如nil, number, string等)。
20 |
21 | 当Lua调用C函数时,被调用的C函数将得到一个新的堆栈。这一堆栈与之前调用此函数的堆栈无关,也有其它C函数的堆栈无关。这一新的堆栈用调用C函数要用到的参数初始化,同时,这一堆栈也被用以返回函数调用结果。
22 |
23 | 为了便于操作,在API的中大量操作都并不依从堆栈只能操作栈顶元素的严格规则。而通过索引引用堆栈的任一元素。一个正整数索引可以看作某一元素在堆栈中的绝对位置(从1开始计数),一个负整数索引可以看作某一元素相对于栈顶的偏移量。
24 |
25 | 特别地,如果堆栈中有n个元素,那么索引1指向第一个元素(即第一个压入栈的元素)索引n指向最后一个元素;反过来,索引-1指向最后一个元素(即栈顶元素)索引-n指向第一个元素。当一个索引大于1并小于n时我们称其为一个有效索引(即1 <= abs(index) <= top)。
26 |
27 | ## 接口解析
28 |
29 | ### lua\_newstate
30 |
31 | lua_State *lua_newstate (lua_Alloc f, void *ud);
32 |
33 | 创建一个新的独立 state,不能创建返回 NULL。形参 f 是 allocator 函数,Lua 通过这个函数来为这个 state 分配内存。第二个形参 ud,是一个透明指针,每次调用时,Lua简单地传给 allocator 函数。
34 |
35 | ### lua\_open/lua\_close
36 |
37 | lua\_open 被 lua_newstate 替换,可以使用luaL_newstate从标准库中创建一个标准配置的 state,如: lua_State *L = luaL_newstate(); 。
38 |
39 | void lua_close (lua_State *L);
40 |
41 | 销毁指定的 state 中所有的对象,并释放指定的 state 中使用的所有动态内存。
42 |
43 | ### lua\_load/lua\_call/lua\_pcall/lua\_cpcall
44 |
45 | 这些函数的目的就是让我们能够执行压入栈中的函数,该函数可能是lua中定义的函数,可能是C++重定义的函数,当然我们一般是用来执行lua中执行的函数,C++中定义的基本上可以直接调用的。
46 |
47 | int lua_load (lua_State *L,
48 | lua_Reader reader,
49 | void *data,
50 | const char *chunkname);
51 |
52 | void lua_call(lua_State *L, int nargs, int nresults);
53 | void lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);
54 | void lua_cpcall(lua_State *L, int nargs, int nresults, int errfunc, void *ud);
55 |
56 | L是执行环境,可以理解为当前栈,nargs参数个数,nresults返回值个数。lua_pcall和该函数区别是多一个参数,用于发生错误处理时的代码返回。lua_cpcall则又多一个用于传递用户自定义的数据结构的指针。
57 |
58 | lua_call的运行是无保护的,他与lua_pcall相似,但是在错误发生的时候她抛出错误而不是返回错误代码。当你在应用程序中写主流程的代码时,不应该使用 lua_call,因为你应该捕捉任何可能发生的错误。当你写一个函数的代码时,使用lua_call是比较好的想法,如果有错误发生,把错误留给关心她的人去处理。所以,写应用程序主流程代码用lua_pcall,写C Native Function代码时用lua_call。
59 |
60 | **示例1:**
61 |
62 | Lua 代码:
63 |
64 | a = f("how", t.x, 14)
65 |
66 | C 代码:
67 |
68 | lua_getfield(L, LUA_GLOBALSINDEX, "f"); /* function to be called */
69 | lua_pushstring(L, "how"); /* 1st argument */
70 | lua_getfield(L, LUA_GLOBALSINDEX, "t"); /* table to be indexed */
71 | lua_getfield(L, -1, "x"); /* push result of t.x (2nd arg) */
72 | lua_remove(L, -2); /* remove 't' from the stack */
73 | lua_pushinteger(L, 14); /* 3rd argument */
74 | lua_call(L, 3, 1); /* call 'f' with 3 arguments and 1 result */
75 | lua_setfield(L, LUA_GLOBALSINDEX, "a"); /* set global 'a' */
76 |
77 | 在上面的例子除了描述了lua_call的使用外,还对lua_getfield的使用有一定的参考价值。特别是学习如何在一个表中获取他的值。
78 |
79 | 在上面的例子中,可能再调用lua_getfield时就会忘记调用lua_remove,当然这是我想象自己使用时会犯下的错。lua_getfield函数功能是从指定表中取出指定元素的值并压栈。上面获取t.x的值的过程就是先调用:
80 |
81 | lua_getfield(L, LUA_GLOBALSINDEX, "t");
82 |
83 | 从全局表中获取t的值,然而t本身是一个表,现在栈顶的值是t表。于是再一次调用:
84 |
85 | lua_getfield(L, -1, "x");
86 |
87 | 从t中取出x的值放到栈上,-1表示栈顶。那该函数执行完成后t的位置由-1就变成-2了,所以下面一句 lua_remove 索引的是-2,必须把t给remove掉,否则栈中就是4个参数了。上面的最后一句 lua_setfield 的目的是把返回值取回赋给全局变量a,**因为在lua_call执行完成后,栈顶的就是返回值了**。
88 |
89 | **示例2:**
90 |
91 | //test.lua
92 | function printmsg()
93 | print("hello world")
94 | end
95 | x = 10
96 |
97 | //test.c
98 | #include
99 | #include
100 |
101 | #include
102 | #include
103 | #include
104 |
105 | int main(int argc, const char *argv[]) {
106 | lua_State *L;
107 | if(NULL == (L = luaL_newstate())) {
108 | perror("luaL_newstate failed");
109 | return -1;
110 | }
111 | luaL_openlibs(L);
112 | if(luaL_loadfile(L, "./test.lua")) {
113 | perror("loadfile failed");
114 | return -1;
115 | }
116 | lua_pcall(L, 0, 0, 0);
117 |
118 | lua_getglobal(L, "printmsg");
119 | lua_pcall(L, 0, 0, 0);
120 |
121 | lua_close(L);
122 | return 0;
123 | }
124 |
125 | 上面的代码就是在test.c中调用test.lua的函数printmsg函数。
126 |
127 | 对于上面的C代码,我想大家都知道几个函数的大概作用:
128 |
129 | - luaL\_newstate():创建一个新的Lua虚拟机
130 | - luaL\_openlibs():打开一些必要的库,比如print等
131 | - luaL\_loadfile():手册上写的是"This function uses lua\_load to load the chunk in the filenamed filename." 而lua_load就是把编译过的chunk放在stack的顶部。理解chunk很重要,后面会具体讲到
132 | - lua_pcall:执行栈上的函数调用
133 |
134 | 一开始我一直认为既然 luaL_loadfile 执行以后,就可以直接用 lua_getglobal 获得test.lua中的函数,其实不然。**手册中明确提到,lua_load把一个lua文件当作一个chunk编译后放到stack的栈顶,而什么是chunk呢?chunk就是一个可执行语句的组合,可以是一个文件也可以是一个string**,“Lua handles a chunk as the body of an anonymous function with a variable number of arguments”这是Lua对chunk也就是lua文件的处理方式,就是认为是一个可变参数的匿名函数。也就是说,调用后栈上有一个匿名函数,这个函数的body就是文件中所有的内容。
135 |
136 | 在 luaL_loadfile 后,调用 lua_gettop 以及 lua_type 可以知道栈的大小为1,放在栈上的是一个 function 类型的value。为什么 loadfile 后我们不能直接获取到 printmsg 这个函数呢,那是因为刚才提到的,loadfile仅仅视编译lua文件,并不执行这个文件,也就是说只是在栈上形成了一个匿名函数。只有执行这个函数一次,才会使得printmsg可以通过 lua_getglobal 获取,否则,全局变量是空的。我在手册上看到这样一句话:Lua在执行函数的时候,函数会实例化,获得的 closure 也是这个函数的最终值。其实不管是函数,还是其他类型,如果不执行的话,它们只是被编译,并不能在进程的空间种获取到他们,感觉就像c的库一样,他们的编译文件.so已经存在,但是如果你不调用它,那么库中所有的变量不能被实例化,调用者也就无法访问。其实pringmsg和x本质是一样的,只是他们类型不同而已。
137 |
138 | ### lua\_getfield/lua\_setfield
139 |
140 | void lua_getfield (lua_State *L, int index, const char *k);
141 |
142 | 把值 t[k] 压入堆栈,t 是给定有效的索引 index 的值,和在 Lua 中一样,这个函数可能会触发元方法 index 事件。
143 |
144 | void lua_setfield (lua_State *L, int index, const char *k);
145 |
146 | 相当于 t[k] = v,t 是给定的有效索引 index 的值,v 是堆栈顶部的值,这个函数会弹出这个值,和在 Lua 中一样,这个函数可能会触发 newindex 元方法事件。
147 |
148 |
149 | ### lua\_getglobal/lua\_setglobal
150 |
151 | lua_getglobal
152 |
153 | void lua_getglobal (lua_State *L, const char *name);
154 |
155 | 把全局 name 的值压入栈顶,它被定义为宏(macro):
156 |
157 | #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, s)
158 |
159 | lua_setglobal
160 |
161 | void lua_setglobal (lua_State *L, const char *name);
162 |
163 | 从栈中弹出一个值并赋值给全局 name,它被定义成宏(macro):
164 |
165 | #define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, s)
166 |
167 | ### lua\_gettop/lua\_settop/lua\_pop
168 |
169 | 在任何时候,你都可以通过调用lua_gettop函数取得栈顶元素的索引:
170 |
171 | int lua_gettop (lua_State *L);
172 |
173 | 因为索引从1开始计数,lua_gettop的返回值等于这个堆栈的元素个数(当堆栈为空时返回值为0)
174 |
175 | void lua_settop (lua_State* L, int index );
176 |
177 | lua_settop用于把堆栈的栈顶索引设置为指定的数值,它可以接受所有可接受索引。如果新的栈顶索引比原来的大,则新的位置用nil填充。如果index为0,则将删除堆栈中的所有元素。在lua.h中定义了如下一个宏:
178 |
179 | #define lua_pop(L,n) lua_settop(L,-(n)-1)
180 |
181 | 用以把堆栈上部的n个元素删除。
182 |
183 | ### lua\_pushvalue/lua\_insert/lua\_remove/lua\_replace
184 |
185 | void lua_pushvalue (lua_State* L, int index);
186 | void lua_remove (lua_State* L, int index);
187 | void lua_insert (lua_State* L, int index);
188 | void lua_replace (lua_State* L, int index);
189 |
190 | lua_pushvalue压入一个元素的值拷贝到指定的索引处,相反地,lua_remove删除给定索引的元素,并将之一索引之上的元素来填补空缺。同样地,lua_insert在上移给定索引之上的所有元素后再在指定位置插入新元素。Lua_replace将栈顶元素压入指定位置而不移动任何元素(因此指定位置的元素的值被替换)。这些函数都仅接受有效索引(你不应当使用假索引调用lua_remove或lua_insert,因为它不能解析为一个堆栈位置)。下面是一个例子,栈的初始状态为10 20 30 40 50 *(从栈底到栈顶,“*”标识为栈顶,有:
191 |
192 | lua_pushvalue(L, 3) --> 10 20 30 40 50 30*
193 | lua_pushvalue(L, -1) --> 10 20 30 40 50 30 30*
194 | lua_remove(L, -3) --> 10 20 30 40 30 30*
195 | lua_remove(L, 6) --> 10 20 30 40 30*
196 | lua_insert(L, 1) --> 30 10 20 30 40*
197 | lua_insert(L, -1) --> 30 10 20 30 40* (没影响)
198 | lua_replace(L, 2) --> 30 40 20 30*
199 | lua_settop(L, -3) --> 30 40*
200 | lua_settop(L, 6) --> 30 40 nil nil nil nil*
201 |
202 | ### lua\_gettable/lua\_settable
203 |
204 |
205 | void lua_gettable (lua_State *L, int index);
206 |
207 | 把 t[k] 压入堆栈,t 是给出的有效的索引 index 的值,k 是栈顶的值,这个函数会从堆栈中弹出 key,并将结果值放到它的位置,和在 Lua 一样,函数可能会触发一个元方法 index 事件。
208 |
209 | void lua_settable (lua_State *L, int index);
210 |
211 | 相当于 t[k]=v,t 是给出的有效的索引 index 的值,v 是堆栈顶部的值,k 是堆栈顶部下面的值。这个函数会从堆栈中弹出 key 和 value 的值,和在 Lua 中一样,函数可能会触发元方法 newindex 事件。
212 |
213 | ### lua\_concat
214 |
215 | void lua_concat (lua_State *L, int n);
216 |
217 | 用来连接字符串,等价于Lua中的..操作符:自动将数字转换成字符串,如果有必要的时候还会自动调用metamethods。另外,她可以同时连接多个字符串。调用lua_concat(L,n)将连接(同时会出栈)栈顶的n个值,并将最终结果放到栈顶。
218 |
219 | ### lua\_type/lua\_typename
220 |
221 | int lua_type (lua_State *L, int index);
222 |
223 | lua_type返回堆栈元素的值类型,当使用无效索引时返回LUA_TNONE(如当堆栈为空的时候),lua_type返回的类型代码为如下在lua.h中定义的常量:LUA_TNIL,LUA_TNUMBER,LUA_TBOOLEAN,LUA_TSTRING,LUA_TTABLE,LUA_TFUNCTION,LUA_USERDATA,LUA_TTHEARD,LUA_TLIGHTUSERDATA。下面的函数可以将这些常量转换为字符串:
224 |
225 | const char* lua_typename (lua_State* L, int type);
226 |
227 | ### lua\_checkstack
228 |
229 | 当你使用Lua API的时候,你有责任控制堆栈溢出。函数
230 |
231 | int lua_checkstack (lua_State *L, ine extra);
232 |
233 | 将把堆栈的尺寸扩大到可以容纳top+extra个元素;当不能扩大堆栈尺寸到这一尺寸时返回假。这一函数从不减小堆栈的尺寸;当前堆栈的尺寸大于新的尺寸时,它将保留原来的尺寸,并不变化。
234 |
235 |
236 | ### lua\_is***
237 |
238 | int lua_isnumber(lua_State *L, int index);
239 | int lua_isboolean(lua_State *L, int index);
240 | int lua_isfunction(lua_State *L, int index);
241 | int lua_istable(lua_State *L, int index);
242 | int lua_isstring(lua_State *L, int index);
243 | int lua_isnil(lua_State *L, int index);
244 | int lua_iscfunction(lua_State *L, int index);
245 |
246 | 带lua_is*前辍的函数在当堆栈元素对象与给定的类型兼容时返回1,否则返回0。Lua_isboolean是个例外,它仅在元素类型为布尔型时成功(否则没有意思,因为任何值都可看作布尔型)。当使用无效索引时,它们总返回0。Lua_isnumber接受数字或者全部为数字的字符串;lua_isstring打接受字符串和数值,lua_isfunction接受lua函数和C函数;lua_isuserdata也可接受完全和轻量级两种userdata。如果想区分C函数和lua函数,可以使用lua_iscfunction函数;同样地,想区分完全和轻量级userdata可以使用lua_islightuserdata;区分数字和数字组成的字符串可以使用lua_type。
247 |
248 | API函数中还有比较堆栈中的两个值 的大小的函数:
249 |
250 | int lua_equal(lua_State *L, int index1, int index2);
251 | int lua_rawequal(lua_State *L, int index1, int index2);
252 | int lua_lessthan(lua_State *L, int index1, int index2);
253 |
254 | lua_equal和lua_lessthan与相对应的lua操作符等价(参考2.5.2)。lua_rawequal直接判断两个值的原始值,而非通过调用元方法来比较。以上的函数当索引无效时返回0。
255 |
256 |
257 | ### lua\_to***
258 |
259 | int lua_toboolean(lua_State *L, int index);
260 | lua_CFunction lua_tocfunction(lua_State *L, int index);
261 | lua_Integer lua_tointeger(lua_State *L, int index);
262 | const char *lua_tolstring(lua_State *L, int index);
263 | lua_Number lua_tonumber(lua_State *L, int index);
264 | void *lua_topointer(lua_State *L, int index);
265 | lua_State *lua_tothread(lua_State *L, int index);
266 | const char *lua_tostring(lua_State *L, int index);
267 |
268 | 这些函数可通过任意可接受索引调用,如果用无效索引为参数,则和给定值并不匹配类型一样。
269 | lua_toboolean转换指定索引lua值为C“布尔型”值(0或1)。当lua值仅为false或nil时返回0(如果你仅想接受一个真正的布尔值,可以先使用lua_isboolean去测试这个值的类型。
270 |
271 | lua_tonumber转换指定索引的值为数字(lua_Number默认为double)。这一lua值必须数字或可转换为数字的字符串(参考2.2.1),否则lua_tonumber返回0。
272 |
273 | lua_tostring将指定索引的值转换为字符串(const char*)。lua值必须为字符串或数字,否则返回NULL。当值为数字,lua_tostring将会把堆栈的原值转换为字符串(当lua_tostring应用到键值上时会使lua_next出现难以找出原因的错误)。lua_tostring返回一个完全对齐的字符串指针,这一字符串总是’/0’结尾(和C一样),但可能含有其它的0。如果你不知道一个字符串有多少个0,你可以使用lua_strlen取得真实长度。因为lua有垃圾收集机制,因此不保证返回的字符串指针在对应的值从堆栈中删除后仍然有效。如果你以后还要用到当前函数返回的字符串,你应当备份它或者将它放到registry中(参考3.18)。
274 |
275 | lua_tofunction将堆栈中的值转换为C函数指针,这个值必须为C函数指针,否则返回NULL。数据类型lua_CFunction将在3.16节讲述。
276 |
277 | lua_tothread转换堆栈中的值为lua线程(以lua_State*为表现形式),此值必须是一个线程,否则返回NULL。
278 |
279 | lua_topointer转换堆栈中的值为通用C指针(void*)。这个值必须为userdata、表、线程或函数,否则返回NULL。lua保证同一类型的不同对象返回不同指针。没有直接方法将指针转换为原值,这一函数通常用以获取调试信息。
280 |
281 |
282 | ### lua\_push***
283 |
284 | void lua_pushboolean(lua_State *L, int b);
285 | void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
286 | void lua_pushcfunction(lua_State *L, lua_CFunction f);
287 | const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
288 | void lua_pushinteger (lua_State *L, lua_Integer n);
289 | void lua_pushliteral
290 | void lua_pushlstring(lua_State *L, const char *s, size_t len);
291 | void lua_pushnil(lua_State *L);
292 | void lua_pushnumber(lua_State *L, lua_Number n);
293 | void lua_pushstring(lua_State *L, const char *s);
294 | const char *lua_pushvfstring (lua_State *L,
295 | const char *fmt,
296 | va_list argp);
297 |
298 | 这些函数接受一个C值,并将其转换为对应的lua值,然后将其压入堆栈。lua_pushlstring和lua_pushstring对给定的字符串生成一个可以互转的拷贝,这是个例外。lua_pushstring能压C字符串(即以0结尾并且内部没有0),否则建议使用更通用的lua_pushlstring,它能指定长度。
299 |
300 | 你同样可以压入“格式化”字符串:
301 |
302 | const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
303 | const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp);
304 |
305 | 这两个函数向堆栈压入格式化字符串并返回指向字符串的指针。它们跟sprintf和vsprintf很象但有如下的重要不同:
306 |
307 | - 你不用申请内存去保存格式化结果,这结果是一个lua字符串并且lua自己会小心管理内存(并通过垃圾收集机制释放)。
308 | - 使用转义字符受限。它们没有标志量、宽度和精确度。转义字符能够是’%%’(插入一个”%”)、’%s’(插入一个以0结尾的字符串)、’%f’(插入一个lua_Number)、’%d’(插入一个int)和’%c’(插入一个用int表示的字符)。
309 |
310 | ### lua\_register
311 |
312 | void lua_register (lua_State *L, const char *name, lua_CFunction f);
313 |
314 | 设置 C 函数 f 为新的全局变量 name 的值,它被定义为宏(macro):
315 |
316 | #define lua_register(L,n,f) (lua_pushcfunction(L, f), lua_setglobal(L, n))
317 |
318 | ### 完整示例
319 |
320 | #include
321 | #include
322 | #include
323 | #include
324 | #include
325 |
326 | void
327 | load(lua_State *L, const char *fname, int *w, int *h) {
328 | if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0 ,0)) {
329 | printf("Error Msg is %s.\n", lua_tostring(L, -1));
330 | return;
331 | }
332 | lua_getglobal(L, "width"); // #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
333 | lua_getglobal(L, "height");
334 | if (!lua_isnumber(L, -2)) {
335 | printf("'width' should be a number\n");
336 | return;
337 | }
338 | if (!lua_isnumber(L, -1)) {
339 | printf("'height' should be a number\n", );
340 | return;
341 | }
342 | *w = lua_tointeger(L, -2);
343 | *h = lua_tointeger(L, -1);
344 | }
345 |
346 | int
347 | main() {
348 | lua_State *L = luaL_newstate();
349 | int w, h;
350 | load(L, "D:/test.lua", &w, &h);
351 | printf("width = %d, height = %d\n", w, h);
352 | lua_close(L);
353 | return 0;
354 | }
355 |
356 | ## 导航
357 | * [目录](00.md)
358 | * 上一章:[Table 数据结构](06.md)
359 | * 下一章:[Lua 与 C/C++ 交互](08.md)
--------------------------------------------------------------------------------
/08.md:
--------------------------------------------------------------------------------
1 | # Lua - Lua 与 C/C++ 交互
2 |
3 | ## 绑定Lua和C/C++的库
4 |
5 | - [CPPlua](http://sourceforge.net/projects/cpplua/)
6 | - [tolua](http://www.tecgraf.puc-rio.br/~celes/tolua/)
7 | - [tolua++](http://www.codenix.com/~tolua/)
8 | - [luawrapper](http://www.d2-life.com/LBS/blogview.asp?logID=41)
9 | - [luabind](http://luabind.sourceforge.net/)
10 | - [luaplus](http://luaplus.org/)
11 |
12 | ## Lua调用C/C++
13 |
14 | ### 简介
15 |
16 | Lua(念“鲁啊”)作为一门发展成熟的脚本语言,正在变得越来越流行。它也可以作为和C/C++执行脚本交互的语言。并且Lua的整个库很小,Lua 5.1版本整个静态链接的lua.dll才164KB,所以Lua很轻量,特别适合轻量级脚本嵌入。
17 |
18 | 这节要讲Lua和C/C++的交互——Lua通过C/C++导出的dll来调用。
19 |
20 | **LUA调用C文件中的函数方法**
21 |
22 | - C中注册函数
23 |
24 | lua_pushcfunction(l, l_sin); //注册在lua中使用的c函数l_sin
25 | lua_setglobal(l, "mysin"); //设定绑定到lua中的名字为mysin
26 |
27 | - C中提供的函数其定义要符合:
28 |
29 | typedef int function(lua_State *L)
30 |
31 | ### 准备工作
32 |
33 | 安装完Lua,需要在Visual Studio中配置Lua路径,使得你的编译器能搜寻到。关于VS2010的配置,见我的博文《VS2010 C++目录配置》一文。完成后新建一个Dll工程便可以了。
34 |
35 | 我们用一个在Lua中显示Windows对话框的程序来简要介绍一下,程序虽小,但五脏俱全。程序如下:
36 |
37 | // 将一些有用的Win32特性导出
38 | // 以便在Lua中使用
39 | extern "C"
40 | {
41 | #include
42 | #include
43 | #include
44 | #pragma comment(lib, "lua.lib")
45 | };
46 |
47 | #include
48 | #include
49 | using namespace std;
50 |
51 | static const char* const ERROR_ARGUMENT_COUNT = "参数数目错误!";
52 | static const char* const ERROR_ARGUMENT_TYPE = "参数类型错误!";
53 |
54 | // 发生错误,报告错误
55 | void ErrorMsg(lua_State* luaEnv, const char* const pszErrorInfo)
56 | {
57 | lua_pushstring(luaEnv, pszErrorInfo);
58 | lua_error(luaEnv);
59 | }
60 |
61 | // 检测函数调用参数个数是否正常
62 | void CheckParamCount(lua_State* luaEnv, int paramCount)
63 | {
64 | // lua_gettop获取栈中元素个数.
65 | if (lua_gettop(luaEnv) != paramCount)
66 | {
67 | ErrorMsg(luaEnv, ERROR_ARGUMENT_COUNT);
68 | }
69 | }
70 |
71 | // 显示Windows对话框.
72 | // @param [in] pszMessage string 1
73 | // @param [in] pszCaption string 2
74 | extern "C" int ShowMsgBox(lua_State* luaEnv)
75 | {
76 | const char* pszMessage = 0;
77 | const char* pszCaption = 0;
78 |
79 | // 检测参数个数是否正确.
80 | CheckParamCount(luaEnv, 2);
81 |
82 | // 提取参数.
83 | pszMessage = luaL_checkstring(luaEnv, 1);
84 | pszCaption = luaL_checkstring(luaEnv, 2);
85 |
86 | if (pszCaption && pszMessage)
87 | {
88 | ::MessageBox(
89 | NULL,
90 | pszMessage,
91 | pszCaption,
92 | MB_OK | MB_ICONINFORMATION
93 | );
94 | }
95 | else
96 | {
97 | ErrorMsg(luaEnv, ERROR_ARGUMENT_TYPE);
98 | }
99 |
100 | // 返回值个数为0个.
101 | return 0;
102 | }
103 |
104 | // 导出函数列表.
105 | static luaL_Reg luaLibs[] =
106 | {
107 | {"ShowMsgBox", ShowMsgBox},
108 | {NULL, NULL}
109 | };
110 |
111 | // Dll入口函数,Lua调用此Dll的入口函数.
112 | extern "C" __declspec(dllexport)
113 | int luaopen_WinFeature(lua_State* luaEnv)
114 | {
115 | const char* const LIBRARY_NAME = "WinFeature";
116 | luaL_register(luaEnv, LIBRARY_NAME, luaLibs);
117 |
118 | return 1;
119 | }
120 |
121 | ### 程序解析
122 |
123 | 首先我们包含Lua的头文件,并链入库文件。注意:Lua的头文件为C风格,所以用external “C”来含入。在此例中,我们最终的导出函数为“ShowMsgBox”。
124 |
125 | 每一个导出函数的格式都为:
126 |
127 | extern “C”int Export_Proc_Name(luaState* luaEnv);
128 |
129 | 其中,luaState*所指的结构中包含了Lua调用此Dll时必备的Lua环境。那么Lua向函数传递参数该怎么办呢?实际上是用luaL_check[type]函数来完成的。如下:
130 |
131 | const char* pHelloStr = luaL_checkstring(luaEnv, 1);
132 | double value = luaL_checknumber(luaEnv, 2);
133 | int ivalue = luaL_checkint(luaEnv, 3);
134 |
135 | luaL_check系列函数的第二个参数是Lua调用该函数时传递参数从坐到右的顺序(从1开始)。
136 |
137 | 然后我们看到,static的一个luaL_Reg的结构数组中包含了所有要导出的函数列表。最后通过luaopen_YourDllName的一个导出函数来完成一系列操作。YourDllName就是你最终的Dll的名字(不含扩展名)。因为你在Lua中调用此Dll时,Lua会根据此Dll名字找luaopen_YourDllName对应的函数,然后从此函数加载该Dll。
138 |
139 | Dll入口函数格式如下:
140 |
141 | extern "C" __declspec(dllexport)
142 | int luaopen_WinFeature(lua_State* luaEnv)
143 | {
144 | const char* const LIBRARY_NAME = "WinFeature";
145 | luaL_register(luaEnv, LIBRARY_NAME, luaLibs);
146 |
147 | return 1;
148 | }
149 |
150 | 我们通过luaL_register将LIBRARY_NAME对应的库名,以及luaL_Reg数组对应的导出列表来注册到lua_State*对应的Lua环境中。
151 |
152 | ### Lua调用
153 |
154 | 那么我们要如何调用该Dll呢?首先,把该Dll放到你的Lua能搜寻到的目录——当前目录、Lua安装目录下的clibs目录,然后通过require函数导入。
155 |
156 | 因为Lua中如果你的函数调用参数只有一个,并且该参数为字符串的话,函数调用时的括号是可以省略的,所以:require(“YourLibName”)和requir“YourLibName”都是合法的。我们把刚刚生成的WinFeature.dll文件拷贝到C盘下,然后在C盘启动Lua。示例如下:
157 |
158 | > require "WinFeature"
159 | > for k, v in pairs(WinFeature) do
160 | >> print(k, v)
161 | >> end
162 | ShowMsgBox functon:0028AB90
163 | >
164 |
165 | 可以看到,函数调用方式都是“包名.函数名”,而包名就是你的Dll的名字。我们可以用下面的方式查看一个包中的所有函数:
166 |
167 | for k, v in pairs(PackageName) do
168 | print(k, v)
169 | end
170 |
171 | 然后我们调用WinFeature.ShowMsgBox函数:
172 |
173 | > WinFeature.ShowMsgBox("Hello, this is a msgBox", "Tip")
174 |
175 | 会弹出对话框显示内容为"Hello, this is a msgBox"和标题为"Tip"。
176 |
177 | ### Lua堆栈详解
178 |
179 | 唔,那么lua_State结构如何管理Lua运行环境的呢?Lua又是如何将参数传递到C/C++函数的呢?C/C++函数又如何返回值给Lua呢?……这一切,都得从Lua堆栈讲起。
180 |
181 | Lua在和C/C++交互时,Lua运行环境维护着一份堆栈——不是传统意义上的堆栈,而是Lua模拟出来的。Lua与C/C++的数据传递都通过这份堆栈来完成,这份堆栈的代表就是lua_State*所指的那个结构。
182 |
183 | #### 堆栈结构解析
184 |
185 | 堆栈通过lua_push系列函数向堆栈中压入值,通过luaL_check系列从堆栈中获取值。而用luaL_check系列函数时传递的参数索引,比如我们调用WinFeature.ShowMsgBox(“Hello”, “Tip”)函数时,栈结构如下:
186 |
187 | 栈顶
188 | "Tip" 2或者-1
189 | "Hello" 1或者-2
190 | 栈底
191 |
192 | 其中,参数在栈中的索引为参数从左到右的索引(从1开始),栈顶元素索引也可以从-1记起。栈中元素个数可以用lua_gettop来获得,如果lua_gettop返回0,表示此栈为空。(lua_gettop这个函数名取得不怎么样!)
193 |
194 | #### 提取参数
195 |
196 | luaL_check系列函数在获取值的同时,检测这个值是不是符合我们所期望的类型,如果不是,则抛出异常。所有这个系列函数如下:
197 |
198 | luaL_checkany —— 检测任何值(可以为nil)
199 | luaL_checkint —— 检测一个值是否为number(double),并转换成int
200 | luaL_checkinteger —— 检测一个值是否为number(double),并转换成lua_Integer(prtdiff_t),在我的机子上,ptrdiff_t被定义为int
201 | luaL_checklong —— 检测一个值是否为number(double),并转换成long
202 | luaL_checklstring —— 检测一个值是否为string,并将字符串长度传递在[out]参数中返回
203 | luaL_checknumber —— 检测一个值是否为number(double)
204 | luaL_checkstring —— 检测一个值是否为string并返回
205 | luaL_checkudata —— 检测自定义类型
206 |
207 | #### 传递返回值
208 |
209 | 当我们要传递返回值给Lua时,可以用lua_push系列函数来完成。每一个导出函数都要返回一个int型整数,这个整数是你的导出函数的返回值的个数。而返回值通过lua_push系列函数压入栈中。比如一个Add函数:
210 |
211 | extern “C” int Add(lua_State* luaEnv)
212 | {
213 | CheckParamCount(luaEnv, 2);
214 |
215 | double left = luaL_checknumber(luaEnv, 1);
216 | double right = luaL_checknumber(luaEnv, 2);
217 |
218 | double result = left + right;
219 | lua_pushnumber(luaEnv, result);
220 |
221 | return 1;
222 | }
223 |
224 | 可以看出,我们用lua\_pushnumber把返回值压入栈,最后返回1——1代表返回值的个数。lua\_push系列函数如下:
225 |
226 | lua_pushboolean —— 压入一个bool值
227 | lua_pushcfunction —— 压入一个lua_CFunction类型的C函数指针
228 | lua_pushfstring —— 格式化一个string并返回,类似于sprintf
229 | lua_pushinteger —— 压入一个int
230 | lua_pushlightuserdata —— 压入自定义数据类型
231 | lua_pushliteral —— 压入一个字面值字符串
232 | lua_pushlstring —— 压入一个规定长度内的string
233 | lua_pushnil —— 压入nil值
234 | lua_pushnumber —— 压入lua_Number(double)值
235 | lua_pushstring —— 压入一个string
236 | lua_pushthread —— 压入一个所传递lua_State所对应的线程,如果此线程是主线程,则返回1
237 | lua_pushvalue —— 将所传递索引处的值复制一份压入栈顶
238 | lua_pushvfstring —— 类似lua_pushfstring
239 |
240 | 通过这些函数,我们可以灵活的使用C/C++的高性能特性,来导出函数供Lua调用。
241 |
242 | ## C/C++调用Lua脚本
243 |
244 | ### 简介
245 |
246 | **C调用LUA文件中的函数方法**
247 |
248 | lua_getglobal(L, ) //获取lua中的函数
249 | lua_push*() //调用lua_push系列函数,把输入参数压栈。例如lua_pushnumber(L, x)
250 | lua_pcall(L, , , <错误处理函数地址>)
251 |
252 | 例如:
253 |
254 | lua_settop(m_pLua,0);
255 | lua_getglobal(m_pLua,"mainlogic");
256 | lua_pushlstring(m_pLua,(char*)msg.getBuf(),msg.size());
257 | int ret = 0;
258 | ret = lua_pcall(m_pLua,1,4,0);
259 |
260 | 上一节介绍了如何在Lua中调用C/C++代码,本节介绍如何在C/C++中调用Lua脚本。本节介绍一个例子,通过Lua来生成一个XML格式的便签。便签格式如下:
261 |
262 |
263 |
264 | 发送方姓名
265 | 接收方姓名
266 | 发送时间
267 | 便签内容
268 |
269 |
270 | 我们通过C/C++来输入这些信息,然后让Lua来生成这样一个便签文件。
271 |
272 | ### Lua代码
273 |
274 | xmlHead = '\n'
275 |
276 | -- Open note file to wriet.
277 | function openNoteFile(fileName)
278 | return io.open(fileName, "w")
279 | end
280 |
281 | -- Close writed note file.
282 | function closeNoteFile(noteFile)
283 | noteFile:close()
284 | end
285 |
286 | function writeNestedLabel(ioChanel, label, nestCnt)
287 | if nestCnt == 0 then
288 | ioChanel:write(label)
289 | return
290 | end
291 |
292 | for i = 1, nestCnt do
293 | ioChanel:write("\t")
294 | end
295 |
296 | ioChanel:write(label)
297 | end
298 |
299 | function generateNoteXML(fromName, toName, msgContent)
300 | local noteFile = openNoteFile(fromName .. "_" .. toName .. ".xml")
301 | if not noteFile then
302 | return false
303 | end
304 |
305 | noteFile:write(xmlHead)
306 | noteFile:write("\n")
307 |
308 | local currNestCnt = 1
309 | writeNestedLabel(noteFile, "", currNestCnt)
310 | noteFile:write(fromName)
311 | writeNestedLabel(noteFile, "\n", 0)
312 |
313 | writeNestedLabel(noteFile, "", currNestCnt)
314 | noteFile:write(toName)
315 | writeNestedLabel(noteFile, "\n", 0)
316 |
317 | local sendTime = os.time()
318 | writeNestedLabel(noteFile, "", currNestCnt)
319 | noteFile:write(sendTime)
320 | writeNestedLabel(noteFile, "\n", 0)
321 |
322 | writeNestedLabel(noteFile, "", currNestCnt)
323 | noteFile:write(msgContent)
324 | writeNestedLabel(noteFile, "\n", 0)
325 |
326 | noteFile:write("\n")
327 | closeNoteFile(noteFile)
328 |
329 | return true
330 | end
331 |
332 | 我们通过openNoteFile和closeNoteFile来打开/关闭XML文件。generateNoteXML全局函数接受发送方姓名、接收方姓名、便签内容,生成一个XML便签文件。便签发送时间通过Lua标准库os.time()函数来获取。writeNestedLabel函数根据当前XML的缩进数目来规范XML输出格式。此文件很好理解,不再赘述。
333 |
334 | ### C++调用Lua脚本
335 |
336 | extern "C"
337 | {
338 | #include
339 | #include
340 | #include
341 | #pragma comment(lib, "lua.lib")
342 | };
343 |
344 | #include
345 | #include
346 | using namespace std;
347 |
348 | // 初始化Lua环境.
349 | lua_State* initLuaEnv()
350 | {
351 | lua_State* luaEnv = lua_open();
352 | luaopen_base(luaEnv);
353 | luaL_openlibs(luaEnv);
354 |
355 | return luaEnv;
356 | }
357 |
358 | // 加载Lua文件到Lua运行时环境中
359 | bool loadLuaFile(lua_State* luaEnv, const string& fileName)
360 | {
361 | int result = luaL_loadfile(luaEnv, fileName.c_str());
362 | if (result)
363 | {
364 | return false;
365 | }
366 |
367 | result = lua_pcall(luaEnv, 0, 0, 0);
368 | return result == 0;
369 | }
370 |
371 | // 获取全局函数
372 | lua_CFunction getGlobalProc(lua_State* luaEnv, const string& procName)
373 | {
374 | lua_getglobal(luaEnv, procName.c_str());
375 | if (!lua_iscfunction(luaEnv, 1))
376 | {
377 | return 0;
378 | }
379 |
380 | return lua_tocfunction(luaEnv, 1);
381 | }
382 |
383 | int main()
384 | {
385 | // 初始化Lua运行时环境.
386 | lua_State* luaEnv = initLuaEnv();
387 | if (!luaEnv)
388 | {
389 | return -1;
390 | }
391 |
392 | // 加载脚本到Lua环境中.
393 | if (!loadLuaFile(luaEnv, ".\\GenerateNoteXML.lua"))
394 | {
395 | cout<<"Load Lua File FAILED!"<>fromName;
406 |
407 | cout<<"\nEnter destination name:"<>toName;
409 |
410 | cout<<"\nEnter message content:"<
495 |
496 | Jack
497 | Joe
498 | 1317971623
499 | Hello, Can you help me?
500 |
501 |
502 | ## C 作为动态库文件被 Lua 调用
503 |
504 | ### C/C++中的入口函数定义
505 |
506 | 一定是要定义成:
507 | luaopen\_(dll或so文件的文件名称),(dll或so文件的文件名称)必须和dll或so文件名称保持一致。
508 |
509 | 例如(C++ windows情况):
510 |
511 | #ifdef _WIN32
512 | #define _EXPORT extern "C" __declspec(dllexport)
513 | #else //unix/linux
514 | #define _EXPORT extern "C"
515 | #endif
516 | _EXPORT int luaopen_capi_mytestlib(lua_State *L)
517 | {
518 | struct luaL_reg driver[] = {
519 | {"average", average1},
520 | {NULL, NULL},};
521 | luaL_register(L, "mylib", driver);
522 | //luaL_openlib(L, "mylib", driver, 0);
523 | return 1;
524 | }
525 |
526 | ### 动态库要供LUA调用的function
527 |
528 | 其定义要符合:
529 |
530 | typedef int function(lua_State *L)
531 |
532 | ### 在动态库调用LUA注册
533 |
534 | 将要调用的函数放到这个结构体里:
535 |
536 | struct luaL_Reg lib[] ={}
537 |
538 | 在动态库的入口函数里调用luaL_register将这个结构体注册,在这个入口函数注册结构体时,要注册成:
539 |
540 | luaL_register(L,"XXX",lib);
541 |
542 | ### 在写脚本的时候,使用require("XXX")
543 |
544 | 就是入口函数的luaopen_后面的XXX,注意大小写敏感
545 |
546 | ### 编译生成的动态库命令成XXX.so或XXX.dll(win)
547 |
548 | 同入口函数的luaopen_后面的XXX一致
549 |
550 | **示例:**
551 |
552 | **C文件如下:**
553 |
554 | #include
555 | #include "lua/lua.h"
556 | #include "lua/lualib.h"
557 | #include "lua/lauxlib.h"
558 | static int add(lua_State *L)
559 | {
560 | int a,b,c;
561 | a = lua_tonumber(L,1);
562 | b = lua_tonumber(L,2);
563 | c = a+b;
564 | lua_pushnumber(L,c);
565 | printf("test hello!!!\r\n");
566 | return 1;
567 | }
568 | static const struct luaL_Reg lib[] =
569 | {
570 | {"testadd",add},
571 | {NULL,NULL}
572 | };
573 |
574 | int luaopen_testlib(lua_State *L)
575 | {
576 | luaL_register(L,"testlib",lib);
577 | return 1;
578 | }
579 |
580 | 编译: gcc test.c -fPIC -shared -o testlib.so
581 |
582 | **lua脚本编写:**
583 |
584 | require("testlib")
585 | c = testlib.testadd(15,25)
586 | print("The result is ",c);
587 |
588 | **示例:**
589 |
590 | int lua_createmeta (lua_State *L, const char *name, const luaL_reg *methods) {
591 | if (!luaL_newmetatable (L, name))
592 | return 0;
593 |
594 | luaL_openlib (L, NULL, methods, 0);
595 |
596 | lua_pushliteral (L, "__gc");
597 | lua_pushcfunction (L, methods->func);
598 | lua_settable (L, -3);
599 | lua_pushliteral (L, "__index");
600 | lua_pushvalue (L, -2);
601 | lua_settable (L, -3);
602 | lua_pushliteral (L, "__metatable");
603 | lua_pushliteral (L, "you're not allowed to get this metatable");
604 | lua_settable (L, -3);
605 | return 1;
606 | }
607 |
608 | 示例中的luaopen_testlib函数替换为:
609 |
610 | int luaopen_testlib(lua_State *L)
611 | {
612 | lua_createmeta(L,"testlib",lib);
613 | return 1;
614 | }
615 |
616 | ## 导航
617 | * [目录](00.md)
618 | * 上一章:[常用的 C API](07.md)
619 | * 下一章:[编译 Lua 字节码](09.md)
--------------------------------------------------------------------------------
/09.md:
--------------------------------------------------------------------------------
1 | # Lua - 编译 Lua 字节码
2 |
3 | 在使用 lua 脚本作为游戏逻辑开发的时候,特别是游戏前端开发,例如 Cocos2d-x 可以绑定 lua 来做 2D 游戏开发,现在 Unity 前端的 Lua 绑定方案都有很多,使用 lua 脚本来开发已经很普遍的了,为了方式代码被获取,一般都是需要把 lua 编译为字节码后再打包游戏的。
4 |
5 | 使用 lua jit
6 |
7 | ```bash
8 | luajit -b src.lua out.lua
9 | ```
10 |
11 | 使用原生 lua 解释器
12 |
13 | ```bash
14 | luac -o out.luac src.lua
15 | ```
16 |
17 | * [目录](00.md)
18 | * 上一章:[Lua 与 C/C++ 交互](08.md)
19 | * 下一章:[LuaJIT 介绍](10.md)
--------------------------------------------------------------------------------
/10.md:
--------------------------------------------------------------------------------
1 | # Lua - LuaJIT 介绍
2 |
3 | luajit(www.luajit.org)是 lua 的一个Just-In-Time也就是运行时编译器。
4 |
5 | ## 什么是 JIT
6 |
7 | JIT = Just In Time即时编译,是动态编译的一种形式,是一种优化虚拟机运行的技术。
8 | 程序运行通常有两种方式,一种是静态编译,一种是动态解释,即时编译混合了这二者。Java和.Net/mono中都使用了这种技术。
9 | 然而IOS中禁止使用(不是针对JIT,而是所有的动态编译都不支持)!
10 |
11 | ## 为什么要使用JIT
12 |
13 | ### 解释执行
14 |
15 | - 效率低
16 | - 代码暴露
17 |
18 | ### 静态编译
19 |
20 | - 不够灵活,无法热更新
21 | - 平台兼容性差
22 |
23 | ### JIT
24 |
25 | - 效率:高于解释执行,低于静态编译
26 |
27 | - 安全性:一般都会先转换成字节码
28 |
29 | - 热更新:无论源码还是字节码本质上都是资源文件
30 |
31 | - 兼容性:虚拟机会处理平台差异,对用户透明
32 |
33 | ## LuaJIT
34 |
35 | **vs. Lua**
36 | Lua主要由以下三部分组成:
37 |
38 | 1. 语法实现
39 | 2. 库函数
40 | 3. 字节码
41 |
42 | LuaJIT主要由以下四部分组成:
43 |
44 | 1. 语法实现
45 | 2. Trace JIT编译器
46 | 3. 库函数
47 | 1. 原生库++(强化过的原生库)
48 | 2. bit
49 | 3. ffi
50 | 4. jit
51 | 4. 字节码
52 |
53 | 注:最新luajit对应 lua5.1.5.
54 |
55 | ## trace jit编译器
56 |
57 | 
58 |
59 | 与jvmjit大致相同。
60 | 所谓trace便是一段线性的字节码序列。热点trace被编译成机器码,非热点trace解释执行。
61 | 注:并不是所有的代码都能被JIT。
62 |
63 | ## bytecode
64 |
65 | bytecode基本上可以认为是虚拟机的指令码(“基本上”是因为luajit使用了uleb128)。
66 |
67 | 优点:
68 |
69 | 1. 减少文件大小。
70 | 2. 生成函数原型更快。
71 | 3. 增加被破解的难度。
72 | 4. 对源代码轻微的优化。
73 | 5. 库函数和可执行文件
74 |
75 | 编译步骤分三步走:
76 |
77 | 
78 |
79 | minilua:实际上是lua原生代码的一个子集,用来执行lua脚本并生成平台相关的指令。
80 | buildvm:用来生成操作码/库函数到汇编/C语言的映射,用来jit编译。
81 | lib
82 | exec:可以执行lua代码活转换字节码。
83 |
84 | ## 编码
85 |
86 | 命令行执行
87 |
88 | ```bash
89 | luajit –b
90 | ```
91 |
92 | 虚拟机会判断是否是字节码,所以无需做额外的操作。
93 | 另外,可以混用,即:一部分文件编成字节码,另一部分保持源代码。
94 |
95 | iSO64位报错问题
96 | Cannot load incompatible bytecode!
97 | 这个错是因为在luajit里使用gcr用来比较对象指针,在64位环境下只有47位有效值(默认用户内存不会超过128T)。其余17位中有4位保存对象类型,即一段内存中保存了两条信息。所以在函数栈操作中有些地方需要一个空值占位。因为字节码直接反映了函数栈操作,所以64位和32位字节码不同。
98 |
99 |
100 |
101 | * [目录](00.md)
102 | * 上一章:[编译 Lua 字节码](09.md)
103 | * 下一章:[Lua 5.1 程序接口](11.md)
--------------------------------------------------------------------------------
/11.md:
--------------------------------------------------------------------------------
1 | # 附录一 Lua 5.1 程序接口
2 |
3 | ## Lua functions
4 | _G
5 | _VERSION
6 |
7 | assert
8 | collectgarbage
9 | dofile
10 | error
11 | getfenv
12 | getmetatable
13 | ipairs
14 | load
15 | loadfile
16 | loadstring
17 | module
18 | next
19 | pairs
20 | pcall
21 | print
22 | rawequal
23 | rawget
24 | rawset
25 | require
26 | select
27 | setfenv
28 | setmetatable
29 | tonumber
30 | tostring
31 | type
32 | unpack
33 | xpcall
34 |
35 | coroutine.create
36 | coroutine.resume
37 | coroutine.running
38 | coroutine.status
39 | coroutine.wrap
40 | coroutine.yield
41 |
42 | debug.debug
43 | debug.getfenv
44 | debug.gethook
45 | debug.getinfo
46 | debug.getlocal
47 | debug.getmetatable
48 | debug.getregistry
49 | debug.getupvalue
50 | debug.setfenv
51 | debug.sethook
52 | debug.setlocal
53 | debug.setmetatable
54 | debug.setupvalue
55 | debug.traceback
56 |
57 | file:close
58 | file:flush
59 | file:lines
60 | file:read
61 | file:seek
62 | file:setvbuf
63 | file:write
64 |
65 | io.close
66 | io.flush
67 | io.input
68 | io.lines
69 | io.open
70 | io.output
71 | io.popen
72 | io.read
73 | io.stderr
74 | io.stdin
75 | io.stdout
76 | io.tmpfile
77 | io.type
78 | io.write
79 |
80 | math.abs
81 | math.acos
82 | math.asin
83 | math.atan
84 | math.atan2
85 | math.ceil
86 | math.cos
87 | math.cosh
88 | math.deg
89 | math.exp
90 | math.floor
91 | math.fmod
92 | math.frexp
93 | math.huge
94 | math.ldexp
95 | math.log
96 | math.log10
97 | math.max
98 | math.min
99 | math.modf
100 | math.pi
101 | math.pow
102 | math.rad
103 | math.random
104 | math.randomseed
105 | math.sin
106 | math.sinh
107 | math.sqrt
108 | math.tan
109 | math.tanh
110 |
111 | os.clock
112 | os.date
113 | os.difftime
114 | os.execute
115 | os.exit
116 | os.getenv
117 | os.remove
118 | os.rename
119 | os.setlocale
120 | os.time
121 | os.tmpname
122 |
123 | package.cpath
124 | package.loaded
125 | package.loaders
126 | package.loadlib
127 | package.path
128 | package.preload
129 | package.seeall
130 |
131 | string.byte
132 | string.char
133 | string.dump
134 | string.find
135 | string.format
136 | string.gmatch
137 | string.gsub
138 | string.len
139 | string.lower
140 | string.match
141 | string.rep
142 | string.reverse
143 | string.sub
144 | string.upper
145 |
146 | table.concat
147 | table.insert
148 | table.maxn
149 | table.remove
150 | table.sort
151 |
152 | ## C API
153 |
154 | lua_Alloc
155 | lua_CFunction
156 | lua_Debug
157 | lua_Hook
158 | lua_Integer
159 | lua_Number
160 | lua_Reader
161 | lua_State
162 | lua_Writer
163 |
164 | lua_atpanic
165 | lua_call
166 | lua_checkstack
167 | lua_close
168 | lua_concat
169 | lua_cpcall
170 | lua_createtable
171 | lua_dump
172 | lua_equal
173 | lua_error
174 | lua_gc
175 | lua_getallocf
176 | lua_getfenv
177 | lua_getfield
178 | lua_getglobal
179 | lua_gethook
180 | lua_gethookcount
181 | lua_gethookmask
182 | lua_getinfo
183 | lua_getlocal
184 | lua_getmetatable
185 | lua_getstack
186 | lua_gettable
187 | lua_gettop
188 | lua_getupvalue
189 | lua_insert
190 | lua_isboolean
191 | lua_iscfunction
192 | lua_isfunction
193 | lua_islightuserdata
194 | lua_isnil
195 | lua_isnone
196 | lua_isnoneornil
197 | lua_isnumber
198 | lua_isstring
199 | lua_istable
200 | lua_isthread
201 | lua_isuserdata
202 | lua_lessthan
203 | lua_load
204 | lua_newstate
205 | lua_newtable
206 | lua_newthread
207 | lua_newuserdata
208 | lua_next
209 | lua_objlen
210 | lua_pcall
211 | lua_pop
212 | lua_pushboolean
213 | lua_pushcclosure
214 | lua_pushcfunction
215 | lua_pushfstring
216 | lua_pushinteger
217 | lua_pushlightuserdata
218 | lua_pushliteral
219 | lua_pushlstring
220 | lua_pushnil
221 | lua_pushnumber
222 | lua_pushstring
223 | lua_pushthread
224 | lua_pushvalue
225 | lua_pushvfstring
226 | lua_rawequal
227 | lua_rawget
228 | lua_rawgeti
229 | lua_rawset
230 | lua_rawseti
231 | lua_register
232 | lua_remove
233 | lua_replace
234 | lua_resume
235 | lua_setallocf
236 | lua_setfenv
237 | lua_setfield
238 | lua_setglobal
239 | lua_sethook
240 | lua_setlocal
241 | lua_setmetatable
242 | lua_settable
243 | lua_settop
244 | lua_setupvalue
245 | lua_status
246 | lua_toboolean
247 | lua_tocfunction
248 | lua_tointeger
249 | lua_tolstring
250 | lua_tonumber
251 | lua_topointer
252 | lua_tostring
253 | lua_tothread
254 | lua_touserdata
255 | lua_type
256 | lua_typename
257 | lua_upvalueindex
258 | lua_xmove
259 | lua_yield
260 |
261 | ## auxiliary library
262 |
263 | luaL_Buffer
264 | luaL_Reg
265 |
266 | luaL_addchar
267 | luaL_addlstring
268 | luaL_addsize
269 | luaL_addstring
270 | luaL_addvalue
271 | luaL_argcheck
272 | luaL_argerror
273 | luaL_buffinit
274 | luaL_callmeta
275 | luaL_checkany
276 | luaL_checkint
277 | luaL_checkinteger
278 | luaL_checklong
279 | luaL_checklstring
280 | luaL_checknumber
281 | luaL_checkoption
282 | luaL_checkstack
283 | luaL_checkstring
284 | luaL_checktype
285 | luaL_checkudata
286 | luaL_dofile
287 | luaL_dostring
288 | luaL_error
289 | luaL_getmetafield
290 | luaL_getmetatable
291 | luaL_gsub
292 | luaL_loadbuffer
293 | luaL_loadfile
294 | luaL_loadstring
295 | luaL_newmetatable
296 | luaL_newstate
297 | luaL_openlibs
298 | luaL_optint
299 | luaL_optinteger
300 | luaL_optlong
301 | luaL_optlstring
302 | luaL_optnumber
303 | luaL_optstring
304 | luaL_prepbuffer
305 | luaL_pushresult
306 | luaL_ref
307 | luaL_register
308 | luaL_typename
309 | luaL_typerror
310 | luaL_unref
311 | luaL_where
312 |
313 | ## 导航
314 | * [目录](00.md)
315 | * 上一章:[LuaJIT 介绍](10.md)
316 | * 下一章:[附录二 Lua 5.2 程序接口](12.md)
--------------------------------------------------------------------------------
/12.md:
--------------------------------------------------------------------------------
1 | # 附录二 Lua 5.2 程序接口
2 |
3 | ## Lua functions
4 |
5 | _G
6 | _VERSION
7 |
8 | assert
9 | collectgarbage
10 | dofile
11 | error
12 | getmetatable
13 | ipairs
14 | loadfile
15 | load
16 | next
17 | pairs
18 | pcall
19 | print
20 | rawequal
21 | rawget
22 | rawlen
23 | rawset
24 | require
25 | select
26 | setmetatable
27 | tonumber
28 | tostring
29 | type
30 | xpcall
31 |
32 | bit32.arshift
33 | bit32.band
34 | bit32.bnot
35 | bit32.bor
36 | bit32.btest
37 | bit32.bxor
38 | bit32.extract
39 | bit32.lrotate
40 | bit32.lshift
41 | bit32.replace
42 | bit32.rrotate
43 | bit32.rshift
44 |
45 | coroutine.create
46 | coroutine.resume
47 | coroutine.running
48 | coroutine.status
49 | coroutine.wrap
50 | coroutine.yield
51 |
52 | debug.debug
53 | debug.getuservalue
54 | debug.gethook
55 | debug.getinfo
56 | debug.getlocal
57 | debug.getmetatable
58 | debug.getregistry
59 | debug.getupvalue
60 | debug.setuservalue
61 | debug.sethook
62 | debug.setlocal
63 | debug.setmetatable
64 | debug.setupvalue
65 | debug.traceback
66 | debug.upvalueid
67 | debug.upvaluejoin
68 |
69 | file:close
70 | file:flush
71 | file:lines
72 | file:read
73 | file:seek
74 | file:setvbuf
75 | file:write
76 |
77 | io.close
78 | io.flush
79 | io.input
80 | io.lines
81 | io.open
82 | io.output
83 | io.popen
84 | io.read
85 | io.stderr
86 | io.stdin
87 | io.stdout
88 | io.tmpfile
89 | io.type
90 | io.write
91 |
92 | math.abs
93 | math.acos
94 | math.asin
95 | math.atan
96 | math.atan2
97 | math.ceil
98 | math.cos
99 | math.cosh
100 | math.deg
101 | math.exp
102 | math.floor
103 | math.fmod
104 | math.frexp
105 | math.huge
106 | math.ldexp
107 | math.log
108 | math.max
109 | math.min
110 | math.modf
111 | math.pi
112 | math.pow
113 | math.rad
114 | math.random
115 | math.randomseed
116 | math.sin
117 | math.sinh
118 | math.sqrt
119 | math.tan
120 | math.tanh
121 |
122 | os.clock
123 | os.date
124 | os.difftime
125 | os.execute
126 | os.exit
127 | os.getenv
128 | os.remove
129 | os.rename
130 | os.setlocale
131 | os.time
132 | os.tmpname
133 |
134 | package.config
135 | package.cpath
136 | package.loaded
137 | package.loadlib
138 | package.path
139 | package.preload
140 | package.searchers
141 | package.searchpath
142 |
143 | string.byte
144 | string.char
145 | string.dump
146 | string.find
147 | string.format
148 | string.gmatch
149 | string.gsub
150 | string.len
151 | string.lower
152 | string.match
153 | string.rep
154 | string.reverse
155 | string.sub
156 | string.upper
157 |
158 | table.concat
159 | table.insert
160 | table.pack
161 | table.remove
162 | table.sort
163 | table.unpack
164 |
165 | ## C API
166 |
167 | lua_Alloc
168 | lua_CFunction
169 | lua_Debug
170 | lua_Hook
171 | lua_Integer
172 | lua_Number
173 | lua_Reader
174 | lua_State
175 | lua_Unsigned
176 | lua_Writer
177 |
178 | lua_absindex
179 | lua_arith
180 | lua_atpanic
181 | lua_call
182 | lua_callk
183 | lua_checkstack
184 | lua_close
185 | lua_compare
186 | lua_concat
187 | lua_copy
188 | lua_createtable
189 | lua_dump
190 | lua_error
191 | lua_gc
192 | lua_getallocf
193 | lua_getctx
194 | lua_getfield
195 | lua_getglobal
196 | lua_gethook
197 | lua_gethookcount
198 | lua_gethookmask
199 | lua_getinfo
200 | lua_getlocal
201 | lua_getmetatable
202 | lua_getstack
203 | lua_gettable
204 | lua_gettop
205 | lua_getupvalue
206 | lua_getuservalue
207 | lua_insert
208 | lua_isboolean
209 | lua_iscfunction
210 | lua_isfunction
211 | lua_islightuserdata
212 | lua_isnil
213 | lua_isnone
214 | lua_isnoneornil
215 | lua_isnumber
216 | lua_isstring
217 | lua_istable
218 | lua_isthread
219 | lua_isuserdata
220 | lua_len
221 | lua_load
222 | lua_newstate
223 | lua_newtable
224 | lua_newthread
225 | lua_newuserdata
226 | lua_next
227 | lua_pcall
228 | lua_pcallk
229 | lua_pop
230 | lua_pushboolean
231 | lua_pushcclosure
232 | lua_pushcfunction
233 | lua_pushfstring
234 | lua_pushinteger
235 | lua_pushlightuserdata
236 | lua_pushliteral
237 | lua_pushlstring
238 | lua_pushnil
239 | lua_pushnumber
240 | lua_pushstring
241 | lua_pushthread
242 | lua_pushvalue
243 | lua_pushvfstring
244 | lua_rawequal
245 | lua_rawget
246 | lua_rawgeti
247 | lua_rawlen
248 | lua_rawset
249 | lua_rawseti
250 | lua_rawgetp
251 | lua_rawsetp
252 | lua_register
253 | lua_remove
254 | lua_replace
255 | lua_resume
256 | lua_setallocf
257 | lua_setfield
258 | lua_setglobal
259 | lua_sethook
260 | lua_setlocal
261 | lua_setmetatable
262 | lua_settable
263 | lua_settop
264 | lua_setupvalue
265 | lua_setuservalue
266 | lua_status
267 | lua_toboolean
268 | lua_tocfunction
269 | lua_tointeger
270 | lua_tointegerx
271 | lua_tolstring
272 | lua_tonumber
273 | lua_tonumberx
274 | lua_topointer
275 | lua_tostring
276 | lua_tothread
277 | lua_tounsigned
278 | lua_tounsignedx
279 | lua_touserdata
280 | lua_type
281 | lua_typename
282 | lua_upvalueid
283 | lua_upvalueindex
284 | lua_upvaluejoin
285 | lua_version
286 | lua_xmove
287 | lua_yield
288 | lua_yieldk
289 |
290 | ## auxiliary library
291 |
292 | luaL_Buffer
293 | luaL_Reg
294 |
295 | luaL_addchar
296 | luaL_addlstring
297 | luaL_addsize
298 | luaL_addstring
299 | luaL_addvalue
300 | luaL_argcheck
301 | luaL_argerror
302 | luaL_buffinit
303 | luaL_buffinitsize
304 | luaL_callmeta
305 | luaL_checkany
306 | luaL_checkinteger
307 | luaL_checkint
308 | luaL_checklong
309 | luaL_checklstring
310 | luaL_checknumber
311 | luaL_checkoption
312 | luaL_checkstack
313 | luaL_checkstring
314 | luaL_checktype
315 | luaL_checkudata
316 | luaL_checkunsigned
317 | luaL_checkversion
318 | luaL_dofile
319 | luaL_dostring
320 | luaL_error
321 | luaL_execresult
322 | luaL_fileresult
323 | luaL_getmetafield
324 | luaL_getmetatable
325 | luaL_getsubtable
326 | luaL_gsub
327 | luaL_len
328 | luaL_loadbuffer
329 | luaL_loadbufferx
330 | luaL_loadfile
331 | luaL_loadfilex
332 | luaL_loadstring
333 | luaL_newlib
334 | luaL_newlibtable
335 | luaL_newmetatable
336 | luaL_newstate
337 | luaL_openlibs
338 | luaL_optinteger
339 | luaL_optint
340 | luaL_optlong
341 | luaL_optlstring
342 | luaL_optnumber
343 | luaL_optstring
344 | luaL_optunsigned
345 | luaL_prepbuffer
346 | luaL_prepbuffsize
347 | luaL_pushresult
348 | luaL_pushresultsize
349 | luaL_ref
350 | luaL_requiref
351 | luaL_setfuncs
352 | luaL_setmetatable
353 | luaL_testudata
354 | luaL_tolstring
355 | luaL_traceback
356 | luaL_typename
357 | luaL_unref
358 | luaL_where
359 |
360 | ## 导航
361 | * [目录](00.md)
362 | * 上一章:[附录一 Lua 5.1 程序接口](11.md)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Andy Cai
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 《Lua编程入门》
2 |
3 | 学习Lua过程中的一些笔记整理
4 |
5 | ## 开始阅读
6 |
7 | [开始阅读吧](<00.md>)
8 |
9 | 1. [Lua 基础知识](01.md)
10 | 2. [环境与模块](02.md)
11 | 3. [函数与面向对象](03.md)
12 | 4. [标准库](04.md)
13 | 5. [协程 Coroutine](05.md)
14 | 6. [Table 数据结构](06.md)
15 | 7. [常用的 C API](07.md)
16 | 8. [Lua 与 C/C++ 交互](08.md)
17 | 9. [编译 Lua 字节码](09.md)
18 | 10. [LuaJIT 介绍](10.md)
19 | 11. [附录一 Lua 5.1 程序接口](11.md)
20 | 12. [附录二 Lua 5.2 程序接口](12.md)
--------------------------------------------------------------------------------
/img/img0001.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andycai/luaprimer/138cb58366880051e414bb71346050ba845bc1b0/img/img0001.jpg
--------------------------------------------------------------------------------
/img/img0002.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andycai/luaprimer/138cb58366880051e414bb71346050ba845bc1b0/img/img0002.jpg
--------------------------------------------------------------------------------