Lua 5.3 参考手册
http://www.runoob.com/manual/lua53doc/manual.html
Lua
Lua 是一门扩展式程序设计语言,被设计成支持通用过程式编程,并有相关数据描述设施。 同时对面向对象编程、函数式编程和数据驱动式编程也提供了良好的支持。 它作为一个强大、轻量的嵌入式脚本语言,可供任何需要的程序使用。 Lua 由 clean C(标准 C 和 C++ 间共通的子集) 实现成一个库。
作为一门扩展式语言,Lua 没有 "main" 程序的概念: 它只能 嵌入 一个宿主程序中工作, 该宿主程序被称为 被嵌入程序 或者简称 宿主 。 宿主程序可以调用函数执行一小段 Lua 代码,可以读写 Lua 变量,可以注册 C 函数让 Lua 代码调用。 依靠 C 函数,Lua 可以共享相同的语法框架来定制编程语言,从而适用不同的领域。 Lua 的官方发布版包含一个叫做 lua 的宿主程序示例, 它是一个利用 Lua 库实现的完整独立的 Lua 解释器,可用于交互式应用或批处理。
基本数据类型
Lua 是一门动态类型语言。 这意味着变量没有类型;只有值才有类型。 语言中不设类型定义。 所有的值携带自己的类型。
Lua 中有八种基本类型: nil、boolean、number、string、function、userdata、 thread 和 table。
- Nil 是值 nil 的类型, 其主要特征就是和其它值区别开;通常用来表示一个有意义的值不存在时的状态。
- Boolean 是 false 与 true 两个值的类型。 nil 和 false 都会导致条件判断为假; 而其它任何值都表示为真。
- String 表示一个不可变的字节序列。Lua 的字符串与编码无关; 它不关心字符串中具体内容。
- Number 代表了整数和实数(浮点数)。对于何时使用哪种内部形式,Lua 有明确的规则, 但它也按需作自动转换。 因此,程序员多数情况下可以选择忽略整数与浮点数之间的差异或者假设完全控制每个数字的内部表现方式。 标准 Lua 使用 64 位整数和双精度(64 位)浮点数。
- Function 函数类型,在lua中可以做全局或局部函数,也可做Table的域函数。
- UserData 类型允许将 C 中的数据保存在 Lua 变量中。 用户数据类型的值是一个内存块, 有两种用户数据: 完全用户数据 ,指一块由 Lua 管理的内存对应的对象; 轻量用户数据 ,则指一个简单的 C 指针。 用户数据在 Lua 中除了赋值与相等性判断之外没有其他预定义的操作。
- Thread 类型表示了一个独立的执行序列,被用于实现协程。 Lua 的线程与操作系统的线程毫无关系。 Lua 为所有的系统,包括那些不支持原生线程的系统,提供了协程支持。
- Table 是一个关联数组, 也就是说,这个数组不仅仅以数字做索引,除了 nil 和 NaN 之外的所有 Lua 值 都可以做索引。 (Not a Number 是一个特殊的数字,它用于表示未定义或表示不了的运算结果,比如
0/0
。) 表可以是 异构 的; 也就是说,表内可以包含任何类型的值( nil 除外)。 任何键的值若为 nil 就不会被记入表结构内部。 换言之,对于表内不存在的键,都对应着值 nil 。
String
这个库提供了字符串处理的通用函数。 例如字符串查找、子串、模式匹配等。 当在 Lua 中对字符串做索引时,第一个字符从 1 开始计算(而不是 C 里的 0 )。 索引可以是负数,它指从字符串末尾反向解析。 即,最后一个字符在 -1 位置处,等等。
字符串库中的所有函数都在表 string
中。 它还将其设置为字符串元表的 __index
域。 因此,你可以以面向对象的形式使用字符串函数。 例如,string.byte(s,i)
可以写成 s:byte(i)
。
string.byte (s [, i [, j]])
返回字符 s[i]
, s[i+1]
, ... ,s[j]
的内部数字编码。 i
的默认值是 1 ; j
的默认值是 i
。 这些索引以函数 string.sub
的规则修正。
string.char (···)
接收零或更多的整数。 返回和参数数量相同长度的字符串。 其中每个字符的内部编码值等于对应的参数值。
string.dump (function [, strip])
返回包含有以二进制方式表示的(一个 二进制代码块 )指定函数的字符串。 之后可以用 load
调用这个字符串获得 该函数的副本(但是绑定新的上值)。 如果 strip
为真值, 二进制代码块不携带该函数的调试信息 (局部变量名,行号,等等。)。
string.find (s, pattern [, init [, plain]])
查找第一个字符串 s
中匹配到的 pattern
(参见 §6.4.1)。 如果找到一个匹配,find
会返回 s
中关于它起始及终点位置的索引; 否则,返回 nil。 第三个可选数字参数 init
指明从哪里开始搜索; 默认值为 1 ,同时可以是负值。 第四个可选参数 plain
为 true 时, 关闭模式匹配机制。 此时函数仅做直接的 “查找子串”的操作, 而 pattern
中没有字符被看作魔法字符。 注意,如果给定了 plain
,就必须写上 init
。
string.format (formatstring, ···)
返回不定数量参数的格式化版本, 格式化串为第一个参数(必须是一个字符串)。 格式化字符串遵循 ISO C 函数 sprintf
的规则。 不同点在于选项 *
, h
, L
, l
, n
, p
不支持, 另外还增加了一个选项 q
。 q
选项将一个字符串格式化为两个双引号括起,对内部字符做恰当的转义处理的字符串。 该字符串可以安全的被 Lua 解释器读回来。
例如:
选项 A
and a
(如果有的话), E
, e
, f
, G
, and g
都期待一个对应的数字参数。 选项 c
, d
, i
, o
, u
, X
, and x
则期待一个整数。 选项 q
期待一个字符串; 选项 s
期待一个没有内嵌零的字符串。 如果选项 s
对应的参数不是字符串,它会用和 tostring
一致的规则转换成字符串。
string.gmatch (s, pattern)
返回一个迭代器函数。 每次调用这个函数都会继续以 pattern
(参见 §6.4.1) 对 s
做匹配,并返回所有捕获到的值。 如果 pattern
中没有指定捕获,则每次捕获整个 pattern
。
下面这个例子会循环迭代字符串 s
中所有的单词, 并逐行打印:
s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do
print(w)
end
下一个例子从指定的字符串中收集所有的键值对 key=value
置入一张表:
t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
t[k] = v
end
string.gsub (s, pattern, repl [, n])
将字符串 s
中,所有的(或是在 n
给出时的前 n
个) pattern
(参见 §6.4.1)都替换成 repl
,并返回其副本。 repl
可以是字符串、表、或函数。 gsub
还会在第二个返回值返回一共发生了多少次匹配。 gsub
这个名字来源于 Global SUBstitution 。
如果 repl
是一个字符串,那么把这个字符串作为替换品。 字符 %
是一个转义符: repl
中的所有形式为 %*d*
的串表示 第 d 个捕获到的子串,d 可以是 1 到 9 。 串 %0
表示整个匹配。 串 %%
表示单个 %
。
如果 repl
是张表,每次匹配时都会用第一个捕获物作为键去查这张表。
如果 repl
是个函数,则在每次匹配发生时都会调用这个函数。 所有捕获到的子串依次作为参数传入。
任何情况下,模板中没有设定捕获都看成是捕获整个模板。
如果表的查询结果或函数的返回结果是一个字符串或是个数字, 都将其作为替换用串; 而在返回 false 或 nil 时不作替换 (即保留匹配前的原始串)。
这里有一些用例:
x = string.gsub("hello world", "(%w+)", "%1 %1")
--> x="hello hello world world"
x = string.gsub("hello world", "%w+", "%0 %0", 1)
--> x="hello hello world"
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
--> x="world hello Lua from"
x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
--> x="home = /home/roberto, user = roberto"
x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
return load(s)()
end)
--> x="4+5 = 9"
local t = {name="lua", version="5.3"}
x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
--> x="lua-5.3.tar.gz"
string.len (s)
接收一个字符串,返回其长度。 空串 ""
的长度为 0 。 内嵌零也统计在内,因此 "a\000bc\000"
的长度为 5 。
string.lower (s)
接收一个字符串,将其中的大写字符都转为小写后返回其副本。 其它的字符串不会更改。 对大写字符的定义取决于当前的区域设置。
string.match (s, pattern [, init])
在字符串 s
中找到第一个能用 pattern
(参见 §6.4.1)匹配到的部分。 如果能找到,match
返回其中的捕获物; 否则返回 nil 。 如果 pattern
中未指定捕获, 返回整个 pattern
捕获到的串。 第三个可选数字参数 init
指明从哪里开始搜索; 它默认为 1 且可以是负数。
string.pack (fmt, v1, v2, ···)
返回一个打包了(即以二进制形式序列化) v1
, v2
等值的二进制字符串。 字符串 fmt
为打包格式(参见 §6.4.2)。
string.packsize (fmt)
返回以指定格式用 string.pack
打包的字符串的长度。 格式化字符串中不可以有变长选项 's
' 或 'z
' (参见 §6.4.2)。
string.rep (s, n [, sep])
返回 n
个字符串 s
以字符串 sep
为分割符连在一起的字符串。 默认的 sep
值为空字符串(即没有分割符)。 如果 n
不是正数则返回空串。
string.reverse (s)
返回字符串 s
的翻转串。
string.sub (s, i [, j])
返回 s
的子串, 该子串从 i
开始到 j
为止; i
和 j
都可以为负数。 如果不给出 j
,就当它是 -1 (和字符串长度相同)。 特别是, 调用 string.sub(s,1,j)
可以返回 s
的长度为 j
的前缀串, 而 string.sub(s, -i)
返回长度为 i
的后缀串。
如果在对负数索引转义后 i
小于 1 的话,就修正回 1 。 如果 j
比字符串的长度还大,就修正为字符串长度。 如果在修正之后,i
大于 j
, 函数返回空串。
string.unpack (fmt, s [, pos])
返回以格式 fmt
(参见 §6.4.2) 打包在字符串 s
(参见 string.pack
) 中的值。 选项 pos
(默认为 1 )标记了从 s
中哪里开始读起。 读完所有的值后,函数返回 s
中第一个未读字节的位置。
string.upper (s)
接收一个字符串,将其中的小写字符都转为大写后返回其副本。 其它的字符串不会更改。 对小写字符的定义取决于当前的区域设置。
6.4.1 – 匹配模式
Lua 中的匹配模式直接用常规的字符串来描述。 它用于模式匹配函数 string.find
, string.gmatch
, string.gsub
, string.match
。 这一节表述了这些字符串的语法及含义(即它能匹配到什么)。
字符类:
字符类 用于表示一个字符集合。 下列组合可用于字符类:
**x: **(这里 x 不能是 魔法字符
^$()%.[]*+-?
中的一员) 表示字符 x 自身。**
.
: **(一个点)可表示任何字符。**
%a
: **表示任何字母。**
%c
: **表示任何控制字符。**
%d
: **表示任何数字。**
%g
: **表示任何除空白符外的可打印字符。**
%l
: **表示所有小写字母。**
%p
: **表示所有标点符号。**
%s
: **表示所有空白字符。**
%u
: **表示所有大写字母。**
%w
: **表示所有字母及数字。**
%x
: **表示所有 16 进制数字符号。**
%*x*
: **(这里的 x 是任意非字母或数字的字符) 表示字符 x。 这是对魔法字符转义的标准方法。 所有非字母或数字的字符 (包括所有标点,也包括非魔法字符) 都可以用前置一个 '%
' 放在模式串中表示自身。-
**
[*set*]
: 表示 set 中所有字符的联合。 可以以 '-
' 连接,升序书写范围两端的字符来表示一个范围的字符集。 上面提到的%
x 形式也可以在 set 中使用 表示其中的一个元素。 其它出现在 set 中的字符则代表它们自己。 例如,[%w_]
(或[_%w]
) 表示所有的字母数字加下划线),[0-7]
表示 8 进制数字,[0-7%l%-]
表示 8 进制数字加小写字母与 '-
' 字符。交叉使用类和范围的行为未定义。 因此,像
[%a-z]
或[a-%%]
这样的模式串没有意义。 **
[^*set*]
: **表示 set 的补集, 其中 set 如上面的解释。
所有单个字母表示的类别(%a
,%c
,等), 若将其字母改为大写,均表示对应的补集。 例如,%S
表示所有非空格的字符。
如何定义字母、空格、或是其他字符组取决于当前的区域设置。 特别注意:[a-z]
未必等价于 %l
。
模式条目:
模式条目 可以是
- 单个字符类匹配该类别中任意单个字符;
- 单个字符类跟一个 '
*
', 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 '
+
', 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 '
-
', 将匹配零或更多个该类的字符。 和 '*
' 不同, 这个条目总是匹配尽可能短的串; - 单个字符类跟一个 '
?
', 将匹配零或一个该类的字符。 只要有可能,它会匹配一个; -
%*n*
, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。 -
%b*xy*
, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目%b()
可以匹配到括号平衡的表达式。 -
%f[*set*]
, 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 '\0
' 一样。
模式:
模式 指一个模式条目的序列。 在模式最前面加上符号 '^
' 将锚定从字符串的开始处做匹配。 在模式最后面加上符号 '$
' 将使匹配过程锚定到字符串的结尾。 如果 '^
' 和 '$
' 出现在其它位置,它们均没有特殊含义,只表示自身。
捕获:
模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。 当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号。 例如,对于模式 "(a*(.)%w(%s*))"
, 字符串中匹配到 "a*(.)%w(%s*)"
的部分保存在第一个捕获物中 (因此是编号 1 ); 由 ".
" 匹配到的字符是 2 号捕获物, 匹配到 "%s*
" 的那部分是 3 号。
作为一个特例,空的捕获 ()
将捕获到当前字符串的位置(它是一个数字)。 例如,如果将模式 "()aa()"
作用到字符串 "flaaap"
上,将产生两个捕获物: 3 和 5 。
Table
这个库提供了表处理的通用函数。 所有函数都放在表 table
中。
记住,无论何时,若一个操作需要取表的长度, 这张表必须是一个真序列,或是拥有 __len
元方法 (参见 §3.4.7 )。 所有的函数都忽略传入参数的那张表中的非数字键。
table.concat (list [, sep [, i [, j]]])
提供一个列表,其所有元素都是字符串或数字,返回字符串 list[i]..sep..list[i+1] ··· sep..list[j]
。 sep
的默认值是空串, i
的默认值是 1 , j
的默认值是 #list
。 如果 i
比 j
大,返回空串。
table.insert (list, [pos,] value)
在 list
的位置 pos
处插入元素 value
, 并后移元素 list[pos], list[pos+1], ···, list[#list]
。 pos
的默认值为 #list+1
, 因此调用 table.insert(t,x)
会将 x
插在列表 t
的末尾。
table.move (a1, f, e, t [,a2])
将元素从表 a1
移到表 a2
。 这个函数做了次等价于后面这个多重赋值的等价操作: a2[t],··· = a1[f],···,a1[e]
。 a2
的默认值为 a1
。 目标区间可以和源区间重叠。 索引 f
必须是正数。
table.pack (···)
返回用所有参数以键 1,2, 等填充的新表, 并将 "n
" 这个域设为参数的总数。 注意这张返回的表不一定是一个序列。
table.remove (list [, pos])
移除 list
中 pos
位置上的元素,并返回这个被移除的值。 当 pos
是在 1 到 #list
之间的整数时, 它向前移动元素 list[pos+1], list[pos+2], ···, list[#list]
并删除元素 list[#list]
; 索引 pos
可以是 #list + 1
,或在 #list
为 0 时可以是 0 ; 在这些情况下,函数删除元素 list[pos]
。
pos
默认为 #list
, 因此调用 table.remove(l)
将移除表 l
的最后一个元素。
table.sort (list [, comp])
在表内从 list[1]
到 list[#list]
原地 对其间元素按指定次序排序。 如果提供了 comp
, 它必须是一个可以接收两个列表内元素为参数的函数。 当第一个元素需要排在第二个元素之前时,返回真 (因此 not comp(list[i+1],list[i])
在排序结束后将为真)。 如果没有提供 comp
, 将使用标准 Lua 操作 <
作为替代品。
排序算法并不稳定; 即当两个元素次序相等时,它们在排序后的相对位置可能会改变。
table.unpack (list [, i [, j]])
返回列表中的元素。 这个函数等价于 return list[i], list[i+1], ···, list[j]
; i
默认为 1 ,j
默认为 #list
。
lua 面向对象
LUA作为一种世界上广泛使用的游戏脚本语言,有其强大的一面。现在的游戏脚本,基本上都基于面向对象了,因为非面向对象的语言写游戏这种复杂的脚本明显过于麻烦。而LUA不是面向对象的语言,但是为什么会有这么多游戏使用呢?因为LUA之中有强大的table,这个类极为强大,可以当做数组,对象,类,哈希表总之什么都是。。。并且LUA之中具有metatable“元表”这个概念,所以你还可以使用table来构建其他语言的各种数据结构,因为这只是限制了table的功能而已,所以lua的强悍之处在于,它是一种可以自定义数据结构行为的语言,
1,类
类一般都有类名,属性声明,构造体,方法,属性。下面用LUA实现类的模拟,类的模拟有很多种,但是都必须用到__index。这里为了方便只是用一种定式,并且soyomaker的脚本以后也会采用这种定式。
--声明,这里声明了类名还有属性,并且给出了属性的初始值。
Class = {x=0,y=0}
--这句是重定义元表的索引,就是说有了这句,这个才是一个类。具体的解释,请百度。
Class.__index = Class
--构造体,构造体的名字是随便起的,习惯性改为new()
function Class:new(x,y)
local self = {} --初始化self,如果没有这句,那么类所建立的对象改变,其他对象都会改变
setmetatable(self, Class) --将self的元表设定为Class
self.x = x --属性值初始化
self.y = y
return self --返回自身
end
--这里定义类的其他方法
function Class:test()
print(self.x,self.y)
end
function Class:plus()
self.x = self.x + 1
self.y = self.y + 1
end
好了一个类定义完毕。现在我们可以开始建立对象了。
a = Class:new(10,20)
a:test()
b = Class:new(11,23)
b:test()
b:plus()
b:test()
结果是
10 20
11 23
12 24
看,两个对象a和b互不影响,并且都可以使用Class的方法,这就是一个类了。
2,继承
LUA之中没有继承的概念,但是可以模拟,我们建立一个新的类Main,它是Class类的子类,包含其一切方法。
--声明了新的属性Z
Main = {z=0}
--设置类型是Class
setmetatable(Main, Class)
--还是和类定义一样,表索引设定为自身
Main.__index = Main
--这里是构造体,看,加上了一个新的参数
function Main:new(x,y,z)
local self = {} --初始化对象自身
self = Class:new(x,y) --将对象自身设定为父类,这个语句相当于其他语言的super
setmetatable(self, Main) --将对象自身元表设定为Main类
self.z= z --新的属性初始化,如果没有将会按照声明=0
return self
end
--定义一个新的方法
function Main:go()
self.x = self.x + 10
end
--重定义父类的方法
function Main:test()
print(self.x,self.y,self.z)
end
好了然后我们建立对象
c = Main:new(20,40,100)
c:test()
d = Main:new(10,50,200)
d:go()
d:plus()
d:test()
c:test()
运行,结果是
20 40 100
21 51 200
20 40 100
明显,他可以运行父类的方法,并且可以重载父类的方法。所以Main继承了Class,为其子类。
3,多态
LUA作为动态语言,支持多态是基本,但是对象呢,这里我们加个方法证明这一点。
Class = {x=0,y=0}
Class.__index = Class
function Class:new(x,y)
local self = {}
setmetatable(self, Class)
self.x = x
self.y = y
return self
end
function Class:test()
print(self.x,self.y)
end
--新定义的一个函数gto()
function Class:gto()
return 100
end
--这里会引用gto()
function Class:gio()
return self:gto() * 2
end
function Class:plus()
self.x = self.x + 1
self.y = self.y + 1
end
然后在main哪里加入新的定义。
Main = {z=0}
setmetatable(Main, Class)
Main.__index = Main
function Main:new(x,y,z)
local self = {}
self = Class:new(x,y)
setmetatable(self, Main)
self.z= z
return self
end
--重新定义了gto()
function Main:gto()
return 50
end
function Main:go()
self.x = self.x + 10
end
function Main:test()
print(self.x,self.y,self.z)
end
然后我们开始测试
a = Class:new(10,20)
print(a:gio())
d = Main:new(10,50,200)
print(d:gio())
print(a:gio())
结果是
200
100
200
可见其实LUA也可以使用多态的。
教程讲到这里,这三个东西是游戏脚本上面基本上最为重要的东西,完全证明了,LUA一样可以构建一个完整的游戏脚本,并且因为LUA的特性而言,只会比JAVA这些更加简单而不会更加困难,另外就是LUA语言就是开发来嵌入其他语言中使用的,所以游戏中使用LUA作为脚本是绝对明智的选择。