当前位置 博文首页 > Lua极简入门指南(一):基础知识篇

    Lua极简入门指南(一):基础知识篇

    作者:admin 时间:2021-02-08 21:21

    本文是《Programming in Lua 3rd》读书笔记。

    Chunks

    一个 Chunk 就是一组被执行的语句,例如一个文件或者交互模式下的一行。

    标识符(identifiers)

    我们应该避免使用以 _ 开头并跟上一个或者多个大写字母的字符串来作标识符,它们被保留作特殊的用途(例如:_VERSION)。

    注释

    单行注释使用

    复制代码 代码如下:

    --

    多行注释使用
    复制代码 代码如下:

    --[[ 和 --]]

    类型简介

    Lua 存在的数据类型包括:

    1.nil。此类型只有一个值 nil。用于表示“空”值。全局变量默认为 nil,删除一个已经赋值的全局变量只需要将其赋值为 nil(对比 JavaScript,赋值 null 并不能完全删除对象的属性,属性还存在,值为 null)

    2.boolean。此类型有两个值 true 和 false。在 Lua 中,false 和 nil 都表示条件假,其他值都表示条件真(区别于 C/C++ 等语言的是,0 是真)

    3.number。双精浮点数(IEEE 754 标准),Lua 没有整数类型

    4.string。你可以保存任意的二进制数据到字符串中(包括 0)。字符串中的字符是不可以改变的(需要改变时,你只能创建一个新的字符串)。获取字符串的长度,可以使用 # 操作符(长度操作符)。例如:print(#”hello”)。字符串可以使用单引号,也可以使用双引号包裹,对于多行的字符串还可以使用 [[ 和 ]] 包裹。字符串中可以使用转义字符,例如 \n \r 等。使用 [[ 和 ]] 包裹的字符串中的转义字符不会被转义

    5.userdata。用于保存任意的 C 数据。userdata 只能支持赋值操作和比较测试

    6.function。函数是第一类值(first-class value),我们能够像使用其他变量一样的使用函数(函数能够保存在变量中,可以作为参数传递给函数)

    7.thread。区别于我们常常说的系统级线程

    8.table。被实现为关联数组(associative arrays),可以通过任何值来进行索引(nil 除外)。和全局变量一样,table 中未赋值的域为 nil,删除一个域只需要将其赋值为 nil(实际上,全局变量就是被放置在一个 table 中)

    type 函数用于返回值的类型:

    复制代码 代码如下:

    print(type("Hello World")) --> string
    print(type(10.4*3))        --> number
    print(type(print))         --> function
    print(type(type(X)))       --> string

    在 Lua 中,任何的变量都可以保存任何的值。

    table 使用简介

    使用构造表达式可以创建一个 table:

    复制代码 代码如下:

    -- 创建一个空的 table
    a = {}
     
    -- 创建并初始化一个 table,这里
    -- days[1] == "Sunday"
    -- days[2] == "Monday"
    -- ...
    days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }
     
    -- 创建并初始化一个 table,这里
    -- a["x"] == 10
    -- a["y"] == 20
    a = { x = 10, y = 20 }

    使用 [] 操作符访问 table 的域:

    复制代码 代码如下:

    a = {}
    k = "x"
    a[k] = 10
    a["x"] = 20
    print(a["y"]) --> nil
    a.x = 30

    注意,a.name 的语法等价于 a["name"]。

    table 可以用于表示数组,这时候索引为整数,并且从 1(而非 0)开始,例如:

    复制代码 代码如下:

    a = { 'a', 'b' }
    a[1] == 'a'
    a[2] == 'b'

    长度操作符可以获取 table 数组部分的长度:

    复制代码 代码如下:

    a = {}
    a[1] = 1
    a[2] = 2
    print(#a) --> 2
     
    a.a = 1
    a.b = 2
    print(#a) --> 2
     
    a = {}
    a.a = 1
    a.b = 2
    print(#a) --> 0

    表达式

    算术操作符

    1.+(加)
    2.-(减)
    3.*(乘)
    4./(除)
    5.^(幂)
    6.%(取模)

    任何算术操作符都试图将操作数转换为数值类型,例如:

    复制代码 代码如下:

    print(10 + '1') --> 11

    关系操作符

    1.<(小于)
    2.>(大于)
    3.<=(小于等于)
    4.>=(大于等于)
    5.==(等于)
    6.~=(不等于)

    两个不同类型的值是不相等的,例如:

    复制代码 代码如下:

    nil ~= false

    table、userdata 类型是通过引用进行比较的,例如:

    复制代码 代码如下:

    a = {}; a.x = 1; a.y = 0
    b = {}; b.x = 1; b.y = 0
    c = a

    这里 a 和 c 引用一个相同的对象,因此 a == c,但是 a ~= b(即便 a、b 内容相同)。

    逻辑操作符

    1.and
    2.or
    3.not

    逻辑操作符有返回值。对于 and 操作来说,如果第一个操作数为 false 时返回此操作数,否则返回第二个操作数。对于 or 操作来说,如果第一个操作数不为 false 时返回此操作数,否则返回第二个操作数。

    连接操作符

    字符串连接可以使用连接操作符 “..”,例如:

    复制代码 代码如下:

    print("Hello " .. "World")

    连接操作符试图将操作数转化为字符串,例如:
    复制代码 代码如下:

    print("number: " .. 1)

    语句

    多赋值(multiple assignment)支持,例如:

    复制代码 代码如下:

    a, b = 1, 2
    print(a) --> 1
    print(b) --> 2

    多赋值的一个惯用法就是交换两个变量的值:
    复制代码 代码如下:

    x, y = 1, 2
    x, y = y, x
    print(x) --> 2
    print(y) --> 1

    创建局部变量使用 local:
    复制代码 代码如下:

    j = 10       -- 全局变量 j
    local i = 10 -- 局部变量 i

    局部变量的作用域限制于他们声明的块(block)。块(block)包括:

    1.控制结构的主体部分
    2.函数体
    3.chunk
    4.do-end

    范例:

    复制代码 代码如下:

    if true then
        local x = 20
        print(x) --> 20
    end
     
    print(x) --> nil

    我们可以使用 do-end 关键字来构造一个块:
    复制代码 代码如下:

    do
        local x = 20
        print(x) --> 20
    end
     
    print(x) --> nil

    访问局部变量要快于访问全局变量。在 Lua 中有一个习惯用法:
    复制代码 代码如下:

    local foo = foo

    用于创建一个局部变量并初始化为同名的全局变量。这样做常常出于两个原因:

    1.避免某些类型的全局变量被修改
    2.提高访问速度

    控制结构

    if then elseif else

    复制代码 代码如下:

    if a < 0 then
        a = 0
    end
     
    if a < b then
        return a
    else
        return b
    end
     
    if op == '+' then
        r = a + b
    elseif op == '-' then
        r = a - b
    elseif op == '*' then
        r = a * b
    elseif op == '/' then
        r = a / b
    else
        error('invalid operation')
    end

    Lua 中没有 switch 语句。

    while

    复制代码 代码如下:

    local i = 1
    while a[i] do
        print(a[i])
        i = i + 1
    end

    repeat

    复制代码 代码如下:

    repeat
        line = io.read()
    until line ~= ''
    print(line)

    区别于 while,repeat 会先执行循环体,然后判断测试条件。

    数值型 for(numeric for)

    for 有两种:

    数值型 for(numeric for)

    1.泛型 for(generic for)
    2.数值型 for 的语法如下:

    复制代码 代码如下:

    for var = exp1, exp2, exp3 do
        <something>
    end

    这里 exp1 作为 var 的初始值,exp2 为 var 的最大值,exp3 为 var 每次递增的值,exp3 是可选的,默认为 1。范例:
    复制代码 代码如下:

    -- 输出 1 2 3
    for i = 1, 3 do
        print(i)
    end

    有一些需要注意的地方:

    1.for 中的 exp1、exp2、exp3 只会被计算一次值,例如:

    复制代码 代码如下:

    for i = 1, f(x) do print(i) end

    这里的 f(x) 只会被调用一次

    2.控制变量 var 只是一个局部变量
    3.不要尝试去修改控制变量 var 的值(结果是未知的)

    泛型 for

    泛型 for 通过一个迭代器函数来实现遍历,例如:

    复制代码 代码如下:

    for k, v in pairs(t) do
        print(k, v)
    end

    这里的 pairs 就是一个迭代器函数,此 for 循环遍历 table t,每次获取到的 key 保存在变量 k 中,获取到的 value 保存在变量 v 中。除了 pairs 还有其他的迭代器可以用:

    1.io.lines 可用于迭代文件中的行
    2.ipairs 可用于迭代 table 的数组部分

    我们还可以自己编写迭代器。

    break、return、goto

    break 语句用于跳出一个循环(for、repeat、while)。

    return 语句用于为函数返回结果。在 Lua 中,return 语句必须是一个块的最后一条语句,看一个例子:

    复制代码 代码如下:

    function foo()
        -- 语法错误
        return
        local i = 1
    end

    有时候,我们出于某些原因(例如为了 debug),我们需要在一个函数中插入一个 return 语句,这时候可以这么做:

    复制代码 代码如下:

    function foo()
        -- ...
        do return end
        -- ...
    end

    goto 语句用于在函数中跳转。goto 语句可以让执行跳转到特定的标签(label)处,例如:

    复制代码 代码如下:

    goto quit
    print('come on')
    ::quit::
    print('quit')

    这里输出 quit。正如我们看到的,标签的写法为 ::name::。goto 跳转也是存在限制的:

    1.不允许跳转到一个块中去
    2.不允许跳转到函数之外去
    3.不允许跳入局部变量的作用域中

    对于第三点,看一个例子:

    复制代码 代码如下:

    goto quit
    local a
    ::quit::
    print('quit')

    这里,会出现语法错误(jumps into the scope of local 'a')。但是,有一个细节需要注意,我们先修改上面的例子:

    复制代码 代码如下:

    goto quit
    local a
    ::quit::

    执行成功,没有语法错误。这是因为局部变量的作用域结束于变量定义的块的最后一个非 void 语句,而标签被认为是一个 void 语句,对于上面的例子来说,a 的作用域在 ::quit:: 之前就结束了,因此 goto quit 并没有跳入局部变量 a 的作用域中。

    利用 goto 可以比较方便的编写状态机,例如(s1、s2 为状态):

    复制代码 代码如下:

    ::s1:: do
        local c = io.read(1)
        if c == '0' then goto s2
        elseif c == nil then print'ok'; return
        else goto s1
        end
    end
     
    ::s2:: do
        local c = io.read(1)
        if c == '0' then goto s1
        elseif c == nil then print'not ok'; return
        else goto s2
        end
    end
     
    goto s1

    js