03 - 数据类型 / Data Types
数据类型 / Data Types
Lua 是动态类型语言,变量没有类型,值才有类型。同一个变量可以先存数字,再存字符串。
Lua is dynamically typed — variables don’t have types, values do. The same variable can hold a number, then a string.
🟢 基础 / Basics — 8 种类型一览
Lua 有 8 种基本类型:
| 类型 / Type | 示例 / Example | 说明 / Description |
|---|---|---|
nil | nil | 表示"无值" / absence of value |
boolean | true, false | 布尔 / boolean |
number | 42, 3.14 | 数字 / number |
string | "hello" | 字符串 / string |
table | {1, 2, 3} | 表 / table(唯一的数据结构) |
function | function() end | 函数 / function |
thread | coroutine.create(...) | 协程 / coroutine |
userdata | C 扩展创建 | C 数据 / C data |
1. nil — “什么都没有”
-- nil 表示"无值"或"不存在"
-- nil means "no value" or "does not exist"
local x -- x 的值是 nil
print(x) -- nil
print(type(x)) -- "nil"
-- 未赋值的变量默认为 nil
print(undefined_var) -- nil(不是错误!)
-- 将变量设为 nil 等同于"删除"它
local name = "Lua"
name = nil -- name 现在是 nil,字符串 "Lua" 可被 GC 回收
-- nil 在条件判断中为 false
if nil then
print("不会执行")
else
print("nil is falsy") -- 输出这个
end
-- 只有两个 falsy 值:nil 和 false
-- 其他所有值(包括 0 和 "")都是 truthy!
-- Only two falsy values: nil and false
-- Everything else (including 0 and "") is truthy!
if 0 then print("0 is truthy!") end -- 会执行!
if "" then print('"" is truthy!') end -- 会执行!
2. boolean — 布尔
local a = true
local b = false
-- boolean 只有两个值
print(type(true)) -- "boolean"
print(type(false)) -- "boolean"
-- 比较运算返回 boolean
print(1 > 2) -- false
print(1 == 1) -- true
print("a" ~= "b") -- true
-- and / or 的短路行为
-- 在 Lua 中,and/or 返回操作数,不只是 true/false
print(1 and 2) -- 2(第一个为真,返回第二个)
print(nil and 2) -- nil(第一个为假,直接返回)
print(1 or 2) -- 1(第一个为真,直接返回)
print(nil or 2) -- 2(第一个为假,返回第二个)
3. number — 数字
-- Lua 5.3+ 区分整数和浮点数
-- Lua 5.3+ distinguishes integer and float
local int = 42 -- 整数 / integer
local float = 3.14 -- 浮点数 / float
local sci = 1.5e10 -- 科学记数法 / scientific notation
local hex = 0xFF -- 十六进制 / hexadecimal (255)
local oct = 0o77 -- 八进制 / octal (63) (Lua 5.3+)
local bin = 0b1010 -- 二进制 / binary (10) (Lua 5.3+)
print(type(42)) -- "integer" (Lua 5.3+)
print(type(42.0)) -- "float" (Lua 5.3+)
-- 整数和浮点数的自动转换
-- Auto conversion between integer and float
print(1 + 2) -- 3 (integer)
print(1 + 2.0) -- 3.0 (float)
print(7 / 2) -- 3.5 (float)
print(7 // 2) -- 3 (integer, floor division)
print(7.0 // 2) -- 3.0 (float, Lua 5.3+)
4. string — 字符串
-- 字符串是不可变的字节序列
-- Strings are immutable byte sequences
local s1 = "Hello"
local s2 = 'Hello'
local s3 = [[Multi
line]]
-- 字符串长度
print(#"Hello") -- 5
print(#"你好") -- 6(UTF-8 每个中文字符 3 字节)
-- 注意:# 返回的是字节数,不是字符数!
local s = "你好Hello"
print(#s) -- 11(不是 7)
-- 要计算 Unicode 字符数,需要额外的库
-- 字符串连接
print("Hello" .. " " .. "World") -- "Hello World"
-- 字符串是不可变的
local s = "Hello"
-- s[1] = "h" -- 错误!不能修改字符串中的字符
s = "hello" -- 这是创建了一个新字符串
5. table — 表
-- table 是 Lua 唯一的数据结构,万能容器
-- table is Lua's only data structure, a universal container
-- 创建空表
local t = {}
-- 数组式用法(下标从 1 开始!)
local fruits = {"apple", "banana", "cherry"}
print(fruits[1]) -- "apple"(不是 fruits[0]!)
print(fruits[3]) -- "cherry"
-- 字典式用法
local person = {
name = "Alice",
age = 30,
city = "Beijing",
}
print(person.name) -- "Alice"
print(person["age"]) -- 30
-- 混合用法
local data = {
"first", -- data[1]
"second", -- data[2]
name = "mixed", -- data["name"]
[100] = "sparse", -- data[100]
}
6. function — 函数
-- 函数是一等公民,可以存在变量中、作为参数传递
-- Functions are first-class citizens
-- 两种定义方式 / Two ways to define
local function add(a, b) -- 语法糖
return a + b
end
local subtract = function(a, b) -- 匿名函数赋值
return a - b
end
print(add(3, 2)) -- 5
print(subtract(3, 2)) -- 1
-- 函数可以作为参数 / Functions as arguments
local function apply(f, x, y)
return f(x, y)
end
print(apply(add, 10, 5)) -- 15
print(apply(subtract, 10, 5)) -- 5
🟡 进阶 / Intermediate — 深入类型系统
1. 类型判断的正确姿势 / Proper Type Checking
-- type() 返回字符串
print(type(42)) -- "number"
print(type(42)) -- "integer" in Lua 5.3+(但 type() 仍返回 "number")
-- 在 Lua 5.3+ 中区分整数和浮点数
print(math.type(42)) -- "integer"
print(math.type(42.0)) -- "float"
-- 判断是否为整数
local function isInteger(n)
return type(n) == "number" and n == math.floor(n)
end
-- 判断是否为 NaN
local function isNaN(n)
return n ~= n -- NaN 是唯一不等于自身的值
end
print(isNaN(0/0)) -- true
print(isNaN(1/0)) -- false(这是 inf,不是 NaN)
-- 安全的类型检查模式 / Safe type checking pattern
local function isString(v)
return type(v) == "string"
end
local function isTable(v)
return type(v) == "table"
end
local function isCallable(v)
if type(v) == "function" then return true end
local mt = getmetatable(v)
return mt and mt.__call ~= nil
end
2. nil vs false 的陷阱 / nil vs false Trap
-- 这是 Lua 最常见的坑之一!
local t = {
key1 = "hello",
key2 = false, -- 注意:值是 false
key3 = nil, -- 注意:值是 nil(等于不存在)
}
-- 检查 key 是否存在 / Checking if key exists
print(t.key1 ~= nil) -- true(存在)
print(t.key2 ~= nil) -- true(false 不是 nil,key 存在)
print(t.key3 ~= nil) -- false(nil 表示不存在)
-- 陷阱:不能用 `or` 区分 "不存在" 和 "值为 false"
local value = t.key2 or "default"
print(value) -- "default"(因为 false 是 falsy,但我们想要 false!)
-- 正确做法:显式检查 nil
local function getOrDefault(t, key, default)
local value = t[key]
if value == nil then
return default
end
return value -- 即使是 false,也返回 false
end
print(getOrDefault(t, "key2", "default")) -- false
print(getOrDefault(t, "key3", "default")) -- "default"
3. 数字精度与陷阱 / Number Precision & Pitfalls
-- 浮点数精度问题 / Floating-point precision
print(0.1 + 0.2) -- 0.30000000000000004
print(0.1 + 0.2 == 0.3) -- false!
-- 正确的浮点数比较方式
local function nearlyEqual(a, b, epsilon)
epsilon = epsilon or 1e-10
return math.abs(a - b) < epsilon
end
print(nearlyEqual(0.1 + 0.2, 0.3)) -- true
-- 整数的范围(Lua 5.3+,默认 64 位有符号整数)
print(math.maxinteger) -- 9223372036854775807
print(math.mininteger) -- -9223372036854775808
-- 整数溢出是回绕的(wrapping),不报错
print(math.maxinteger + 1) -- -9223372036854775808(变成了最小值)
-- 0/0 = NaN
print(0/0) -- -nan
-- 1/0 = inf
print(1/0) -- inf
print(-1/0) -- -inf
-- NaN 不等于任何值,包括自身
local nan = 0/0
print(nan == nan) -- false
print(nan == nan) -- false
print(nan ~= nan) -- true(这是判断 NaN 的方式)
4. 字符串内部机制 / String Internals
-- 字符串驻留(String Interning)
-- 相同内容的字符串在内存中只有一份
local a = "hello"
local b = "hello"
print(a == b) -- true
print(a == b) -- true(比较的是指针,非常快!)
-- 所以在 Lua 中,字符串比较非常快
-- 字符串比较实际上是先比指针(同一份就相等),再比内容
-- 字符串内部结构 / Internal structure (TString):
-- +--------+--------+--------+---------+
-- | header | hash | len | bytes...|
-- +--------+--------+--------+---------+
-- - hash: 预计算的哈希值,用于表查找
-- - len: 字节长度
-- - bytes: 实际内容(以 '\0' 结尾)
-- 字符串拼接创建新字符串
local s = ""
for i = 1, 10000 do
s = s .. "a" -- 每次创建新字符串,O(n^2)!
end
-- 高效替代方案:table.concat
local parts = {}
for i = 1, 10000 do
parts[i] = "a"
end
local s = table.concat(parts) -- O(n),高效!
5. table 的数组部分 / Table Array Part
-- table 在内部有两种存储:数组部分和哈希部分
-- Tables have two internal storage modes: array part and hash part
-- 数组部分:连续的整数索引
local arr = {10, 20, 30, 40, 50}
-- 内部存储为数组:[10, 20, 30, 40, 50]
-- arr[1] -> 直接索引,O(1)
-- 哈希部分:非整数键
local dict = {name="Lua", version="5.4"}
-- 内部存储为哈希表
-- dict["name"] -> 哈希查找,O(1) 平均
-- 混合使用
local mixed = {
10, 20, 30, -- 数组部分: [1]=10, [2]=20, [3]=30
x = "hello", -- 哈希部分: "x" -> "hello"
y = "world", -- 哈希部分: "y" -> "world"
}
-- # 运算符只计算数组部分的长度
print(#arr) -- 5
print(#mixed) -- 3(只算整数键部分)
-- 长度的定义:从 1 开始的连续整数键的个数
-- Length definition: number of consecutive integer keys starting from 1
local t = {[1]="a", [2]="b", [4]="d"}
print(#t) -- 2(key 3 缺失,所以长度为 2)
🔴 高级 / Advanced — 类型系统底层原理
1. TValue:Lua 值的内部表示 / Internal Value Representation
// Lua 内部用 TValue 结构体表示所有值
// Lua uses TValue internally to represent all values
// 简化的 TValue 结构(Lua 5.4):
typedef struct {
int tt_; // 类型标签 / type tag
union {
lua_Integer i; // 整数值
lua_Number n; // 浮点值
TString *s; // 字符串指针
Table *h; // 表指针
LClosure *f; // 函数指针
void *p; // 通用指针(userdata)
int b; // 布尔值
} value;
} TValue;
Lua 值的内存布局 / Memory layout of Lua values:
nil: [ type:nil | (empty) ] 8 bytes
boolean: [ type:bool | 0 or 1 ] 8 bytes
integer: [ type:int | 64-bit int ] 16 bytes
float: [ type:flt | 64-bit double ] 16 bytes
string: [ type:str | pointer ─────────► TString (header + bytes)
table: [ type:tbl | pointer ─────────► Table (array + hash)
func: [ type:func | pointer ─────────► Closure
关键点 / Key Insights:
- 小类型(nil, boolean)内联在 TValue 中,零开销
- 大类型(string, table, function)通过指针引用,TValue 本身只有 8-16 字节
- 这就是为什么赋值 table 是引用传递,不是拷贝
2. 整数与浮点数的内部处理 / Integer vs Float Internals
-- Lua 5.3+ 的算术运算规则
-- Arithmetic rules in Lua 5.3+
-- 1. 两个整数运算 → 整数结果
print(1 + 2) -- 3 (integer)
print(7 // 2) -- 3 (integer)
-- 2. 任一操作数是浮点数 → 浮点结果
print(1 + 2.0) -- 3.0 (float)
print(7.0 // 2) -- 3.0 (float)
-- 3. 除法总是返回浮点数
print(6 / 2) -- 3.0 (float,不是 3!)
print(6 // 2) -- 3 (integer,整除)
-- 4. 位运算只支持整数
print(0xFF & 0x0F) -- 15 (bitwise AND)
print(1 << 4) -- 16 (left shift)
print(0xFF | 0x0F) -- 255 (bitwise OR)
print(~0xFF) -- -256 (bitwise NOT)
-- 位运算会截断浮点数
print(3.7 | 0) -- 3(隐式转为整数)
3. 字符串的哈希与相等性 / String Hashing & Equality
-- Lua 字符串比较的内部过程:
-- 1. 先比较指针(interned string 相同时直接相等)
-- 2. 指针不同再比较长度
-- 3. 长度相同再比较内容(memcmp)
-- 这意味着:
-- - 短字符串比较非常快(指针比较)
-- - 字符串作为表的 key 也很高效(使用预计算的 hash)
-- 字符串驻留的实际影响
local t = {}
local s1 = string.rep("a", 100) -- 创建新字符串
local s2 = string.rep("a", 100) -- 创建另一个相同内容的字符串
print(s1 == s2) -- true(值相等)
-- 但 s1 和 s2 可能不是同一个指针(取决于是否被驻留)
-- Lua 会自动驻留短字符串,长字符串不驻留
-- Lua auto-interns short strings, but not long ones
4. type() 的字节码实现
-- type() 实际上只是读取值的类型标签
function checkType(v)
local t = type(v)
-- type(v) 编译为一条 GETTABUP 指令,访问全局 type 函数
-- type 函数内部只是返回 v.tt_ 的字符串映射
end
-- 性能提示:频繁检查类型时,用局部变量缓存 type 函数
local type = type -- 缓存到局部变量
local function process(v)
if type(v) == "string" then
-- ...
elseif type(v) == "number" then
-- ...
end
end
-- 这样避免每次都查找全局变量 type
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | 8 种类型、nil/false 区别、table 从 1 开始、字符串不可变 |
| 🟡 进阶 | nil vs false 陷阱、浮点精度问题、字符串驻留、# 运算符的定义 |
| 🔴 高级 | TValue 内部表示、整数/浮点运算规则、字符串哈希机制、type() 实现 |