├── .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", 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 | ![img](./img/img0002.jpg) 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 | ![img](./img/img0001.jpg) 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 --------------------------------------------------------------------------------