0912学习

Lua的基本类型


  • 简单变量类型:
    • number:数字类型,所有的数字都是number类型,内部实现中区分为整形和浮点型。
    • string:字符串类型 , “xxx” 或 ‘xxx’
    • boolean 布尔类型 , true 或 false
    • nil 空类型 , 未声明的变量均为nil类型
  • 复杂变量类型:
    • function:函数类型, 函数存储在变量中 可以作为函数参数和返回值或存储在表中。
      • C Function
      • Lua Function
      • light C Function
    • userdata:用户自定义数据类型 , 表示任意存储在变量中的C数据结构
      • userdata
      • light userdata:他是一个void *指针,不受垃圾管理器管理,他没有元表
    • table:表类型,类似字典的一种关联数组,索引为数字或字符串,利用{}创建表
    • thread:lua中的协程

string, lua function, userdata, threadtable这些可以被垃圾回收管理的类型


注释符


  • 单行注释:两个连续的连字符(--)
  • 长注释或多行注释:两个连续的连字符加两对连续左方括号(--[[ ]])

Boolean


  • Boolean值falsenil之外的所有值都视为
  • 空字符串也视为

逻辑运算符and or not


  • 逻辑运算符and运算结果为:如果它的第一个操作数为,返回第一个操作数否则返回第二个操作数
  • 逻辑运算符or运算结果为:如果它的第一个操作数不为,返回第一个操作数,否则返回第二个

  1. --5
  2. print(4 and 5)
  3. --nil
  4. print(nil and 13)
  5. --false
  6. print(false and 12)
  7. --6
  8. print(" " and 6)
  9. --0
  10. print(0 or 5)
  11. --hello
  12. print(false or "hello")
  13. --false
  14. 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中没有三目运算符

  1. local x = 3
  2. x = x or 4
  3. y = 2;
  4. --3
  5. print(x)
  6. --2
  7. print(x > y and y or x)

数值


  • lua5.2及之前的版本中,所有的数值都以双精度浮点格式表示
  • 从5.3版本开始,Lua为数值提供了俩种选择:integer的64位整型和被称为float的双精度浮点型

  1. local num = 100
  2. --利用type(变量名)可以返回当前变量的类型名 (返回值类型是string)
  3. print("num:", type(num))
  4. --在少数情况下,当需要区分整型值和浮点值时,可以使用math.type
  5. print("math num type:", math.type(num))
  6. print("math num type:",math.type(3.09))

  • floor除法会对得到的商向负无穷取整,从而保证结果是一个整数
  • 取模公式:a % b == a - ((a // b) * b)
  • 对于任意指定的正常量K,即使x是负数,表达式 x%K的结果也永远在[0,k-1]之间

  1. --Lua中使用//表示为地板除,数学库中的math.floor()向下取整的效果是一样
  2. --对一个数进行除法运算后向下取整
  3. --3//2 1
  4. print("3//2", 3 // 2)
  5. --3/2 1.5
  6. print("3/2", 3 / 2)
  7. --1
  8. print("math.floor:", math.floor(3 / 2))
  9. --3
  10. print("math.floor:", math.floor(3.5))
  11. --表达式 i % 2 结果均为 0 1
  12. --1
  13. print(9 % 2)
  14. --0
  15. print(12 % 2)
  16. --判断A 是否大于或小于B
  17. --A % B ,如果A 小于等于B ,其结果是 A
  18. --9
  19. print(9 % 11)
  20. --X 保留 N位小数:x-x%0.01 恰好是x保留两位小数的结果
  21. local x = 10.1234
  22. --10.12
  23. print(x - x % 0.01)
  24. --10.1
  25. print(x - x % 0.1)
  26. --任意范围的角度归一化到 [0,2Π)
  27. print(360 % (2 * math.pi))
  28. -- -10 % 3 = -10 - floor(-10 / 3) *3 = -10 - (-4) * 3 = -10 + 12 = 2
  29. --2
  30. print(-10 % 3)
  31. --math.floor():向负无限取整
  32. --将数值X 向最近的整数取整,可以对x+0.5调用math.floor()函数
  33. local x = 2.1;
  34. --2
  35. print(math.floor(x + 0.5))
  36. x = 2.5
  37. --3
  38. print(math.floor(x + 0.5))
  39. --math.ceil向正无限取整
  40. --3
  41. print(math.ceil(x))
  42. --11
  43. print(math.ceil(10.1))
  44. --math.modf() 向零取整 拆分成商和余数
  45. -- 10 0.2
  46. print(math.modf(10.2))
  47. -- -10 -0.2
  48. print(math.modf(-10.2))
  49. --当不带参数调用时,该函数将返回一个在 [0,1)范围内均匀分布的伪随机整数
  50. print(math.random())
  51. --当带有一个整型值n的参数调用时,该函数将返回一个在[0,n)范围内均匀分布的伪随机整数
  52. print(math.random(10))
  53. --当带有两个整型值 L U 的参数调用时,该函数返回在[L,U]范围内的伪随机整数
  54. print(math.random(20, 30))
  55. --通过增加0.0 的方法将整型值强制转换为浮点型值,一个整型值总是可以被转换成浮点型值
  56. x = -3
  57. --integer
  58. print(math.type(x))
  59. x = x + 0.0
  60. --float
  61. print(math.type(x))
  62. --数值强转为整型值
  63. ---3
  64. 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方法


  1. --显式地将一个字符串转换成数值
  2. --当这个字符串的内容不能表示为有效数字时该函数返回nil;
  3. --否则,该函数就按照Lua语法扫描器的规则返回对应的整型值或浮点类型值:
  4. --10.1
  5. print(tonumber(" 10.1"))
  6. --nil
  7. print(tonumber("10.e"))
  8. --默认情况下,函数tonumber使用的是十进制,但是也可以指明使用二进制到三十六进制之间的任意进制
  9. --37
  10. print(tonumber("100101", 2))
  11. --将数值转换成字符串
  12. --true
  13. print(tostring(23) == "23")
  14. --返回字符串s的长度,等价于 #s
  15. --4
  16. print(#"1234")
  17. --4
  18. print(string.len("1234"))
  19. --返回将字符串s重复n次的结果
  20. --abc-abc
  21. print(string.rep("abc", 2, "-"))
  22. --abcabc
  23. print(string.rep("abc", 2))
  24. --用于字符串翻转
  25. --cba
  26. print(string.reverse("abc"))
  27. --abc
  28. print(string.lower("ABC"))
  29. --ABC
  30. print(string.upper("abc"))
  31. --从字符串s中提取第i个到第j个字符(包括第i j个字符,字符串的第一个字符索引为1);
  32. --该函数支持负数索引。负数索引从字符串的结尾开始技数:索引-1代表字符串的最后一个字符,索引-2代表倒数第二个字符
  33. --o,world!
  34. print(string.sub(s, 5))
  35. --hello,world!
  36. print(string.sub(s, 1, -1))
  37. --hello,world!
  38. print(string.sub(s, 1, #s))
  39. --ello,world
  40. print(string.sub(s, 2, -2));
  41. --接收零个或多个整数作为参数,然后将每个整数转换成对应的字符,最后返回由这些字符连接而成的字符串
  42. print(string.char())
  43. --abc
  44. print(string.char(97,98,99))
  45. s = "abc"
  46. --返回字符串s中第1个字符的内部数值表示,第二个参数是可选的
  47. --97
  48. print(string.byte(s))
  49. --97
  50. print(string.byte(s, 1))
  51. --97 98 99
  52. print(string.byte(s, 1, -1))
  53. --97 98
  54. print(string.byte(s, 1, 2))
  55. --指定的字符串中进行模式搜索
  56. --如果该函数在指定的字符串中找到了匹配的模式,则返回模式的开始和结束位置,否则返回nil
  57. --7 9
  58. print(string.find("hello world!","wor"))
  59. --nil
  60. print(string.find("Hello,world!","wor2"))
  61. local s = "Hello12345World"
  62. --6 10
  63. print(string.find(s, "%d+"))
  64. --string.match返回的是目标字符串中与模式相匹配的那部分子串,并不是该模式所在的位置
  65. --12345
  66. print(string.match(s, "%d+"))
  67. s = "Hello world!";
  68. --则把所有匹配的模式用另一个字符串替换 该函数的第二个返回值中返回发生替换的次数
  69. --He..o wor.d! 3
  70. print(string.gsub(s, "l", "."))
  71. --gsub还有可选的第四个参数,可以限制替换的次数
  72. --开始限制替换次数
  73. --He*lo world! 1
  74. print(string.gsub(s, "l", '*', 1))
  75. --__llo world! 1
  76. print(string.gsub(s, "He", "__"))
  77. --He__o world! 1
  78. print(string.gsub(s, "ll", "__"))
  79. --Hello world! 0
  80. print(string.gsub(s, "A", "C"))
  81. local replaceStr = function(str)
  82. return "[" .. string.upper(str) .. "]"
  83. end
  84. --一个函数,该函数的参数是被匹配的字符串,该函数的返回值将会作为目标字符串去进行替换匹配的内容
  85. --He[L][L]o wor[L]d! 3
  86. print(string.gsub(s, "l", replaceStr))
  87. local replaceTable = { ['l'] = 2 }
  88. --gsub的第三个参数是一个table,也就是说,当gsub的第三个参数是一个table时,
  89. --如果在查找的字符串中有与第二个参数相匹配的内容,就会将该内容作为key
  90. --在table中查找该key对应的value;如果该table中没有这个key,则不进行替换
  91. --He22o wor2d! 3
  92. print(string.gsub(s, 'l', replaceTable))
  93. --使用find来实现一个我们自己的gmatch,功能和gmatch是差不多的
  94. local SGMatch = function(world, pattern)
  95. local returnTable = {}
  96. local i, j = string.find(world, pattern)
  97. local index = 0
  98. while i do
  99. returnTable[#returnTable + 1] = string.sub(world, i, j)
  100. i, j = string.find(world, pattern, j + 1)
  101. end
  102. return function()
  103. index = index + 1
  104. return returnTable[index]
  105. end
  106. end
  107. for v in SGMatch(str, "%a+") do
  108. print(v)
  109. end

长串和短串

Lua将长度小于40字节的字符串视为短字符串,短字符串会用哈希表(拉链法实现的哈希表)缓存起来,当Lua声明一个短字符串时,如果缓存中已经存在相同的串则会重复利用,这表示相同内容的短串在一个Lua虚拟机中只会存在一份


  • Lua的字符串分为短字符串长字符串
  • 短字符串:长度小于等于40为短字符串。相同字符的短字符串是共用同一个的。用hash表使用拉链法缓存起来的,5.3使用版本的hash算法根其他版不一样,要慢一点
  • 长字符串:长度大于40为长字符串。相同的两个长字符串是两个独立的字符串

设计原因


  • 复用度:短串复用度会比长串要高
  • 哈希效率:由于长串的字符串比较多,如果要把组成它的字符序列进行哈希,耗时会比短串长

Lua设计与实现–字符串篇
深入Lua:字符串管理



  • a.x代表的是a["x"]
  • a[x]则是指由变量x对应的值索引的表

  1. local sunday = "monday"
  2. local monday = "sunday"
  3. local t = {
  4. sunday = "monday",
  5. [sunday] = monday,
  6. }
  7. --sunday monday sunday
  8. print(t[sunday], t.sunday, t[t.sunday])

pairs和ipairs


  • pairs:遍历的顺序是随机的,但是一定会遍历整个表
  • ipairs:从1开始遍历,如果发现返回值为nil,直接跳出
  1. local tabFiles = {
  2. [3] = "test3",
  3. [6] = "test3",
  4. [4] = "test4",
  5. }
  6. --0
  7. print(#tabFiles)
  8. --没有打印东西
  9. for k, v in ipairs(tabFiles) do
  10. print(k, v)
  11. end
  12. local tbl = { "alpha", "beta", [3] = "uno", ["two"] = "dos" }
  13. --3
  14. print(#tbl)
  15. for k, v in ipairs(tbl) do
  16. --1 alpha
  17. --2 beta
  18. --3 uno
  19. print(k, v)
  20. end
  21. --数值型for循环
  22. for i = 1, #tbl do
  23. print(tbl[i])
  24. end

table的长度

  • #:获取的table长度,可能不准确,原因跟ipairs类似

Lua语言中模拟安全访问操作符

  1. local temp = {}
  2. local zip = (((company or temp).director or temp).address or temp).zipcode
  3. --nil
  4. print(zip)

table库方法


  1. local t = { 10, 20, 30 }
  2. --向序列的指定位置插入一个元素,其他元素依次后移
  3. --{14, 10, 20, 30}
  4. table.insert(t, 1, 14)
  5. --不带参数:会在序列最后插入指定的元素,并且不会移动任何元素
  6. --{14, 10, 20, 30,40}
  7. table.insert(t, 40)
  8. --删除并返回指定序列位置的元素,
  9. --然后将其后的元素向前移动填充删除元素后的空洞。
  10. --如果调用函数时不指定位置,该函数会删除序列的最后一个元素
  11. --{10, 20, 30,40}
  12. table.remove(t, 1)
  13. --{10, 20, 30}
  14. table.remove(t)
  15. --返回表中的特定项连接后的数据,要求所连接的数据必须为数字或者字符串
  16. --10_20_30
  17. print(table.concat(t, "_"))
  18. local tbl = { "a", "b", "c" }
  19. local newtbl = { 1, 2, 3, 5 }
  20. --原型:table.move(a1,f,e,t[,a2])
  21. --函数作用: 把表a1中从下标fevalue移动到表a2中,位置为a2下标从t开始
  22. --函数参数: a1a1下标开始位置fa1下标结束位置et选择移动到的开始位置(如果没有a2,默认a1的下标)
  23. table.move(tbl, 2, 3, 2, newtbl)
  24. --1_b_c_5
  25. print(table.concat(newtbl, "_"))
  26. table.move(tbl, 2, 3, 3)
  27. --a_b_b_c
  28. print(table.concat(tbl, "_"))
  29. table.move(tbl, 1, #tbl, 2)
  30. --a_a_b_b_c
  31. print(table.concat(tbl, "_"))

拼接字符串


  • 使用table.concat或者..
  • ..每次拼接都会产生一个新的字符串
  • table.concat将table中的元素转换成字符串,再用分隔符连接起来。
  • table.concat没有频繁申请内存,只有当写满一个8192的BUFF时,才会生成一个TString,最后生成多个TString时,会有一次内存申请并合并。在大规模字符串合并时,应尽量选择这种方式

table是否为nil或者空表


  1. --next查找下一个值,默认从第一个值开始
  2. function IsTableEmpty(t)
  3. return t == nil or next(t) == nil
  4. end
  5. --false
  6. print(IsTableEmpty(tbl))

table的排序

  1. --默认的情形之下,如果表内既有stringnumber类型,
  2. --则会因为两个类型直接compare而出错,所以需要自己写func来转换一下。
  3. table.sort(tbl, function(a, b)
  4. return tostring(a) > tostring(b)
  5. end)

函数


  • 函数只有一个参数且该参数是字符串常量表构造器时,括号是可选的print "Hello wolrd" 等价于print("Hello wolrd")
  • 通过抛弃多余参数和将不足的参数设为nil的方式调整参数个数
  • 允许一个函数返回多个结果
  • 可变长参数函数
  1. function add(...)
  2. local sum = 0
  3. --able.pack,保存所有参数,将其放在一个表中返回,
  4. --但是这个表还有一个保存了参数个数的额外字段"n"
  5. local pack = table.pack(...)
  6. for _, v in ipairs(pack) do
  7. sum = sum + v
  8. end
  9. return sum
  10. end
  11. print(add(1, 2, 3, 4))
  12. function nonil(...)
  13. local arg = table.pack(...)
  14. for i = 1, arg.n do
  15. if arg[i] == nil then
  16. return false
  17. end
  18. end
  19. return true
  20. end
  21. --false
  22. print(nonil(1, 2, nil, 3))
  23. --true
  24. print(nonil(1, 2, 3))
  • 使用函数select 遍历可变长参数
    • select 总是具有一个固定的参数selector , 以及数量可变的参数
    • 如果selector 是数值n,返回第n个参数后的所有参数
    • 如果selector为#,返回额外参数的总数
  1. --a b c
  2. print(select(1, 'a', 'b', 'c'))
  3. --b c
  4. print(select(2, 'a', 'b', 'c'))
  5. --c
  6. print(select(3, 'a', 'b', 'c'))
  7. --3
  8. print(select('#', 'a', 'b', 'c'))
  9. function add(...)
  10. local sum = 0
  11. for i = 1, select('#', ...) do
  12. sum = sum + select(i, ...)
  13. end
  14. return sum
  15. end
  16. --0
  17. print(add())
  18. --6
  19. print(add(1, 2, 3))
  • table.pack:参数列表转换成table
  • table.unpack:将table转换成参数列表
  1. --1 2 3
  2. print(table.unpack({ 1, 2, 3 }))
  3. --显式地限制返回元素的范围
  4. --2 3
  5. print(table.unpack({ 1, 2, 3 }, 2, 3))
  6. local f = string.find
  7. local t = { "Hello world", 'll', }
  8. --3 4
  9. print(f(table.unpack(t)))
  10. --lua端实现的unpack
  11. function unpack(t, i, n)
  12. i = i or 1
  13. n = n or #t
  14. if i <= n then
  15. return t[i], unpack(t, i + 1, n)
  16. end
  17. end
  18. --3 4
  19. print(f(unpack(t)))
  • 尾调用:进行尾调用时不使用任何额外的栈空间,形如 return fun(args)的调用才是尾调用
  • 注意在使用长度操作符#对数组其长度时,数组不应该包含nil值,否则很容易出错
  1. local t = {
  2. '5',
  3. nil,
  4. '6'
  5. }
  6. --3
  7. print(#t)
  8. print(#{ 1, nil }) --1
  9. print(#{ 1, nil, 1 }) --3
  10. print(#{ 1, nil, 1, ['hello'] = '1', nil, 1 }) --5
  11. tb1 = {1, 2, 3, 4, 5}
  12. tb1[7] = 7
  13. print("tb1 length: "..#tb1)
  14. -- tb1 length: 7
  15. tb2 = {1, 2, 3, 4, 5}
  16. tb2[8] = 8
  17. print("tb2 length: "..#tb2)
  18. -- tb2 length: 8
  19. tb3 = {1, 2, 3, 4, 5}
  20. tb3[8] = 8
  21. tb3[9] = 9
  22. print("tb3 length: "..#tb3)
  23. -- tb3 length: 9
  24. tb4 = {1, 2, 3, 4, 5}
  25. tb4[2] = nil
  26. tb4[3] = nil
  27. -- tb4 length: 5
  28. print("tb4 length: "..#tb4)
  29. tb4[8]=1
  30. -- tb4 length: 1
  31. print("tb4 length: "..#tb4)

table实现分析


  • 在table的设计上,采用了array数组hashtable(哈希表)两种数据的结合
  • table会将部分整形key作为下标放在数组中,其余的整形key和其他类型的key都放在hash表中

  1. typedef union TKey {
  2. struct {
  3. TValuefields;
  4. int next; /* 用于标记链表下一个节点 */
  5. } nk;
  6. TValue tvk;
  7. } TKey;
  8. typedef struct Node {
  9. TValue i_val;
  10. TKey i_key;
  11. } Node;
  12. typedef struct Table {
  13. CommonHeader;
  14. lu_byte flags; /* 1<<p means tagmethod(p) is not present */
  15. lu_byte lsizenode; /* log2 of size of 'node' array */
  16. unsigned int sizearray; /* size of 'array' array */
  17. TValue *array; /* array part */
  18. Node *node;
  19. Node *lastfree; /* any free position is before this position */
  20. struct Table *metatable;
  21. GCObject *gclist;
  22. } 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节点原先的坑位

image-20220920010603493.png
2019070100095817.png

Rehash并不一定代表hash表的扩容,而是根据table里面的key的个数和类型,重新更合适的分配array的大小和hash表的大小,可能会扩容、可能不变、也有可能缩小


  1. TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
  2. Node *mp;
  3. TValue aux;
  4. if (ttisnil(key)) luaG_runerror(L, "table index is nil");
  5. else if (ttisfloat(key)) {
  6. lua_Integer k;
  7. if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */
  8. setivalue(&aux, k);
  9. key = &aux; /* insert it as an integer */
  10. }
  11. else if (luai_numisnan(fltvalue(key)))
  12. luaG_runerror(L, "table index is NaN");
  13. }
  14. mp = mainposition(t, key);
  15. if (!ttisnil(gval(mp)) || isdummy(t)) { /* main position is taken? */
  16. Node *othern;
  17. Node *f = getfreepos(t); /* get a free place */
  18. if (f == NULL) { /* cannot find a free place? */
  19. rehash(L, t, key); /* grow table */
  20. /* whatever called 'newkey' takes care of TM cache */
  21. return luaH_set(L, t, key); /* insert key into grown table */
  22. }
  23. lua_assert(!isdummy(t));
  24. othern = mainposition(t, gkey(mp));
  25. if (othern != mp) { /* is colliding node out of its main position? */
  26. /* yes; move colliding node into free position */
  27. while (othern + gnext(othern) != mp) /* find previous */
  28. othern += gnext(othern);
  29. gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */
  30. *f = *mp; /* copy colliding node into free pos. (mp->next also goes) */
  31. if (gnext(mp) != 0) {
  32. gnext(f) += cast_int(mp - f); /* correct 'next' */
  33. gnext(mp) = 0; /* now 'mp' is free */
  34. }
  35. setnilvalue(gval(mp));
  36. }
  37. else { /* colliding node is in its own main position */
  38. /* new node will go into free position */
  39. if (gnext(mp) != 0)
  40. gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */
  41. else lua_assert(gnext(f) == 0);
  42. gnext(mp) = cast_int(f - mp);
  43. mp = f;
  44. }
  45. }
  46. setnodekey(L, &mp->i_key, key);
  47. luaC_barrierback(L, t, key);
  48. lua_assert(ttisnil(gval(mp)));
  49. return gval(mp);
  50. }

从上面代码可以看出,插入一个节点是先直接插到hashtable上的,array上都都没有值插入,其实array上的值是在rehash的时候会将hashtable中的节点移动过去


#table取长度问题

  • luaH_getn就是用来求table的长度值的函数

  1. int luaH_getn (Table *t) {
  2. unsigned int j = t->sizearray;
  3. if (j > 0 && ttisnil(&t->array[j - 1])) {
  4. /* there is a boundary in the array part: (binary) search for it */
  5. unsigned int i = 0;
  6. while (j - i > 1) {
  7. unsigned int m = (i+j)/2;
  8. if (ttisnil(&t->array[m - 1])) j = m;
  9. else i = m;
  10. }
  11. return i;
  12. }
  13. /* else must find a boundary in hash part */
  14. else if (isdummy(t)) /* hash part is empty? */
  15. return j; /* that is easy... */
  16. else return unbound_search(t, j);
  17. }
  18. static int unbound_search (Table *t, unsigned int j) {
  19. unsigned int i = j; /* i is zero or a present index */
  20. j++;
  21. /* find 'i' and 'j' such that i is present and j is not */
  22. while (!ttisnil(luaH_getint(t, j))) {
  23. i = j;
  24. if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */
  25. /* table was built with bad purposes: resort to linear search */
  26. i = 1;
  27. while (!ttisnil(luaH_getint(t, i))) i++;
  28. return i - 1;
  29. }
  30. j *= 2;
  31. }
  32. /* now do a binary search between them */
  33. while (j - i > 1) {
  34. unsigned int m = (i+j)/2;
  35. if (ttisnil(luaH_getint(t, m))) j = m;
  36. else i = m;
  37. }
  38. return i;
  39. }
  • lua源码并没有进行遍历查找,而是通过二分查找
  • 先对数组查找,如果table数组部分的最后一个元素为nil,那么将在数组部分进行二分查找
  • 如果table数组部分的最后一个元素不为nil,那么将在hash部分进行二分查找
  • table中要想删除一个元素等同于向对应key赋值为nil,等待垃圾回收。但是删除table一个元素时候,并不会触发表重构行为,即不会触发rehash操作
  • 如果数组中的某个key被赋值为nil,同时最后一个元素不为nil,此时这个数组中的nil就被忽略,直接针对hash部分二分查找,导致找不对

泛型for的语法

  • 泛型for保存了三个值:一个迭代函数一个不可变状态一个控制变量
  1. for var-list in exp-list do
  2. body
  3. end
  • var-list是由一个或多个变量名组成的列表,用逗号分隔
  • exp-list是一个或多个表达式组成的列表,用逗号分隔
  • 变量列表中的第一个(或唯一的)变量称为控制变量
  • 控制变量在循环过程中永远不为nil, 因为当控制变量为nil 时,循环就
  • 泛型for就等价于:
  1. for var_1,...,var_n in explist do block end
  2. -- 等价于
  3. do
  4. local _f, _s, _var =exlist -- 表达式返回 迭代器,不可变状态,控制变量初始值
  5. while true do
  6. local var_1,..,var_n = _f(_s,_var) -- 把不可变状态、控制变量初始值作为迭代器的参数,迭代器返回n个值
  7. _var = var_1
  8. if _var == nil then break end -- 迭代器返回的第一个值为nil时,结束循环
  9. end
  10. end

  1. function values(l)
  2. local i = 0
  3. return function()
  4. i = i + 1
  5. return l[i]
  6. end
  7. end
  8. local ll = { 10, 20, 30 }
  9. local value = values(ll)
  10. while true do
  11. local v = value()
  12. if v then
  13. print(v)
  14. else
  15. break
  16. end
  17. end
  18. for k in values(ll) do
  19. print(k)
  20. end

无状态迭代器


  • 是一种自身不保存任何状态的迭代器,可以在多个循环中使用同一个无状态迭代器,避免创建新闭包的开销
  • 无状态迭代器,通过返回三个值(迭代函数、不可变状态、控制变量),再通过for循环 迭代器,从而完成循环
  1. local iter = function(t, i)
  2. i = i + 1
  3. local v = t[i]
  4. if v then
  5. return i, v
  6. end
  7. end
  8. --定义的ipairs 函数中会 返回 三个值,其中 分别是
  9. --迭代函数 iter、不可变状态表t 控制变量的初始值0
  10. local ipairs = function(t)
  11. return iter, t, 0
  12. end
  13. --for 循环 ipairs(t) 返回的值符合泛型for执行需要的值,成功完成迭代器的循环
  14. --与 非无状态迭代器相比, 无状态迭代器(例 ipairs)每次for循环时,
  15. --都是调用同一迭代函数 iter(),因为没有创建闭包,所以开销小
  16. for k, v in ipairs({ 10, 2, 3 }) do
  17. print(k, v)
  18. end
  19. --调用next(t, nil)时,返回表中的第一个键值对。当所有元素被遍历完,函数next返回nil
  20. local pairs = function(t)
  21. return next, t, nil
  22. end
  23. for k, v in pairs({ ["hello"] = 1, ["world"] = 2 }) do
  24. print(k, v)
  25. end
  26. local sort = function(t, i)
  27. local a = {}
  28. i = i + 1
  29. for k, _ in pairs(t) do
  30. a[#a + 1] = k
  31. end
  32. table.sort(a)
  33. if a[i] then
  34. --控制变量
  35. return i, a[i], t[a[i]]
  36. end
  37. end
  38. local sortTable = function(t)
  39. return sort, t, 0
  40. end
  41. for _, k, v in sortTable({ ["luaH_set"] = 10,
  42. ["luaH_get"] = 24,
  43. ["luaH_present"] = 48,
  44. }) do
  45. print(k, v)
  46. end
  47. local fromToIter = function(t, i)
  48. if i <= t[1] then
  49. --第一个表示变量
  50. --第二个表示返回值
  51. return i + t[2], i
  52. end
  53. end
  54. local fromTo = function(n, m, step)
  55. return fromToIter, { m, step }, n
  56. end
  57. --数值型for应该是指 for i = n,m,1 do something end ,由 nm 步长为1
  58. for _, v in fromTo(10, 20, 5) do
  59. print(v)
  60. end

元表


  • 元表是一类特殊的表,能修改一个值在面对一个未知操作时的行为
  • Lua语言中的每一个值都可以有元表
  • 在Lua语言中,只能为表设置元表;如果要给其他类型的值设置元表,则必须通过C代码或调试库完成
  • 字符串标准库为所有的字符串都设置了同一个元表,而其他类型默认情况下没有元表

  1. local t = {}
  2. local m = {}
  3. setmetatable(m, t)
  4. --table: 0x7fb9e540dca0 table: 0x7fb9e540dca0
  5. print(getmetatable(m), t)
  6. --table: 0x7fa5d4408680 table: 0x7fa5d4408680
  7. print(getmetatable("hello"), getmetatable("world"))
  8. --nil nil
  9. 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时回调


  1. local Set = {}
  2. local DefaultMetable = {}
  3. function Set.New(l)
  4. local set = {}
  5. setmetatable(set, DefaultMetable)
  6. for _, v in pairs(l) do
  7. set[v] = true
  8. end
  9. return set
  10. end
  11. function Set.Union(a, b)
  12. local res = Set.New {}
  13. for k in pairs(a) do
  14. res[k] = true
  15. end
  16. for k in pairs(b) do
  17. res[k] = true
  18. end
  19. return res
  20. end
  21. function Set.ToString(set)
  22. local l = {}
  23. for e in pairs(set) do
  24. l[#l + 1] = tostring(e)
  25. end
  26. return "{" .. table.concat(l, ", ") .. "}"
  27. end
  28. local s1 = Set.New { 10, 20, 30, 50 }
  29. local s2 = Set.New { 30, 1 }
  30. --table: 0x7f9e61c0e6d0 table: 0x7f9e61c0e6d0 table: 0x7f9e61c0e6d0
  31. print(getmetatable(s1), getmetatable(s2), DefaultMetable)
  32. DefaultMetable.__add = Set.Union
  33. local s3 = s1 + s2
  34. --{1, 30, 10, 20, 50}
  35. print(Set.ToString(s3))
  36. DefaultMetable.__tostring = Set.ToString
  37. --{1, 30, 10, 20, 50}
  38. print(s3)
  39. DefaultMetable.__gc = function(t)
  40. print("回收:", t)
  41. end
  42. local key = {}
  43. local defaultMetable = { __index = function(t, k)
  44. return t[key]
  45. end }
  46. local SetDefault = function(t, d)
  47. t[key] = d
  48. setmetatable(t, defaultMetable)
  49. end
  50. local tab = { x = 10, y = 30 }
  51. SetDefault(tab, 100)
  52. --100
  53. print(tab.z)
  54. function Track(origin)
  55. local proxy = {}
  56. local m = {
  57. __index = function(t, k)
  58. print("*access to element " .. tostring(k))
  59. return origin[k]
  60. end,
  61. __newindex = function(t, k, v)
  62. print("*update of element " .. tostring(k) .. " to" .. tostring(v))
  63. origin[k] = v
  64. end,
  65. __len = function()
  66. return #origin
  67. end
  68. }
  69. setmetatable(proxy, m)
  70. return proxy
  71. end
  72. t = {}
  73. t = Track(t)
  74. --*update of element 2 tohello
  75. t[2] = "hello"
  76. --*access to element 2
  77. print(t[2])
  78. t = Track({ 10, 20 })
  79. print(#t)
  80. --只读表
  81. function ReadOnly(t)
  82. local proxy = {}
  83. local mt = {
  84. __index = function(_, k)
  85. return t[k]
  86. end,
  87. __newindex = function()
  88. error("attempt to update a read-only table", 2)
  89. end
  90. }
  91. setmetatable(proxy, mt)
  92. return proxy
  93. end
  94. local days = ReadOnly { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }
  95. print(days[1])
  96. --days[1] = "M"

协程


线程与协同程序的主要区别:

  • 一个具有多个线程的程序可以同时运行几个线程
  • 一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停

协程状态

  • 挂起(suspended)
  • 运行(running)
  • 死亡(dead)
  • 正常(normal)

  1. local co = coroutine.create(function()
  2. print("hello world")
  3. end)
  4. --suspended
  5. print(coroutine.status(co))
  6. coroutine.resume(co)
  7. --dead
  8. print(coroutine.status(co))
  9. local co = coroutine.create(function()
  10. print("hello world")
  11. end)
  12. --suspended
  13. print(coroutine.status(co))
  14. coroutine.resume(co)
  15. --dead
  16. print(coroutine.status(co))
  17. function foo(a)
  18. print("foo", a)
  19. return coroutine.yield(2 * a)
  20. end
  21. local co = coroutine.create(function(a, b)
  22. print("co-body", a, b)
  23. local r = foo(a)
  24. print("co-body r:", r)
  25. local x, y = coroutine.yield(a + b, a - b)
  26. print("co-body x,y", x, y)
  27. return b, "end"
  28. end)
  29. --co-body 10 1
  30. --foo 10
  31. --main==== true 20
  32. -----------
  33. --co-body r: r
  34. --main==== true 11 9
  35. -----------
  36. --co-body x,y X Y
  37. --main==== true 1 end
  38. ---------
  39. --coroutine.resume 的参数,作为传参传入
  40. --coroutine.yield的参数,作为返回值,通过coroutine.resume返回
  41. print("main====", coroutine.resume(co, 10, 1))
  42. print("---------")
  43. print("main====", coroutine.resume(co, 'r'))
  44. print("---------")
  45. print("main====", coroutine.resume(co, "X", "Y"))
  46. print("-------")
  47. function receiver(prod)
  48. local _, v = coroutine.resume(prod)
  49. return v
  50. end
  51. function send(x)
  52. coroutine.yield(x)
  53. end
  54. function product()
  55. return coroutine.create(function()
  56. while true do
  57. local x = io.read()
  58. send(x)
  59. end
  60. end)
  61. end
  62. function filter(prod)
  63. return coroutine.create(function()
  64. for line = 1, math.huge do
  65. local x = receiver(prod)
  66. x = string.format("%5d %s", line, x)
  67. send(x)
  68. end
  69. end)
  70. end
  71. function consume(prod)
  72. while true do
  73. local x = receiver(prod)
  74. io.write(x, "\n")
  75. end
  76. end
  77. consume(filter(product()))

面向对象

  • 冒号操作符:在一个方法调用中增加一个额外的实参,或在方法定义中增加一个额外的隐藏形参
  • self作为函数的参数,调用时指定操作的对象

lua加密策略

  • 类似cocos的加密方式,对文件打上加密标记头,然后文件内容呢,加密后存放。需要修改lua加载文件的部分代码。密钥是写死在代码里,所以通过反编译代码很容易获取到。blowfish
  • luac编译后使用,luac编译后的代码,采用工具能够恢复一部分,可读性不强,可以作为一般应用的加密方式
  • 将加密解密的函数,由服务端传入。客户端执行这个函数加载相应模块。需要封装读取文件接口给LUA用。
    这种方式,非常隐蔽
  • 修改lua虚拟机中,指令的编号,然后使用luac进行编译。这样的方式luac编译后的字节码中,指令编号与其它的不同,是非常好的机密方式

lua c api
0904学习