您当前的位置: 首页 > 
  • 1浏览

    0关注

    322博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

学习Lua碰到的问题、踩坑记录

森明帮大于黑虎帮 发布时间:2022-06-04 16:25:26 ,浏览量:1

文章目录
  • 一、遍历字典是无序的
  • 二、Lua的遍历和C#不同
  • 三、同名传参和同名字段
  • 四、传参是引用传递
  • 五、rawget()和rawset()
  • 六、在Windows上安装Lua
      • 1.get a binary
      • 2.选择自己电脑的版本
      • 3.解压文件,(存放文件地址文件夹名全部不要出现中文最佳)
      • 4.修改环境变量
      • 5.win+R,cmd,输入lua或者lua53后如下即安装完成
      • 6.最后intellij idea 中推荐一个EmmyLua,使用安装比较简单,在设置setting里面选择Plugins,查找EmmyLua安装即可
  • 七、把数值转成16进制的格式
  • 八、把16进制色号转成三元数Color
  • 九、去掉商里的小数
  • 十、string.format() 保留3位小数
  • 十一、向上/下取整, 四舍五入
  • 十二、string拆成表, (不支持中文)
  • 十三、敏感词过滤, 替换成*
  • 十四、通配符, 正则表达式
  • 十五、用正则实现trim()
  • 十六、字符串截取string.sub()
  • 十七、数组增删
  • 十八、if后面跟一个方法, 这个方法会运行
  • 十九、匿名函数
  • 二十、死循环
  • 二十一、右移: “>>”, 按位与: “&”
  • 二十二、左移数值越界
  • 二十三、可变参数: ‘…’
  • 二十四、浅拷贝表
  • 二十五、字典用table.sort()排序
  • 二十六、多条件排序
  • 二十七、Lua里没有string.split()
  • 二十八、lua特殊写法
  • 二十九、变量名
  • 三十、今天星期几
  • 三十一、os.time(): 秒
  • 三十二、裆下日期转成表, 再把表转成秒
  • 三十三、看身份证满18没有
  • 三十四、Lua没有"继承"的弊端
  • 三十五、取字符串的长度
  • 三十六、#号
  • 三十七、身份证验证
  • 三十八、逻辑运算符的优先级
  • 三十九、虚变量
  • 四十、匹配字母和数字
  • 四十一、if not a
  • 四十二、墙裂推荐一个VS Code的插件: Rainbow Fart
  • 四十三、数组洗牌打乱
  • 四十四、JavaBean的要求规范
  • 四十五、实现Switch
  • 四十六、实现continue
  • 四十七、string.format
  • 四十八、三元表达式
  • 四十九、"逻辑与"的截断机制
  • 五十、问题:Lua的输出乱码
  • 五十一、问题:Lua不是内部或外部命令,也不是可运行的程序或批处理文件
  • 五十二、判断table里有没有某元素
  • 五十三、(不)连续表的遍历
  • 五十四、正则表达式
  • 五十五、取模和取余
  • 五十六、#汉字长度
  • 五十七、赋值多个值
  • 五十八、交换2个值
  • 五十九、返回多个值
  • 六十、方法后面不加小括号
  • 六十一、按位运算
  • 六十二、// 运算, 取商

一、遍历字典是无序的
t = {}
t.a = '1'
t.b = '2'
t.c = '3'
t.d = '4'
for i,v in pairs(t) do
   print(v)
end

-- 每次输出都是乱序的!!!

二、Lua的遍历和C#不同
function Cnt()
    print('执行Cnt方法')
    return 3
end
for i = 1, Cnt() do
   print(i)
end

输出:
执行Cnt方法
1
2
3

三、同名传参和同名字段

同名传参, 第1个传参无效。

function Do(arg, arg)
    print(arg)
end
Do(1, 2)

输出:
2

同名字段, 第2个字段无效。

a, a = 1, 2
print(a)

输出:
1

四、传参是引用传递

表作为参数传递时, 是引用传递, 传的是地址。

在C#里也是这样的: 如下↓, 改变"参数表"(arg)的内容, “原来的表”(tab)也会改变。

function ReName(arg)
    arg.name = '咸蛋超人'
end
tab = {name = '奥特曼'}
ReName(tab)
print(tab.name)

输出:
咸蛋超人

但是, 改变arg表自身, tab却不会变。

function ReName(arg)
    arg = {name = '咸蛋超人'}
end
tab = {name = '奥特曼'}
ReName(tab)
print(tab.name)

输出:
奥特曼

改变attr.name, 会同时改变ultraman.attr.name。 置空attr.name, 会同时置空ultraman.attr.name。 如下↓:

ultraman = {attr = {name = '奥特曼'}}
attr = ultraman.attr

attr.name = '咸蛋超人'
print(ultraman.attr.name)

attr.name = nil
print(ultraman.attr.name)

输出:
咸蛋超人
nil

改变attr, 不会同时改变ultraman.attr。 置空attr, 不会同时置空ultraman.attr。 如下↓:

ultraman = {attr = {name = '奥特曼'}}
attr = ultraman.attr

attr = {name = '咸蛋超人'}
print(ultraman.attr.name)

attr = nil
print(ultraman.attr.name)

输出:
奥特曼
奥特曼

结论: 改变attr内部字段, ultraman.attr会同时改变。 但是改变attr自身, ultraman.attr不变。

在lua中, 如果我想置空attr怎么办呢? 只能在方法外把它置空。 在C#中, 可以给传参添加ref关键字, 这样一来, 置空attr, 原本的attr也会被置空。

五、rawget()和rawset()

只对自身get和set, 而不访问元表。

otab = {a = '我是元表a'}
tab = {a = '我是a'}
setmetatable(tab, {__index = otab, __newindex = otab})

-- 获取tab的a, 与元表无瓜
print(rawget(tab, 'a'))
print(tab.a)
print(otab.a)

-- 给tab的b赋值, 与元表无瓜
rawset(tab, 'b', '我是b')
print(tab.b)
print(otab.b)

输出:
我是a
我是a
我是元表a

我是b
nil

六、在Windows上安装Lua

Lua下载地址:Lua:download

1.get a binary

在这里插入图片描述

2.选择自己电脑的版本

在这里插入图片描述

3.解压文件,(存放文件地址文件夹名全部不要出现中文最佳)

在这里插入图片描述

4.修改环境变量

在这里插入图片描述

5.win+R,cmd,输入lua或者lua53后如下即安装完成

在这里插入图片描述

6.最后intellij idea 中推荐一个EmmyLua,使用安装比较简单,在设置setting里面选择Plugins,查找EmmyLua安装即可

在这里插入图片描述

七、把数值转成16进制的格式
print(string.format('%x', 1221197823))

输出:
48c9ffff

八、把16进制色号转成三元数Color

比如色号是: FFA2FC, 先用string.sub()把她肢解为: 红FF, 绿A2, 蓝FC。 再用tonumber(str, 16)把16进制转为10进制, (例如: FF转为255)。 再用rgb分别除以255。 最后new一个三元数Color。

local colorString = 'FFA2FC'
local r = tonumber(string.sub(colorString, 1, 2), 16) / 255
local g = tonumber(string.sub(colorString, 3, 4), 16) / 255
local b = tonumber(string.sub(colorString, 5, 6), 16) / 255
return UnityEngine.Color(r, g, b)

九、去掉商里的小数

用"/"来做除法, 得到的结果中总会带有小数, 像这样:

print(700 / 100)
print(701 / 100)

输出:
7.0
7.01

需要实现: 当结果是7.0时, 去掉小数部分, 结果为7。 当结果是7.01时, 保留小数部分, 结果为7.01。

思路: 用math.modf(), 把整数部分与小数部分拆开:

local t1, t2 = math.modf(700 / 100)
print(t1)
print(t2)

输出:
7
0.0

再加个判断"小数部分是否为0"就好了:

function GetFixedQuotient(num1, num2)
    quotient = num1 / num2
    local t1, t2 = math.modf(quotient)
    -- 去掉数字后面的.0
    if t2 == 0 then
        return t1
    else
        return quotient
    end
end

print(GetFixedQuotient(700, 100))
print(GetFixedQuotient(701, 100))

输出:
7
7.01

十、string.format() 保留3位小数
print(string.format('%.3f', 0.5))
print(string.format('%.3f', 0.0005))
print(string.format('%.3f', 0.0004))
print(string.format('%.3f', 500))

输出:
0.500
0.001(这里四舍五入了)
0.000(这里四舍五入了)
500.000

十一、向上/下取整, 四舍五入
-- 取整
print('向上取整: 1 / 3 = ' .. math.ceil(1 / 3))
print('向下取整: 1 / 3 = ' .. math.floor(1 / 3))
-- 想实现四舍五入, 就+0.5
print('四舍五入: 1 / 3 = ' .. math.floor(1 / 3 + 0.5))
print('四舍五入: 2 / 3 = ' .. math.floor(2 / 3 + 0.5))
-- 保留3位小数的四舍五入, 就先*1000再+0.5再/1000
print('四舍五入: 1 / 3 = ' .. math.floor(1 / 3 * 1000 + 0.5) / 1000)
print('四舍五入: 2 / 3 = ' .. math.floor(2 / 3 * 1000 + 0.5) / 1000)

输出:
向上取整: 1 / 3 = 1
向下取整: 1 / 3 = 0
四舍五入: 1 / 3 = 0
四舍五入: 2 / 3 = 1
四舍五入: 1 / 3 = 0.333
四舍五入: 2 / 3 = 0.667

十二、string拆成表, (不支持中文)
input = "2b*)我"
tabA = {}
for i = 1, #input do
    let = string.sub(input, i, i)
    table.insert(tabA, i, let)
    print(i, let)
end

输出:
1	2
2	b
3	*
4	)
5	�
6	�
7	�

十三、敏感词过滤, 替换成*
input = 'ABc大大大'
word = 'bc大'
lower = string.lower(input)
result = lower
if string.find(lower, tostring(word)) ~= nil then
    -- 汉字占3个字符, 所以不能直接用string.len()
    local _, count = string.gsub(word, '[^\128-\193]', '')
    local stars = string.rep('*', count)
    result = string.gsub(lower, tostring(word), stars)
end
print(result)

输出: A***大大

十四、通配符, 正则表达式
s = "abc \"it's a cat\""
_, _, _, d = string.find(s, "([\"'])(.-)%1")
print(d) -----输出: it's a cat

十五、用正则实现trim()
name = '   蒂纳  奥特曼  '
local _name = string.gsub(name, '^%s*(.-)%s*$', '%1')
print(_name)

输出:蒂纳  奥特曼

十六、字符串截取string.sub()
s = 'abcde'

print(string.sub(s, 1, 3)) -- 从1数到3
print(string.sub(s, 3, 3)) -- 从3数到3
print(string.sub(s, 3)) -- 从3数到末尾
print(string.sub(s, 8)) -- 没有8,直接空了
print(string.sub(s, 3, -1)) -- 从3数到5(末尾)
print(string.sub(s, 3, -2)) -- 从3数到4(末尾-1)
print(string.sub(s, -4, -2)) --从2(末尾-3)数到4(末尾-1)
print(string.sub(s, -9))-- 从头数到尾

-- 输出:
abc
c
cde

cde
cd
bcd
abcde

十七、数组增删

数组能用, 字典不行。

table.insert(list, [pos, ]value), -- 不给pos就是#list+1
table.remove(list[, pos]), -- 不给pos就是#list

注意: 在for的时候, 要从后往前删。

十八、if后面跟一个方法, 这个方法会运行
function fun()
    print(2)
end
if fun() then
end

输出: 2

↓写成这样则不执行。

if fun then
end

十九、匿名函数

注意: 直接 a = fun(), fun会立即执行, 不想立即执行的话, 就用个匿名方法把它包起来, 或者像delegate的+=一样, 方法后面别带括号。

function foo(arg)
    print(arg)
end

---------1----------
arg1 = 1
-- 直接执行, 输出1
fun = foo(arg1)

---------2----------
arg2 = 2
-- 不执行
fun = function()
    foo(arg2)
end
-- 执行, 输出2
fun()

---------3----------
arg3 = 3
-- 不执行
fun = foo
-- 执行, 输出3
fun(arg3)

进阶: 2个不同来源的传参:

function fooo(arg1, arg2)
    print(arg1, arg2)
end

a = 2
-- 不执行
fun = function(b)
    fooo(a, b)
end
-- 执行, 输出2,3
fun(3)

二十、死循环

这样写就是死循环↓:

for i = 1, i + 10 do
    print(i)
end

正确写法↓:

local endNum = i + 10
for i = 1, endNum do
    print(i)
end

二十一、右移: “>>”, 按位与: “&”
print(0x0000ffff)
print(32794992 >> 16)
print(500 & 65535)
print(500 & 63)

输出:
65535
500
500
52

---------------------------------------------------------------
先看: 0x0000ffff   (得: 65535)
16进制的: 0x0000ffff, 就是10进制的: 65535, 就是2进制的: 11111111111111111111(16个1)
(10进制的: 65536, 就是2进制的: 1000000000000000000(16个0), 就是 1>>16)

再看: 32794992 >> 16   (得: 500)
10进制的: 32794992, 就是2进制的: ‭1111101000110100101110000‬
左移16位, 后面16位就被丢掉了, 只剩下111110100, 就是10进制的: 500
(计算器算 32794992 ÷ 65536 = 500.41186523438)

再看 500 & 65535   (得: 500)
一个2进制数, 只要小于16个1, 它和 11111111111111(16个1) 做按位与, 还是它自己
所以 500(111110100) 和 11111111111111(16个1) 做按位与, 还是它自己

再看 500 & 63
10进制的: 63, 就是2进制的: 111111
500(111110100) 和 111111 做按位与
就相当于高于6位的都丢掉了, 结果是 110100, 也就是10进制的 52

实例: id是64位, 其组成为:14位没用 - 8位大区id - 10位服务器id - 32位玩家id。 需求: 取服务器id。 分析: 先右移32位, 去除玩家id, 再和1111111111(10个1)做按位与, 就能得到10位的服务器id。

local id = 3063948711696181930
zoneID = (id >> 32) & 1023
print(zoneID)

二十二、左移数值越界
local id = 3063948711696181930
-- 即‭为2进制的: 10 1010 1000 0101 0101 0101 0001 1110 1010 1010 1010 1010 1010 1010 1010 1010
zoneID = (id 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->node))  /* hash part is empty? */
    return j;  /* that is easy... */
  else return unbound_search(t, j);
}


还是先数组,数组没有后hash部分。再来看下关于hash表部分的取长度

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;
    j *= 2;
    if (j > cast(unsigned int, MAX_INT)) {  /* overflow? */
      /* table was built with bad purposes: resort to linear search */
      i = 1;
      while (!ttisnil(luaH_getint(t, i))) i++;
      return i - 1;
    }
  }
  /* 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;
}

j++保证j是hash部分的第一个值,从j开始,如果j位置是有值的,那么将j扩大两倍,再检查两倍之后hash表中是否可以取到值,直到找到没有值的地方,这个值就在i 到 j这个区间中。然后再用折半查找找到 i 到 j之间找到的最后一个nil的,前面的就是它的长度了。 错略看来。luaH_getint用来取值。 const TValue luaH_getint (Table t, int key)而它的声明看来 ,第二个参数是key,通过key来取value, 而外面对传入的key是++的操作 可知计算长度用来寻找的这个key一定是个整形,而且还得是连续的(不一定)。(当然这个是不深究细节实现错略看下来的分析。。。。。)。

再来验证下:

local test1 = {1 , 3 , [4] = 4 , [6] = 6 ,[2] = 2} print(#test1) 打印结果: 2 也就是上面源码中,会先遍历数组部分,数组部分有就结束,没有再遍历hash表部分

local test1 = {[4] = 4 , [6] = 6 ,[2] = 2} print(#test1) 打印结果:0 数组之后的第一位是j++ 如果value是nil, i 是 0 ,j 是1 返回值是0

看两个一起的: local test1 = {[1] = 1 , [2] = 2 ,[4] = 4 ,[6] = 6} print(#test1)

local test1 = {[1] = 1, [2] = 2 ,[5] = 5 ,[6] = 6} print(#test1)

两个的输出结果是6和2 ,而且要是将第一个打印出来 是1 2 3 4 nil 6 中间差一个就能打出来后面的,差两个就不行了 why?就是因为上面源码中得算法。

举个例子: local test1 = {[1] = 1 , [2] = 2, [3] = 3 ,[4] = 4 ,[6] = 6} 第一个while循环结束, i == 4 ,j == 8, 通过下面的折半查找(具体细节还是拿笔算下吧。。。) 最后i == 6了

而local test1 = {[1] = 1, [2] = 2 ,[5] = 5 ,[6] = 6} 第一个while循环后, i == 2 , j == 4 , 折半查找后 i == 2

恩,就是这样了,如果不清楚这个的话,那么在实际操作的时候,会遇到很奇怪的问题而浪费大量时间。。。。

最后local test1 = { [‘a’] = 1, [‘b’] = 2 ,[‘c’] = 3} print(#test1) 打印结果: 0 key必须是整形才能用#取。

其他取数组长度形式 如果是字符串或者其他形式的,还是采用循环pairs这种形式去取为好。

三十七、身份证验证

问: 既然有身份证验证的网络接口, 为什么仍需要前端验证呢? 答: 需要先过滤掉一些奇葩(长度不对, 特殊字符, 生日2222年13月32日), 减轻服务器的压力

--验证身份证信息
--只支持18位身份证的验证

--[[
#身份证18位编码规则:dddddd yyyymmdd xxx y   
#dddddd:地区码   
#yyyymmdd: 出生年月日   
#xxx:顺序类编码,无法确定,奇数为男,偶数为女   
#y: 校验码,该位数值可通过前17位计算获得  
#  
#18位号码加权因子为(从右到左) Wi = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2,1 ]  
#验证位 Y = [ 1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2 ]   
#校验位计算公式:Y_P = mod( ∑(Ai×Wi),11 )   
#i为身份证号码从右往左数的 2...18 位; Y_P为脚丫校验码所在校验码数组位置  
参考代码:
      https://github.com/yujinqiu/idlint
]]
local string_len = string.len
local tonumber = tonumber

-- // wi =2(n-1)(mod 11) 
local wi = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1 }; 
-- // verify digit 
local vi= { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' }; 

local function isBirthDate(date)
    local year = tonumber(date:sub(1,4))
    local month = tonumber(date:sub(5,6))
    local day = tonumber(date:sub(7,8))
    if year  2100 or month >12 or month  month_days[month] or day  =  i then
            tbl[i], tbl[j] = tbl[j], tbl[i]
        end
    end
end

四十四、JavaBean的要求规范
  • 1、所有属性为private
  • 2、提供默认构造方法
  • 3、提供getter和setter
  • 4、实现serializable接口

这是为了向后兼容, 改的时候只改方法内部就好了,便于封装重用, 便于让其他程序员或者框架使用。

四十五、实现Switch

记得多看几遍这个myMetaTable干了什么。

table[4] = print('我是4')

local table = {
    [1] = print('我是1'),
    a = print('我是a'),
    b = print('我是b'),
    __default = print('我是default')
}

myMetaTable = {
    __index = function(t, k)
        print(t == table, k)
        return rawget(t, '__default')
    end
}

setmetatable(table, myMetaTable)

function switch(case)
    return table[case]
end

table.c = print('我是c')

switch(1)
switch(4)
switch(a)
switch('b')
switch('c')
switch(d)
print('卧槽, 这Lua的调用顺序好乱啊')


输出:

我是4
我是1
我是a
我是b
我是default
我是c
true	1
true	4
true	nil
true	b
true	c
true	nil
卧槽, 这Lua的调用顺序好乱啊

四十六、实现continue

思路: 用一次性while里的if-break作为continue。

for i = 1, 10 do
    while true do
        if i > 3 and i             
关注
打赏
1664288938
查看更多评论
0.0705s