zoukankan      html  css  js  c++  java
  • lua语言(1):安装、基本结构、函数、输入输出

    楔子

    我们这次来一起学习一下lua这门语言,因为python也可以调用lua。lua这门语言非常的简单,为什么说简单呢?首先lua的解释器是由C语言编写的,并且源码总共两万两千多行,可以说非常的少,就冲解释器的源代码数量,就决定了这门语言难不倒哪里去。

    也正因为lua的精简性,导致它无法独立地开发大型应用,lua存在的意义主要是为了和其它语言结合,提供相应的扩展功能,比如在C++中引入lua脚本等等。

    下面一起开始lua的学习之旅吧。

    lua的安装

    这里我们使用的是lua5.3,一个比较新的版本。直接去下载对应操作系统的lua即可,我这里使用的是Windows。lua下载我提供了百度网盘,可以直接下载:链接:https://pan.baidu.com/s/1UO2s_Xtof1XQIWJLP3cs2A 提取码:ne3q

    下载之后是一个压缩包,解压之后就是图中显示的那样。我们看到,确实非常的精简,当然lua也提供了相应的标准库,不过在5.3版本中都嵌入到解释器里面了,也就是图中的lua53.dll。

    将该目录设置到环境变量中,然后在终端中输入lua,即可进入到lua的交互式环境中。这里我就不演示了,我们下面会使用ide进行操作。关于ide,我这里使用的是pycharm,然后下载一个lua的插件即可。

    下面老规矩,我们来打印一个hello world,这个仪式感不能丢。

    print("hello world") 
    

    执行的时候,在控制台就会显示出"hello world"。估计有的小伙伴觉得,你这写的是python吧。其实,lua中的打印函数也是print,并且也是内置的,并且lua中的字符串可以使用单引号、也可以使用双引号,当然三引号不可以。

    lua语言入门

    任何一门语言都提供了不同类型的数据结构,那么lua中都有哪些数据结构呢?

    • nil:空
    • boolean:布尔类型,分别是true和false
    • number:数值型,整型和浮点型都属于number
    • string:字符串
    • table:表
    • function:函数
    • userdata:用户数据
    • thread:线程

    lua总共提供了以上8中数据类型,目前只需要知道一下即可,我们先将一些基础概念等前置工作说一下,后面会一点一点地慢慢介绍。

    lua中的关键字有22个

    and、break、do、else、elseif、end、false、goto、for、function、if、in、local、nil、not、or、repeat、return、then、true、until、while,这些关键字不用刻意去记,当然既然学习lua,肯定有其它编程语言基础,所以这些关键字显然是大部分都见过的。至于不认识的关键字,我们后面也会慢慢遇到。

    lua中的注释

    lua中也分为单行注释,多行注释。

    lua的单行注释和SQL一样,使用两个减号:--

    -- 这是单行注释
    

    lua的多行注释:以--[[开头、]]结尾,里面写注释。

    --[[
    
     这是多行注释
     并且开头的--和[[之间不可以有空格,结尾是两个]
     
    ]]
    

    下面这种写法也是多行注释,不是两行单行注释。

    --[[
    
     这也是多行注释
     不是两行单行注释
     
    --]]
    

    lua中的数值

    接下来让我们学习lua中的数据类型吧,首先是lua中的数值。

    我们说lua中的数值类型为number,整型和浮点型都为number。

    -- lua和python类似,在创建变量时不需要指定类型
    -- 解释器会自动根据赋的值来判断
    a = 123
    b = 3.14
    print(a, b)  -- 123	3.14
    
    -- lua中,每一行语句的结尾也不需要加分号,直接换行即可
    -- 当然加分号也是可以的,跟python是类似的
    c = 123;
    d = 456
    print(c, d)  -- 123	456
    
    -- 并且在python中,如果加上了分号,那么两行赋值可以写一行
    -- 比如 e = 1; f = 2
    -- 这在lua中也是可以的
    e = 1; f = 2
    print(e, f)  -- 1	2
    
    -- 但是lua比较彪悍的是,不加分号也可以
    -- 如果在python中这么写,则肯定是报错的
    g = 3 h = 4
    print(g, h)  -- 3	4
    
    -- 但是我们不建议将多行赋值语句写在同一行里面,最好要分行写
    -- 当然在lua中也可以使用多元赋值
    a, b = 1, 2
    print(a, b)  -- 1	2
    
    -- 这里可能有人发现了,我们在最上面已经创建了a和b这两个变量了
    -- 但是我们下面又创建了,这一点和python类似,可以创建多个同名变量
    -- 比如创建a = 1,然后又创建a = 2,这是允许的
    -- 只不过这相当于发生了更新,将a的值由1变成了2,当然即便赋值为其它类型也是可以的
    -- 比如我先创建a = 数值,然后再将a的值换成字符串也是可以的,这一点和python也是类似的
    -- 因为在lua中,全局变量是通过table、也就是"表"来存储的
    -- 这个table后面会详细说,你暂时可以理解为哈希表,或者当成python中的字典,而且python中全局变量也是通过字典存储的
    

    我们通过lua中的数值型,来演示了lua中如何创建一个变量,并且还介绍了lua中全局变量的存储方式,下面再来看看如何区分整型和浮点

    a = 123
    b = 123.  -- .后面不写东西的话,默认是.0
    c = .123  -- .前面不写东西的话,默认是0.
    print(a, b, c)  -- 123	123.0	0.123
    
    -- lua中,可以使用type函数检测变量的类型
    print(type(a))  -- number
    print(type(b))  -- number
    print(type(c))  -- number
    
    -- 这个type是内置的,它检测的是lua中的基础类型
    -- 而我们说lua中不区分整型和浮点型,如果想精确区分的话,那么可以使用math.type
    -- 整型是integer,浮点型是float
    print(math.type(a))  -- integer
    print(math.type(b))  -- float
    print(math.type(c))  -- float
    -- 如果一个数值中出现了小数点,那么math.type得到的就是float
    
    -- 使用type和math.type得到的都是一个字符串
    -- 另外,我们看到,我们是直接使用的math.type,这个math是哪里来的
    -- 这类似于一个外部包,比如python中也有math
    -- 只不过在lua中我们不需要导入,直接用即可,包括后面处理字符串用的包也是如此
    

    整型和浮点之间的比较

    print(3 == 3.0)  -- true
    print(-3 == -3.0)  -- true
    
    -- 我们看到,如果小数点后面是0,那么也是相等的,这一点和python也是一样的
    -- lua中也支持如下方式
    print(3e3)  -- 3000.0
    
    -- 但是我们看到,得到是浮点
    -- 在lua中,只要十进制数中出现了小数点、或者出现了幂运算,那么得到的都是一个浮点
    -- 准确的说,math.type检测的结果是float,因为lua中不区分整型和浮点,它们都是number
    -- 这里我们说浮点只是为了方便,理解意思即可
    
    -- lua中a ^ b表示a的b次方
    -- 如果运算中出现了浮点数,或者发生了幂运算,那么结果就是浮点
    print(3 ^ 2)  -- 9.0
    print(3 * 3)  -- 9
    

    lua中也支持16进制,以及一个数的取反操作

    print(0x123)  -- 291
    print(0X123)  -- 291
    

    算术运算

    算数运算没啥好说的,对于相加、相减、相乘、取模,如果是两个整型,那么结果还是整型,如果出现了浮点,那么结果为浮点。

    print(1 + 2, 1 + 2.)  -- 3	3.0
    print(1 * 2, 1 * 2.)  -- 2	2.0
    print(1 - 2, 1 - 2.)  -- -1	-1.0
    print(13 % 5, 13. % 5)  -- 3	3.0
    

    如果相除,那么结果一定为浮点

    print(3 / 2, 4 / 2, 4 / 2.)  -- 1.5	2.0	2.0
    

    当然在lua中,还有一个地板除,会对商向下取整,这是在lua5.3中引入的。

    print(3 // 2, 4 // 2)  -- 1	2
    -- 另外,如果里面出现了浮点,那么即使是地板除,也一样会得到小数
    print(4 // 2.)  -- 2.0
    
    -- 虽然是浮点,但我们看到的是1.0, 相当于还是有向下取整的效果
    print(4 // 2.5)  -- 1.0
    

    当然我们说,lua中还有幂运算,使用^表示

    print(3 ^ 4)  -- 81.0
    print(3e4)  -- 30000.0
    
    -- 我们说出现了幂运算,得到的一定是浮点
    

    关系运算

    关系运算符的话,大部分都是一样的。只有不等于,在其它编程语言中一般是!=,但是在lua中是~=

    位运算

    位运算和主流编程语言也是比较类似,尤其是python,感觉lua的很多设计都和python比较相似

    -- 按位与 &
    print(15 & 20)  -- 4
    -- 按位或 |
    print(15 | 20)  -- 31
    -- 按位异或 ~, 在python中是^,但我们说^在lua是位运算,lua中是 ~
    print(15 ~ 20)  -- 27
    -- 取反, 取反的话也是 ~
    print(~20)  --  -21
    
    -- 左移
    print(2 << 3)  -- 16
    -- 右移
    print(16 >> 2)  -- 4
    
    -- 以上这些操作符是在5.3当中才提供的,如果是之前的版本,则不能使用这些操作符
    

    数学库

    lua也提供了一个数学库,叫做math,里面定义了一些用于计算的函数,比如:sin、cos、tan、asin、floor、ceil。这个可以在用的时候,自己查看,这里就不演示了。

    lua中的字符串

    下面我们看看lua中的字符串,lua中字符串既可以使用双引号、也可以是但引号。注意:lua中的字符串是不可变量,不能本地修改,只能创建新的字符串。

    name = "komeiji satori"
    print(name) -- komeiji satori
    
    -- 使用#可以获取其长度
    print(#name, #"name") -- 14	4
    
    -- 使用.. 可以将两个字符串连接起来
    print("aa" .. "bb") -- aabb
    print("name: " .. name) -- name: komeiji satori
    
    -- ..的两边可以没有空格,但是为了规范,建议前后保留一个空格
    -- ..前后还可以跟数字,会将数字转成字符串
    print("abc" .. 3, 3 .. 4, 3 .. "abc") -- abc3	34	3abc
    -- 另外如果是..的前面是数字的话,那么..的前面必须有空格
    -- 也就是写成类似于 3 .. 的形式  不可以写 3..
    -- 因为3后面如果直接出现了. 那么这个.会被当成小数点来解释
    

    lua内部也支持多行字符串,使用[[]]表示

    msg = [[
        你好呀
        你在什么地方呀
        你吃了吗
    ]]
    
    print(msg)
    --[[
        你好呀
        你在什么地方呀
        你吃了吗
    
    ]]
    

    字符串和数字的转换

    lua中可以将字符串与数值相加,也可以相互转换

    print("10" + 2)  -- 12.0
    
    -- 如果字符串和整型运算,那么得到的也是浮点
    -- 你可以认为只有整型和整型运算才有可能得到整型,而字符串不是整型
    
    -- 调用tonumber可以将字符串显式的转为整型
    print(type(tonumber("10")))  -- number
    print(tonumber("10") + 2)  -- 12
    
    -- 如果转化失败,那么结果为nil
    print(tonumber("ff"))  -- nil
    
    -- 当然有些时候我们的数字未必是10进制,比如上面的ff,它可以是16进制
    -- 如果需要进制,那么就给tonumber多传递一个参数即可
    print(tonumber("ff", 16))  -- 255
    print(tonumber("11101", 2))  -- 29
    print(tonumber("777", 8))  -- 511
    
    -- 8进制,允许出现的最大数是7,所以转化失败,结果为nil
    print(tonumber("778", 8))  -- nil
    
    
    -- 整型转化成字符串,则是tostring
    print(tostring(100) == "100")  -- true
    print(tostring(100) == 100)  -- false
    
    -- 我们看到整型和字符串是可以相加的,当然相减也可以,会将字符串转成浮点
    -- 也可以判断是否相等或者不相等,这个时候会根据类型判断,不会隐式转化了,由于两者类型不一样,直接不相等
    -- 但是两者是无法比较大小的,只能判断是否相等或者不等
    -- 为什么这么做,是因为 2 < 15 ,但是 "2" > "15"
    -- 所以为了避免混淆,在比较的时候lua不会隐式转化、加上类型不同也无法比较大小,因此直接抛异常
    

    字符串标准库

    lua处理字符串还可以使用一个叫做string的标准库,这个标准库在5.3中也是内嵌在解释器里面的,我们直接通过string.xxx即可使用。下面就来看看string这个标准库都提供了哪些函数吧,这里说一下lua中的字符串是以字节为单位的,不是以字符为单位的,因此string的大部分函数不适合处理中文(也有少数例外),如果是中文,可以使用后面要介绍的utf8

    -- 查看字符串的长度
    print(string.len("abc"), #"abc")  -- 3	3
    -- 一个汉字占三个字节,默认是以字节为单位的,计算的是字节的个数
    print(string.len("古明地觉"), #"古明地觉")  -- 12	12
    
    -- 重复字符串n次
    print(string.rep("abc", 3))  -- abcabcabc
    -- 如果是单纯的重复字符串的话,也可以对中文操作,因为不涉及字符的局部截取
    print(string.rep("古明地觉", 3))  -- 古明地觉古明地觉古明地觉
    
    -- 字符串变小写,可以用于中文,但是没意义
    print(string.lower("aBc"))  -- abc
    print(string.lower("古明地觉"))  -- 古明地觉
    
    -- 同理还有转大写
    print(string.upper("aBc"))  -- ABC
    print(string.upper("古明地觉"))  -- 古明地觉
    
    -- 字符串翻转,这个不适合中文
    print(string.reverse("abc"))  -- cba
    print(string.reverse("古明地觉"))  -- ��谜厘椏�
    -- 我们看到中文出现了乱码,原因就是这个翻转是以字节为单位从后向前翻转
    -- 而汉字占3个字节,需要以3个字节为单位翻转
    
    
    -- 字符串截取,注意:lua中索引是从1开始的
    -- 结尾也可以写成-1,并且字符串截取包含首位两端
    print(string.sub("abcd", 1, -1))  -- abcd
    print(string.sub("abcd", 2, -2))  -- bc
    -- 可以只指定开头,不指定结尾,但是不可以开头结尾都不指定
    print(string.sub("abcd", 2))  -- bcd
    
    -- 同样不适合中文,除非你能准确计算字节的数量
    print(string.sub("古明地觉", 1, 3))  -- 古
    print(string.sub("古明地觉", 1, 4))  -- 古�
    -- 超出范围,就为空字符串
    print(string.sub("古明地觉", 100, 400) == "")  -- true
    
    
    -- 将数字转成字符
    print(string.char(97))  -- a
    -- 如果是多个数字,那么在转化成字符之后会自动拼接成字符串
    print(string.char(97, 98, 99))  -- abc
    
    
    -- 字符转成数字
    -- 默认只转化第1个
    print(string.byte("abc"))  -- 97
    -- 可以手动指定转化第几个字符
    print(string.byte("abc", 2))  -- 98
    print(string.byte("abc", -1))  -- 99
    -- 超出范围,那么返回nil
    print(string.byte("abc", 10) == nil)   -- nil
    -- 转化多个字符也是可以的,这里转化1到-1之间的所有字符
    print(string.byte("abc", 1, -1))  -- 97	98	99
    -- 越界也没事,有几个就转化几个
    print(string.byte("abc", 1, 10))  -- 97	98	99
    -- 另外,这里是返回了多个值,我们也可以用多个变量去接收
    a, b, c = string.byte("abc", 1, 10)
    print(a, b, c)  -- 97	98	99
    
    
    -- 关乎lua返回值,这涉及到了函数,我们后面会说
    -- 字符串的格式化,格式化的风格类似于C
    print(string.format("name = %s, age = %d, number = %03d", "古明地觉", 17, 1))  -- name = 古明地觉, age = 17, number = 001
    
    -- 字符串的查找,会返回两个值,分别是开始位置和结束位置
    print(string.find("abcdef", "de"))  -- 4	5
    -- 不存在则为nil
    print(string.find("abcdef", "xx"))  -- nil
    
    
    -- 字符串的全局替换,这个替换可以用中文
    -- 返回替换之后的字符串和替换的个数
    print(string.gsub("古名地觉 名名 那么可爱", "名", "明"))  -- 古明地觉 明明 那么可爱	3
    -- 我们同样可以使用返回值去接
    new_str, count = string.gsub("古名地觉 名名 那么可爱", "名", "明")
    print(new_str)  -- 古明地觉 明明 那么可爱
    

    关于处理ASCII字符,string库为我们提供了以上的支持,我们看到支持的东西还是比较少的,因为lua的源码总共才两万两千多行,所以这就决定了它没办法提供过多的功能。lua主要还是用来和别的语言结合的,然而事实上,string库提供的东西也不少了。

    下面来看看utf-8,我们说string库不是用来处理unicode字符的,如果处理unicode字符的话,需要使用utf8这个库

    -- lua中存储unicode字符使用的编码是utf-8
    -- 计算长度
    print(utf8.len("古明地觉"))  -- 4
    
    -- 类似于string.byte,这两个可以通用
    print(utf8.codepoint("古明地觉", 1, -1))  -- 21476	26126	22320	35273
    
    -- 类似于string.char,这两个可以通用
    print(utf8.char(21476, 26126, 22320, 35273))  -- 古明地觉
    
    
    -- 截取,使用string.sub
    -- 可以使用utf-8编码,不同字符占的字节大小可能不一样,这时候怎么截取呢
    -- 使用utf8.offset计算出,偏移到第n个字符的字节量
    print(string.sub("古明地觉", utf8.offset("古明地觉", 2)))  -- 明地觉
    print(string.sub("古明地觉", utf8.offset("古明地觉", -2)))  -- 地觉
    
    
    
    -- 遍历,遍历使用了for循环,我们后面说,现在先看一下
    for i, c in utf8.codes("古a明b地c觉") do
        print(i, c, utf8.char(c))
        --[[
            1	21476	古
            4	97	    a
            5	26126	明
            8	98	    b
            9	22320	地
            12	99	    c
            13	35273	觉
        ]]
    end
    

    以上便是lua处理字符串的一些操作, 尽管功能提供的不是非常的全面,但这与lua的定位有关。

    lua的控制结构

    本来是介绍lua中的数据类型的,但是由于里面很多特性都用到了循环,所以先来介绍一下lua的控制语句吧。

    条件语句

    我们来看看lua中的条件语句怎么写,lua中条件语句可以其它编程语言类似,单独的if、if和else、if...elseif...elseif...else。

    -- 单独的if
    if condition then
        statement
    end
    
    -- if和else
    if condition then
        statement
    else
        statement
    end
    
    -- if..elseif...else
    -- 注意:是elseif,不是else if,else和if之间需要连起来
    -- 可以写多个elseif,但是只能有至多一个if和一个else,当然else也可以没有,但if必须有
    if condition then
        statement
    elseif condition then
        statement
    elseif condition then
        statement
    else
        statement
    end    
    

    if和elseif后面必须加上一个then,类似于python中必须加上一个冒号一样,但是else则不需要then,另外每个if语句,在结尾处必须有一个end,来标志这个if语句块的结束。

    既然结尾处有end,那么lua中也就不需要缩进了。但是python中是必须严格按照缩进来的,而对于lua则不被缩进约束,但还是那句话,为了代码的可读性还是建议按照python的规范来编写

    a = 85
    
    if a > 80 then
        print("a > 80")
    end
    -- a > 80
    
    
    if a > 85 then
        print("a > 85")
    else
        print("a <= 85")
    end
    -- a <= 85
    
    
    if a < 60 then
        print("不及格")
    elseif a < 85 then
        print("及格")
    elseif a < 100 then
        print("优秀")
    elseif a == 100 then
        print("满分")
    else
        print("无效的分数")
    end
    -- 优秀
    

    我们说,lua中的if不受缩进影响,那么有时候单个if我们也可以写在一行

    a = 85
    
    -- 这么写也是可以的
    if a > 80 then print("a > 80") end
    
    -- if 和 else也可以写在一行
    if a > 85 then print("a > 85") else print("a <= 85") end
    
    -- 甚至if elseif else也可以写在一行,或者自由换行也是可以的
    if a < 60 then 
        print("不及格") elseif a 
    < 85 then print("及格") elseif 
    a <= 100 then print("优秀") else 
    print("无效的分数") end
    
    -- 对于if elseif else,最好不要像上面那么写,尽管它是支持的,但是不要这么做
    -- 由此我们看到,lua不是靠缩进来规范语句的,而是靠关键字
    

    嵌套if也是类似的

    age = 18
    gender = "female"
    
    if gender == "female" then
        if age >= 18 then
            print("或许我们之间可以做一场交易")
        else
            print("国家有一套完整的刑法")
        end 
    
    else
        if age >= 18 then
            print("女装好看的话也是可以的")
        else
            print("溜了溜了")
        end
    end
    
    -- 或许我们之间可以做一场交易
    

    上面的嵌套if是标准写法,但是我们说lua中的if不受缩进的约束

    age = 18
    gender = "female"
    
    if gender == "female" then
        if age >= 18 then
    print("或许我们之间可以做一场交易")
        else print("国家有一套完整的刑法")
        end
    
    else
        if age >= 18 then
    print("女装好看的话也是可以的")
        else
            print("溜了溜了")
        end end
    
    -- 或许我们之间可以做一场交易
    

    所以我们不考虑缩进也是可以的,但是不要这么写,一定要按照规范来写。如果是python的话,要是这样不管缩进,直接就报错了。

    循环while语句

    循环while语句,格式如下。

    while condition do
        statement
    end    
    

    举个例子

    i =
    1 sum = 0
    
    while i < 10 do
        sum = sum + i
        i = i + 1
    end
    
    print(string.format("sum = %d", sum))  -- sum = 45
    

    repeat ... until

    repeat ... until说白点,就是一直重复做,直到满足某个条件停下来。

    i =
    1 sum = 0
    
    -- 不断的执行 sum = sum + 1   i = i + 1,直到满足 i >= 10的时候停下来
    repeat
        sum = sum + i
        i = i + 1
    until i >= 10
    
    print(string.format("sum = %d", sum))  -- sum = 45
    

    这里忘记提了,我们这里的条件只有一个,如果是多个条件呢?和python一样,使用and、or

    age = 18
    gender = "female"
    
    if age == 18 and gender == "female" then
        print("或许我们之间可以进行一场交易")
    else
        print("抱歉,打扰了")
    end
    -- 或许我们之间可以进行一场交易
    
    
    a = 12
    b = 5
    
    if a > 12 or b <= 5 then
        print("~~~")
    else
        end
    -- ~~~
    
    
    -- 从上面我们可以看到,语句块里面可以不写任何东西,如果是python的话,则是需要使用pass作为占位符
    -- and是左右两边都成立,整体才成立,而or则是两边有一个成立,整体就成立
    
    
    --  当然lua中还有not表示取反,得到布尔值
    -- 这里着重强调一点,在lua中只有false和nil才为假,其它全部为真
    -- 这里和python不一样,在python中0、""是假,但在lua中是真
    -- 再次强调,lua中只有false和nil才是假
    print(not 0)  -- false
    print(not "")  -- false
    print(not not "")  -- true
    
    -- 0和""为真,所以使用not得到假,两个not得到真
    

    循环for语句

    for语句分为两种,我们来看一下

    for a = 1, 8 do
        print(a)
    end
    --[[
    1
    2
    3
    4
    5
    6
    7
    8
    ]]
    
    
    -- 我们看到循环打印了1到8
    -- 当然还可以跟上步长
    for a = 1, 8, 3 do
        print(a)
    end
    --[[
    1
    4
    7
    ]]
    

    上面是简单的遍历数字,for循环还可以遍历表,等介绍表的时候我们再说。

    break

    break则是跳出循环体,可以用于for、while、repeat,注意:没有continue

    for a = 1, 8 do
        print(a)
        break
    end
    -- 1
    
    
    -- 比如,如果a是偶数,那么我们就继续下一层循环
    -- 但是lua中没有continue,我们可以是if else进行模拟
    for a = 1, 8 do
        if a % 2 == 0 then
        else print(a) end
    end
    --[[
    1
    3
    5
    7
    ]]
    

    lua中的表

    下面我们来看看lua中的表(Table),表是lua语言中最主要(事实上也是唯一)的数据结构,lua中的表既可以当做数组来用,也可以当成哈希表来用。这个和python中的字典非常类似,比如:我们之前用查看变量类型的math.type,本质上就是以字符串"type"来检索表math。而在python中,比如调用math.sin,本质也是从math模块的属性字典里面查找key为"sin"对应的value。

    # python代码
    import math
    print(math.sin(math.pi / 2))  # 1.0
    print(math.__dict__["sin"](math.pi / 2))  # 1.0
    

    然后看看在lua中如何创建表

    -- 类似于python中的字典,lua中创建表直接使用大括号即可
    t = {}
    -- 返回的是表的一个引用
    print(t)  -- table: 00000000010b9160
    -- 类型为table
    print(type(t) == "table")  -- true
    

    在这里我们需要介绍一下lua中的变量,当然lua中分为全局变量和局部变量,这两者我们会在函数中细说。总之,我们目前创建的都是全局变量,其有一个特点:

    -- 对于没有创建的变量,可以直接打印,结果是一个nil
    print(a)  -- nil
    
    -- c这个变量没有创建,因此是nil,那么d也是nil
    d = c
    print(d)  -- nil
    
    -- 所以我们看到程序中,明明没有这个变量,但是却可以使用,只不过结果为nil
    -- 那么如果我们将一个已经存在的变量赋值为nil,是不是等于没有创建这个变量呢?
    -- 答案是正确的,如果将一个变量赋值为nil,那么代表这个变量对应的内存就会被回收
    name = "shiina mashiro"
    name = nil  -- "shiina mashiro"这个字符串会被回收
    

    之所以介绍全局变量这个特性,是因为在我们的表中,nil是一个大坑,我们往下看。

    a = {}
    
    a["name"] = "古明地觉"
    a["age"] = 16
    
    -- 打印a只是返回一个引用
    print(a)  -- table: 00000000000290e0
    print(a["name"], a["age"])  -- 古明地觉	16
    
    -- 更改表的元素
    -- table类似于哈希表,key是不重复的,所以重复赋值相当于更新
    a["age"] = a["age"] + 1
    print(a["age"])  -- 17
    
    -- 还记得我们之前说,全局变量也是通过table存储的吗
    -- 我们可以一个变量不断地赋值,赋上不同类型的值
    -- 就是因为table对应value没有限制
    -- 可以附上任意类型的value,相当于发生了更新操作
    a["age"] = 18
    print(a["age"])  -- 18
    a["age"] = "十六"
    print(a["age"])  -- 十六
    
    
    -- 创建table返回的是一个引用
    b = a
    -- 此时的b和a指向的是同一个table,修改b会影响到a
    b["name"] = "satori"
    print(a["name"])  -- satori
    
    
    -- 我们说赋值给nil,等价于回收对象
    a = nil 
    -- 但是只将a赋值为nil,显然还不够,因为还有b在指向上面的table
    b = nil 
    -- 这样的话,table就被回收了
    

    我们说lua中的table既可以做哈希表,也可以当做数组,有兴趣可以看lua的原代码,非常的精简。下面我们来看看table如何当成数组来使用:

    a = {}
    
    for i = 1, 10 do
        a[i] = i * 2
    end
    
    print(a[3])  -- 6
    
    
    -- table中如果key是整型,那么会通过数组的方式来存储
    -- table在C中是一个结构体,里面实现了哈希表和数组两种结构
    -- 如果key是整型,那么会通过数组的方式来存储,如果不是,会当成哈希表来存储
    -- 注意:如果当成数组使用,那么索引也是从1开始的。
    
    -- 此时是通过哈希表存储的
    a["x"] = 233
    print(a["x"])  -- 233
    
    -- 除了a["x"]这种方式,还可以使用a.x,这两者在lua中是等价的
    print(a.x)  -- 233
    
    -- a["name"]和a.name是等价的,但是和a[name]不是等价的,因为name是一个变量,而name = "x",所以结果是a["x"]或者a.x
    a["name"] = "椎名真白"
    name = "x"
    print(a["name"], a.name, a[name])  -- 椎名真白	椎名真白	233
    

    我们说2和2.0是相等的,所以在table中是怎么样表现的呢?

    a = {}
    
    a[2] = 123
    print(a[2.0])  -- 123
    
    a[2.0] = 456
    print(a[2])  -- 456
    
    
    -- 所以这两者是等价的,因为2.0会被隐式转化为2
    -- 事实上这在python的字典中也有类似的现象
    -- d = {}; d[True] = 1; d[1] = 2; d[1.0] = 3; print(d)
    -- 上面那行代码在python里面执行一下,看看会发生什么
    
    -- 但是对于字符串则不一样的
    a = {}
    a[2] = 123
    a["2"] = 456
    print(a[2], a["2"])  -- 123	456
    
    
    -- 如果访问表中一个不存在的key呢?
    print(a["xxx"])  -- nil
    
    -- 我们看到得到的是一个nil
    -- 显然我们想到了,如果将一个key对应的值显式的赋值为nil,那么也等价于删除这个元素
    a[2] = nil 
    

    表构造器

    估计有人目前对table即可以当数组又可以当哈希表会感到困惑,别着急我们会慢慢说。

    我们目前创建表的时候,都是创建了一张空表,其实在创建的时候是可以指定元素的。

    a = {"a", "b", "c" }
    print(a[1], a[2], a[3])  -- a	b	c
    -- 我们看到,我们没有指定key,所以此时表里面的三个元素是通过数组存储的,这种存储方式叫做"列表式(list-style)"
    -- 索引默认是1 2 3 4...
    
    
    -- 此外,还可以这么创建
    b = {name="mashiro", age=18 }
    print(b["name"], b["age"])  -- mashiro	18
    -- 第二种方式是通过哈希表存储的,这种存储方式叫做"记录式(record-style)"
    -- 此时的"name"、"age"我个人习惯称之为key
    -- 因为本人是做python的,就按照python中的dict就是由key: value组成的
    -- 当然你也可以称之为索引
    
    
    -- 但如果我们存储的key是数字或者说特殊字符呢?答案是使用[]包起来
    b = {["+"]="add", [3] = "xxx"}  -- 必须使用 ["+"]=和[3]= 不能是单独的 +=和 3=
    -- 同理获取也只能是b["+"]和b[3],不可以是b.+和b.3
    print(b["+"], b[3])  -- add xxx
    
    
    -- 表也是可以嵌套的
    a["table"] = b
    print(a["table"]["+"])  -- add
    
    
    -- 此外,两种方式也可以混合使用
    mix = {'a', name='mashiro', 'b', age=18 }
    print(mix[1], mix[2])  -- a	b
    print(mix["name"], mix["age"])  -- mashiro	18
    
    -- 这里有必要详细说明一下,即使是混合使用
    -- 如果没有显式地指定key、也就是列表式,那么会以数组的形式存储,索引默认是1 2 3...
    -- 所以a[1]是'a',  a[2]是'b'
    
    
    -- 如果是这种情况呢?
    mix = {'a', [2] = 1 }
    print(mix[2])  -- 1
    mix = {'a', 'b', [2] = 1 }
    print(mix[2])  -- b
    
    -- 解释一下,首先啊对于单个标量来说,默认就是用数组存储的,索引就是 1 2 3...
    -- 但是我们在通过记录式设置的时候,对应的key使用的如果也是数组的索引
    -- 那么记录式中设置的值会被顶掉
    --[[
    比如:mix = {'a', [2] = 1 }, 数组的最大索引是1,所以[2] = 1是没有问题的
    但是mix = {'a', 'b', [2] = 1 },数组最大索引是2,所以[2] = 1会被顶掉,因为冲突了
    ]]
    
    
    -- 事实上
    -- mix = {'a', 'b', [2] = 1 }这种方式就等价于mix = {[1] = 'a', [2] = 'b', [2] = 1 }
    -- 如果key是整型,那么通过数组存储, 否则通过哈希表存储
    -- 只不过我们手动指定[2] = 1会先创建,然后被[2] = 'b'顶掉罢了
    a = {'a', [1] = 1 }
    print(a[1])  -- 'a'
    a = {[1] = 1, 'a'}
    print(a[1])  -- 'a'
    -- 不管顺序,a[1]都会是'a'
    

    估计有人还有疑问,那就是a = {}; a[1] = 1; a[100] = 100或者a = {1, [100] = 100},如果这样创建的话,那么中间的元素是什么?因为我们说key是整型是以数组存储的,而数组又是连续的存储的空间,而我们只创建了两个元素,索引分别是1和100,那么其它元素是以什么形式存在呢?带着这些疑问,我们先往下看。

    数组、列表和序列

    现在我们知道了如果想表示常见的数组、或者列表(数组不用说,列表是python中的,都是一段连续的存储空间),那么只需要使用整型作为索引即可。

    而且在lua的table中,可以使用任意数字作为索引,只不过默认是从1开始的,lua中很多其他机制也遵循此惯例。

    但是table的长度怎么算呢?我们知道对字符串可以使用#,同理对table也是如此。

    a = {1, 2, 3, name = 'mashiro', 'a' }
    print(#a)  -- 4
    
    -- 但是我们看到,结果为4,可明明里面有5个元素啊
    -- 因为#计算的是索引为整型的元素的个数,更准确的说#计算的是使用数组存储的元素的个数
    
    a = {[0] = 1, 2, 3, 4, [-1]=5}
    print(#a)  -- 3
    -- 此时的结果是3,因为0和-1虽然是整型,但它们并没有存储在数组里
    -- 因为lua索引默认是从1开始,如果想要被存储的数组里面,那么索引必须大于0
    
    a = {1, 2, [3.0]="xxx", [4.1] = "aaa" }
    print(#a)  -- 3
    -- 这里同样是3,因为3.0会被隐式转化为3,因此数组里面有3个元素,但是4.1不会
    

    所以我们看到,#计算的是存储在数组里面的元素,也就是table中索引为正整型的元素,但真的是这样吗?

    首先对于数组中存在空(nil)的table,#获取长度是不可靠的,它只适用于数组中所有元素都不为nil的table。事实上,将#应用于table获取长度一直是饱受争议,以前很多人建议如果数组中存在nil,那么使用#操作符直接抛出异常,或者说扩展一下#的语义。然而,这些建议都是说起来容易做起来难,主要是在lua中数组实际上是一个table,而table的长度不是很好理解。

    我们举例说明:

    a = {1, 2, 3, 4 }
    a[2] = nil
    print(#a)  -- 4
    
    -- 上面我们很容易得出这是一个长度为4,第二个元素为nil的table
    -- 但是下面这个例子呢? 没错,就是我们之前说的
    b = {}
    b[1] = 1
    b[100] = 100
    -- 是否应该认为这是一个具有100个元素,98个元素为nil的table呢?
    -- 如果我们再将a[100]设置成nil呢,该列表长度又是多少呢?是100、99还是1呢
    print(#b)  -- 1
    
    
    -- lua作者的想法是,像C语言使用作为字符串的结束一样,lua中可以使用nil来隐式地表示table的结束
    -- 所以此时的结果是1
    -- 但是a的第二个元素也是nil啊,为什么是4呢,别急往下看
    c = {1, nil, 3, 4}
    print(#c)  -- 4
    -- 啊嘞嘞,咋变成4了,难道不是1吗?别急,继续看
    d = {1, nil, 3, 4, nil }
    print(#d)  -- 1
    
    -- 我屮艸芔茻,为啥又变成1了。
    -- 如果在table中出现了nil,那么#的结果是不可控的
    -- 有可能你多加一个nil,结果就变了。当然,不要去探究它的规律,因为这没有意义
    -- 总之不要在table中写nil,在table中写nil是原罪。不管是列表式、还是记录式,都不要写nil,因为设置为nil,就表示删除这个元素
    
    -- 回到b这个table中
    -- 我们说它的长度为1
    print(#b)  -- 1
    -- 但是数组中确实存在索引为100的元素
    print(b[100])  -- 100
    
    
    -- 所以对b这个table,其中数组到底是怎么存储的,其实没必要纠结
    -- 就当成索引为2到索引为99的元素全部是nil即可,但是计算长度的时候是不准的
    -- 总之table中最好不要出现nil
    

    遍历表

    我们可以使用for循环去遍历table

    a = {"a", "b", name="mashiro", "c", age=18, "d" }
    
    -- for循环除了for i = start, end, step这种方式之外,还可以作用在表上面
    -- 只不过需要使用pairs将table包起来,for k, v in pairs(t)
    for index, value in pairs(a) do
        print(index, value)
        --[[
        1	    a
        2	    b
        3	    c
        4	    d
        age	    18
        name    mashiro
        ]]
    end
    -- 这里for循环中出现了两个循环变量,分别表示索引和值
    -- 如果只有一个变量,那么得到的是索引,或者哈希表的key
    -- 我们name和age好像顺序不对啊,是的,因为是通过哈希表存储的,所以不保证顺序
    -- 但是对于数组来说,则是按照索引从小到大的方式存储、并输出的
    
    
    
    -- 除了pairs,还有ipairs
    -- ipars是只遍历存在于数组里面的元素
    a = {[4] = "a", [3] = "b", name="mashiro", [1] = "c", age=18, [2] = "d" }
    for index, value in ipairs(a) do
        print(index, value)
        --[[
        1	c
        2	d
        3	b
        4	a
        ]]
    end
    -- 打印按照索引从小到大打印,但是不建议这么创建table
    

    如果table中出现了nil,那么我们使用for循环去遍历会发生什么奇特的现象呢?

    -- 不过在此之前,还是先来看看一个坑向的
    a = {[3] = 1, 'a', 'b', 'c' }
    -- 这个时候a[3]是多少呢?
    print(a[3])  -- c
    
    -- 我们说只要是列表式,都是从1开始,所以[3] = 1最终会被[3] = 'c'所顶掉
    
    
    -- 下面我们来看看table中出现了nil,for循环会如何表现
    a = {'a', nil, 'b', 'c' }
    print(#a)  -- 4
    
    for index, value in ipairs(a) do
        print(index, value)
        --[[
        1   a
        ]]
    end
    -- 长度虽然是4(当然我们知道这不准),但是在遍历的时候一旦遇到nil就会终止遍历。当然这个nil要是数组中的nil,不是哈希表中的nil
    -- 但如果是pairs,那么会遍历值不为nil的所有记录
    a = {'a', nil, 'b', 'c', name=nil, age=18}
    for index, value in pairs(a) do
        print(index, value)
        --[[
        1	a
        3	b
        4	c
        age	18
        ]]
    end
    -- 但是我们看到值"b"对应的索引是3,尽管前面的是nil,但是毕竟占了一个坑
    -- 所以"b"对应的索引是3
    
    
    -- 当然我们还可以使用获取长度、数值遍历的方式,当然前提是table中不能出现nil
    a = {'a', 'b', 123, 'xx' }
    for idx = 1, #a do
        print(a[idx])
        --[[
        a
        b
        123
        xx
        ]]
    end
    

    表标准库

    表的标准库提供一些函数,用于对表进行操作,注意:这个标准库也叫table。

    a = {10, 20, 30 }
    print(a[1], a[2], a[3])  -- 10	20	30
    
    -- 使用table.insert可以插入一个值
    -- 接收参数为:table 插入位置 插入的值
    table.insert(a, 2, "xxx")
    print(a[1], a[2], a[3], a[4])  -- 10   xxx	20	30
    -- 如果不指定位置,那么默认会添加在结尾
    -- 此时传递两个参数即可:table 插入的值
    table.insert(a, "古明地觉")
    print(a[#a])  -- 古明地觉
    
    
    -- 既然有insert,那么就会有remove
    -- 接收参数:table 移除的元素的位置(索引)
    print(a[1], a[2], a[3], a[4], a[5])  -- 10	xxx	20	30
    table.remove(a, 3)
    print(a[1], a[2], a[3], a[4], a[5])  -- 10	xxx	30	古明地觉    nil
    
    -- 我们看到使用remove之后,后面的元素会依次向前移动
    -- 因此无需担心会出现nil什么的
    -- 不过这也说明了,remove的效率不是很高,因为设置到元素的移动
    -- 但是table中的函数都是C实现的,也是很快的,因此也不用太担心
    
    -- 另外,在lua5.3中,还提供了一个move函数
    -- table.move(table, start, end, target),表示将table中[start, end]之间的元素移动到索引为target的位置上
    -- 也是start位置的元素跑到target上面,start + 1 -> target + 1、 end -> target + end - start
    t = {1, 2, 3, 4}
    table.move(t, 2, #t, 3)
    print(t[1], t[2], t[3], t[4], t[5])  -- 1	2	2	3	4
    -- 很好理解,{1 2 3 4}中索引为[2, #t],移动到索引为3的位置上,因此结果是1 2 2 3 4,结果会多出一个
    
    -- 这里的move实际上是将一个值从一个地方拷贝 copy 到另一个地方
    -- 另外,我们除了可以将元素移动到table本身之外,还可以移动到另一个table
    t1 = {"a", "b", "c", "d" }
    t2 = {"x", "y" }
    -- 表示将t1中[2, #t1]的元素移动到t2中索引为2的地方
    table.move(t1, 2, #t1, 2, t2)
    for idx = 1, #t2 do
        print(t2[idx])
        --[[
        x
        b
        c
        d
        ]]
    end
    
    -- table标准库中还提供了concat函数,会将表里面的元素拼接起来
    a = {1, 2, "xxx", 3, "aaa" }
    print(table.concat(a))  -- 12xxx3aaa
    

    来个思考题吧

    a = "b"
    b = "a"
    
    t = {a = "b", [a] = b }
    print(t.a, t[a], t[t.b], t[t[b]])
    
    -- 上面的print会打印出什么呢?我们分析一下
    -- 首先看t这个表,其中a = "b"无需多说
    -- 关键是[a] = b,我们说a和b都是变量,并且a = "b" b = "a", 所以结果等价于["b"] = "a", 即:b = "a"
    -- 因此这里的t可以看做是 {a = "b", b = "a"}
    
    -- 那么t.a显然是"b"
    -- t[a]等于t["b"],因此结果是"a"
    -- t.b结果是"a",那么t[t.b]等于是t["a"],所以结果是"b"
    -- t[b] -> t["a"] -> "b",那么t[t[b]] -> t["b"] -> "a",因此结果是"a"
    -- 所以print会打印出: "b" "a" "b" "a"
    
    
    -- 下个问题
    a = {}
    a.a = a
    print(a)  -- table: 0000000000d98ef0
    print(a.a)  -- table: 0000000000d98ef0
    print(a.a.a)  -- table: 0000000000d98ef0
    
    -- 我们发现打印的都是一样的,我们说lua中的table返回的一个引用
    -- a.a = a,本身显然陷入了套娃的状态
    

    lua中的函数

    下面我们来介绍一下lua中的函数,lua的函数可以说是非常的有意思,尤其是它的参数和返回值的设定很有趣,不过在介绍之前,我们需要来说一下lua中的全局变量和局部变量。

    -- 我们直接创建的变量,默认是全局的,在哪里都可以使用
    -- 如果想创建一个局部变量,那么需要使用local关键字
    -- 这样创建的变量就只能在对应的作用域中生效
    if 2 > 1 then a = 123 end
    print(a)  -- 123
    -- 当上面的if语句执行完之后,a这个变量就被创建了
    
    if 2 > 1 then local b = 123 end
    print(b)  -- nil
    -- 我们看到此时打印的是nil,因为上面if语句中的变量b,我们使用local关键字
    -- 代表它是一个局部的,只能在对应的if语句中使用,外面没有b这个变量,所以打印结果为nil
    
    
    name = "mashiro"
    if 2 > 1 then local name = "satori" end
    print(name)  -- mashiro
    if 2 > 1 then name = "satori" end
    print(name)  -- satori
    -- 如果是local,那么相当于创建了新的局部变量,if里面的name和外部的name是不同的name
    -- 但如果没有local,那么创建的都是全局变量,而外面已经存在name,因此相当于直接对外部的name进行修改
    
    
    for i = 1, 10 do
    end
    -- 不仅是if,for循环也是如此,里面如果使用了local关键字创建的变量,那么外部也是无法使用的
    -- 这里我们看一下循环变量i, 我们发现变量i在循环结束之后也不能使用了,当然python中是可以的
    print(i)  -- nil
    
    
    i = 0
    for i = 1, 10 do
    end
    print(i)  -- 0
    -- 我们看到打印的是0,说明for循环的i和外部的i是没有关系的
    
    
    -- 不仅是for循环,while循环也是如此
    -- 还有repeat ... until
    i = 1 sum = 0
    repeat
        sum = sum + i
        -- 尽管x是局部变量但是它对until是可见的
        local x = sum
    until x > 30
    
    
    -- 再比如函数,还没有介绍,但是可以先感受一下
    function add()
        b = "aaa"
    end
    print(b)  -- nil
    add()
    print(b)  -- aaa
    -- 当我们直接print(b)结果为nil
    -- 但是当执行了add()之后,b = "aaa"就执行了
    -- 而我们说,只要没有local,那么创建的变量都是全局的
    -- 所以再次打印b就得到了字符串"aaa"
    
    -- 另外如果是在全局的话,即便加上了local,它还是一个全局变量
    a = "xx"
    local a = "xx"
    -- 上面两种方式没有区别,因为这是在全局中定义的,所以即使加上了local也没问题
    

    然后我们来看看如何在lua中定义一个函数,lua中函数的定义规则如下:

    function func_name(arg1, arg2) do
        statement
        statement
        statement
        ...
    end
    

    lua函数的参数传递

    我们来看看给函数传递参数该怎么做?

    function f1(a, b, c)
        print(a, b, c)
    end
    
    f1(1, 2, 3)  -- 1	2	3
    f1(1, 2)  -- 1	2	nil
    f1(1, 2, 3, 4)  -- 1	2	3
    
    -- 我们看到如果参数传递的不够,会自动使用nil填充
    -- 如果传递多了,会自动丢弃
    -- lua中不支持关键字参数传递
    
    
    -- lua中函数也不支持默认参数,但是通过上面这个特性,我们可以实现默认参数的效果
    function f2(a, b, c)
        -- 我们希望给c一个默认参数,假设就叫"xxx"吧
        -- 如果c为nil,那么结果就是"xxx",因为lua中false和nil为假
        c = c or "xxx"
        print(a, b, c)
    end
    
    f2(1, 2, 3)  -- 1	2	3
    f2(1, 2)  -- 1	2	xxx
    

    lua函数的返回值

    lua中支持多返回值

    -- 比如我们之前使用的string.find函数,也是返回了两个值
    function f1()
        -- 使用return返回,如果没有return,那么相当于返回了一个nil
    end
    
    x = f1()
    print(x)  -- nil
    
    
    function f2()
        return 1, 2
    end
    -- 接收的变量和返回值一一对应
    x = f2()
    -- 所以x是返回值的第一个值,这一点和python不同,python则是一个元组
    print(x)  -- 1
    
    x, y, z = f2()
    print(x, y, z)  -- 1	2	nil
    -- 如果接收的变量多于返回值的个数,那么剩下的变量使用nil填充
    

    然后看一下lua中的一些特殊情况

    function f1()
        return "a", "b"
    end
    
    x, y = f1()
    print(x, y)  -- a	b
    x = f1()
    print(x)  -- a
    x, y, z = f1()
    print(x, y, z)  -- a	b	nil
    
    -- 上面的都很好理解
    x, y, z = 10, f1()
    print(x, y, z)  -- 10	a	b
    -- 这个也简单
    
    -- 那么,下面的结果如何呢?
    x, y, z = 10, f1(), 11
    print(x, y, z)  -- 10	a	11
    -- 我们看到只用了f1返回的一个值
    
    x, y, z = f1(), 10 
    print(x, y, z)  -- a	10	nil
    -- 惊了,难道不应该是 a b 10吗
    

    lua的返回值有如下规律:

    • 如果等号的右边只有一个函数调用,比如x, y, z = f1(),那么f1的所有的返回值都会被使用,分别按照顺序分配给x、y、z三个变量,不够的赋为nil,多余的丢弃
    • 如果等号的右边除了函数调用,还有其它的值,比如:x, y, z = f1(), 10 那么如果调用不是在最后一个,那么只返回一个值,如果在最后一个,那么会尽可能的返回多个值

    怎么理解呢?

    比如:x, y, z = f1(), 10,显然f1()的右边还有值,那么不好意思,不管f1返回的多少个值,只有第一个有效。x, y, z = 10, f1(),f1()的右边没有值了,显然它是最后一个,那么要尽可能的返回多个值,10给了x,那么f1返回的"a"和"b"就会给y和z。

    如果是x, y, z = 10, 20, f1(),这个时候10和20会赋值给x和y,那么尽管f1返回两个值,但是只剩下一个变量了,所以f1的第一个返回值会赋值给z

    再次举例说明

    function f1()
        return "a", "b"
    end
    
    -- f1()后面没有东西了,位于最后一个,因此尽可能匹配更多的返回值
    x, y, z = 10, f1()
    print(x, y, z)  -- 10	a	b
    
    -- f1返回两个值,加上后面的10正好三个,看似能够匹配x y z
    -- 但是f1()是一个函数调用,它的后面还有东西,因此在这种情况下,我们说f1只有一个返回值生效
    -- 如果f1没有返回值,那么相当于返回了一个nil
    -- 所以按照顺序匹配的话,x = f1的第一个返回值,y = 10, z = nil
    x, y, z = f1(), 10
    print(x, y, z)  -- a	10	nil
    
    
    -- 尽管f1()在最后面,但我们说是尽可能多的匹配
    -- x和y已经找到10和20了,所以只能是f1的第一个返回值赋值给z
    x, y, z = 10, 20, f1()
    print(x, y, z)  -- 10	20	a
    
    
    -- 显然此时已经轮不到f1了
    x, y, z = 10, 20, 30, f1()
    print(x, y, z)  -- 10	20	30
    
    
    function f2() end
    
    
    -- 即使f2什么也没有返回,但是会给一个nil
    x, y, z = f2(), 10
    -- 所以x是nil,y是10,z是nil
    print(x, y, z)  -- nil	10	nil
    

    相信此时你对lua中函数的返回值已经有一个大致的了解了,但是我们看到上面的例子中,函数调用的右边只是一个普通的值,如果是多个函数调用怎么办?我们来看看

    function f1()
        return 1, 2
    end
    
    function f2()
        return 3
    end
    
    x, y, z = f1(), f2()
    print(x, y, z)  -- 1	3	nil
    
    -- 我们看到结果和之前是类似的
    -- f1()后面还有东西,尽管不是普通的值,但不管咋样,有东西就对了
    -- f1()不是最后一个,那么不好意思,只有返回值的第一个会赋值给变量
    -- 因此1会赋值给x,f2()位于最后一个,会尽可能多的匹配,但是只有一个值
    -- 因此f2返回的3,会赋值给y,z的话就是nil
    x, y, z = f1(), f2(), "xx"
    print(x, y, z)  -- 1	3	xx
    
    
    -- 如果f2返回了两个值呢?
    function f2() return 3, 4 end
    
    x, y, z = f1(), f2()
    print(x, y, z)  -- 1	3	4
    -- 很好理解
    
    x, y, z = f1(), f2(), "xx"
    print(x, y, z)  -- 1	3	xx
    -- f1调用和f2调用后面都有东西,因此都只有返回的第一个值生效
    

    lua中函数的返回值我们已经揭开它的庐山真面目了,但是函数的返回值还有一个特点, 我们来看一下

    function f1()
        return "a", "b"
    end
    
    print(f1())  -- a   b
    -- 这没有问题
    
    
    -- 我们看到函数依旧无法摆脱这个命运
    -- 即便是打印,如果后面还有东西,那么只有自身的第一个返回值会被打印出来
    print(f1(), 1)  -- a    1
    -- 对于其它的函数也是如此
    print(string.find("hello world", "wor"))  -- 7	9
    print(string.find("hello world", "wor"), "xxx")  -- 7	xxx
    
    -- 事实上不光是print,我们知道函数的返回值可以作为另一个函数的参数
    function f1() return 1, 2 end
    function f2(a, b, c) print(a, b, c) end
    -- 我们看到,除了赋值,作为另一个函数的参数,也是如此
    f2(f1())  -- 1  2   nil
    f2("xx", f1())  -- xx	1	2
    f2(f1(), "xx")  -- 1    xx  nil
    
    
    -- 同理,即便是对于表,也是一样的
    t = {f1() }
    -- 很好理解,元素个数为2,就是f1的返回值
    print(#t, t[1], t[2])  -- 2	 a	b
    
    t = {f1(), "xxx" }
    print(#t, t[1], t[2])  -- 2	 a	xxx
    -- 惊了,我们看到明明加进去一个元素,居然还只有两个元素
    -- 说明即使在表中,只要函数调用后面有东西,函数的返回值只有第一个生效
    
    
    -- 最后lua函数还有一个特点
    -- 如果将函数调用,再次使用括号括起来,那么强制只有第一个返回值返回
    a, b = f1()
    print(a, b)  -- a   b
    a, b = (f1())
    print(a, b)  -- a   nil
    
    -- 当我们使用()将函数调用包起来之后
    -- 使得无论函数返回了多少个值,其结果只有第一个值有效
    -- 因此对于a, b = (f1())来说,a的结果就是f1函数的第一个返回值,b为nil
    

    lua函数的可变长参数

    lua中函数可以通过可变长参数来接收任意个数的参数,通过...来实现。这个...就是lua函数可变长参数的定义方式,我们使用的时候直接使用这个...即可。

    -- 可变长参数...一定要位于普通的参数之后
    function f1(a, b, ...)
        -- 刚才我们举的例子,定义变量都是全局变量
        -- 但是工作中,函数里面的变量,如果不需要外接使用,那么一定要定义成局部的
        -- 当然即便外接需要使用,也可以通过返回值的方式。只不过为了方便,所以就没有加local关键字
    
        -- 我们来看看这个...是什么玩意
        print(..., type(...))
    end
    
    -- 首先1会传递给a,2会传递给b,剩余的参数会都传递给...
    -- 我们看到它是一个number
    f1(1, 2, 3, "xxx")  -- 3	number
    f1(1, 2, "xxx", 3)  -- xxx	string
    
    
    -- 我们似乎看到了一个奇特的现象,我们给...明明传递了两个参数
    -- 但是从结果上来看,貌似相当于只传递了一个
    
    -- 我们再来举个栗子
    function f2(...)
        local a, b = ...
        print(a, b)
    end
    
    f2("a")  -- a	nil
    f2("a", "b")  -- a	b
    f2("a", "b", "c")  -- a	 b
    
    -- 我们看到...确实不止一个参数,具体是几个则取决于我们传递几个
    -- 但是直接打印的时候只有第一个生效,查看类型的时候也是查看第一个值的类型
    

    这个...算是比较奇葩的东西,为什么说奇葩呢?因为它没有特定的类型,这个...只能在函数中使用,至于到底是什么类型,则取决于第一个值的类型,但它又不止一个值。

    再来看个栗子:

    -- 首先我们print(nil)是可以打印出来东西的,结果就是nil
    print(nil)  -- nil
    -- 一个不存在的变量结果也可以nil相等
    print( a == nil)  -- true
    
    function f1(...)
        print(... == nil)
    end
    
    -- 我们看到当我们什么也不传递的时候,结果等于nil
    f1()  -- true
    -- 但是如果我们尝试打印这个...的时候,是打印不出来nil的
    
    
    function f2(...)
        print(type(...))
    end
    
    f2()  -- 代码报错
    --[[
    C:lualua.exe: lua/5.lua:16: bad argument #1 to 'type' (value expected)
    stack traceback:
    	[C]: in function 'type'
    	lua/5.lua:16: in function 'f2'
    	lua/5.lua:19: in main chunk
    	[C]: in ?
    ]]
    
    -- 我们看到执行函数f2的时候报错了,提示我们:type函数需要一个value
    -- 但我们明明传递了一个...过去啊
    -- 如果...在接收不到值的时候,那么它就相当于不存在一样
    -- 在和别的值进行比较的时候、或者说赋值的时候,...会是nil。但是在作为函数的参数的时候,则相当于不存在
    -- 比如:print(...)你以为会打印出nil吗?答案是不会的,此时的print(...)等价于print()
    -- 同理type(...)等价于type(),而type需要一个参数,所以报错了。
    

    所以这个...算是比较奇葩的一个存在,我们可以换个方式来理解,尽管这样肯定是不准确的,但是却可以从某种角度上变得容易理解。

    function f1(...)
        local a, b = ...
        print(a, b, 2 == ..., ...)
    end
    
    -- 假设我们执行f1(2, 3, 4)的时候,2,3,4会传递给...
    -- 对于赋值来说,你可以认为把...替换成了2,3,4
    -- 因此local a, b = ...等价于 local a, b = 2, 3, 4,所以a是2、b是3
    -- 但是对于比较、或者作为函数参数来说,可以认为是把...换成了2,3,4中的第一个值
    f1(2, 3, 4)  -- 2	3	true	2	3	4
    -- 2 == ...为什么是true呢?因为...的第一个值是2
    
    
    -- 如果我们什么也不传递的话,假设是执行f1(),显然没有参数会传递给...
    -- 因此此时的...就什么也不是,你就可以认为这个...不存在
    -- 如果是赋值或者比较的话,那么...会变成nil,如果不是作为参数的参数等于不存在
    -- 因此local a, b = ...等价于local a, b = nil
    -- 2 == ...等价于2 == nil, 至于print(...)等于print()
    f1()  -- nil	nil   false
    
    -- 我们执行f1()的时候,print只打印了3个值,因为...相当于不存在
    
    
    -- 当然我们也可以显式的传递nil
    function f2(...)
        local a, b, c, d = ...
        print(a, b, c, d)
    end
    
    f2(nil, 1, nil, 2)  -- nil	1	nil	2
    -- 即便是nil,也会按照顺序原封不动地传递过去
    

    但有些时候,我们不知道...究竟代表了多少个参数,这个时候怎么办呢?答案是变成一个table。

    function f1(...)
        local t = {... }
        print(table.concat(t))
    end
    
    -- 此时的t = {...}等价于t = {1, 2, "xxx"}
    f1(1, 2, "xxx")  -- 12xxx
    
    -- 如果出现了nil,比如:执行f1(1, 2, nil, "xxx")会报错,因为nil不能被合并
    function f2(...)
        local t = {... }
        for idx, val in pairs(t) do
            print(idx, val)
        end
    end
    
    f2(1, 2, nil, "xxx")
    --[[
    1	1
    2	2
    4	xxx
    ]]
    
    -- 我们看到里面的nil并没有被打印出来
    -- 因为当中出现了nil,我们说pairs会打印所有值不为nil的
    -- 如果是ipairs,那么"xxx"也不会被打印,因为ipairs遍历到nil就结束了
    

    所以遍历...的时候,可以将其放在{}里面变成一个表,但是缺陷是里面会出现nil,尽管在遍历的时候可以使用pairs保留所有部位nil的值,但还是不够完美,我们希望能够将所有的值保留下来。这个时候可以使用table.pack,将...变成一个表,这种做法和{...}的区别就是,前者保留了所有的值,并且还提供了一个额外的值计算表中元素的个数。下面举例说明:

    function f1(...)
        -- 会返回一个表
        -- 这个表和我们平时创建的表虽然都是表,但是调用table.pack返回的表会有一个额外的属性:n
        -- 执行t.n会返回表中所有元素的个数,也包括nil。
        -- 需要注意的是:我们平时手动创建的表没有n这个属性,只有调用table.pack返回的表才有这个属性
        local t = table.pack(...)
        -- 获取表的元素个数,我们之前使用的是#t
        -- 对于调用table.pack返回的表也可以这么做,只是结果未必使我们想要的
        print(t.n, #t)
    end
    
    f1(nil, nil, 3, 4, 5)  -- 5   5
    f1(1, 2, 3, nil, nil)  -- 5   3
    f1(nil, nil, 3, 4, nil)  -- 5   0
    
    -- 我们看到无论什么时候,t.n返回的永远是表中元素的个数
    -- 但是#t就不一定了,从上面的结果我们可以看到结果让人有点捉摸不透啊
    -- 第三个例子,结果直接是0了,是不是很诡异呢?
    -- 所以说不要在创建表的时候在里面写nil,这是原罪
    -- 因为写上了nil,你不知道元素到底有多少个
    -- 比如f1(nil, nil, 3, 4, 5),显然nil是被算进去了的,但是f1(1, 2, 3, nil, nil)中的nil就没有
    -- 至于f1(nil, nil, 3, 4, nil)更可怕,别说nil没有算进去,把原本能算进去的两个元素也给拖下水了
    
    -- 但是有些对技术非常专研的人,可能会探究过nil出现的个数、以及出现的位置,对#t产生的影响
    -- 甚至发现了一些规律,并且感觉还满靠谱的。但是千万不要认为这是对的,也没有必要去探究
    -- 总之不要在表中写nil,但是对于当前这个例子而言,因为表示通过对...打包得到的
    -- 而...是认为手动传递的,数量显然是在我们的掌控范围内,而t.n也能准确返回元素个数
    -- 所以在...中传递nil是没有问题的
    
    -- 我们也可以进行遍历
    function f2(...)
        local t = table.pack(...)
        for i = 1, t.n do
            print(t[i])
        end
    end
    
    f2(nil, 1, 2, nil, 3)
    --[[
    nil
    1
    2
    nil
    3
    ]]
    
    -- 所有的值都被打印的出来
    

    另一种遍历可变长参数的方式是使用select

    print(select(1, "a", "b", "c"))  -- a	b	c
    print(select(2, "a", "b", "c"))  -- b	c
    print(select(3, "a", "b", "c"))  -- c
    print(select(4, "a", "b", "c"))  --
    print(select("#", "a", "b", "c"))  -- 3
    
    -- 我们看到select(n, "a", "b", "c", ...)的作用是返回第n个元素以及其后的所有元素
    -- 如果n表示数字,而是"#"的话,那么会返回所有元素的个数
    function f1(...)
        print(select("#", ...))
    end
    
    -- select获取元素也是返回包括nil在内的所有元素的个数
    print(f1(nil, nil, 3, 4, nil))  -- 5
    
    
    -- 比如我们可以算出传入的参数中,值为整型的和
    function sum(...)
        local s = 0
        for i = 1, select("#", ...) do
            -- 尽管select(i, ...)可能返回多个值,但是在之前我们已经知道
            -- 如果我们只有一个变量接收的话,那么只会将返回的第一个值赋值给val
            local val = select(i, ...)
            if type(val) == "number" then
                -- 这里我们进行了检测,如果能确保传递的一定是number
                -- 那么也可以直接写成 s = s + select(i, ...)
                -- 因为运算的话,也是只有返回的第一个值会参与运算
                s = s + val
            end
        end
        return s
    end
    
    print(sum(1, 2, 3, 4))  -- 10
    print(sum(1, 2, 3, "xx", 4))  -- 10
    print(sum(1, 2, nil, "xx", 4))  -- 7
    

    我们看到了table.pack,可以将...打包成一个表,同理table.unpack也可以对一张表进行解包。

    function f1(a, b)
        print(a, b)
    end
    
    t = {1, 2, 3 }
    f1(table.unpack(t))  -- 1	2
    
    
    print(
        string.find("mashiro", "shiro")
    )  -- 3	7
    
    print(
        string.find(
            table.unpack({"mashiro", "shiro"})
        )
    )  -- 3	7
    
    -- 所以传递参数的时候,f1(a, b) <==> f1(table.unpack({a, b}))
    --[[
    如果熟悉python的话,那么这个类似于python中的*
    def f1(a, b):
        pass
        
    f1(1, 2) <==> f1(*[1, 2])
    ]]
    

    table.unpack还可以只对部分元素进行解包。

    function f1(a, b)
        print(a, b)
    end
    
    t = {1, 2, 3, 4, 5 }
    -- 对于索引为2以及后面的元素进行解包,所以结果是2 3 4 5
    -- 因此a和b为2、3
    f1(table.unpack(t, 2))  -- 1	2
    f1(table.unpack(t, 4))  -- 4	5
    f1(table.unpack(t, 5))  -- 5	nil
    f1(table.unpack(t, 6))  -- nil	nil
    
    -- 对索引为1到5的元素进行解包
    f1(table.unpack(t, 1, 5))  -- 1    2
    -- 反着写则不存在
    f1(table.unpack(t, 3, 1))  -- nil  nil
    -- 起始和结束相等,那么结果只有一个值
    f1(table.unpack(t, 4, 4))  -- 4  nil
    

    lua函数中的goto

    之所以把goto放在最后面,是因为它不是必须存在的,有的人甚至不建议使用goto,但是在某些时候goto还是很有用的,所以还是要介绍一下它

    事实上这个goto,肯定不用我说,大家都知道它是用来跳转的。但是我们就来看看lua中的函数如何使用goto来进行跳转。

    需要注意的是:lua中的goto只能在函数中使用,当然其它语言也是如此,并且跳转只能在当前的代码块中进行跳转,一个函数不可能通过goto跳转到另一个函数里面去。

    function f1(a)
        -- 定义一个标签,通过::标签名::来进行定义
        -- 之所以设置成这样,也是为了给程序员一个提示作用吧
        ::label1::
        print(11)
    end
    
    f1()  -- 11
    
    -- 标签定义之后,代码依旧正常执行
    -- 没有goto,你可以认为标签相当于不存在
    
    
    function f2()
    
        -- 跳转到label1中
        goto label1
    
        ::label1::
        print(123)
        ::label2::
        print(456)
    end
    
    f2()
    --[[
    123
    456
    ]]
    -- 注意:跳转到一个标签的时候,会执行其后面的所有代码
    -- 标签只是一个位置:跳转到这个位置,然后执行该位置后面的所有代码
    
    function f3()
        -- 跳转到label2中
        goto label2
        ::label1::
        	print(123)
        ::label2::
        	print(456)
    end
    f3()  -- 456
    

    通过标签,我们可以使用continue的功能

    function f1()
        for i = 1, 5 do
            if i == 3 then
                goto continue
            end
            print(i)
            -- 直接跳转到结尾即可
            ::continue::
        end
    end
    
    f1()
    --[[
    1
    2
    4
    5
    ]]
    

    lua中的输入、输出

    由于lua语言强调可移植性和嵌入性,所以lua本身并没有提供太多与外部交互的机制,从图形、数据、网络访问等等大多数IO操作,应该有宿主程序来完成。如果不考虑一些外部库,但就lua本身来说,只能操作一些基本的文件。

    lua中提供了一个库叫做io,专门用来进行操作文件的。当然print函数也是通过io来实现的,因为print进行打印,本质上是将内容写入到控制台当中,因为控制台你也可以理解为一个文件。

    io.write("xxx")
    io.write("yyy")
    -- xxxyyy
    
    -- print也是调用了io.write,但是我们看到最终输出了xxxyyy
    -- 因为print自带了换行,io.write是将内容原本输出
    

    至于读取也很简单,使用io.read()可以从命令行中读取一行

    -- 执行完之后会卡主
    name = io.read()
    -- 输入mashiro,然后回车,那么程序会读取输入,赋值给name,执行下面代码
    print(name)  -- mashiro
    
    
    age = io.read()
    -- 从控制台当中读取的都是string类型
    print(16, type(age))  -- 16   string
    

    读取和写入文件

    io.read和io.write,我们可以用来从控制台输入、输出,如果是文件的话,则是io.open(文件名, 模式)

    f = io.open("1.txt", "r")
    --[[
    关于文件的读取模式,有以下几种
    r:只读,文本模式,文件必须存在
    w:只写,文本模式,不存在会创建,存在则清空
    a:追加,文本模式,不存在会创建,存在则追加
    rb:只读,二进制模式,文件必须存在
    wb:只写,二进制模式,不存在会创建,存在则清空
    ab:追加,二进制模式,不存在会创建,存在则追加
    
    r+:可读可写,文件同样要求必须存在,指针会自动位于文件的开头位置
    w+:可读可写,文件不存在会创建,存在则清空
    a+:可读可写,文件不存在会创建,存在则追加,指针位于文件的结尾
    rb+、wb+、ab+也是同理
    ]]
    
    print(f)  -- file (00007ffff819fa90)
    -- 我们看到读取文件得到的是一个userdata类型
    print(type(f))  -- userdata
    
    -- f是一个文件句柄,如果想要读取里面的内容,需要使用f:read,注意:不是f.read
    -- 关于:运算符,我们会在后面讨论,目前只需要知道通过:来调用read即可
    print(f:read("a"))
    --[[
    when i was young,
    i'd listen to the radio,
    waiting for my favourite song
    ]]
    
    -- 但是我们看到,我们在read函数里面传入了一个字符串"a"
    -- 如果不传,那么只会读取一行
    -- 传入字符串"a",那么会全部读取进来
    
    -- 一旦文件读取完毕,指针会移到文件的结尾,那么再次读取会返回nil
    print(f:read())  -- nil
    

    我们说,io.read里面如果不传值,那么会只读取一行,传入"a"则是全部读取,除此之外还可以传入一个数字,读取指定字节的文本。

    f = io.open("1.txt", "r")
    
    -- 读取一行,这里不包括换行符
    -- 如果想把换行符也读取进来的话,可以通过f:read("L")
    -- 而事实上,f:read()等价于f:read("l"),表示读取一行的同时不读取结尾换行符
    print(f:read())  -- when i was young,
    
    -- 再次读取40个字符
    print(f:read(40))
    --[[
    i'd listen to the radio,
    waiting for my
    ]]
    
    -- 关闭文件
    f:close()
    
    -- 至于写文件我们就不说了,比较简单
    -- 总之记得关闭文件,然后内容会写到文件里面,当然程序结束时也会关闭文件
    -- 但如果想在程序运行期间强行将内容刷到文件里面去,可以使用f:flush(),和其它编程语言都是类似的
    

    如果读取的时候文件不存在,怎么办?

    f, err_msg, err_code = io.open("不存在的文件.txt", "r")
    print(f)  -- nil
    print(err_msg)  -- 不存在的文件.txt: No such file or directory
    print(err_code)  -- 2
    
    -- 如果文件不存在,会返回三个值
    -- 分别是nil、错误信息、以及错误码
    
    
    -- 关于状态码需要解释一下
    -- 文件读取实际上调用的是操作系统的接口,文件不存在操作系统会返回一个错误
    -- 对于每个错误类型,都有一个状态码,这个状态码所有编程语言都是一样的,因为它们调用的是操作系统的接口
    -- 以Python为例,文件不存在会抛出一个OsError
    --[[
    try:
        open("xxx")
    except OSError as e:
        print(e.errno)  # 2
        print(e)  # [Errno 2] No such file or directory: 'xxx'
        print(e.args)  # (2, 'No such file or directory')
    ]]
    -- 状态码都是一样的
    
    
    -- 另外,我们还可以使用assert函数
    f = assert(io.open("不存在的文件", "r"))
    --[[
    C:lualua.exe: lua/5.lua:27: 不存在的文件: No such file or directory
    stack traceback:
    	[C]: in function 'assert'
    	lua/5.lua:27: in main chunk
    	[C]: in ?
    ]]
    -- 这里就报错了,如果文件不存在,那么错误信息会被展示出来
    -- 并且这里是发生了异常,程序在此就终止了
    -- 但是我们有时候并不知道文件是否存在,所以希望在文件不存在的时候,程序还能往下走
    -- 这个时候就不要使用assert,而是读取之后,检测是否为nil
    

    io库还提供了三个预定义的三个C语言流的句柄:io.stdin、io.stdout、io.stderr

    -- 从控制台中读取除了io.read(),还可以使用io.stdin:read()
    name = io.stdin:read()
    -- 写入控制台,除了io.write(name),还可以使用io.stdout:write(name)
    io.stdout:write(name, '
    ')  -- mashiro
    -- 除了写入的话,除了io.stdout还有io.stderr,这两者都会将内容输出到控制台
    -- 但是io.stderr表示标准错误文件,io.stdout表示标准输出文件
    io.stderr:write(name, '
    ')  -- mashiro
    
    -- io.stderr:write打印出来的内容会变成红色,表示输出的一个错误信息
    -- 另外,尽管我们是先调用的io.stdout:write,后调用的io.stderr:write
    -- 但是当打印的时候,你会发现红色的"mashiro"在上面,说明io.stderr:write写入的内容先被打印了出来
    
    -- 因为stdout是行缓冲,stderr不带缓冲区,会直接输出,目的是为了更早的看到错误信息
    

    io还提供了input和output,io.read实际上就是io.input():read的缩写、io.write是io.output():write的缩写。

    name = io.input():read()
    io.output():write(name)  -- mashiro
    

    其他文件操作

    文件的指针是可以移动的

    f = io.open("1.txt", "r")
    
    -- 我们读取了15个字节,所以指针会偏移15个字节
    f:read(15)
    
    -- 调用f:seek()即可获取指针当前的位置
    print(f:seek())  -- 15
    
    -- 当然我们还可以移动指针
    -- 移动指针有三种方式:
    -- f:seek("set", count) 从文件开头的位置向后移动count个字节, 返回移动文件指针的位置
    -- f:seek("cur", count) 从指针当前的位置向后移动count个字节, 返回移动文件指针的位置
    -- f:seek("end", count) 从文件结尾的位置向后移动count个字节, 返回移动文件指针的位置
    -- 如果count小于0,那么就是向前移动 abs(count) 个字节
    
    -- 从开头向后移动11个字节,返回11,当然也可以单独使用f:seek()进行获取
    print(f:seek("set", 11))  -- 11
    -- 从当前(11)位置向后移动11个字节,结果是11 + 11
    print(f:seek("cur", 11))  -- 22
    -- 从结尾位置向前移动11个字节,事实上即便指针超出文件字节数量范围也没有错,只不过读取到的是一个nil
    print(f:seek("end", -11))  -- 63
    
    -- 如果不传递第二个参数、也就是偏移量的话,那么相当于传递了个0
    print(f:seek("set", 0))  -- 0
    print(f:seek("cur", 0))  -- 0
    -- 事实上,我们可以通过这种办法来计算文件的字节数
    -- 直接将指针移动到文件结尾,返回的结果就是文件的字节数
    print(f:seek("end", 0))  -- 74
    

    io还提供一个io.tmpfile 会返回一个操作临时文件的句柄,该句柄是以读写模式打开的 当程序 运行结束后,该临时文件会被自动移除(删除)

    f = io.tmpfile()
    
    -- 由于是可读写,那么写完之后文件指针会处于结尾
    f:write("xxxx")
    -- 此时如果想读到内容,需要将文件指针移到开头
    f:seek("set", 0)
    print(f:read("a"))  -- xxxx
    

    此外,除了io,还有一个os,os.rename可以对文件重命名,os.remove可以删除文件

    上述所有函数在报错时,都会返回 nil、错误信息、错误码

    其他系统调用

    函数os.exit可以用来终止程序的执行,里面可以传递两个参数,都是可选的。第一个参数为程序返回的状态,可以是一个数值0或者布尔true,表示执行成功;第二个参数如果为true,那么会关闭lua状态并调用所有析构器释放所占用的内存(不过这种方式通常是非必要的,因为操作系统会在进程退出时释放其占用的资源)

    -- os.getenv可以用来获取环境变量
    print(os.getenv("USERNAME"))  -- satori
    
    -- os.execute则是执行系统命令,类似于python中的os.system
    -- 其有三个返回值,第一个返回值为布尔型,为true表示成功运行完成
    -- 第二个返回值是一个字符串,如果字符串是"exit",表示正常结束,为"signal"表示因信号而中断
    -- 第三个返回值是返回状态(前提是正常终结)或者终结该程序的信号代码
    
    -- 下面举例说明
    a, b, c = os.execute("cd ..")
    print(a)  -- true
    print(b)  -- exit
    print(c)  -- exit
    

    执行操作系统命令,还有一个io.popen。对于os.execute来说,执行的命令结果会执行显示在控制台,如果你Windows是gbk编码还会产生乱码。但是io.popen则是将内容输入到指定位置

    f = io.popen("python 2.py", "r")
    
    print(f:read())  -- C:Userssatori
    
  • 相关阅读:
    VMware安装CentOS系统与配置全过程
    Matplotlib学习笔记
    python正则表达式字符记录
    HyperLedger Fabric 学习思路分享
    fabric-sdk-container v1.0-beta 新增支持多服务节点
    HyperLedger/Fabric SDK使用Docker容器镜像快速部署上线
    HyperLedger/Fabric JAVA-SDK with 1.1
    HyperLedger Fabric 1.1 手动部署单机单节点
    Hyperledger Fabric CA User’s Guide——配置设置(四)
    Hyperledger Fabric CA User’s Guide——开始(三)
  • 原文地址:https://www.cnblogs.com/traditional/p/13071336.html
Copyright © 2011-2022 走看看