三元运算符 |
|
有时,将 if-then-else 条件语句用作表达式会更可取。考虑以下代码
if x < 0 then print('x is negative') else print('x is non-negative') end
从风格上来说,应该避免像 print('x is ' ...)
这样的重复([DRY]),特别是如果这段重复的代码实际上是更复杂的东西。解决这个问题的一种方法是使用变量
local sign if x < 0 then sign = 'negative' else sign = 'non-negative' end print('x is ' .. sign)
但现在我们引入了新的风格问题:虽然值的命名(sign
)对于文档目的很有用,但这个名称在四个地方重复出现,它的作用域不必要地扩展到了 print
语句之外,而且代码的长度和复杂性可以说增加了。我们真正想做的可能是将 if-then-else 融入表达式,就像这样
local sign = if x < 0 then 'negative' else 'non-negative' end print('x is ' .. sign) -- or just this... print('x is ' .. if x < 0 then 'negative' else 'non-negative' end)
其中 if a then b else c end
形式将是一个表达式,当 a
为真时计算结果为 b
,否则计算结果为 c
。然而,Lua 中不支持这种语法。一些语言直接支持这种结构:它被称为条件三元运算符 [1]。它被称为“三元”是因为它有三个操作数:a
、b
和 c
。例如,在 C 语言中,三元运算符写成
sign = (x < 0) ? "negative" : "non-negative";
三元运算也可以像“elseif
”子句一样进行链式操作
x = (a < amin) ? amin : (a > amax) ? amax : a;
这里,三元运算符具有右结合性,这意味着括号是根据第一行(而不是第二行)隐含的
x = (a < amin) ? amin : ((a > amax) ? amax : a); x = ((a < amin) ? amin : (a > amax)) ? amax : a;
这类似于以下 Lua if-then-else 语句是等价的
if a < amin then x = amin elseif a > amax then x = amax else x = a end
if a < amin then x = amin else if a > amax then x = amax else x = a end end
尽管 Lua 明确地缺少三元运算符,但有一些方法可以近似地实现它,如下所述。
一种经常使用且强烈推荐的解决方案是将 and
和 or
二元运算符组合起来,以近似地实现三元运算符
x = a and b or c x = a and b or c and d or e
有关这些二元运算符的特殊属性(使它们能够以这种方式工作)的详细信息,请参阅 ProgrammingInLua ? 或 ExpressionsTutorial。
print('x is ' .. (x < 0 and 'negative' or 'non-negative')) -- this works!
主要问题是,如果 a
或 c
计算结果为真,而 b
或 d
分别计算结果为假,那么这个表达式将不会完全像三元运算符那样运行。这里,“计算结果为假”意味着该值是 false
或 nil
,“计算结果为真”意味着不计算结果为假。在上面的第一行中,a and b or c
被解释为 (a and b) or c
(因为 and
的优先级高于 or
),如果 a
计算结果为真,那么表达式将变为 b or c
,如果 b
计算结果为假,那么表达式将变为 c
(而不是你可能想要的 b
)。
通常,就像我们最初的例子一样,三元运算符的第二个操作数永远不会计算结果为假,所以你可以随意使用这种习惯用法,但要注意这个问题。如果 b
将计算结果为假,请更改 a
,使其计算结果正好相反,从而交换 b
和 c
print((x < 0 and false or true)) -- this fails!
print((x >= 0 and true or false)) -- this works!
您可以通过匿名函数(或闭包)在表达式中插入任意语句,这包括 if-then-else 语句。
print('x is ' .. (function() if x < 0 then return 'negative' else return 'non-negative' end end)())
主要缺点是这会在每次执行时创建一个匿名闭包,这可能会降低紧密循环的性能。此外,Lua 中的匿名函数语法有点冗长(如 ShortAnonymousFunctions 中所述)。
另请参阅 [ExpressionsAsStatements]。
也可以将 if
写成函数
function fif(condition, if_true, if_false) if condition then return if_true else return if_false end end print( fif(condition, a, b) )
但这没有短路优势,除非条件表示为匿名闭包以进行延迟评估。
function fif(condition, if_true, if_false) if condition then return if_true() else return if_false() end end local x = fif(condition, function() return a end, function() return b end) print(x) --> false
为了避免上述 nil 的问题,您可以将这些值“装箱”到某个永远不会为 nil 的表达式中。不幸的是,装箱会带来开销。
local condition, a, b = true, false, true local x = (condition and {a} or {b})[1] print(x) --> false
以下是一个类似的解决方案,但使用函数
local False = {} local Nil = {} local function bwrap(o) return o == nil and Nil or o == false and False or o end local function bunwrap(o) if o == Nil then return nil elseif o == False then return false else return o end end local x = bunwrap(condition and bwrap(a) or b) print(x) --> false
以下是一种有趣的(很少使用)堆栈式方法,堆栈大小为 1
local save, restore do local o_saved save = function(o) o_saved = o; return true end restore = function() return o_saved end end local x = (condition and save(a) or save(b)) and restore() print(x) --> false
以下是扩展 Lua 语法以更直接地支持三元运算符的一些建议。
也许最 Lua 式的语法扩展,不引入任何新关键字,并尽可能保留当前的条件语法,类似于以下这些
x = if a then b elseif c then d else e end x = (if a then b elseif c then d else e end) x = (a then b elseif c then d else e)
end
的评论
x = (a then b else c)
的论据,需要括号
有些人建议使用以下语法
x = a then b else c x = a then b or c
但它们在条件语句中使用时会导致歧义
if a() then b() else c() then d() end
正如 John Backus 多年前指出的那样,将条件作为条件三元运算符的中间参数具有语法上的优势
x = a when a < b else b
如果 c
是第一个为真的条件,则 x
的值为 a
。如果 when
被定义为 and
但参数互换(因此其第一个参数为惰性求值),则可以使用当前的 Lua 语法来实现这一点。不幸的是,这不像最初看起来那样容易实现,因为它意味着将表达式的求值推迟到后续表达式被求值之后。这就是 Python 新的条件表达式所做的事情 [2]。--匿名
另请参阅 LuaList:2006-09/msg00608.html 中的类似评论。
LuaMetaLua 包含一个示例 ([ifexpr.mlua]) 用于添加此语法。
local foo = if bar then 1 else 2
--DavidManura 等人。
- http://www.lualearners.org/tutorial?tut=74