# 是 Lua 中的长度运算符,但它的行为有些特殊,需要仔细理解。
一、基础用法
1. 获取字符串长度
local str = "Hello"
print(#str) -- 5
local chinese = "你好"
print(#chinese) -- 6(UTF-8 编码,每个中文字符占 3 字节)
-- 空字符串
print(#"") -- 0
2. 获取数组(序列)长度
local arr = {10, 20, 30, 40, 50}
print(#arr) -- 5
-- 空表
print(#{}) -- 0
二、数组长度的关键概念
1. 什么是"序列"(Sequence)
Lua 中,# 只对序列有效。序列的定义是:
- 索引从 1 开始
- 连续的正整数索引(1, 2, 3, ... n)
- 中间没有空洞(nil)
-- 有效的序列
local seq1 = {10, 20, 30} -- #seq1 = 3
local seq2 = {"a", "b", nil, "d"} -- 不是序列,因为中间有 nil
-- 无效的情况(有"空洞")
local bad = {}
bad[1] = "a"
bad[2] = "b"
bad[4] = "d" -- 跳过了 3
print(#bad) -- 结果不确定!可能是 2 或 4
2. 长度运算符的工作原理
local t = {10, 20, 30, nil, 50}
-- 找最后一个非 nil 的索引
-- 但遇到 nil 会停止,所以结果不确定
print(#t) -- 可能是 3(在 30 处停止)
-- 更明确的例子
local t2 = {10, 20, nil}
print(#t2) -- 2(在 nil 前停止)
三、实际应用示例
1. 遍历数组
local fruits = {"apple", "banana", "cherry"}
-- 方法1:使用 # 运算符
for i = 1, #fruits do
print(i, fruits[i])
end
-- 方法2:使用 ipairs(推荐,更安全)
for i, fruit in ipairs(fruits) do
print(i, fruit)
end
2. 向数组末尾添加元素
local stack = {10, 20, 30}
-- 在末尾添加
table.insert(stack, 40) -- 使用内置函数
-- 或者
stack[#stack + 1] = 40 -- 手动添加
print(#stack) -- 5
3. 获取最后一个元素
local queue = {"first", "second", "third"}
local last = queue[#queue] -- "third"
-- 删除最后一个元素
queue[#queue] = nil
print(#queue) -- 2
四、常见陷阱和解决方案
陷阱 1:包含 nil 的数组
local data = {10, 20, nil, 40, 50}
print(#data) -- 结果不确定!可能是 2 或 5
-- 解决方案:不要用 nil 表示空值,用特殊值
local data2 = {10, 20, false, 40, 50} -- 用 false 代替 nil
print(#data2) -- 5(确定)
陷阱 2:非连续索引
local sparse = {}
sparse[1] = "a"
sparse[100] = "b"
print(#sparse) -- 1(不是 100!)
-- 解决方案:如果需要稀疏数组,自己记录长度
local sparse2 = {
data = {[1] = "a", [100] = "b"},
length = 2 -- 手动维护长度
}
陷阱 3:字符串中的特殊字符
local str = "Hello\nWorld" -- 包含换行符
print(#str) -- 11(\n 算 1 个字符)
local str2 = "Hello\0World" -- 包含空字符
print(#str2) -- 11(\0 也算 1 个字符)
五、高级用法
1. 实现栈(Stack)
local Stack = {}
Stack.__index = Stack
function Stack:new()
return setmetatable({items = {}}, self)
end
function Stack:push(item)
self.items[#self.items + 1] = item
end
function Stack:pop()
local len = #self.items
if len == 0 then return nil end
local item = self.items[len]
self.items[len] = nil
return item
end
function Stack:peek()
return self.items[#self.items]
end
function Stack:isEmpty()
return #self.items == 0
end
function Stack:size()
return #self.items
end
-- 使用
local s = Stack:new()
s:push(10)
s:push(20)
s:push(30)
print(s:pop()) -- 30
print(s:size()) -- 2
2. 实现队列(Queue)
local Queue = {}
Queue.__index = Queue
function Queue:new()
return setmetatable({
items = {},
head = 1,
tail = 0
}, self)
end
function Queue:enqueue(item)
self.tail = self.tail + 1
self.items[self.tail] = item
end
function Queue:dequeue()
if self.head > self.tail then return nil end
local item = self.items[self.head]
self.items[self.head] = nil -- 允许垃圾回收
self.head = self.head + 1
return item
end
function Queue:size()
return self.tail - self.head + 1
end
-- 使用
local q = Queue:new()
q:enqueue("A")
q:enqueue("B")
q:enqueue("C")
print(q:dequeue()) -- A
print(q:size()) -- 2
3. 安全的获取表长度函数
-- 自定义安全的长度函数
function safeLength(t)
if type(t) ~= "table" then
return 0
end
local len = 0
for i = 1, math.huge do
if t[i] == nil then
break
end
len = i
end
return len
end
-- 测试
local t1 = {10, 20, 30}
local t2 = {10, nil, 30} -- 有 nil
print(safeLength(t1)) -- 3
print(safeLength(t2)) -- 1(在第一个 nil 处停止)
六、与其他函数对比
| 方式 |
用途 |
特点 |
#t |
获取序列长度 |
快速,但对非序列结果不确定 |
ipairs(t) |
遍历序列 |
遇到 nil 停止,安全 |
pairs(t) |
遍历所有键值 |
遍历所有键,不保证顺序 |
table.insert(t, v) |
插入元素 |
自动处理长度 |
table.remove(t) |
删除元素 |
自动处理长度 |
七、总结要点
# 对字符串:返回字节数(不是字符数)
# 对表:只对序列有效(连续的正整数索引,无 nil)
- 避免陷阱:不要在数组中间放 nil
- 替代方案:需要稀疏数组时,自己维护长度或使用其他数据结构