什么是C的三元运算符的惯用Go?

时间:2013-11-14 13:39:09

标签: go ternary-operator conditional-operator

在C / C ++(以及该系列的许多语言)中,根据条件声明和初始化变量的常用习惯用法是使用三元条件运算符:

int index = val > 0 ? val : -val

Go没有条件运算符。实现上述相同代码的最惯用方法是什么?我来到了以下解决方案,但它看起来相当冗长

var index int

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

还有更好的东西吗?

10 个答案:

答案 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)

eold的答案很有趣,很有创意,甚至可能很聪明。

但是,建议改为:

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()
}

使用它:假设我们有这些函数来计算ab

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上尝试这些示例。