0912学习
Lua的基本类型
- 简单变量类型:
number
:数字类型,所有的数字都是number类型,内部实现中区分为整形和浮点型。string
:字符串类型 , “xxx” 或 ‘xxx’boolean
布尔类型 , true 或 falsenil
空类型 , 未声明的变量均为nil类型
- 复杂变量类型:
function
:函数类型, 函数存储在变量中 可以作为函数参数和返回值或存储在表中。
C Function
Lua Function
light C Function
userdata
:用户自定义数据类型 , 表示任意存储在变量中的C数据结构
userdata
light userdata
:他是一个void *
指针,不受垃圾管理器管理,他没有元表
table
:表类型,类似字典的一种关联数组,索引为数字或字符串,利用{}创建表thread
:lua中的协程
string
, lua function
, userdata
, thread
和table
这些可以被垃圾回收管理
的类型
注释符
单行注释
:两个连续的连字符(--
)长注释或多行注释
:两个连续的连字符加两对连续左方括号(--[[ ]]
)
Boolean
- Boolean值
false
和nil
之外的所有值都视为真
零
和空字符串
也视为真
逻辑运算符and or not
- 逻辑运算符
and
运算结果为:如果它的第一个
操作数为假
,返回第一个操作数
,否则
返回第二个操作数
- 逻辑运算符
or
运算结果为:如果它的第一个
操作数不为假
,返回第一个
操作数,否则
返回第二个
。
--5
print(4 and 5)
--nil
print(nil and 13)
--false
print(false and 12)
--6
print(" " and 6)
--0
print(0 or 5)
--hello
print(false or "hello")
--false
print(nil or false)
短路取值
x = x or v
等价于:if not x then x = v end
a and b or c
:等价于C语言的三目运算符a?b:c
- lua中没有三目运算符
local x = 3
x = x or 4
y = 2;
--3
print(x)
--2
print(x > y and y or x)
数值
- lua5.2及之前的版本中,所有的数值都以双精度浮点格式表示
- 从5.3版本开始,Lua为数值提供了俩种选择:
integer的64位整型
和被称为float的双精度浮点型
local num = 100
--利用type(变量名)可以返回当前变量的类型名 (返回值类型是string)
print("num:", type(num))
--在少数情况下,当需要区分整型值和浮点值时,可以使用math.type
print("math num type:", math.type(num))
print("math num type:",math.type(3.09))
floor除法
会对得到的商向负无穷取整
,从而保证结果是一个整数- 取模公式:
a % b == a - ((a // b) * b)
- 对于任意指定的
正常量K
,即使x是负
数,表达式x%K
的结果也永远在[0,k-1]
之间
--Lua中使用//表示为地板除,数学库中的math.floor()向下取整的效果是一样
--对一个数进行除法运算后向下取整
--3//2 1
print("3//2", 3 // 2)
--3/2 1.5
print("3/2", 3 / 2)
--1
print("math.floor:", math.floor(3 / 2))
--3
print("math.floor:", math.floor(3.5))
--表达式 i % 2 结果均为 0 或 1
--1
print(9 % 2)
--0
print(12 % 2)
--判断A 是否大于或小于B
--A % B ,如果A 小于等于B ,其结果是 A
--9
print(9 % 11)
--X 保留 N位小数:x-x%0.01 恰好是x保留两位小数的结果
local x = 10.1234
--10.12
print(x - x % 0.01)
--10.1
print(x - x % 0.1)
--任意范围的角度归一化到 [0,2Π)
print(360 % (2 * math.pi))
-- -10 % 3 = -10 - floor(-10 / 3) *3 = -10 - (-4) * 3 = -10 + 12 = 2
--2
print(-10 % 3)
--math.floor():向负无限取整
--将数值X 向最近的整数取整,可以对x+0.5调用math.floor()函数
local x = 2.1;
--2
print(math.floor(x + 0.5))
x = 2.5
--3
print(math.floor(x + 0.5))
--math.ceil向正无限取整
--3
print(math.ceil(x))
--11
print(math.ceil(10.1))
--math.modf() 向零取整 拆分成商和余数
-- 10 0.2
print(math.modf(10.2))
-- -10 -0.2
print(math.modf(-10.2))
--当不带参数调用时,该函数将返回一个在 [0,1)范围内均匀分布的伪随机整数
print(math.random())
--当带有一个整型值n的参数调用时,该函数将返回一个在[0,n)范围内均匀分布的伪随机整数
print(math.random(10))
--当带有两个整型值 L 和 U 的参数调用时,该函数返回在[L,U]范围内的伪随机整数
print(math.random(20, 30))
--通过增加0.0 的方法将整型值强制转换为浮点型值,一个整型值总是可以被转换成浮点型值
x = -3
--integer
print(math.type(x))
x = x + 0.0
--float
print(math.type(x))
--数值强转为整型值
---3
print(math.tointeger(x))
字符串
- 字符使用8个比特位来存储
- 字符串是不可变值
- Lua语言会负责字符串的分配和释放
- 长度操作符 (
#
) 获取字符串的长度 - 使用连接操作符
..
(两个点)来进行字符串连接 - 双引号和单引号声明字符串是
等价
的。它们二者唯一的区别在于,使用双引号声明的字符串中出现单引号时,单引号不用转义;使用单引号声明的字符串中出现双引号时,双引号可以不用转义 - 使用一对
双方括号
来声明长字符串/多行字符串常量 - 被方括号括起来的内容可以包括很多行,并且内容中的转义序列
不会被转义
模式匹配
.
:任意字符%a
:字母%c
:控制字符%d
:数字%g
:除空格以外的可打印字符%l
:小写字母%p
:标点符号%s
:空白字符%u
:大写字母%w
:字母和数字%x
:十六进制数字%A
:任意非字母的字符,大小形式表示对应的补集%?
:匹配一个问号%%
:匹配一个百分号[]
:字符集,[%w_]
匹配字母和数字以及下划线,[%[%]]
匹配方括号,[0-9a-fA-F]
相当于%x
^
:在字符集前面加^
就可以得到这个字符集的补集。[^\n]
匹配换行符以外的其他所有字符+
:重复一次或多次*
:重复零次或多次-
:重复零次或多次(最小匹配)?
:出现0次或1次
string方法
--显式地将一个字符串转换成数值
--当这个字符串的内容不能表示为有效数字时该函数返回nil;
--否则,该函数就按照Lua语法扫描器的规则返回对应的整型值或浮点类型值:
--10.1
print(tonumber(" 10.1"))
--nil
print(tonumber("10.e"))
--默认情况下,函数tonumber使用的是十进制,但是也可以指明使用二进制到三十六进制之间的任意进制
--37
print(tonumber("100101", 2))
--将数值转换成字符串
--true
print(tostring(23) == "23")
--返回字符串s的长度,等价于 #s
--4
print(#"1234")
--4
print(string.len("1234"))
--返回将字符串s重复n次的结果
--abc-abc
print(string.rep("abc", 2, "-"))
--abcabc
print(string.rep("abc", 2))
--用于字符串翻转
--cba
print(string.reverse("abc"))
--abc
print(string.lower("ABC"))
--ABC
print(string.upper("abc"))
--从字符串s中提取第i个到第j个字符(包括第i个 和 第j个字符,字符串的第一个字符索引为1);
--该函数支持负数索引。负数索引从字符串的结尾开始技数:索引-1代表字符串的最后一个字符,索引-2代表倒数第二个字符
--o,world!
print(string.sub(s, 5))
--hello,world!
print(string.sub(s, 1, -1))
--hello,world!
print(string.sub(s, 1, #s))
--ello,world
print(string.sub(s, 2, -2));
--接收零个或多个整数作为参数,然后将每个整数转换成对应的字符,最后返回由这些字符连接而成的字符串
print(string.char())
--abc
print(string.char(97,98,99))
s = "abc"
--返回字符串s中第1个字符的内部数值表示,第二个参数是可选的
--97
print(string.byte(s))
--97
print(string.byte(s, 1))
--97 98 99
print(string.byte(s, 1, -1))
--97 98
print(string.byte(s, 1, 2))
--指定的字符串中进行模式搜索
--如果该函数在指定的字符串中找到了匹配的模式,则返回模式的开始和结束位置,否则返回nil
--7 9
print(string.find("hello world!","wor"))
--nil
print(string.find("Hello,world!","wor2"))
local s = "Hello12345World"
--6 10
print(string.find(s, "%d+"))
--string.match返回的是目标字符串中与模式相匹配的那部分子串,并不是该模式所在的位置
--12345
print(string.match(s, "%d+"))
s = "Hello world!";
--则把所有匹配的模式用另一个字符串替换 该函数的第二个返回值中返回发生替换的次数
--He..o wor.d! 3
print(string.gsub(s, "l", "."))
--gsub还有可选的第四个参数,可以限制替换的次数
--开始限制替换次数
--He*lo world! 1
print(string.gsub(s, "l", '*', 1))
--__llo world! 1
print(string.gsub(s, "He", "__"))
--He__o world! 1
print(string.gsub(s, "ll", "__"))
--Hello world! 0
print(string.gsub(s, "A", "C"))
local replaceStr = function(str)
return "[" .. string.upper(str) .. "]"
end
--一个函数,该函数的参数是被匹配的字符串,该函数的返回值将会作为目标字符串去进行替换匹配的内容
--He[L][L]o wor[L]d! 3
print(string.gsub(s, "l", replaceStr))
local replaceTable = { ['l'] = 2 }
--gsub的第三个参数是一个table,也就是说,当gsub的第三个参数是一个table时,
--如果在查找的字符串中有与第二个参数相匹配的内容,就会将该内容作为key,
--在table中查找该key对应的value;如果该table中没有这个key,则不进行替换
--He22o wor2d! 3
print(string.gsub(s, 'l', replaceTable))
--使用find来实现一个我们自己的gmatch,功能和gmatch是差不多的
local SGMatch = function(world, pattern)
local returnTable = {}
local i, j = string.find(world, pattern)
local index = 0
while i do
returnTable[#returnTable + 1] = string.sub(world, i, j)
i, j = string.find(world, pattern, j + 1)
end
return function()
index = index + 1
return returnTable[index]
end
end
for v in SGMatch(str, "%a+") do
print(v)
end
长串和短串
Lua将长度小于40字节的字符串视为短字符串,短字符串会用哈希表(拉链法实现的哈希表)缓存起来,当Lua声明一个短字符串时,如果缓存中已经存在相同的串则会重复利用,这表示相同内容的短串在一个Lua虚拟机中只会存在一份
- Lua的字符串分为
短字符串
和长字符串
。 - 短字符串:长度小于等于
40
为短字符串。相同字符的短字符串是共用同一个的
。用hash表使用拉链法缓存起来的,5.3使用版本的hash算法根其他版不一样,要慢一点 - 长字符串:长度大于40为长字符串。
相同的两个长字符串是两个独立的字符串
设计原因
- 复用度:短串复用度会比长串要高
- 哈希效率:由于长串的字符串比较多,如果要把组成它的字符序列进行哈希,耗时会比短串长
表
a.x
代表的是a["x"]
a[x]
则是指由变量x对应的值
索引的表
local sunday = "monday"
local monday = "sunday"
local t = {
sunday = "monday",
[sunday] = monday,
}
--sunday monday sunday
print(t[sunday], t.sunday, t[t.sunday])
pairs和ipairs
pairs
:遍历的顺序是随机的
,但是一定会遍历整个表ipairs
:从1开始遍历,如果发现返回值为nil,直接跳出
local tabFiles = {
[3] = "test3",
[6] = "test3",
[4] = "test4",
}
--0
print(#tabFiles)
--没有打印东西
for k, v in ipairs(tabFiles) do
print(k, v)
end
local tbl = { "alpha", "beta", [3] = "uno", ["two"] = "dos" }
--3
print(#tbl)
for k, v in ipairs(tbl) do
--1 alpha
--2 beta
--3 uno
print(k, v)
end
--数值型for循环
for i = 1, #tbl do
print(tbl[i])
end
table的长度
#
:获取的table长度,可能不准确,原因跟ipairs类似
Lua语言中模拟安全访问操作符
local temp = {}
local zip = (((company or temp).director or temp).address or temp).zipcode
--nil
print(zip)
table库方法
local t = { 10, 20, 30 }
--向序列的指定位置插入一个元素,其他元素依次后移
--{14, 10, 20, 30}
table.insert(t, 1, 14)
--不带参数:会在序列最后插入指定的元素,并且不会移动任何元素
--{14, 10, 20, 30,40}
table.insert(t, 40)
--删除并返回指定序列位置的元素,
--然后将其后的元素向前移动填充删除元素后的空洞。
--如果调用函数时不指定位置,该函数会删除序列的最后一个元素
--{10, 20, 30,40}
table.remove(t, 1)
--{10, 20, 30}
table.remove(t)
--返回表中的特定项连接后的数据,要求所连接的数据必须为数字或者字符串
--10_20_30
print(table.concat(t, "_"))
local tbl = { "a", "b", "c" }
local newtbl = { 1, 2, 3, 5 }
--原型:table.move(a1,f,e,t[,a2])
--函数作用: 把表a1中从下标f到e的value移动到表a2中,位置为a2下标从t开始
--函数参数: 表a1,a1下标开始位置f,a1下标结束位置e,t选择移动到的开始位置(如果没有a2,默认a1的下标)
table.move(tbl, 2, 3, 2, newtbl)
--1_b_c_5
print(table.concat(newtbl, "_"))
table.move(tbl, 2, 3, 3)
--a_b_b_c
print(table.concat(tbl, "_"))
table.move(tbl, 1, #tbl, 2)
--a_a_b_b_c
print(table.concat(tbl, "_"))
拼接字符串
- 使用
table.concat
或者..
..
每次拼接都会产生一个新的字符串table.concat
将table中的元素转换成字符串,再用分隔符
连接起来。table.concat
没有频繁申请内存,只有当写满一个8192
的BUFF时,才会生成一个TString
,最后生成多个TString
时,会有一次内存申请并合并。在大规模字符串合并时,应尽量选择这种方式
table是否为nil或者空表
--next查找下一个值,默认从第一个值开始
function IsTableEmpty(t)
return t == nil or next(t) == nil
end
--false
print(IsTableEmpty(tbl))
table的排序
--默认的情形之下,如果表内既有string,number类型,
--则会因为两个类型直接compare而出错,所以需要自己写func来转换一下。
table.sort(tbl, function(a, b)
return tostring(a) > tostring(b)
end)
函数
- 函数只有
一个参数
且该参数是字符串常量
或表构造器
时,括号是可选的print "Hello wolrd"
等价于print("Hello wolrd")
- 通过
抛弃多余参数
和将不足的参数设为nil
的方式调整参数个数 - 允许一个函数返回多个结果
- 可变长参数函数
function add(...)
local sum = 0
--able.pack,保存所有参数,将其放在一个表中返回,
--但是这个表还有一个保存了参数个数的额外字段"n"
local pack = table.pack(...)
for _, v in ipairs(pack) do
sum = sum + v
end
return sum
end
print(add(1, 2, 3, 4))
function nonil(...)
local arg = table.pack(...)
for i = 1, arg.n do
if arg[i] == nil then
return false
end
end
return true
end
--false
print(nonil(1, 2, nil, 3))
--true
print(nonil(1, 2, 3))
- 使用函数select 遍历可变长参数
- select 总是具有一个
固定
的参数selector
, 以及数量可变的参数
- 如果selector 是数值
n
,返回第n个参数后的所有参数
- 如果selector为
#
,返回额外参数的总数
- select 总是具有一个
--a b c
print(select(1, 'a', 'b', 'c'))
--b c
print(select(2, 'a', 'b', 'c'))
--c
print(select(3, 'a', 'b', 'c'))
--3
print(select('#', 'a', 'b', 'c'))
function add(...)
local sum = 0
for i = 1, select('#', ...) do
sum = sum + select(i, ...)
end
return sum
end
--0
print(add())
--6
print(add(1, 2, 3))
table.pack
:参数列表转换成tabletable.unpack
:将table转换成参数列表
--1 2 3
print(table.unpack({ 1, 2, 3 }))
--显式地限制返回元素的范围
--2 3
print(table.unpack({ 1, 2, 3 }, 2, 3))
local f = string.find
local t = { "Hello world", 'll', }
--3 4
print(f(table.unpack(t)))
--lua端实现的unpack
function unpack(t, i, n)
i = i or 1
n = n or #t
if i <= n then
return t[i], unpack(t, i + 1, n)
end
end
--3 4
print(f(unpack(t)))
- 尾调用:进行尾调用时不使用任何额外的栈空间,形如
return fun(args)
的调用才是尾调用 - 注意在使用长度操作符
#
对数组其长度时,数组不应该包含nil
值,否则很容易出错
local t = {
'5',
nil,
'6'
}
--3
print(#t)
print(#{ 1, nil }) --1
print(#{ 1, nil, 1 }) --3
print(#{ 1, nil, 1, ['hello'] = '1', nil, 1 }) --5
tb1 = {1, 2, 3, 4, 5}
tb1[7] = 7
print("tb1 length: "..#tb1)
-- tb1 length: 7
tb2 = {1, 2, 3, 4, 5}
tb2[8] = 8
print("tb2 length: "..#tb2)
-- tb2 length: 8
tb3 = {1, 2, 3, 4, 5}
tb3[8] = 8
tb3[9] = 9
print("tb3 length: "..#tb3)
-- tb3 length: 9
tb4 = {1, 2, 3, 4, 5}
tb4[2] = nil
tb4[3] = nil
-- tb4 length: 5
print("tb4 length: "..#tb4)
tb4[8]=1
-- tb4 length: 1
print("tb4 length: "..#tb4)
table实现分析
- 在table的设计上,采用了
array数组
和hashtable
(哈希表)两种数据的结合 - table会将部分整形key作为下标放在数组中,其余的整形key和其他类型的key都放在hash表中
typedef union TKey {
struct {
TValuefields;
int next; /* 用于标记链表下一个节点 */
} nk;
TValue tvk;
} TKey;
typedef struct Node {
TValue i_val;
TKey i_key;
} Node;
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int sizearray; /* size of 'array' array */
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
struct Table *metatable;
GCObject *gclist;
} Table;
flags
: 元方法的标记,用于查询table是否包含某个类别的元方法lsizenode
: (1<<lsizenode
)表示table的hash部分大小sizearray
: table的数组部分大小array
: table的array数组首节点node
:table的hash表首节点lastfree
: 表示table的hash表空闲节点的游标metatable
: 元表gclist
: table gc相关的参数
table hash表冲突解决
- 开放定址法:就是一旦发生冲突,通过某种探测技术,去寻找下一个空的地址,只要哈希表足够大,总能找到一个空的位置,并且记录下来作为它的freepos。
- 拉链法:这种方法的思路是将产生冲突的元素建立一个
单链表
,并将头指针地址
存储至Hash 表
对应桶
的位置。这样定位到Hash表桶
的位置后可通过遍历单链表
的形式来查找元素 - 开放定址法相比链地址法节省更多的内存,但在插入和查找的时候拥有更高的复杂度
- table中的hash表的实现结合了以上两种方法的一些特性,通过
lastfree
指针找到下一个空的位置,采用拉链法的形式处理冲突,但是链表中的额外的节点是hash表中的node节点,并不需要额外开辟链表节点
插入新节点流程
- 首先检查key值是否合法,若是nil,则直接报错返回
- 获取key对应的
MainPosition
,其中MainPosition
等于hash(key)%table_size
,所谓的mainposition是指作为数组访问时的位置索引 - 确定
mainPosition
是否被占用,如果mainPosition
节点等于dummyNode或者对应mainPosition
的节点value
不为null,则表明该MainPosition
处于占用状态 - 发现
mainPosition
这个索引已经有其他node占用(标记为colliderNode),则需要通过freePos
游标开始向前查找 - 如果
freePos
没有找到空的Node,则就需要调用rehash
来扩容,再重新走一次插入流程 - 反之,确定colliderNode节点的MainPosition是否与待插入节点的
mainPosition
一致 - 如果相同直接把待插入节点插入到freePos找到的空Node中
- 如果不同,说明
该节点是被“挤”到该位置来的
,那么把ColliderNode节点挪到freepos
找到的空node中去,然后让待插入节点入住colliderNode节点原先的坑位
Rehash
并不一定代表hash表的扩容,而是根据table里面的key的个数和类型,重新更合适的分配array的大小和hash表的大小,可能会扩容、可能不变、也有可能缩小
TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
Node *mp;
TValue aux;
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisfloat(key)) {
lua_Integer k;
if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */
setivalue(&aux, k);
key = &aux; /* insert it as an integer */
}
else if (luai_numisnan(fltvalue(key)))
luaG_runerror(L, "table index is NaN");
}
mp = mainposition(t, key);
if (!ttisnil(gval(mp)) || isdummy(t)) { /* main position is taken? */
Node *othern;
Node *f = getfreepos(t); /* get a free place */
if (f == NULL) { /* cannot find a free place? */
rehash(L, t, key); /* grow table */
/* whatever called 'newkey' takes care of TM cache */
return luaH_set(L, t, key); /* insert key into grown table */
}
lua_assert(!isdummy(t));
othern = mainposition(t, gkey(mp));
if (othern != mp) { /* is colliding node out of its main position? */
/* yes; move colliding node into free position */
while (othern + gnext(othern) != mp) /* find previous */
othern += gnext(othern);
gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */
*f = *mp; /* copy colliding node into free pos. (mp->next also goes) */
if (gnext(mp) != 0) {
gnext(f) += cast_int(mp - f); /* correct 'next' */
gnext(mp) = 0; /* now 'mp' is free */
}
setnilvalue(gval(mp));
}
else { /* colliding node is in its own main position */
/* new node will go into free position */
if (gnext(mp) != 0)
gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */
else lua_assert(gnext(f) == 0);
gnext(mp) = cast_int(f - mp);
mp = f;
}
}
setnodekey(L, &mp->i_key, key);
luaC_barrierback(L, t, key);
lua_assert(ttisnil(gval(mp)));
return gval(mp);
}
从上面代码可以看出,插入一个节点是先直接插到hashtable
上的,array
上都都没有值插入,其实array
上的值是在rehash的时候会将hashtable中的节点移动过去
#table
取长度问题
luaH_getn
就是用来求table的长度值的函数
int luaH_getn (Table *t) {
unsigned int j = t->sizearray;
if (j > 0 && ttisnil(&t->array[j - 1])) {
/* there is a boundary in the array part: (binary) search for it */
unsigned int i = 0;
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(&t->array[m - 1])) j = m;
else i = m;
}
return i;
}
/* else must find a boundary in hash part */
else if (isdummy(t)) /* hash part is empty? */
return j; /* that is easy... */
else return unbound_search(t, j);
}
static int unbound_search (Table *t, unsigned int j) {
unsigned int i = j; /* i is zero or a present index */
j++;
/* find 'i' and 'j' such that i is present and j is not */
while (!ttisnil(luaH_getint(t, j))) {
i = j;
if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */
/* table was built with bad purposes: resort to linear search */
i = 1;
while (!ttisnil(luaH_getint(t, i))) i++;
return i - 1;
}
j *= 2;
}
/* now do a binary search between them */
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(luaH_getint(t, m))) j = m;
else i = m;
}
return i;
}
- lua源码并没有进行
遍历查找
,而是通过二分查找
- 先对数组查找,如果table
数组
部分的最后一个元素为nil
,那么将在数组部分进行二分查找
- 如果table数组部分的
最后一个元素不为nil
,那么将在hash部分进行二分查找
- table中要想
删除一个元素等同于向对应key赋值为nil
,等待垃圾回收。但是删除table一个元素时候,并不会触发表重构行为,即不会触发rehash操作
- 如果数组中的某个key被赋值为nil,同时最后一个元素不为nil,此时这个数组中的nil就被忽略,直接针对hash部分二分查找,导致找不对
泛型for的语法
- 泛型for保存了三个值:
一个迭代函数
、一个不可变状态
、一个控制变量
for var-list in exp-list do
body
end
var-list
是由一个或多个变量名
组成的列表
,用逗号
分隔exp-list
是一个或多个表达式组成的列表,用逗号分隔
- 变量列表中的
第一个
(或唯一的)变量称为控制变量
- 控制变量在循环过程中永远不为nil, 因为当控制变量为nil 时,循环就
- 泛型for就等价于:
for var_1,...,var_n in explist do block end
-- 等价于
do
local _f, _s, _var =exlist -- 表达式返回 迭代器,不可变状态,控制变量初始值
while true do
local var_1,..,var_n = _f(_s,_var) -- 把不可变状态、控制变量初始值作为迭代器的参数,迭代器返回n个值
_var = var_1
if _var == nil then break end -- 迭代器返回的第一个值为nil时,结束循环
end
end
function values(l)
local i = 0
return function()
i = i + 1
return l[i]
end
end
local ll = { 10, 20, 30 }
local value = values(ll)
while true do
local v = value()
if v then
print(v)
else
break
end
end
for k in values(ll) do
print(k)
end
无状态迭代器
- 是一种自身不保存任何状态的迭代器,可以在多个循环中使用同一个无状态迭代器,避免创建新闭包的开销
- 无状态迭代器,通过返回三个值(
迭代函数、不可变状态、控制变量
),再通过for循环 迭代器,从而完成循环
local iter = function(t, i)
i = i + 1
local v = t[i]
if v then
return i, v
end
end
--定义的ipairs 函数中会 返回 三个值,其中 分别是
--迭代函数 iter、不可变状态表t 和 控制变量的初始值0
local ipairs = function(t)
return iter, t, 0
end
--for 循环 ipairs(t) 返回的值符合泛型for执行需要的值,成功完成迭代器的循环
--与 非无状态迭代器相比, 无状态迭代器(例 ipairs)每次for循环时,
--都是调用同一迭代函数 iter(),因为没有创建闭包,所以开销小
for k, v in ipairs({ 10, 2, 3 }) do
print(k, v)
end
--调用next(t, nil)时,返回表中的第一个键值对。当所有元素被遍历完,函数next返回nil
local pairs = function(t)
return next, t, nil
end
for k, v in pairs({ ["hello"] = 1, ["world"] = 2 }) do
print(k, v)
end
local sort = function(t, i)
local a = {}
i = i + 1
for k, _ in pairs(t) do
a[#a + 1] = k
end
table.sort(a)
if a[i] then
--控制变量
return i, a[i], t[a[i]]
end
end
local sortTable = function(t)
return sort, t, 0
end
for _, k, v in sortTable({ ["luaH_set"] = 10,
["luaH_get"] = 24,
["luaH_present"] = 48,
}) do
print(k, v)
end
local fromToIter = function(t, i)
if i <= t[1] then
--第一个表示变量
--第二个表示返回值
return i + t[2], i
end
end
local fromTo = function(n, m, step)
return fromToIter, { m, step }, n
end
--数值型for应该是指 for i = n,m,1 do something end ,由 n到m 步长为1
for _, v in fromTo(10, 20, 5) do
print(v)
end
元表
- 元表是一类特殊的表,能修改一个值在面对一个未知操作时的行为
- Lua语言中的每一个值都可以有元表
- 在Lua语言中,只能为表设置元表;如果要给其他类型的值设置元表,则必须通过C代码或调试库完成
- 字符串标准库为所有的字符串都设置了同一个元表,而其他类型默认情况下没有元表
local t = {}
local m = {}
setmetatable(m, t)
--table: 0x7fb9e540dca0 table: 0x7fb9e540dca0
print(getmetatable(m), t)
--table: 0x7fa5d4408680 table: 0x7fa5d4408680
print(getmetatable("hello"), getmetatable("world"))
--nil nil
print(getmetatable(10), getmetatable(1))
元方法
元方法 | 含义 |
__add | 加法 |
__mul | 乘法 |
__sub | 减法 |
__div | 除法 |
__idiv | floor 除法 |
__unm | 负数 |
__mod | 取模 |
__pow | 幂运算 |
__band | 按位与 |
__bor | 按位或 |
__bxor | 按位异或 |
__bnot | 按位取反 |
__shl | 向左移位 |
__shr | 向右移位 |
__eq | 等于 |
__lt | 小于 |
__le | 小于等于 |
__tostring | tostring 的返回值 |
__len | 实现了长度操作符 |
__index | 表的查询 |
__newindex | 表的更新 |
__pairs | 遍历原来的表一样遍历代理 |
__call | 表可以通过变量名当作一个函数调用__call方法 |
__gc | table被gc时回调 |
local Set = {}
local DefaultMetable = {}
function Set.New(l)
local set = {}
setmetatable(set, DefaultMetable)
for _, v in pairs(l) do
set[v] = true
end
return set
end
function Set.Union(a, b)
local res = Set.New {}
for k in pairs(a) do
res[k] = true
end
for k in pairs(b) do
res[k] = true
end
return res
end
function Set.ToString(set)
local l = {}
for e in pairs(set) do
l[#l + 1] = tostring(e)
end
return "{" .. table.concat(l, ", ") .. "}"
end
local s1 = Set.New { 10, 20, 30, 50 }
local s2 = Set.New { 30, 1 }
--table: 0x7f9e61c0e6d0 table: 0x7f9e61c0e6d0 table: 0x7f9e61c0e6d0
print(getmetatable(s1), getmetatable(s2), DefaultMetable)
DefaultMetable.__add = Set.Union
local s3 = s1 + s2
--{1, 30, 10, 20, 50}
print(Set.ToString(s3))
DefaultMetable.__tostring = Set.ToString
--{1, 30, 10, 20, 50}
print(s3)
DefaultMetable.__gc = function(t)
print("回收:", t)
end
local key = {}
local defaultMetable = { __index = function(t, k)
return t[key]
end }
local SetDefault = function(t, d)
t[key] = d
setmetatable(t, defaultMetable)
end
local tab = { x = 10, y = 30 }
SetDefault(tab, 100)
--100
print(tab.z)
function Track(origin)
local proxy = {}
local m = {
__index = function(t, k)
print("*access to element " .. tostring(k))
return origin[k]
end,
__newindex = function(t, k, v)
print("*update of element " .. tostring(k) .. " to" .. tostring(v))
origin[k] = v
end,
__len = function()
return #origin
end
}
setmetatable(proxy, m)
return proxy
end
t = {}
t = Track(t)
--*update of element 2 tohello
t[2] = "hello"
--*access to element 2
print(t[2])
t = Track({ 10, 20 })
print(#t)
--只读表
function ReadOnly(t)
local proxy = {}
local mt = {
__index = function(_, k)
return t[k]
end,
__newindex = function()
error("attempt to update a read-only table", 2)
end
}
setmetatable(proxy, mt)
return proxy
end
local days = ReadOnly { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }
print(days[1])
--days[1] = "M"
协程
线程与协同程序的主要区别:
- 一个具有多个线程的程序可以同时运行几个线程
- 一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停
协程状态
- 挂起(suspended)
- 运行(running)
- 死亡(dead)
- 正常(normal)
local co = coroutine.create(function()
print("hello world")
end)
--suspended
print(coroutine.status(co))
coroutine.resume(co)
--dead
print(coroutine.status(co))
local co = coroutine.create(function()
print("hello world")
end)
--suspended
print(coroutine.status(co))
coroutine.resume(co)
--dead
print(coroutine.status(co))
function foo(a)
print("foo", a)
return coroutine.yield(2 * a)
end
local co = coroutine.create(function(a, b)
print("co-body", a, b)
local r = foo(a)
print("co-body r:", r)
local x, y = coroutine.yield(a + b, a - b)
print("co-body x,y", x, y)
return b, "end"
end)
--co-body 10 1
--foo 10
--main==== true 20
-----------
--co-body r: r
--main==== true 11 9
-----------
--co-body x,y X Y
--main==== true 1 end
---------
--coroutine.resume 的参数,作为传参传入
--coroutine.yield的参数,作为返回值,通过coroutine.resume返回
print("main====", coroutine.resume(co, 10, 1))
print("---------")
print("main====", coroutine.resume(co, 'r'))
print("---------")
print("main====", coroutine.resume(co, "X", "Y"))
print("-------")
function receiver(prod)
local _, v = coroutine.resume(prod)
return v
end
function send(x)
coroutine.yield(x)
end
function product()
return coroutine.create(function()
while true do
local x = io.read()
send(x)
end
end)
end
function filter(prod)
return coroutine.create(function()
for line = 1, math.huge do
local x = receiver(prod)
x = string.format("%5d %s", line, x)
send(x)
end
end)
end
function consume(prod)
while true do
local x = receiver(prod)
io.write(x, "\n")
end
end
consume(filter(product()))
面向对象
- 冒号操作符:在一个方法调用中
增加一个额外的实
参,或在方法定义中增加一个额外的隐藏形参
- self作为函数的参数,调用时指定操作的对象
lua加密策略
- 类似cocos的加密方式,对文件打上加密标记头,然后文件内容呢,加密后存放。需要修改lua加载文件的部分代码。密钥是写死在代码里,所以通过反编译代码很容易获取到。blowfish
- luac编译后使用,luac编译后的代码,采用工具能够恢复一部分,可读性不强,可以作为一般应用的加密方式
- 将加密解密的函数,由服务端传入。客户端执行这个函数加载相应模块。需要封装读取文件接口给LUA用。
这种方式,非常隐蔽 - 修改lua虚拟机中,指令的编号,然后使用luac进行编译。这样的方式luac编译后的字节码中,指令编号与其它的不同,是非常好的机密方式