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

Fab*_*ien 248 ternary-operator go conditional-operator

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

int index = val > 0 ? val : -val
Run Code Online (Sandbox Code Playgroud)

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

var index int

if val > 0 {
    index = val
} else {
    index = -val
}
Run Code Online (Sandbox Code Playgroud)

还有更好的东西吗?

Gus*_*yer 211

正如所指出的那样(并且毫无疑问),使用if+else确实是在Go中进行条件限制的惯用方法.

但是,除了完整var+if+else的代码块之外,这种拼写还经常被使用:

index := val
if val <= 0 {
    index = -val
}
Run Code Online (Sandbox Code Playgroud)

如果你有足够重复的代码块,比如相当于int value = a <= b ? a : b,你可以创建一个函数来保存它:

func min(a, b int) int {
    if a <= b {
        return a
    }
    return b
}

...

value := min(a, b)
Run Code Online (Sandbox Code Playgroud)

编译器将内联这些简单的函数,因此它更快,更清晰,更短.

  • 嘿伙计们,看!我只是将_ternarity_运算符移植到golangs!http://play.golang.org/p/ZgLwC_DHm0._So_高效! (154认同)
  • `c:=(map [bool] int {true:a,false:a - 1})[a> b]`是混淆IMHO的一个例子,即使它有效. (41认同)
  • 如果`if/else`是惯用法,那么Golang可能会考虑让`if/else`子句返回一个值:`x = if {1} else {0}`.Go绝不是唯一以这种方式工作的语言.一个主流的例子是Scala.请参阅:http://alvinalexander.com/scala/scala-ternary-operator-syntax (24认同)
  • @tomwilde你的解决方案看起来很有趣,但它缺乏三元运算符的主要特征之一 - 条件评估. (21认同)
  • @VladimirMatveev将值包装在闭包中;) (12认同)
  • 地图解决方案似乎不能正常工作,因为它计算两个分支,而真正的`?:`运算符应该只计算适用于条件的分支. (3认同)
  • 我想你可以讨论一下if / else是否更清楚,但是它会更短吗? (2认同)
  • 是的,我也会说同样的话@MaxMurphy。这些golangers不知道他们错过了什么!当前“惯用方式”的缺点是您无法以这种方式定义常量 (2认同)
  • 它太长了,看起来很复杂。为简单起见,我使用三元运算符 (2认同)
  • 像这样的 golang 代码让我想起了这门语言是多么的好,以及它如何反映了我多年来编写 Nodejs 和 JavaScript 的方式。感谢您干净的回答! (2认同)
  • @thwd这些评论中没有意识到你明显有多滑稽的人绝对令人jaw目结舌……为皮特的缘故,您甚至称它为“ golangs”!我为您的创意和意想不到的意大利面表示赞赏。 (2认同)
  • 否决,因为该解决方案不等同于 C 三元。此处提供的代码块_不能_用作表达式。立即评估的闭包确实如此。 (2认同)

ish*_*aaq 63

No Go没有三元运算符,使用if/else语法惯用的方式:http://golang.org/doc/faq#Does_Go_have_a_ternary_form

  • 那么仅仅因为语言设计者所看到的,他们就省略了整个“if-else”块的一行行吗?谁说“if-else”不会以类似的方式被滥用?我不是攻击你,只是觉得设计师的借口不够成立 (45认同)
  • “一种语言只需要一个条件控制流结构。” - 当然可以,但为什么不让 `if` 成为一个表达式并允许像 Rust 或 Scala 那样的 `if cond { value1 } else { value3 } 之类的结构呢? (11认同)
  • 我同意。丑陋的三元数是一个编码问题,而不是一个语言问题。三元数在各种语言中都很常见,因此它们是正常的,没有它们是令人惊讶的,如果你问我的话,这违反了 POLA/PLA。 (8认同)
  • 但从语言设计者的角度来思考;他们需要使用语言中其他任何地方未使用的额外语法来扩展语言规范、解析器、编译器等,以获得潜在的可读性枪炮的语法糖。Go 是为阅读而设计的,虽然大多数 C 开发人员可能对三元数足够熟悉,能够足够快地阅读它们,但这并不是一个普遍的真理,当人们开始嵌套它们时,事情就会变得非常糟糕。“另一种语言有它”不是添加语言功能的有效论据。 (4认同)
  • @cthulhu如果这是他们关心的问题,混乱的条件...我想知道他们是否至少可以只允许该三元作为一个操作进行操作,即。只是返回第二个参数的值,但不执行它(不要进一步递归到下一个操作树)...即: x = a ?: b // 如果 a 为 false,则使用 b ...只会返回 a 或 b,但不会进一步评估它们...但我不确定这是否会破坏解析规则。我不认为操作符令人困惑,并且通常只有这个意图,我认为它本身应该具有足够的可读性。 (2认同)
  • 该语言设计者的解释看起来很奇怪,因为它与另一个语言功能相矛盾:if 包含 2 个用分号分隔的语句(请参阅 https://tour.golang.org/flowcontrol/6)。我怀疑第二个是否能让代码清晰。如果只有一个“?”的限制,他们可以实现三元。每条声明。 (2认同)

Pet*_*yer 51

假设您有以下三元表达式(在C中):

int a = test ? 1 : 2;
Run Code Online (Sandbox Code Playgroud)

Go中惯用的方法是简单地使用一个if块:

var a int

if test {
  a = 1
} else {
  a = 2
}
Run Code Online (Sandbox Code Playgroud)

但是,这可能不符合您的要求.就我而言,我需要一个代码生成模板的内联表达式.

我使用了一个立即评估的匿名函数:

a := func() int { if test { return 1 } else { return 2 } }()
Run Code Online (Sandbox Code Playgroud)

这可确保不对两个分支进行评估.

  • 为什么要加上“其他”呢?`a := func() int { if test { return 1 } return 2 }()` 应该有效还是我错了? (9认同)
  • 在这种情况下,“简单”看起来更像是“复杂” (4认同)
  • C 条件表达式(通常称为三元运算符)具有三个操作数:`expr1 ? expr2 : expr3`。如果 `expr1` 的计算结果为 `true`,则计算 `expr2` 并且是表达式的结果。否则,将评估“expr3”并作为结果提供。这是来自 K&amp;R 的 ANSI C 编程语言第 2.11 节。我的 Go 解决方案保留了这些特定的语义。@Wolf 你能澄清一下你的建议吗? (2认同)

小智 34

地图三元很容易阅读,没有括号:

c := map[bool]int{true: 1, false: 0} [5 > 4]
Run Code Online (Sandbox Code Playgroud)

  • 是的,它有效,是类型安全的,甚至是创造性的; 但是,还有其他指标.三元运算符与if/else相同(参见例如[this S/O post](http://stackoverflow.com/questions/3565368/ternary-operator-vs-if-else)).这个响应不是因为1)两个分支都被执行,2)创建一个map 3)调用一个哈希.所有这些都是"快速",但不如if/else快.另外,如果条件{r = foo()} else {r = bar()},我认为它不比var r T更可读 (24认同)
  • 正如[Cassy Foesch指出:](http://stackoverflow.com/a/37199664/2932052)*`简单明了的代码比创意代码更好.* (8认同)
  • @Wolf你能用更简单的方式写 `fmt.Println("Operation %s; reverting to normal form.", (map[bool]string{true: "skipped", false: "failed"})[opkip])`方式? (3认同)
  • 不完全确定为什么它有 -2 ...是的,这是一种解决方法,但它有效并且类型安全。 (2认同)

icz*_*cza 13

前言:无需争论这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
}
Run Code Online (Sandbox Code Playgroud)

我们如何使用它?

i := If(condition).Int(val1, val2)  // Short variable declaration, i is of type int
     |-----------|  \
   type conversion   \---method call
Run Code Online (Sandbox Code Playgroud)

例如一个三元做max()

i := If(a > b).Int(a, b)
Run Code Online (Sandbox Code Playgroud)

三元做abs()

i := If(a >= 0).Int(a, -a)
Run Code Online (Sandbox Code Playgroud)

这看起来很酷,它简单、优雅且高效(它也适用于内联)。

与“真正的”三元运算符相比的一个缺点:它总是评估所有操作数。

为了实现延迟和仅当需要的评估,唯一的选择是使用函数(声明的函数或方法,或函数文字),它们仅在/如果需要时调用:

func (c If) Fint(fa, fb func() int) int {
    if c {
        return fa()
    }
    return fb()
}
Run Code Online (Sandbox Code Playgroud)

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

func calca() int { return 3 }
func calcb() int { return 4 }
Run Code Online (Sandbox Code Playgroud)

然后:

i := If(someCondition).Fint(calca, calcb)
Run Code Online (Sandbox Code Playgroud)

例如,条件是当前年份 > 2020:

i := If(time.Now().Year() > 2020).Fint(calca, calcb)
Run Code Online (Sandbox Code Playgroud)

如果我们想使用函数字面量:

i := If(time.Now().Year() > 2020).Fint(
    func() int { return 3 },
    func() int { return 4 },
)
Run Code Online (Sandbox Code Playgroud)

最后一点:如果你有不同签名的函数,你不能在这里使用它们。在这种情况下,您可以使用具有匹配签名的函数文字来使它们仍然适用。

例如 ifcalca()calcb()也会有参数(除了返回值):

func calca2(x int) int { return 3 }
func calcb2(x int) int { return 4 }
Run Code Online (Sandbox Code Playgroud)

这是您可以使用它们的方式:

i := If(time.Now().Year() > 2020).Fint(
    func() int { return calca2(0) },
    func() int { return calcb2(0) },
)
Run Code Online (Sandbox Code Playgroud)

Go Playground上试试这些例子。

  • 请注意,通用的“If”不适用于带取消引用的 nil 检查,例如“If(p != nil, *p, defaultValue)”,因为参数(包括“*p”)会立即求值。 (2认同)

Phi*_*iny 10

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)
}
Run Code Online (Sandbox Code Playgroud)

这不会超过if/else并且需要强制转换但有效.供参考:

BenchmarkAbsTernary-8 100000000 18.8 ns/op

BenchmarkAbsIfElse-8 2000000000 0.27 ns/op

  • 我认为这不能处理条件评估,不是吗?对于无副作用分支,这并不重要(就像在您的示例中一样),但如果它有副作用,您就会遇到问题。 (3认同)

kfs*_*one 8

正如其他人所指出的,golang 没有三元运算符或任何等效运算符。这是为了提高可读性而经过深思熟虑的决定。

\n

这最近让我遇到了一个场景,以非常有效的方式构建位掩码在惯用方式编写时变得难以阅读,或者在封装为函数时变得非常低效,或者两者兼而有之,因为代码会产生分支:

\n
package lib\n\nfunc maskIfTrue(mask uint64, predicate bool) uint64 {\n  if predicate {\n    return mask\n  }\n  return 0\n}\n
Run Code Online (Sandbox Code Playgroud)\n

生产:

\n
        text    "".maskIfTrue(SB), NOSPLIT|ABIInternal, $0-24\n        funcdata        $0, gclocals\xc2\xb733cdeccccebe80329f1fdbee7f5874cb(SB)\n        funcdata        $1, gclocals\xc2\xb733cdeccccebe80329f1fdbee7f5874cb(SB)\n        movblzx "".predicate+16(SP), AX\n        testb   AL, AL\n        jeq     maskIfTrue_pc20\n        movq    "".mask+8(SP), AX\n        movq    AX, "".~r2+24(SP)\n        ret\nmaskIfTrue_pc20:\n        movq    $0, "".~r2+24(SP)\n        ret\n
Run Code Online (Sandbox Code Playgroud)\n

我从中学到的是要更多地利用 Go;在函数中使用命名结果可以节省我(result int)函数中声明它的一行(并且您可以对捕获执行相同的操作),但是编译器也可以识别这个习惯用法(仅分配一个值 IF)并替换它(如果可能)条件指令。

\n
func zeroOrOne(predicate bool) (result int) {\n  if predicate {\n    result = 1\n  }\n  return\n}\n
Run Code Online (Sandbox Code Playgroud)\n

产生无分支结果:

\n
    movblzx "".predicate+8(SP), AX\n    movq    AX, "".result+16(SP)\n    ret\n
Run Code Online (Sandbox Code Playgroud)\n

然后自由内联。

\n
package lib\n\nfunc zeroOrOne(predicate bool) (result int) {\n  if predicate {\n    result = 1\n  }\n  return\n}\n\ntype Vendor1 struct {\n    Property1 int\n    Property2 float32\n    Property3 bool\n}\n\n// Vendor2 bit positions.\nconst (\n    Property1Bit = 2\n    Property2Bit = 3\n    Property3Bit = 5\n)\n\nfunc Convert1To2(v1 Vendor1) (result int) {\n    result |= zeroOrOne(v1.Property1 == 1) << Property1Bit\n    result |= zeroOrOne(v1.Property2 < 0.0) << Property2Bit\n    result |= zeroOrOne(v1.Property3) << Property3Bit\n    return\n}\n
Run Code Online (Sandbox Code Playgroud)\n

产生https://go.godbolt.org/z/eKbK17

\n
    movq    "".v1+8(SP), AX\n    cmpq    AX, $1\n    seteq   AL\n    xorps   X0, X0\n    movss   "".v1+16(SP), X1\n    ucomiss X1, X0\n    sethi   CL\n    movblzx AL, AX\n    shlq    $2, AX\n    movblzx CL, CX\n    shlq    $3, CX\n    orq     CX, AX\n    movblzx "".v1+20(SP), CX\n    shlq    $5, CX\n    orq     AX, CX\n    movq    CX, "".result+24(SP)\n    ret\n
Run Code Online (Sandbox Code Playgroud)\n


eol*_*old 5

如果您的所有分支都产生副作用或在计算上昂贵,则以下内容将在语义上进行重构:

index := func() int {
    if val > 0 {
        return printPositiveAndReturn(val)
    } else {
        return slowlyReturn(-val)  // or slowlyNegate(val)
    }
}();  # exactly one branch will be evaluated
Run Code Online (Sandbox Code Playgroud)

通常没有开销(内联),最重要的是,不会使用仅使用一次的辅助函数来使名称空间混乱(这会影响可读性和维护性)。现场例子

请注意,如果您要天真地应用Gustavo的方法

    index := printPositiveAndReturn(val);
    if val <= 0 {
        index = slowlyReturn(-val);  // or slowlyNegate(val)
    }
Run Code Online (Sandbox Code Playgroud)

你会得到一个行为不同的程序; 万一val <= 0程序会打印一个非正值而它不应该打印!(类似地,如果反转分支,则会通过不必要地调用慢函数来引入开销。)


nob*_*bar 5

俏皮话虽然被创作者们回避,但也有其一席之地。

这个解决了惰性求值问题,让您可以选择传递要在必要时求值的函数:

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)())
}
Run Code Online (Sandbox Code Playgroud)

输出

hello
hello world
hello world
hello
bye
Run Code Online (Sandbox Code Playgroud)
  • 传入的函数必须返回 aninterface{}以满足内部强制转换操作。
  • 根据上下文,您可以选择将输出转换为特定类型。
  • 如果您想从中返回一个函数,则需要将其包装起来,如 所示c

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


Isa*_*ten 5

现在随着 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
}

Run Code Online (Sandbox Code Playgroud)

请注意,如果在这种情况下使用它,它会崩溃。在这种情况下,只需使用 if 语句(因为您向函数传递一个 nil 指针,而 if 语句如果为 false,则不会调用该部分)


var a *string
fmt.Println(Ternary(a != nil, *a, "some thing else"))
Run Code Online (Sandbox Code Playgroud)

该解决方案使用函数调用它,因此如果为 false 则不会执行

func TernaryPointer[T any](condition bool, If, Else func() T) T {
    if condition {
        return If()
    }
    return Else()
}
Run Code Online (Sandbox Code Playgroud)
var pString *string
fmt.Println(TernaryPointer(
    pString != nil, // condition 
    func() string { return *pString }, // true
    func() string { return "new data" }, // false
))

Run Code Online (Sandbox Code Playgroud)

但在这种情况下,我认为常规的 if 语句更干净(除非 go 在将来添加箭头函数)

给予这个答案他已经回答过的信任