Lua脚本教程:轻松学习基本语法与实例 – wiki基地


Lua脚本教程:轻松学习基本语法与实例

前言:为什么选择Lua?

在当今编程语言百花齐放的时代,Lua凭借其轻量级可扩展性运行高效以及易于嵌入的特性,在众多领域占据了一席之地。从游戏开发(如《魔兽世界》、《Roblox》)、应用脚本(如Redis、Nginx、Neovim)、到嵌入式设备,都能看到Lua的身影。

Lua的设计哲学是简洁、小巧、可移植。它的核心非常精简,但通过其强大的元表(Metatable)机制和C API,可以轻松地进行扩展和定制。对于初学者而言,Lua的语法相对简单直观,接近自然语言,学习曲线平缓,是入门脚本编程的绝佳选择。

本教程旨在引导你从零开始,逐步掌握Lua的基本语法,并通过实例加深理解,为后续更深入的学习或应用打下坚实的基础。本文篇幅较长,内容详尽,希望能覆盖初学者需要了解的核心知识点。

一、 环境搭建:迈出第一步

学习任何编程语言,首先都需要搭建好运行环境。

  1. 获取Lua解释器
    • 访问Lua官方网站 (www.lua.org) 的下载页面。
    • 你可以下载源代码自行编译,或者在大多数操作系统(如Windows、macOS、Linux)上,通常有预编译好的二进制包或可以通过包管理器(如apt、yum、brew、winget)轻松安装。
      • Linux (Debian/Ubuntu): sudo apt update && sudo apt install lua5.3 (版本号可能不同)
      • Linux (Fedora/CentOS): sudo dnf install lua or sudo yum install lua
      • macOS (using Homebrew): brew install lua
      • Windows: 可以下载官方提供的二进制包,解压后将可执行文件路径添加到系统环境变量PATH中,或者使用Scoop/Chocolatey等包管理器安装。
  2. 验证安装
    打开你的终端或命令提示符,输入 lua -v。如果看到类似 Lua 5.x.x Copyright (C) ... 的输出,说明安装成功。
  3. 交互式解释器 (REPL)
    直接在终端输入 lua 并回车,会进入Lua的交互式解释器。你可以在这里逐行输入Lua代码并立即看到结果,非常适合快速测试和学习语法。输入 os.exit() 或按 Ctrl+D (Unix-like) / Ctrl+Z then Enter (Windows) 退出。
  4. 运行Lua脚本文件
    将Lua代码保存到一个以 .lua 为后缀的文件中(例如 hello.lua),然后在终端使用 lua hello.lua 命令来执行该脚本。

二、 Lua基础语法详解

1. 注释

注释是代码中用于解释说明的部分,不会被解释器执行。

“`lua
— 这是一个单行注释

–[[
这是一个
多行注释块。
可以跨越多行。
–]]

— 多行注释还有一种技巧(常用于临时禁用代码块):
–[[
print(“这行代码暂时不执行”)
–]]–
“`

2. “Hello, World!” – 第一个程序

学习编程的传统起点。

lua
print("Hello, World!")

将以上代码保存为 hello.lua,然后在终端运行 lua hello.lua,你将看到输出 Hello, World!print() 是Lua内置的用于向标准输出打印内容的函数。

3. 变量与数据类型

Lua是动态类型语言,变量不需要预先声明类型,类型是在运行时根据赋给变量的值确定的。变量名默认是全局的,除非使用 local 关键字声明为局部变量(强烈推荐使用 local)。

“`lua
— 变量赋值
message = “Hello, Lua!” — 全局变量(不推荐)
local name = “Alice” — 局部变量(推荐)
local age = 30 — 局部变量

print(message)
print(name)
print(age)

— 变量可以随时改变类型
local x = 10
print(type(x)) — 输出: number
x = “I am a string now”
print(type(x)) — 输出: string
“`

Lua有以下几种基本数据类型:

  • nil (空):表示无效值或不存在的值。所有未赋值的全局变量默认为 nilnil 是一种类型,其值也只有 nil
    lua
    local a
    print(a) -- 输出: nil
    print(type(a)) -- 输出: nil
    a = nil -- 显式赋值为 nil
  • boolean (布尔):只有两个值:truefalse。注意,在Lua中,只有 falsenil 被视为“假”,其他所有值(包括数字0和空字符串””)都视为“真”。
    lua
    local is_valid = true
    local has_error = false
    if 0 then print("0 is true in Lua") end -- 会输出
    if "" then print("Empty string is true in Lua") end -- 会输出
    if not nil then print("nil is false") end -- 会输出
    if not false then print("false is false") end -- 会输出
  • number (数字):表示实数(浮点数)。在Lua 5.3及以后版本,默认是双精度浮点数,但也可能根据编译选项支持整数类型。
    lua
    local pi = 3.14159
    local count = 100
    local negative = -5.5
    local scientific = 1.23e4 -- 1.23 * 10^4
    print(type(pi)) -- 输出: number
  • string (字符串):表示字符序列。可以用单引号 ' ' 或双引号 " " 括起来。字符串是不可变的。可以使用 .. 操作符进行拼接。
    “`lua
    local greeting = “Hello”
    local target = ‘World’
    local message = greeting .. “, ” .. target .. “!” — 字符串拼接
    print(message) — 输出: Hello, World!
    print(#message) — 使用 # 获取字符串长度 (字节数)

    — 多行字符串
    local long_string = [[
    这是第一行。
    这是第二行,可以包含 “引号” 和 ‘引号’。
    ]]
    print(long_string)
    * **table (表)**:Lua中最核心、最强大的数据结构。它可以用来实现数组、字典(哈希表)、对象等。Table是**可变**的。索引可以是数字(通常从1开始,这是Lua的一个特点)或其他非`nil`的值(如字符串)。lua
    — 类数组用法 (索引从1开始)
    local days = {“Monday”, “Tuesday”, “Wednesday”}
    print(days[1]) — 输出: Monday
    print(#days) — 获取类数组table的长度 (仅对连续整数索引有效)

    — 类字典用法 (关联数组)
    local person = {
    name = “Bob”,
    age = 25,
    city = “New York”,
    [“is Student”] = false — 键包含空格或特殊字符时用 [] 括起来
    }
    print(person.name) — 使用点号访问 (常用)
    print(person[“age”]) — 使用方括号访问 (更通用)
    person.job = “Engineer” — 添加新键值对
    person.age = person.age + 1 — 修改值

    — 混合使用
    local mixed_table = {10, 20, name=”Test”, [5] = 50}
    print(mixed_table[1]) — 输出: 10
    print(mixed_table.name) — 输出: Test
    print(mixed_table[5]) — 输出: 50
    * **function (函数)**:在Lua中,函数是“一等公民”,意味着它们可以像其他值一样被存储在变量中、作为参数传递、作为返回值返回。lua
    local function greet(name)
    print(“Hello, ” .. name)
    end
    greet(“Charlie”) — 调用函数

    local my_print = print — 函数可以赋值给变量
    my_print(“This works too!”)
    “`
    * userdata (用户数据):用于表示任意的C数据。主要用于Lua与C代码交互。
    * thread (线程):表示独立的执行线程,用于实现协程(coroutine)。

type() 函数可以用来获取一个值的类型名称(字符串形式)。

4. 操作符

  • 算术操作符+ (加), - (减), * (乘), / (浮点除), % (取模), ^ (幂), - (一元负号)。 Lua 5.3+ 还有 // (整除)。
    lua
    local a = 10
    local b = 3
    print(a + b) -- 13
    print(a / b) -- 3.333...
    print(a % b) -- 1
    print(a ^ b) -- 1000
    print(a // b) -- 3 (Lua 5.3+)
  • 关系操作符== (等于), ~= (不等于), < (小于), > (大于), <= (小于等于), >= (大于等于)。比较结果是 truefalse。注意 ~= 表示不等于。
    lua
    print(5 == 5) -- true
    print(5 ~= 5) -- false
    print("a" < "b") -- true (按字典序比较)
    print(10 > 5) -- true
    -- 注意:table, function, userdata 默认按引用比较
    local t1 = {}
    local t2 = {}
    print(t1 == t2) -- false (它们是不同的table对象)
    local t3 = t1
    print(t1 == t3) -- true (指向同一个table对象)
  • 逻辑操作符and, or, not
    • a and b: 如果 afalsenil,结果为 a;否则结果为 b
    • a or b: 如果 a 不为 false 且不为 nil,结果为 a;否则结果为 b
    • not a: 如果 afalsenil,结果为 true;否则结果为 false
      这种“短路求值”特性常用于设置默认值或条件执行。
      lua
      local max_val = input_val or 100 -- 如果input_val为nil或false, 则max_val为100
      local name = person and person.name -- 如果person存在, 才获取person.name
      print(not true) -- false
      print(not nil) -- true
  • 连接操作符.. 用于连接两个字符串。
    lua
    local str1 = "Lua is "
    local str2 = "awesome!"
    print(str1 .. str2) -- Lua is awesome!
  • 长度操作符# 用于获取字符串的长度(字节数)或table(数组部分)的长度。
    lua
    print(#"hello") -- 5
    local arr = {10, 20, 30}
    print(#arr) -- 3
    local map = {a=1, b=2}
    print(#map) -- 0 (长度操作符对纯哈希表部分无效)
    local mixed = {1, 2, a=3, 3}
    print(#mixed) -- 3 (通常只计算到最后一个连续的整数索引)

5. 控制结构

  • if / then / elseif / else / end:条件判断。
    lua
    local score = 75
    if score >= 90 then
    print("Grade: A")
    elseif score >= 80 then
    print("Grade: B")
    elseif score >= 70 then
    print("Grade: C")
    else
    print("Grade: D")
    end -- 不要忘记 'end'

  • while 循环:当条件为真时重复执行代码块。
    lua
    local i = 1
    while i <= 5 do
    print("While loop, iteration: " .. i)
    i = i + 1 -- 记得更新循环变量
    end

  • repeat … until 循环:先执行一次代码块,然后检查条件,如果条件为假(falsenil),则继续循环。
    lua
    local count = 5
    repeat
    print("Repeat loop, count: " .. count)
    count = count - 1
    until count < 0 -- 当 count < 0 时条件为真, 循环结束

  • for 循环

    • 数字型 for:用于固定次数的迭代。
      “`lua
      — 从 1 到 5,步长为 1 (默认)
      for i = 1, 5 do
      print(“Numeric for, i = ” .. i)
      end

      — 从 10 到 1,步长为 -2
      for j = 10, 1, -2 do
      print(“Numeric for with step, j = ” .. j)
      end
      — 循环变量 i 和 j 都是循环内部的局部变量
      * **泛型 for**:使用迭代器函数遍历集合(主要是table)。最常用的是 `pairs` (遍历table所有键值对,顺序不定) 和 `ipairs` (遍历table数组部分,按数字索引顺序)。lua
      local colors = { “red”, “green”, “blue” }
      — 使用 ipairs 遍历数组部分 (推荐用于数组)
      print(“— Using ipairs —“)
      for index, value in ipairs(colors) do
      print(“Index: ” .. index .. “, Value: ” .. value)
      end
      — 输出:
      — Index: 1, Value: red
      — Index: 2, Value: green
      — Index: 3, Value: blue

      local person = { name = “David”, age = 40, city = “London” }
      — 使用 pairs 遍历所有键值对 (用于字典/混合表)
      print(“— Using pairs —“)
      for key, value in pairs(person) do
      print(“Key: ” .. key .. “, Value: ” .. tostring(value)) — 使用 tostring 确保值能打印
      end
      — 输出 (顺序可能不同):
      — Key: name, Value: David
      — Key: age, Value: 40
      — Key: city, Value: London
      “`

  • break:用于提前跳出当前循环(while, repeat, for)。

  • return:用于从函数中返回值,也可以用于提前结束脚本文件的执行。

6. 函数

函数是执行特定任务的代码块。

  • 定义函数
    “`lua
    — 方式一: 标准语法
    local function add(a, b)
    return a + b
    end

    — 方式二: 赋值给变量 (函数是第一类值)
    local subtract = function(a, b)
    return a – b
    end
    * **调用函数**:lua
    local sum = add(10, 5)
    local difference = subtract(10, 5)
    print(“Sum: ” .. sum) — Output: Sum: 15
    print(“Difference: ” .. difference) — Output: Difference: 5
    * **多返回值**:Lua函数可以返回多个结果。lua
    local function get_coordinates()
    return 10, 20 — 返回 x 和 y
    end

    local x, y = get_coordinates() — 使用多重赋值接收
    print(“X: ” .. x .. “, Y: ” .. y) — Output: X: 10, Y: 20

    local just_x = get_coordinates() — 只接收第一个返回值
    print(“Just X: ” .. just_x) — Output: Just X: 10
    * **可变参数**:函数可以接受不定数量的参数,通过 `...` 表示。在函数内部,`...` 可以被打包到一个table中(使用 `{...}`)或通过 `select` 函数访问。lua
    local function print_all(…)
    local args = {…} — 将所有参数打包成一个table
    for i, v in ipairs(args) do
    print(“Arg ” .. i .. “: ” .. tostring(v))
    end
    end

    print_all(“hello”, 123, true)
    — Output:
    — Arg 1: hello
    — Arg 2: 123
    — Arg 3: true
    “`

7. 作用域:local的重要性

在Lua中,变量默认是全局的。这意味着如果你在一个函数内部忘记使用 local 声明变量,它可能会意外地覆盖掉一个同名的全局变量,或者污染全局命名空间。

始终使用 local 声明变量是一个非常好的习惯,除非你明确需要定义一个全局变量(通常在定义模块时)。

“`lua
g_var = 10 — 全局变量 (不推荐)

local function my_func()
local l_var = 20 — 局部变量,只在 my_func 内部可见
g_var = 30 — 修改了全局变量 g_var
undeclared_var = 40 — 错误! 这会创建一个新的全局变量 undeclared_var
end

my_func()
print(g_var) — 输出: 30
— print(l_var) — 错误! l_var 在函数外不可见
print(undeclared_var) — 输出: 40 (意外的全局变量)
“`

使用 local 可以避免命名冲突,提高代码的可读性和可维护性,并且局部变量的访问速度通常比全局变量更快。

8. 模块与require

当代码量增加时,将代码组织成模块是必要的。一个Lua文件通常可以作为一个模块。模块返回一个table,其中包含它希望导出的函数和变量。

  • 创建模块 (mymodule.lua):
    “`lua
    — mymodule.lua
    local M = {} — 创建一个局部 table 作为模块容器

    M.pi = 3.14159

    function M.greet(name)
    print(“Hello from mymodule, ” .. name .. “!”)
    end

    local function internal_helper()
    — 这个函数是模块内部的,不会被导出
    print(“Internal helper called.”)
    end

    M.do_something = function()
    internal_helper()
    print(“Doing something using the helper.”)
    end

    return M — 返回模块 table
    * **使用模块** (`main.lua`):lua
    — main.lua
    — 使用 require 加载模块。Lua 会查找 mymodule.lua 文件。
    — require 只会加载和执行模块文件一次,并缓存结果。
    local mymod = require(“mymodule”)

    print(mymod.pi) — 使用模块导出的变量
    mymod.greet(“Lua User”) — 调用模块导出的函数
    mymod.do_something()

    — mymod.internal_helper() — 错误!内部函数无法访问
    ``require函数会根据预定义的路径(package.path)查找.lua` 文件。

三、 实例演练

现在,让我们通过几个简单的实例来巩固所学的知识。

实例1:简单的计算器

“`lua
— simple_calculator.lua
local function calculate(op, n1, n2)
if op == “+” then
return n1 + n2
elseif op == “-” then
return n1 – n2
elseif op == “*” then
return n1 * n2
elseif op == “/” then
if n2 == 0 then
return nil, “Error: Division by zero” — 返回 nil 和错误信息
else
return n1 / n2
end
else
return nil, “Error: Invalid operator”
end
end

— 获取用户输入 (在实际应用中可能更复杂)
local operation = “+”
local num1 = 10
local num2 = 5

— 或者,如果你在可以交互的环境运行:
— print(“Enter operator (+, -, *, /):”)
— local operation = io.read()
— print(“Enter first number:”)
— local num1 = tonumber(io.read()) — tonumber 将字符串转为数字
— print(“Enter second number:”)
— local num2 = tonumber(io.read())

— if not (operation and num1 and num2) then
— print(“Invalid input.”)
— else
local result, err_msg = calculate(operation, num1, num2)

if err_msg then
print(err_msg)
else
print(“Result: ” .. result)
end
— end
“`

实例2:读取文件内容并统计行数

“`lua
— count_lines.lua
local function count_lines_in_file(filepath)
— io.open 打开文件, “r” 表示读取模式
— file 是文件句柄, err 是错误信息
local file, err = io.open(filepath, “r”)

if not file then
print(“Error opening file: ” .. err)
return nil
end

local line_count = 0
— file:lines() 返回一个迭代器,每次迭代读取一行
for line in file:lines() do
line_count = line_count + 1
end

— 关闭文件很重要
file:close()

return line_count
end

— 假设当前目录下有一个名为 data.txt 的文件
local filename = “data.txt”
— 你需要先创建一个 data.txt 文件并写入一些内容
— 例如:
— Line 1
— Line 2
— Another line

local count = count_lines_in_file(filename)

if count then
print(“File ‘” .. filename .. “‘ has ” .. count .. ” lines.”)
end
“`

实例3:查找Table中的最大值

“`lua
— find_max.lua
local function find_max(numbers)
if type(numbers) ~= “table” or #numbers == 0 then
return nil, “Input must be a non-empty table (array part)”
end

local max_val = numbers[1] — 假设第一个是最大值
for i = 2, #numbers do — 从第二个开始比较
if numbers[i] > max_val then
max_val = numbers[i]
end
end
return max_val
end

local scores = {88, 92, 75, 99, 85, 92}
local max_score, err = find_max(scores)

if err then
print(“Error: ” .. err)
else
print(“The maximum score is: ” .. max_score) — Output: The maximum score is: 99
end

local empty_table = {}
max_score, err = find_max(empty_table)
if err then
print(“Error finding max in empty table: ” .. err) — Output: Error…non-empty table…
end
“`

四、 进阶话题简介 (了解即可)

  • 元表 (Metatables):Lua的核心特性之一。允许你改变table的行为,例如重载操作符(如+, -, []访问)、实现面向对象编程中的继承等。通过 setmetatablegetmetatable 函数操作。
  • 协程 (Coroutines):提供了一种协作式多任务处理方式。不同于抢占式线程,协程需要显式地挂起(coroutine.yield)和恢复(coroutine.resume),使得状态同步更简单。
  • C API:Lua设计之初就考虑了与C/C++的集成。通过C API,可以在C代码中执行Lua脚本,也可以在Lua中调用C函数,实现高性能扩展。

五、 学习资源推荐

  • Lua官方网站: www.lua.org – 最权威的文档和参考手册。
  • 《Programming in Lua》 (PiL): 由Lua的主要设计者编写,是学习Lua最经典的图书(有多个版本对应不同Lua版本,在线版本通常免费)。
  • Lua Users Wiki: lua-users.org/wiki – 包含大量教程、代码片段和库信息。
  • 在线实践平台: 如 Replit、 Glot.io 等支持在线运行Lua代码。

结语

恭喜你!通过本教程,你已经了解了Lua的基础语法,包括变量、数据类型、操作符、控制流、函数、表(Table)以及模块的基本概念,并通过实例进行了实践。

Lua是一门简洁而强大的语言,它的魅力在于“小而美”以及高度的可扩展性。掌握了基础之后,你可以根据自己的兴趣或项目需求,深入探索元表、协程、C API等更高级的主题,或者直接开始在游戏脚本、服务器配置、工具开发等领域应用Lua。

编程学习是一个持续实践的过程。不断地编写代码、解决问题,是提升技能的最佳途径。希望本教程能为你打开Lua世界的大门,祝你学习愉快!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部