在C/ c++(以及该家族的许多语言)中,根据条件声明和初始化变量的常用习语使用三元条件操作符:

int index = val > 0 ? val : -val

Go没有条件运算符。实现上面同一段代码的最惯用的方法是什么?我想出了下面的解决方案,但它似乎相当啰嗦

var index int

if val > 0 {
    index = val
} else {
    index = -val
}

还有更好的办法吗?


当前回答

埃尔德的回答既有趣又有创意,甚至可以说很聪明。

但是,建议改为:

var index int
if val > 0 {
    index = printPositiveAndReturn(val)
} else {
    index = slowlyReturn(-val)  // or slowlyNegate(val)
}

是的,它们都编译成本质上相同的程序集,但是这段代码比调用匿名函数只返回一个可以首先写入变量的值要清晰得多。

基本上,简单清晰的代码比有创意的代码更好。

此外,任何使用map文字的代码都不是一个好主意,因为在Go中映射根本不是轻量级的。自Go 1.3以来,小型地图的随机迭代顺序得到了保证,为了加强这一点,小型地图的内存效率大大降低。

As a result, making and removing numerous small maps is both space-consuming and time-consuming. I had a piece of code that used a small map (two or three keys, are likely, but common use case was only one entry) But the code was dog slow. We're talking at least 3 orders of magnitude slower than the same code rewritten to use a dual slice key[index]=>data[index] map. And likely was more. As some operations that were previously taking a couple of minutes to run, started completing in milliseconds.\

其他回答

如果你所有的分支都产生了副作用,或者计算成本很高,下面是一个语义保留的重构:

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

我从中学到的是多利用一点围棋;在函数中使用一个命名的结果(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 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

传入的函数必须返回一个接口{}以满足内部强制转换操作。 根据上下文,您可以选择将输出强制转换为特定类型。 如果你想从这个函数中返回一个函数,你需要用c来包装它。


这里的独立解决方案也很好,但对于某些用途可能不太清楚。

现在随着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在未来添加箭头函数) 操场上

相信这个答案,他已经回答了