强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Lua 从入门到精通 / 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
nilnil表示"无值" / absence of value
booleantrue, false布尔 / boolean
number42, 3.14数字 / number
string"hello"字符串 / string
table{1, 2, 3}表 / table(唯一的数据结构)
functionfunction() end函数 / function
threadcoroutine.create(...)协程 / coroutine
userdataC 扩展创建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() 实现

下一章:控制流程 / Control Flow