在C / C ++(以及该系列的许多语言)中,根据条件声明和初始化变量的常用习惯用法是使用三元条件运算符:
int index = val > 0 ? val : -val
Go没有条件运算符。实现上述相同代码的最惯用方法是什么?我来到了以下解决方案,但它看起来相当冗长
var index int
if val > 0 {
index = val
} else {
index = -val
}
还有更好的东西吗?
答案 0 :(得分:180)
正如所指出的那样(并且毫无疑问),使用if+else
确实是idiomatic way在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)
编译器将内联这些简单的函数,因此它更快,更清晰,更短。
答案 1 :(得分:55)
No Go没有三元运算符,使用if / else语法是惯用法:http://golang.org/doc/faq#Does_Go_have_a_ternary_form
答案 2 :(得分:45)
假设您有以下三元表达式(在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 } }()
这可确保不对两个分支进行评估。
答案 3 :(得分:28)
在没有括号的情况下,地图三元素很容易阅读:
c := map[bool]int{true: 1, false: 0} [5 > 4]
答案 4 :(得分:7)
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
答案 5 :(得分:4)
如果所有您的分支产生副作用或计算成本高昂,则以下语义保留重构:
index := func() int {
if val > 0 {
return printPositiveAndReturn(val)
} else {
return slowlyReturn(-val) // or slowlyNegate(val)
}
}(); # exactly one branch will be evaluated
通常没有开销(内联),最重要的是,没有使用仅使用一次的辅助函数来混乱您的命名空间(这会妨碍可读性和维护)。 Live Example
请注意,如果你天真地申请Gustavo's approach:
index := printPositiveAndReturn(val);
if val <= 0 {
index = slowlyReturn(-val); // or slowlyNegate(val)
}
你会得到一个带有不同行为的程序;如果val <= 0
程序打印出非正值而不应该打印! (类似地,如果你颠倒了分支,你会通过不必要地调用慢速函数来引入开销。)
答案 6 :(得分:3)
但是,建议改为:
var index int
if val > 0 {
index = printPositiveAndReturn(val)
} else {
index = slowlyReturn(-val) // or slowlyNegate(val)
}
是的,它们都编译成基本相同的程序集,但是这个代码比调用匿名函数更简单,只是为了返回一个本来可以写入变量的值。
基本上,简单明了的代码比创意代码更好。
此外,任何使用地图文字的代码都不是一个好主意,因为Go中的地图根本不是轻量级的。从Go 1.3开始,小地图的随机迭代顺序得到了保证,为了实现这一点,对于小地图来说,它在内存方面的效率要低得多。
因此,制作和删除大量小地图既耗费时间又耗时。我有一段代码使用了一个小地图(两个或三个键,很可能,但常见的用例只有一个条目)但代码是狗慢。我们说的是比使用双切片密钥[index] =&gt; data [index] map重写的相同代码慢至少3个数量级。而且可能更多。由于一些先前需要几分钟才能运行的操作,在几毫秒内开始完成。\
答案 7 :(得分:3)
单线,尽管被创建者所避开,但它们有自己的位置。
这一解决了懒惰的评估问题,方法是让您(必要时)传递要评估的函数:
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
所示那样包装它。独立解决方案here也不错,但是对于某些用途可能不太清楚。
答案 8 :(得分:2)
正如其他人指出的那样,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
答案 9 :(得分:0)
前言:无需争论if else
是可行的方法,我们仍然可以在启用语言的结构中玩耍并找到乐趣。
我的github.com/icza/gox
库中提供了以下If
构造,还有许多其他方法,例如gox.If
类型。
Go允许将方法附加到任何user-defined types上,包括基本类型,例如bool
。我们可以创建一个以bool
作为其underlying type的自定义类型,然后在条件上使用简单类型conversion的情况下,我们可以访问其方法。接收和选择操作数的方法。
类似这样的东西:
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)
这看起来很酷,它简单,优雅且高效(它也是eligible for inlining)。
与“实际”三元运算符相比,它有一个缺点:它始终会评估所有操作数。
要获得延迟且仅在需要时进行评估,唯一的选择是使用函数(declared functions,方法或function literals),这些函数仅在/必要时才调用:>
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 },
)
最后说明:如果您要使用带有不同签名的功能,则不能在此处使用它们。在这种情况下,您可以使用带有匹配签名的函数文字使它们仍然适用。
例如,如果calca()
和calcb()
也会有参数(除了返回值):
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上尝试这些示例。