Lua 运算符
Lua提供了丰富的运算符,包括算术运算符、关系运算符、逻辑运算符、字符串运算符、位运算符(Lua 5.3+)等。所有运算符都有明确的优先级规则。
一、算术运算符
1. 基本算术运算符
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
+ |
加法 | 3 + 2 |
5 |
- |
减法 | 5 - 2 |
3 |
* |
乘法 | 3 * 4 |
12 |
/ |
除法 | 10 / 2 |
5.0 |
% |
取模 | 10 % 3 |
1 |
^ |
幂运算 | 2 ^ 3 |
8 |
- |
负号(一元) | -5 |
-5 |
lua
-- 算术运算符示例
local a, b = 10, 3
print("加法:", a + b) -- 13
print("减法:", a - b) -- 7
print("乘法:", a * b) -- 30
print("除法:", a / b) -- 3.3333333333333
print("取模:", a % b) -- 1
print("幂运算:", a ^ 2) -- 100
print("负号:", -a) -- -10
-- 注意:除法总是返回浮点数
print(type(10 / 2)) -- number (实际上是 5.0)
-- 取模运算的细节
print("10 % 3 =", 10 % 3) -- 1
print("-10 % 3 =", -10 % 3) -- 2 (注意:结果符号与除数相同)
print("10 % -3 =", 10 % -3) -- -2
print("-10 % -3 =", -10 % -3) -- -1
-- 取模运算的特性:a % b == a - math.floor(a/b)*b
2. 算术运算的自动类型转换
lua
-- 字符串在算术运算中会自动转换为数字
print("10" + 5) -- 15
print("3.14" * 2) -- 6.28
print(10 + "5") -- 15
-- 无法转换时返回nil
local result = "hello" + 5
print(result) -- nil
print(type(result)) -- nil
二、关系运算符
1. 基本关系运算符
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
== |
等于 | 5 == 5 |
true |
~= |
不等于 | 5 ~= 3 |
true |
< |
小于 | 3 < 5 |
true |
> |
大于 | 5 > 3 |
true |
<= |
小于等于 | 5 <= 5 |
true |
>= |
大于等于 | 5 >= 5 |
true |
lua
-- 关系运算符示例
local a, b = 10, 5
print("相等:", a == b) -- false
print("不等:", a ~= b) -- true
print("小于:", a < b) -- false
print("大于:", a > b) -- true
print("小于等于:", a <= b) -- false
print("大于等于:", a >= b) -- true
2. 比较运算的特殊规则
lua
-- 规则1:不同类型的值不相等
print(1 == "1") -- false
print(0 == false) -- false
print(nil == false) -- false
-- 规则2:table和userdata比较的是引用,不是内容
local t1 = {1, 2, 3}
local t2 = {1, 2, 3}
local t3 = t1
print(t1 == t2) -- false (不同的引用)
print(t1 == t3) -- true (相同的引用)
-- 规则3:nil只等于nil
print(nil == nil) -- true
3. 浮点数比较的陷阱和解决方案
lua
-- 浮点数精度问题
local a = 0.1 + 0.2
local b = 0.3
print("直接比较:", a == b) -- false
-- 解决方案1:使用误差范围
local epsilon = 1e-10
function float_equal(x, y)
return math.abs(x - y) < epsilon
end
print("使用误差范围:", float_equal(a, b)) -- true
-- 解决方案2:使用math.abs
local threshold = 0.0000001
print("使用math.abs:", math.abs(a - b) < threshold) -- true
-- 解决方案3:转为字符串格式化比较(适用于特定精度需求)
function float_equal_format(x, y, precision)
precision = precision or 6
return string.format("%." .. precision .. "f", x) ==
string.format("%." .. precision .. "f", y)
end
print("格式化比较:", float_equal_format(a, b, 10)) -- true
三、逻辑运算符
1. 基本逻辑运算符
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
and |
逻辑与 | true and false |
false |
or |
逻辑或 | true or false |
true |
not |
逻辑非 | not true |
false |
lua
-- 逻辑运算符示例
print("and 运算:")
print(true and true) -- true
print(true and false) -- false
print(false and true) -- false
print(false and false) -- false
print("\nor 运算:")
print(true or true) -- true
print(true or false) -- true
print(false or true) -- true
print(false or false) -- false
print("\nnot 运算:")
print(not true) -- false
print(not false) -- true
print(not nil) -- true (nil视为false)
print(not 0) -- false (0视为true)
2. 逻辑运算符的短路求值
lua
-- and: 如果第一个操作数为假,直接返回第一个操作数,否则返回第二个操作数
print(1 and 2) -- 2
print(nil and 2) -- nil
print(false and error("不会执行")) -- false (不会触发error)
-- or: 如果第一个操作数为真,直接返回第一个操作数,否则返回第二个操作数
print(1 or 2) -- 1
print(nil or 2) -- 2
print(true or error("不会执行")) -- true (不会触发error)
-- 实用技巧:提供默认值
local config = {}
local value = config.port or 8080
print("端口:", value) -- 8080
-- 实用技巧:条件赋值
local x = nil
local y = x or 100
print("y =", y) -- 100
-- 实用技巧:简化if语句
function print_if_positive(n)
(n > 0) and print("正数:", n)
end
print_if_positive(5) -- 输出: 正数: 5
print_if_positive(-3) -- 无输出
3. 三元运算符模拟
lua
-- Lua没有三元运算符,但可以用and/or模拟
-- condition ? value_if_true : value_if_false
-- 在Lua中: (condition and value_if_true) or value_if_false
local age = 18
local status = (age >= 18 and "成年") or "未成年"
print("状态:", status) -- 成年
-- 注意:当value_if_true为false或nil时,这种方法会失效
local flag = false
local result = (flag and "true") or "false"
print("结果:", result) -- false (正确,因为flag为false)
-- 但当value_if_true本身可能是false时
local show = false
local display = (show and false) or true
print("显示:", display) -- true (错误!应该为false)
-- 这是因为: false and false 返回 false, false or true 返回 true
-- 解决方案:使用显式if语句或函数
function ternary(condition, true_val, false_val)
if condition then
return true_val
else
return false_val
end
end
local display2 = ternary(show, false, true)
print("显示(正确):", display2) -- false
四、字符串运算符
1. 字符串连接运算符
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
.. |
字符串连接 | "Hello" .. "World" |
"HelloWorld" |
lua
-- 字符串连接
local s1 = "Hello"
local s2 = "World"
local s3 = s1 .. " " .. s2
print(s3) -- Hello World
-- 数字会自动转换为字符串
local num = 42
print("答案是: " .. num) -- 答案是: 42
-- 连接大量字符串时注意效率
-- 低效的方式(每次连接都创建新字符串)
local result = ""
for i = 1, 1000 do
result = result .. i -- 每次循环都创建新字符串
end
-- 高效的方式(使用table.concat)
local parts = {}
for i = 1, 1000 do
parts[i] = tostring(i)
end
local result2 = table.concat(parts)
2. 字符串长度运算符
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
# |
字符串长度 | #"Hello" |
5 |
lua
-- 字符串长度
print(#"Hello") -- 5
print(#"") -- 0
print(#"中文") -- 2 (注意:计算的是字符数,不是字节数)
-- 多字节字符处理
local str = "Hello世界"
print("字符串长度:", #str) -- 7 (5个英文字母 + 2个中文字符)
五、位运算符(Lua 5.3+)
Lua 5.3 引入了对位运算的支持,所有位运算符都只对整数有效。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
& |
按位与 | 5 & 3 |
1 |
| ` | ` | 按位或 | `5 |
~ |
按位异或 | 5 ~ 3 |
6 |
>> |
右移 | 8 >> 1 |
4 |
<< |
左移 | 1 << 3 |
8 |
~ |
按位非(一元) | ~5 |
-6 |
lua
-- 位运算符示例
local a, b = 5, 3 -- 二进制: 101, 011
print("按位与:", a & b) -- 1 (二进制: 001)
print("按位或:", a | b) -- 7 (二进制: 111)
print("按位异或:", a ~ b) -- 6 (二进制: 110)
print("左移:", a << 1) -- 10 (二进制: 1010)
print("右移:", a >> 1) -- 2 (二进制: 010)
print("按位非:", ~a) -- -6 (二进制补码表示)
-- 位运算实用技巧
-- 1. 检查奇偶性
function is_odd(n)
return (n & 1) == 1
end
print("5是奇数吗?", is_odd(5)) -- true
print("4是奇数吗?", is_odd(4)) -- false
-- 2. 乘除2的幂次
local x = 10
print("乘以8:", x << 3) -- 80 (10 * 2^3)
print("除以4:", x >> 2) -- 2 (10 / 2^2)
-- 3. 交换两个数(不使用临时变量)
a, b = 5, 10
a = a ~ b
b = a ~ b
a = a ~ b
print("交换后:", a, b) -- 10, 5
-- 4. 判断是否为2的幂
function is_power_of_two(n)
return n > 0 and (n & (n - 1)) == 0
end
print("8是2的幂吗?", is_power_of_two(8)) -- true
print("7是2的幂吗?", is_power_of_two(7)) -- false
六、其他运算符
1. 长度运算符(用于表)
lua
-- 长度运算符#用于获取表的数组部分的长度
local arr = {1, 2, 3, 4, 5}
print("数组长度:", #arr) -- 5
-- 注意:只计算连续整数索引部分
local t = {1, 2, 3}
t[5] = 5
print("非连续数组长度:", #t) -- 3 (因为t[4]是nil)
-- 对于有空洞的数组,行为是未定义的
local sparse = {1, nil, 3}
print("稀疏数组长度:", #sparse) -- 可能是nil或1或3,取决于实现
-- 安全获取表长度的方法
function safe_length(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
print("安全长度:", safe_length(sparse)) -- 2
2. 索引运算符
lua
-- 表索引
local t = {name = "Lua", version = 5.4}
-- 点语法(字符串键)
print(t.name) -- Lua
print(t.version) -- 5.4
-- 方括号语法
print(t["name"]) -- Lua
-- 动态键名
local key = "version"
print(t[key]) -- 5.4
-- 方法调用(实际上是索引+调用)
local str = "hello"
print(str:upper()) -- HELLO (等价于 string.upper(str))
七、运算符优先级
Lua运算符的优先级从高到低如下:
| 优先级 | 运算符 | 描述 |
|---|---|---|
| 1 | ^ |
幂运算 |
| 2 | not # - (一元) |
逻辑非、长度、负号 |
| 3 | * / % |
乘、除、取模 |
| 4 | + - |
加、减 |
| 5 | .. |
字符串连接 |
| 6 | << >> |
位左移、右移 |
| 7 | & |
按位与 |
| 8 | ~ |
按位异或 |
| 9 | ` | ` |
| 10 | < > <= >= ~= == |
关系运算符 |
| 11 | and |
逻辑与 |
| 12 | or |
逻辑或 |
lua
-- 优先级示例
local result = 1 + 2 * 3 ^ 2
print(result) -- 19 (计算顺序: 3^2=9, 2*9=18, 1+18=19)
result = not true and false or true
print(result) -- true (计算顺序: not true=false, false and false=false, false or true=true)
-- 使用括号改变优先级
result = (1 + 2) * 3 ^ 2
print(result) -- 27 (计算顺序: 1+2=3, 3^2=9, 3*9=27)
-- 字符串连接的优先级
local str = "a" .. "b" == "ab"
print(str) -- true (注意: ..的优先级高于==)
-- 容易出错的优先级
local a, b, c = 1, 2, 3
local x = a + b * c -- 7 (不是9)
local y = (a + b) * c -- 9
print("x =", x, "y =", y)
八、特殊运算符和语法糖
1. 多重赋值运算符
lua
-- 多重赋值
local a, b, c = 1, 2, 3
print(a, b, c) -- 1, 2, 3
-- 变量数量多于值数量
local x, y, z = 10, 20
print(x, y, z) -- 10, 20, nil
-- 值数量多于变量数量
local m, n = 1, 2, 3, 4
print(m, n) -- 1, 2 (多余的值被忽略)
-- 交换变量值
a, b = b, a
print("交换后:", a, b) -- 2, 1
-- 函数返回多个值
function get_values()
return 10, 20, 30
end
local p, q, r = get_values()
print(p, q, r) -- 10, 20, 30
2. 复合赋值运算符
Lua不支持C语言风格的复合赋值运算符(如+=、-=等),但可以模拟:
lua
-- Lua没有 += 运算符
local count = 0
count = count + 1 -- 正确的写法
-- count += 1 -- 错误的写法,Lua不支持
-- 模拟复合赋值
function add_assign(t, k, v)
t[k] = (t[k] or 0) + v
end
local stats = {score = 100}
add_assign(stats, "score", 50)
print("分数:", stats.score) -- 150
-- 使用metatable模拟(高级技巧)
local mt = {
__add = function(t, v)
local result = {}
for k, val in pairs(t) do
result[k] = val + v
end
return result
end
}
local vec = {x = 1, y = 2}
setmetatable(vec, mt)
vec = vec + 3
print("向量:", vec.x, vec.y) -- 4, 5
九、运算符重载(通过元表)
Lua允许通过元表重载某些运算符:
lua
-- 定义向量类型
local Vector = {}
function Vector.new(x, y)
local v = {x = x or 0, y = y or 0}
setmetatable(v, Vector.mt)
return v
end
Vector.mt = {
-- 加法重载
__add = function(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end,
-- 减法重载
__sub = function(a, b)
return Vector.new(a.x - b.x, a.y - b.y)
end,
-- 乘法重载(标量乘法)
__mul = function(a, scalar)
if type(scalar) == "number" then
return Vector.new(a.x * scalar, a.y * scalar)
else
error("只能与标量相乘")
end
end,
-- 相等判断重载
__eq = function(a, b)
return a.x == b.x and a.y == b.y
end,
-- 字符串表示重载
__tostring = function(v)
return string.format("Vector(%f, %f)", v.x, v.y)
end,
-- 长度运算符重载
__len = function(v)
return math.sqrt(v.x * v.x + v.y * v.y)
end
}
Vector.__index = Vector
-- 使用重载的运算符
local v1 = Vector.new(1, 2)
local v2 = Vector.new(3, 4)
local v3 = v1 + v2
print("向量加法:", v3) -- Vector(4.000000, 6.000000)
local v4 = v2 - v1
print("向量减法:", v4) -- Vector(2.000000, 2.000000)
local v5 = v1 * 3
print("标量乘法:", v5) -- Vector(3.000000, 6.000000)
print("向量相等?", v1 == v2) -- false
print("向量长度:", #v1) -- 2.2360679774998 (√(1²+2²))
十、运算符使用的最佳实践
- 使用括号提高可读性:即使知道优先级,使用括号可以使意图更清晰
- 注意浮点数比较:永远不要直接比较浮点数是否相等
- 利用短路求值:简化条件判断和提供默认值
- 字符串连接性能:大量字符串连接时使用table.concat
- 理解nil的行为:nil在逻辑运算中的特殊行为
- 类型一致性:确保比较运算中的类型一致
- 位运算符限制:位运算符只适用于整数(Lua 5.3+)
- 表长度陷阱:#运算符只适用于连续数组部分
lua
-- 良好的实践示例
-- 1. 使用括号明确优先级
local result = (a + b) * (c - d) -- 比 a + b * c - d 更清晰
-- 2. 安全的浮点数比较
function float_equal(a, b, epsilon)
epsilon = epsilon or 1e-10
return math.abs(a - b) < epsilon
end
-- 3. 使用and/or提供默认值
local port = config.port or 8080
local host = config.host or "localhost"
-- 4. 高效字符串构建
function build_string(parts)
return table.concat(parts, ", ")
end
-- 5. 安全获取表长度
function table_length(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
-- 6. 类型检查
function safe_add(a, b)
if type(a) ~= "number" or type(b) ~= "number" then
return nil, "参数必须是数字"
end
return a + b
end
总结
Lua的运算符系统虽然简洁但功能强大,理解每个运算符的行为、优先级和特性对于编写正确、高效的Lua代码至关重要。特别注意Lua特有的行为,如逻辑运算符的短路求值、只有nil和false为假、浮点数比较的精度问题等。
通过元表,Lua还允许对运算符进行重载,这为创建自定义类型和实现运算符多态提供了可能。掌握这些知识可以帮助你更好地利用Lua语言的特性,编写出更优雅、更高效的代码。