在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
}
还有更好的办法吗?
当前回答
假设你有以下三元表达式(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 } }()
这确保了两个分支都不会被计算。
其他回答
现在随着go1.18泛型的发布,使用这样的泛型函数非常容易,而且它在整个应用程序中都是可重用的
package main
import (
"fmt"
)
func Ternary[T any](condition bool, If, Else T) T {
if condition {
return If
}
return Else
}
func main() {
fmt.Println(Ternary(1 < 2, "yes", "no")) // yes
fmt.Println(Ternary(1 < 2, 1, 0)) // 1
fmt.Println(Ternary[bool](1 < 2, true, false)) // true
}
注意,如果在这种情况下使用它,它会崩溃。 在这种情况下,只需使用if语句, (因为你传递给函数的是nil指针而不是if语句,如果它是假的就不会调用该section)
var a *string
fmt.Println(Ternary(a != nil, *a, "some thing else"))
解决方案使用函数调用它,因此如果它为false,它将不会被执行
func TernaryPointer[T any](condition bool, If, Else func() T) T {
if condition {
return If()
}
return Else()
}
var pString *string
fmt.Println(TernaryPointer(
pString != nil, // condition
func() string { return *pString }, // true
func() string { return "new data" }, // false
))
但在这种情况下,我认为常规的if语句更干净(除非go在未来添加箭头函数) 操场上
相信这个答案,他已经回答了
我已经编译了一些项目并比较了速度。
/*
go test ternary_op_test.go -v -bench="^BenchmarkTernaryOperator" -run=none -benchmem
*/
package _test
import (
"testing"
)
func BenchmarkTernaryOperatorIfElse(b *testing.B) {
for i := 0; i < b.N; i++ {
if i%2 == 0 {
_ = i
} else {
_ = -i
}
}
}
// https://stackoverflow.com/a/45886594/9935654
func Ternary(statement bool, a, b interface{}) interface{} {
if statement {
return a
}
return b
}
func BenchmarkTernaryOperatorTernaryFunc(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Ternary(i%2 == 0, i, -i).(int)
}
}
// https://stackoverflow.com/a/34636594/9935654
func BenchmarkTernaryOperatorWithFunc(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = func() int {
if i%2 == 0 {
return i
} else {
return -i
}
}
}
}
// https://stackoverflow.com/a/31483763/9935654
func BenchmarkTernaryOperatorMap(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = map[bool]int{true: i, false: -i}[i%2 == 0]
}
}
输出
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
BenchmarkTernaryOperatorIfElse
BenchmarkTernaryOperatorIfElse-8 1000000000 0.4460 ns/op 0 B/op 0 allocs/op
BenchmarkTernaryOperatorTernaryFunc
BenchmarkTernaryOperatorTernaryFunc-8 1000000000 0.3602 ns/op 0 B/op 0 allocs/op
BenchmarkTernaryOperatorWithFunc
BenchmarkTernaryOperatorWithFunc-8 659517496 1.642 ns/op 0 B/op 0 allocs/op
BenchmarkTernaryOperatorMap
BenchmarkTernaryOperatorMap-8 13429532 82.48 ns/op 0 B/op 0 allocs/op
PASS
ok command-line-arguments 4.365s
正如其他人所注意到的,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
我从中学到的是多利用一点围棋;在函数中使用一个命名的结果(result int)为我节省了在函数中声明它的一行(你可以用capture做同样的事情),但是编译器也识别这个习惯用法(只分配一个值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
假设你有以下三元表达式(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 } }()
这确保了两个分支都不会被计算。
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更好,需要施放但有效。仅供参考:
基准基准三元-8 100000000 18.8 ns/op
BenchmarkAbsIfElse-8 2000000000 0.27 ns/op