在 C/C++(以及该系列的许多语言)中,根据条件声明和初始化变量的常用习惯用法使用三元条件运算符:
int index = val > 0 ? val : -val
Go 没有条件运算符。实现与上述相同的代码的最惯用方式是什么?我来到以下解决方案,但它似乎很冗长
var index int
if val > 0 {
index = val
} else {
index = -val
}
有更好的吗?
在 C/C++(以及该系列的许多语言)中,根据条件声明和初始化变量的常用习惯用法使用三元条件运算符:
int index = val > 0 ? val : -val
Go 没有条件运算符。实现与上述相同的代码的最惯用方式是什么?我来到以下解决方案,但它似乎很冗长
var index int
if val > 0 {
index = val
} else {
index = -val
}
有更好的吗?
正如所指出的(希望不出所料),使用if+else
确实是在 Go 中执行条件的惯用方式。
但是,除了完整var+if+else
的代码块之外,这种拼写也经常使用:
index := val
if val <= 0 {
index = -val
}
如果您有一段足够重复的代码,例如 的等价物int value = a <= b ? a : b
,您可以创建一个函数来保存它:
func min(a, b int) int {
if a <= b {
return a
}
return b
}
...
value := min(a, b)
编译器会内联这些简单的函数,因此它更快、更清晰、更短。
No Go 没有三元运算符,使用 if/else 语法是惯用的方式。
为什么 Go 没有 ?: 运算符?
Go 中没有三元测试操作。您可以使用以下方法来实现相同的结果:
if expr { n = trueVal } else { n = falseVal }
Go 没有出现的原因
?:
是该语言的设计者已经看到该操作过于频繁地用于创建难以理解的复杂表达式。表格虽然更长,if-else
但无疑更清晰。一种语言只需要一个条件控制流结构。— 常见问题 (FAQ) - Go 编程语言
假设您有以下三元表达式(在 C 中):
int a = test ? 1 : 2;
Go 中的惯用方法是简单地使用一个if
块:
var a int
if test {
a = 1
} else {
a = 2
}
但是,这可能不符合您的要求。就我而言,我需要一个用于代码生成模板的内联表达式。
我使用了一个立即评估的匿名函数:
a := func() int { if test { return 1 } else { return 2 } }()
这确保了两个分支都不会被评估。
地图三元不带括号很容易阅读:
c := map[bool]int{true: 1, false: 0} [5 > 4]
func Ternary(statement bool, a, b interface{}) interface{} {
if statement {
return a
}
return b
}
func Abs(n int) int {
return Ternary(n >= 0, n, -n).(int)
}
这不会优于 if/else 并且需要强制转换但有效。供参考:
BenchmarkAbsTernary-8 100000000 18.8 ns/op
BenchmarkAbsIfElse-8 2000000000 0.27 ns/op
前言:不用争论这if else
是要走的路,我们仍然可以在支持语言的结构中玩耍并找到乐趣。
我的库中提供了以下If
构造github.com/icza/gox
以及许多其他方法,即gox.If
类型。
Go 允许将方法附加到任何用户定义的类型,包括原始类型,例如bool
. 我们可以创建一个自定义类型,bool
将其作为其基础类型,然后在条件上进行简单的类型转换,我们就可以访问它的方法。接收和选择操作数的方法。
像这样的东西:
type If bool
func (c If) Int(a, b int) int {
if c {
return a
}
return b
}
我们如何使用它?
i := If(condition).Int(val1, val2) // Short variable declaration, i is of type int
|-----------| \
type conversion \---method call
例如三元做max()
:
i := If(a > b).Int(a, b)
三元做abs()
:
i := If(a >= 0).Int(a, -a)
这看起来很酷,它简单、优雅、高效(它也符合 inlining的条件)。
与“真正的”三元运算符相比的一个缺点:它总是计算所有操作数。
为了实现延迟和仅在需要时评估,唯一的选择是使用函数(声明的函数或方法,或函数文字),它们仅在需要时/如果需要时调用:
func (c If) Fint(fa, fb func() int) int {
if c {
return fa()
}
return fb()
}
使用它:假设我们有这些函数来计算a
和b
:
func calca() int { return 3 }
func calcb() int { return 4 }
然后:
i := If(someCondition).Fint(calca, calcb)
例如,条件是当前年份 > 2020:
i := If(time.Now().Year() > 2020).Fint(calca, calcb)
如果我们想使用函数字面量:
i := If(time.Now().Year() > 2020).Fint(
func() int { return 3 },
func() int { return 4 },
)
最后一点:如果你有不同签名的函数,你不能在这里使用它们。在这种情况下,您可以使用具有匹配签名的函数文字使它们仍然适用。
例如 ifcalca()
和calcb()
will 也有参数(除了返回值):
func calca2(x int) int { return 3 }
func calcb2(x int) int { return 4 }
这是您可以使用它们的方式:
i := If(time.Now().Year() > 2020).Fint(
func() int { return calca2(0) },
func() int { return calcb2(0) },
)
在Go Playground上尝试这些示例。
如果您的所有分支都会产生副作用或计算量很大,则以下将是语义保留的重构:
index := func() int {
if val > 0 {
return printPositiveAndReturn(val)
} else {
return slowlyReturn(-val) // or slowlyNegate(val)
}
}(); # exactly one branch will be evaluated
通常没有开销(内联),最重要的是,不会使用仅使用一次的辅助函数(这会妨碍可读性和维护)使您的命名空间变得混乱。现场示例
请注意,如果您天真地应用Gustavo 的方法:
index := printPositiveAndReturn(val);
if val <= 0 {
index = slowlyReturn(-val); // or slowlyNegate(val)
}
你会得到一个行为不同的程序;万一val <= 0
程序会打印一个非正值而它不应该!(类似地,如果你反转分支,你会通过不必要地调用慢速函数来引入开销。)
正如其他人所指出的,golang 没有三元运算符或任何等价物。这是一个经过深思熟虑的决定,旨在提高可读性。
这最近导致我遇到这样一种情况,即以非常有效的方式构造位掩码在惯用方式编写时变得难以阅读,或者在封装为函数时效率非常低,或两者兼而有之,因为代码会产生分支:
package lib
func maskIfTrue(mask uint64, predicate bool) uint64 {
if predicate {
return mask
}
return 0
}
生产:
text "".maskIfTrue(SB), NOSPLIT|ABIInternal, $0-24
funcdata $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
funcdata $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
movblzx "".predicate+16(SP), AX
testb AL, AL
jeq maskIfTrue_pc20
movq "".mask+8(SP), AX
movq AX, "".~r2+24(SP)
ret
maskIfTrue_pc20:
movq $0, "".~r2+24(SP)
ret
我从中学到的是更多地利用 Go;在函数中使用命名结果为(result int)
我节省了在函数中声明它的一行(您可以对捕获执行相同的操作),但编译器也识别此习惯用法(仅分配一个值 IF)并将其替换 - 如果可能的话 - 用有条件的指令。
func zeroOrOne(predicate bool) (result int) {
if predicate {
result = 1
}
return
}
产生无分支结果:
movblzx "".predicate+8(SP), AX
movq AX, "".result+16(SP)
ret
然后自由内联。
package lib
func zeroOrOne(predicate bool) (result int) {
if predicate {
result = 1
}
return
}
type Vendor1 struct {
Property1 int
Property2 float32
Property3 bool
}
// Vendor2 bit positions.
const (
Property1Bit = 2
Property2Bit = 3
Property3Bit = 5
)
func Convert1To2(v1 Vendor1) (result int) {
result |= zeroOrOne(v1.Property1 == 1) << Property1Bit
result |= zeroOrOne(v1.Property2 < 0.0) << Property2Bit
result |= zeroOrOne(v1.Property3) << Property3Bit
return
}
产生https://go.godbolt.org/z/eKbK17
movq "".v1+8(SP), AX
cmpq AX, $1
seteq AL
xorps X0, X0
movss "".v1+16(SP), X1
ucomiss X1, X0
sethi CL
movblzx AL, AX
shlq $2, AX
movblzx CL, CX
shlq $3, CX
orq CX, AX
movblzx "".v1+20(SP), CX
shlq $5, CX
orq AX, CX
movq CX, "".result+24(SP)
ret
One-liners,虽然被创作者回避,但仍有一席之地。
这个解决了惰性求值问题,让您可以选择在必要时传递要求值的函数:
func FullTernary(e bool, a, b interface{}) interface{} {
if e {
if reflect.TypeOf(a).Kind() == reflect.Func {
return a.(func() interface{})()
}
return a
}
if reflect.TypeOf(b).Kind() == reflect.Func {
return b.(func() interface{})()
}
return b
}
func demo() {
a := "hello"
b := func() interface{} { return a + " world" }
c := func() interface{} { return func() string { return "bye" } }
fmt.Println(FullTernary(true, a, b).(string)) // cast shown, but not required
fmt.Println(FullTernary(false, a, b))
fmt.Println(FullTernary(true, b, a))
fmt.Println(FullTernary(false, b, a))
fmt.Println(FullTernary(true, c, nil).(func() string)())
}
输出
hello
hello world
hello world
hello
bye
interface{}
以满足内部强制转换操作。c
.这里的独立解决方案也不错,但对于某些用途可能不太清楚。
eold 的回答很有趣,很有创意,甚至可能很聪明。
但是,建议改为:
var index int
if val > 0 {
index = printPositiveAndReturn(val)
} else {
index = slowlyReturn(-val) // or slowlyNegate(val)
}
是的,它们都编译成本质上相同的程序集,但是这段代码比调用匿名函数更易读,只是为了返回一个本来可以写入变量的值。
基本上,简单明了的代码比创意代码要好。
此外,任何使用地图文字的代码都不是一个好主意,因为地图在 Go 中根本不是轻量级的。从 Go 1.3 开始,保证了小地图的随机迭代顺序,为了强制执行这一点,小地图的内存效率大大降低。
因此,制作和删除大量小地图既费空间又费时。我有一段代码使用了一个小地图(可能有两个或三个键,但常见的用例只有一个条目)但是代码很慢。我们所说的至少比使用双切片 key[index]=>data[index] 映射重写的相同代码慢 3 个数量级。而且可能更多。由于以前需要几分钟才能运行的某些操作开始在几毫秒内完成。\
对三元运算符 Go 中惯用方法的另一项建议:
package main
import (
"fmt"
)
func main() {
val := -5
index := func (test bool, n, d int) int {
if test {
return n
}
return d
}(val > 0, val, -val)
fmt.Println(index)
}