Featured image of post Lua 速通——核心语法与特性

Lua 速通——核心语法与特性

Lua 是一门设计极简但表达力丰富的小语言,本文从语法、表、metatable 到协程,给出一份能上手的核心特性梳理

写在前面

Lua 是一门有意保持小巧的脚本语言——核心语法只有几十条规则,标准库非常精简,参考实现的解释器只有约 2.4 万行 C 代码(含标准库),编译后产物约 250 KB。

但它表达力并不弱:first-class function、metatable、协程、模式匹配——很多被现代语言陆续吸收的特性,Lua 早就有。

本文给一份偏语言本身的速通——讲清楚 Lua 的核心语法、表、面向对象的实现方式、协程,以及一些容易踩的语法陷阱。读完之后你能看懂多数 Lua 代码,也能写出一些不丢人的 Lua。


一、最基本的语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
-- 注释从两个减号开始
print("Hello, world!")

-- 变量(默认全局)
x = 10
local y = 20    -- local 是局部,强烈推荐永远用 local

-- 字符串
local name = "Lua"
print("Hi, " .. name)   -- 字符串拼接用 ..
print(#name)             -- # 取长度

-- 数字
local a = 1.5
local b = 2

-- nil(不存在/未定义)
local nothing = nil

-- 布尔(注意:只有 false 和 nil 是 falsy,0 是 truthy!)
if 0 then print("0 是 truthy") end   -- 会执行

⚠️ 几个和主流语言不一样的地方

  • nil 不是 null——访问不存在的字段返回 nil
  • 0 是真值!这和大多数语言相反
  • 字符串拼接用 ..,不是 +
  • 取长度用 #str,不是 len(str)

二、控制流

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- if
if x > 10 then
    print("big")
elseif x > 5 then
    print("medium")
else
    print("small")
end

-- while
while x > 0 do
    x = x - 1
end

-- repeat-until(条件为 true 时退出)
repeat
    x = x + 1
until x > 100

-- for(数值型)
for i = 1, 10 do print(i) end          -- 1 到 10
for i = 1, 10, 2 do print(i) end       -- 步长 2
for i = 10, 1, -1 do print(i) end      -- 倒序

-- for(迭代器)
for k, v in pairs(table) do print(k, v) end
for i, v in ipairs(array) do print(i, v) end

注意——for 没有 ++ --,循环结束用 end


三、函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function add(a, b)
    return a + b
end

-- 多返回值(Lua 一大特色)
function divmod(a, b)
    return a / b, a % b
end
local q, r = divmod(7, 2)

-- 可变参数
function printAll(...)
    local args = {...}
    for i, v in ipairs(args) do print(i, v) end
end

-- 匿名函数 + 局部变量
local sayHello = function(name)
    print("Hello, " .. name)
end
sayHello("World")

函数是 first-class value——可以传递、返回、存进表,闭包语义和 JS / Python 类似。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function counter()
    local n = 0
    return function()
        n = n + 1
        return n
    end
end

local next = counter()
print(next())   -- 1
print(next())   -- 2

四、Table——Lua 唯一的数据结构

Lua 的核心是 table——既是数组、又是字典、又是对象——一个数据结构表达所有:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 数组式
local arr = {10, 20, 30}
print(arr[1])       -- 10(注意从 1 开始!)
print(#arr)         -- 3

-- 字典式
local user = { name = "alice", age = 30 }
print(user.name)
print(user["name"])

-- 混合
local mixed = { "a", "b", "c", color = "red" }

⚠️ Lua 的数组从 1 开始! 不是 0。这是和 99% 主流语言的不同——也是新手最容易踩的坑。

表的常用操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
table.insert(arr, 40)       -- 末尾追加
table.remove(arr, 1)         -- 移除第 1 个
table.concat(arr, ", ")      -- 拼成字符串
table.sort(arr)              -- 排序

-- 字典遍历
for k, v in pairs(user) do
    print(k, v)
end

-- 数组遍历
for i, v in ipairs(arr) do
    print(i, v)
end

pairs 遍历所有键值对(包括字符串 key);ipairs 只遍历数字索引 1, 2, 3…——遇到 nil 停止。


五、字符串与模式匹配

字符串是 Lua 里仅次于 table 的常用类型,标准库里有不少利器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
local s = "Hello, Lua!"

print(#s)                    -- 11
print(string.upper(s))       -- HELLO, LUA!
print(string.sub(s, 8, 10))  -- Lua
print(string.rep("ab", 3))   -- ababab

-- 也可以用 OO 调用
print(s:upper())
print(s:find("Lua"))         -- 8  10

模式匹配

Lua 没有正则,但有自己的轻量"模式"语法:

1
2
3
4
5
6
local date = "2025-04-22"
local y, m, d = string.match(date, "(%d+)-(%d+)-(%d+)")
print(y, m, d)               -- 2025  04  22

-- 替换
print(string.gsub("hello world", "world", "Lua"))   -- hello Lua  1

%d 数字、%a 字母、%w 字母数字、%s 空白——和 PCRE 不完全一样但够用,性能也好。


六、Metatable 与面向对象

Lua 没有 class,但 metatable 几乎能模拟出任何 OO 特性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
local Animal = {}
Animal.__index = Animal

function Animal.new(name)
    local self = setmetatable({}, Animal)
    self.name = name
    return self
end

function Animal:eat()                -- 注意冒号 :
    print(self.name .. " is eating")
end

local dog = Animal.new("Rex")
dog:eat()                            -- 调用也用冒号

冒号 : 是语法糖——obj:method() 等价于 obj.method(obj),自动传 self。

元方法(metamethods)

metatable 里几个特殊键名能"重载"运算符:

1
2
3
4
5
6
7
8
local Vec = {}
Vec.__index = Vec
Vec.__add = function(a, b) return setmetatable({x = a.x + b.x, y = a.y + b.y}, Vec) end
Vec.__tostring = function(v) return "(" .. v.x .. ", " .. v.y .. ")" end

local v1 = setmetatable({x = 1, y = 2}, Vec)
local v2 = setmetatable({x = 3, y = 4}, Vec)
print(v1 + v2)               -- (4, 6)

常用元方法:__index / __newindex / __add / __sub / __mul / __eq / __lt / __tostring / __call

继承

1
2
3
4
5
local Dog = setmetatable({}, {__index = Animal})

function Dog:bark()
    print(self.name .. ": woof!")
end

子类查找方法时,自身没有就沿 __index 一路向上找——这就是 Lua 的"原型继承"。


七、协程(coroutine)

协程是 Lua 一个常被忽略的强项——轻量级、对称式协程,比线程便宜得多:

1
2
3
4
5
6
7
8
9
local co = coroutine.create(function(a, b)
    print("start: " .. a .. ", " .. b)
    local c = coroutine.yield(a + b)
    print("resumed with: " .. c)
    return "done"
end)

print(coroutine.resume(co, 1, 2))      -- start: 1, 2 \n true  3
print(coroutine.resume(co, 99))        -- resumed with: 99 \n true  done

yield 把控制权还给调用者并把值传出去;resume 重新激活协程并把值传进去。

协程能用来写生成器

1
2
3
4
5
6
7
8
9
local function range(from, to)
    return coroutine.wrap(function()
        for i = from, to do
            coroutine.yield(i)
        end
    end)
end

for v in range(1, 5) do print(v) end   -- 1 2 3 4 5

coroutine.wrapcreate + resume 的简化包装。


八、模块化

1
2
3
4
5
6
7
8
-- mylib.lua
local M = {}

function M.greet(name)
    return "Hello, " .. name
end

return M
1
2
3
-- main.lua
local lib = require("mylib")
print(lib.greet("World"))

require 自动从 package.path 里找 .lua 文件,结果会被缓存——同一个模块多次 require 只加载一次。


九、几个常踩的语法陷阱

1. 数组从 1 开始

1
2
3
local a = {10, 20, 30}
print(a[0])    -- nil(不是 10!)
print(a[1])    -- 10

2. nil 不能放在数组中间

1
2
3
local a = {1, 2, nil, 4}
print(#a)      -- 不一定是 4,可能是 2
for i,v in ipairs(a) do print(v) end   -- 只打到 2

ipairs 遇 nil 停——这种 sparse array 在 Lua 里要特别小心。

3. 0 和 "" 是 truthy

1
2
3
4
if 0 then print("yes") end       -- yes
if "" then print("yes") end       -- yes
if nil then print("yes") end      -- 不打印
if false then print("yes") end    -- 不打印

只有 falsenil 是 falsy。

4. 字符串与数字自动转换

1
2
local x = "10" + 5    -- 15(字符串自动转数字)
local y = 10 .. "abc" -- "10abc"

便利但容易隐藏 bug——关键代码显式 tonumber() / tostring()

5. 默认全局

1
2
3
4
function foo()
    x = 10        -- 这是全局变量!
    local y = 20  -- 这才是局部
end

永远写 local——否则一不小心污染全局。

6. 整数与浮点(5.3+)

Lua 5.3 引入了原生 integer 类型,整数除法用 //

1
2
print(7 / 2)   -- 3.5(浮点)
print(7 // 2)  -- 3 (整除)

5.3 之前所有数字都是 double——老脚本升到 5.3 时除法行为可能微妙改变


十、Lua 的设计哲学

了解一下 Lua 之所以这么设计的几条核心原则:

  • 极简正交:核心特性极少,但每条都正交,能组合出强大能力
  • 机制不策略:metatable 是机制,怎么实现 OO / 继承 / 运算符重载都是策略
  • 数据结构唯一性:只用 table 表达所有复合数据
  • 首类函数 + 闭包:函数式编程的基本元素都齐了

正是这种克制让 Lua 能在两万多行 C 代码里塞下一门完整的语言——也让它的标准库相对寒酸(“麻雀虽小五脏不全”),需要用 luarocks 或 LuaJIT 的 ffi 补足。


十一、Lua 版本与 LuaJIT

Lua 5.x 版本演进

  • 5.1:经典版,使用面最广
  • 5.2:引入 goto、bit32 库
  • 5.3:引入原生 integer 类型、整除 //原生位运算符 & | ~ << >>(bit32 库被废弃)
  • 5.4:generational GC、<const><close> 属性

5.1 → 5.4 不完全兼容——升级时要核对每条变化。

LuaJIT

LuaJIT 是 Lua 的 JIT 编译器实现——比标准 Lua 快 5-10 倍,性能直逼 C。它的 ffi 库还能直接调用 C 函数,扩展能力极强。

不过 LuaJIT 主要兼容 5.1 + 部分 5.2 特性——和 5.3 / 5.4 的 integer 等新特性不兼容

包管理

luarocks 是事实标准——类似 npm/pip。


十二、什么场景该用 Lua

✅ 适合

  • 轻量脚本:启动快、依赖少,比 Python 启动几十倍快
  • DSL / 配置:用作业务规则、配置文件的承载语言
  • 作为宿主程序的扩展层:被嵌入到其他程序里执行业务逻辑(这是它历史上最常见的用法之一)
  • 性能敏感的脚本:LuaJIT 性能接近 C

❌ 不适合

  • 独立大型应用:标准库薄,生态远不如 Python / Node
  • 复杂业务系统:OO 弱、错误处理粗糙
  • 数据科学:没有 NumPy / Pandas 之类生态

小结

把全文压一句:

**Lua 是一门用很少的概念表达很多能力的小语言——核心是 table 与 first-class function,加上 metatable 和协程,几乎能覆盖任何脚本场景。 **

记住几个关键点:

  • 数组从 1 开始
  • local 永远要写
  • nilfalse 才是 falsy
  • 字符串拼接用 ..
  • table 是唯一复合数据结构
  • metatable 是 Lua OO 与运算符重载的统一机制
  • 协程是它被低估的强项

掌握了这些,你已经可以读懂 95% 的 Lua 代码,并写出符合语言风格的脚本。

使用 Hugo 构建
主题 StackJimmy 设计