写在前面
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——访问不存在的字段返回 nil0 是真值!这和大多数语言相反- 字符串拼接用
..,不是 + - 取长度用
#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 不完全一样但够用,性能也好。
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。
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.wrap 是 create + 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 -- 不打印
|
只有 false 和 nil 是 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 永远要写nil 和 false 才是 falsy- 字符串拼接用
.. - table 是唯一复合数据结构
- metatable 是 Lua OO 与运算符重载的统一机制
- 协程是它被低估的强项
掌握了这些,你已经可以读懂 95% 的 Lua 代码,并写出符合语言风格的脚本。