0x01:认识Lua

Lua 是一种轻量小巧的脚本语言,使用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的拓展和定制功能

  • Lua是巴西里约热内卢天主教大学里的一个研究小组于1993年开发的
  • Lua语言从一开始就将自己定位成一个“嵌入式的脚本语言”,提供了如下的特性:

    • 可移植性:使用clean C编写的解释器,可以在Mac、Unix、Windows等多个平台轻松编译通过
    • 可扩展(良好的嵌入性):Lua 提供了非常易于使用的扩展接口(丰富的API),可供宿主程序与Lua脚本之间进行通讯和交换数据
    • 轻量级(非常小的尺寸):它用标准 C 语言编写并以源代码形式开放,编译后仅仅 100多K,可以很方便的嵌入别的程序里
    • Lua的效率很高,是速度最快的脚本语言之一
  • Lua的应用场景

    • 游戏开发:CC++语言实现服务器引擎层,Lua作为脚本层接收引擎层数据并调用引擎层API,实现游戏逻辑、玩法
    • 独立应用脚本
    • Web应用脚本:OpenResty使用Lua来扩展NGINX服务器的功能
    • 扩展和数据库插件:MySQL Proxy、MySQL WorkBench
    • 安全系统:使用lua编写WAF脚本
  • Lua参与游戏开发的优势

    • 编码效率高:由于引擎层相对稳定,而脚本不需要进行编译就能直接被执行,省去了很多编译的时间
    • 开发效率高:大部分脚本,包括Lua在内的都支持热更新功能,这意味着在调试开发期间,可以不用停止服务器引擎层就就可以调试新的脚本代码。这样省去了重启服务的时间(加载数据库数据、静态配置文件等的耗时)
    • 对人员素质要求相对低:由主程级别的人来把控服务器引擎层的质量与稳定性,其他成员负责编写脚本玩法逻辑。在这种情况下,即便脚本出错,也不会导致引擎层宕机等严重问题

0x02:第一个Lua程序

一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势

定神片刻,泼墨挥毫,郑重地在VSCode上写下

-- FILE: 00_hello.lua
-- 第一个lua程序
print("Hello World!")

将上述代码保存为名为00_hello.lua的文件,通过以下命令运行

lua 00_hello.lua

随后,在无情的黑底界面上渐渐浮现出

Hello World!

00_hello_lua

好啦,现在你已经学会基本操作了,让我们来加大亿点点难度吧

基!本!操!作!

-- FILE: 01_fact.lua
-- 定义一个计算阶乘的函数
function fact(n)
    if n == 0 then
        return 1
    else
        return n * fact(n - 1)
    end
end

print("enter a number:")
a = io.read("*n")                   -- 读取一个数字
print(fact(a))

将以上代码保存为01_fact.lua,并在命令行执行

lua 01_fact.lua

01_fact

0x03:程序段

  • 我们将Lua语言执行的每一段代码称为一个程序段(chunk),即为一组命令或表达式组成的序列。
  • 程序段可以是简单的一句表达式构成,例如:print('Hello World!'),也可以是由多句表达式和函数定义组成,例如上面的01_fact

除了将源码保存为Lua文件外,我们也可以直接在交互模式下运行独立解释器。当不带参数在命令行窗口下执行lua时,便可以进入Lua交互界面。

interactive mode

在REPL模式下,每一个完整的程序段都会在按下回车键后直接被执行

> print("Hello World!")

REPL

在REPL模式,可以通过输入EOF控制字符或者调用操作系统库的exit函数退出交互模式

> os.exit()

os.exit

从Lua 5.3开始,可以直接在交互模式(REPL)下输入表达式,Lua解释器会直接输出表达式的值

Lua 5.4.3  Copyright (C) 1994-2021 Lua.org, PUC-Rio
> 
> math.pi / 4
> a = 15
> a^2
> a + 2
> 

REPL2

但是在Lua 5.3之前的老版本,则需要在表达式之前加上一个=,否则解释器会直接报错,为了向下兼容,Lua 5.3 以上的版本也支持了这种语法结构

Lua 5.4.3  Copyright (C) 1994-2021 Lua.org, PUC-Rio
>    
> = math.pi / 4
> a = 15
> = a^2
> = a + 2
> 

REPL3

若是要以代码段的方式运行代码(非REPL模式下),则需要将代码置于print函数中

-- FILE: 02_print.lua
print(math.pi / 4)
a = 15
print(a^2)
print(a + 2)

02_print

在非REPL模式下执行代码时,可以在指令中加入-i参数,可以让Lua解释器在执行完指定程序段后进入交互模式(REPL模式)

lua -i 02_print.lua

REPL4

当然,除了上面的方式,在已经进入REPL模式下的Lua解释器是可以调用函数dofile将程序段加载到解释器中

-- FILE: 03_lib1.lua
-- 程序段
function norm(x, y)
    return math.sqrt(x^2 + y^2)
end

function twice(x)
    return 2.0 * x
end

dofile

0x04:Lua的词法规范

Lua语言中的标识符(或名称)可以是由任意字母、数字和下划线组成的字符串。

  • 命名规则

    • 由字母、下划线和数字组成
    • 不能以数字开头
    • 不能与关键字(保留字)重名
  • 常见的Lua语言保留字(reserved world)
and        break    do        else    elseif
end        false    goto    for        function
if        in        local    nil        not
or        repeat    return    then    true
until    while
  • 注意

    • “下划线 + 大写字母” 组成的标识符通常被Lua语言用作特殊用途,应避免将其用作其他用途
    • “下划线 + 小写字母” 用作哑变量(Dummy variable)
    • Lua语言对大小写是敏感的

0x05:注释

  • 单行注释

Lua使用俩个连续的连字符--表示单行注释的开始,从--之后直到此行结束都是注释

-- 这是一条lua注释
  • 多行注释

使用俩个连续的连字符加上俩对连续左方括号表示长注释或多行注释的开始

--[[
    这是一条
    多行注释
]]

但是为了方便解开注释,我们一般采用--[[--]]包裹注释

--[[
print('Hello World!')  -- 这段代码被注释了
--]]

这样做的原因是,当我们想要重新启用中间这段代码时,只需在--[[前再加一个连字符-即可,而不用大费周折删除前后的符号

---[[
print("Hello World!")  -- 这段代码被启用了
--]]

0x06:全局变量

在Lua语言中,全局变量(Global variable)无须声明即可直接使用。

使用未在Lua中初始化的全局变量不会导致错误,但是使用未初始化的全局变量时,得到的值结果为nil

global variable

当然,如果将nil赋值给一个全局变量时,Lua会回收该全局变量

回收全局变量

Lua不会区分未初始化变量和被赋值为nil的变量,在上述赋值语句执行之后,Lua最终会回收该变量所占用的内存。

0x07:类型与值

Lua是一种动态类型语言(dynamically-typed language),在这种语言中没有类型定义(type definition),即每个值都带有其自身的类型信息。

  • 在Lua语言中有8种基本类型

    • nil
    • 布尔 boolean
    • 数值 number
    • 字符串 string
    • 用户数据 userdata
    • 函数 function
    • 线程 thread
    • table

在lua中,使用type函数可以获取一个值对应的类型名称

type(nil)
type(true)
type(3.14)
type("Hello World")
type(io.stdin)
type(print)
type(type)
type({})

05_type

userdata类型允许把任意的C语言数据保存在Lua语言变量中。在Lua语言中,用户数据类型除了赋值和相等性测试以外,没有其他预设的操作。

用户数据用来表示由应用或C语言编写的库所创建的新类型。

  • 一般情况下,将一个变量用作不同类型时会导致代码的可读性不强;但是,在某些情况下谨慎地使用这个特性可以带来一定程度的便利

nil

  • nil是一种只有一个nil值的类型
  • Lua语言使用nil值来表示无效值(non-value),即没有价值的值
  • 一个全局变量在被第一次赋值之前的默认值就是nil,而将nil赋值给一个全局变量则相当于将其从内存中删除

Boolean

Boolean类型具有俩个值,truefalse,它们代表了传统布尔值。

不过,在Lua语言的条件测试中,将falsenil以外的所有其他值都视为真

特别需要注意的是:在Lua中,零和空字符串也会被Lua视为真

  • Lua语言支持常见的逻辑运算符

    • and:如果它的第一个值为假,则返回其第一个值;否则返回第二个值
    • or:如果它的第一个值不为假,则返回第一个值;否则返回第二个值
    • not:对值进行逻辑取反,而且永远只会返回Boolean类型的值
      andor都遵循短路求值原则,即只在必要时才对第二个值进行求值。

0x08:Lua指令参数

  • 如果在源代码文件第一行加入以下语句
#!/usr/local/bin/lua

#!/usr/bin/env lua

即可不必显式地调用Lua语言解释器也可以直接执行Lua脚本

#!/usr/bin/env lua
-- FILE:06_my_lua.lua
print("Hello World")

执行Lua脚本

  • Lua命令的完整参数如下所示

    lua [options] [script [args]]

    其中,所有的参数都是可选的。当不使用参数直接调用lua时,则会直接进入REPL模式

  • 参数-e允许我们直接在命令行中输入代码

    lua -e "print('Hello World')"

    参数-e

  • 参数-l用于加载库

    lua -i -l00_hello -e "x=10"

    以上指令会首先加载00_hello库,然后执行x=10,最终进入REPL模式

参数-l

  • 在Lua中,我们可以通过全局变量arg来获取解释器传入的参数

    lua -e "fox = 'gugugu'" 07_arg.lua a b

    则解释器会按照如下方式获取参数

07_arg

-- FILE: 07_arg.lua
print("arg[-3]:",arg[-3])
print("arg[-2]:",arg[-2])
print("arg[-1]:",arg[-1])
print("arg[0]:",arg[0])
print("arg[1]:",arg[1])
print("arg[2]:",arg[2])

一般情况下,脚本只会用到索引为正数的参数
当然,Lua也支持可变长参数,可以通过可变长参数表达式来获取

-- FILE: 08_arg_2.lua
print("arg:",...)

08_arg_2

0x09:参考文献

  • 《Lua程序设计(第4版)》
  • 《Lua设计与实现》
Last modification:April 5th, 2021 at 06:20 pm
给狐宝打点钱⑧