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

int index = val > 0 ? val : -val


转到没有条件运算符。实现与上述相同代码的最惯用方式是什么?我来到以下解决方案,但似乎很冗长

var index int

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


还有更好的方法吗?

评论

您可以使用else部分初始化值,然后仅检查条件是否发生变化,但不确定是否更好

无论如何,应该消除很多if / thens。从35年前我编写第一个BASIC程序开始,我们就一直这样做。您的示例可能是:int index = -val + 2 * val *(val> 0);

@hyc您的示例远不如go的惯用代码可读,甚至不如使用三元运算符的C版本可读。无论如何,AFAIK不可能在Go中实现此解决方案,因为布尔值不能用作数值。

想知道为什么go没有提供这样的运算符?

@Fabien的答案中除最后几句话外的所有内容都是错误的逻辑。如果您不需要三元组,则不需要开关,但是它们包括了开关,因此很明显,这不是一个类似的考虑答案。它被滥用的可能性要小于复杂的if语句条件,因此它不是那样。设计师不喜欢它-听起来更有可能。一些开发人员对代码的格式设置不佳或使用括号不应该使有用的语言功能丧失资格,尤其是在需要gofmt且可以完成工作的情况下。

#1 楼

正如指出的那样(并且希望如此),在Go中使用if+else确实是惯用的方式。

除了完整的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)


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

评论


大家好!我刚刚将Ternarity运算符移植到golangs! play.golang.org/p/ZgLwC_DHm0。如此高效!

– thwd
13年11月14日14:35



@tomwilde您的解决方案看起来很有趣,但是缺少三元运算符的主要功能之一-条件评估。

–弗拉基米尔·马特维夫(Vladimir Matveev)
13年11月14日在18:13

@VladimirMatveev将值包装在闭包中;)

–nemo
13年11月14日在20:03

c:=(map [bool] int {true:a,false:a-1})[a> b]是混淆IMHO的示例,即使它可以工作。

–Rick-777
15年2月28日在12:39

如果if / else是惯用的方法,那么Golang可以考虑让if / else子句返回一个值:x =如果{1} else {0}。 Go绝不是以这种方式工作的唯一语言。主流示例是Scala。请参阅:alvinalexander.com/scala/scala-ternary-operator-syntax

–马克斯·墨菲(Max Murphy)
16年8月4日在13:04

#2 楼

Go没有三元运算符,使用if / else语法是惯用的方式。

为什么Go没有?:运算符?
Go中没有三元测试操作。您可以使用以下命令获得相同的结果:Go缺少?:的原因是该语言的设计人员看到该操作过于频繁地用于创建难以理解的复杂表达式。 if-else形式虽然更长,但无疑更清晰。一种语言只需要一个条件控制流构造。
-常见问题(FAQ)-Go编程语言


评论


因此,仅由于语言设计者所见,他们就为整个if-else块省略了一条直线?谁说if-else不会以同样的方式被滥用?我不是在攻击您,我只是觉得设计师的借口不够有效

– Alf Moh
20年9月9日,11:42

我同意。丑三元是一个编码问题,而不是语言问题。三元在各种语言中都很常见,以至于它们是正常的,没有它们是一个惊喜,如果您问我,这违反了POLA / PLA。

– jcollum
20年6月24日在17:41



但是从语言设计者的角度来看它;他们需要使用额外的语法来扩展语言规范,解析器,编译器等,而该语法在语言中的其他任何地方都没有使用过某些语法糖,这是潜在的可读性。 Go是专为阅读而设计的,尽管大多数C开发人员可能对三元体系足够熟悉,以能够足够快地阅读它们,但这并不是一个普遍的真理,当人们开始嵌套它们时,事情就真的南下了。 “这另一种语言有它”不是添加语言功能的有效参数。

–cthulhu
20年8月13日在14:47

@cthulhu如果这是他们的问题,那么混乱的条件……我想知道他们是否至少只能允许该三元数作为一个操作进行操作,即。只是返回第二个参数的值,但是不执行它(不要进一步递归到下一棵运算树中)...即:x = a?:b //如果a为false则使用b ...只会返回a或b,但不会进一步评估它们……但是我不确定这是否会破坏解析规则。我认为操作员不会感到困惑,通常只有这种意图,我认为它本身应该足够可读。

– Ryan Weiss
20-10-18在23:44

#3 楼

假设您具有以下三元表达式(在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 } }()


这确保了两个分支都不会被求值。

评论


很高兴知道,内联anon函数只有一个分支被求值。但是请注意,此类情况超出了C的三元运算符的范围。

–狼
16 Dec 12'在10:34

C条件表达式(通常称为三元运算符)具有三个操作数:expr1? expr2:expr3。如果expr1的计算结果为true,则expr2的计算结果为表达式的结果。否则,将评估expr3并将其作为结果提供。这来自K&R的ANSI C编程语言第2.11节。 My Go解决方案保留了这些特定的语义。 @Wolf您能澄清您的建议吗?

– Peter Boyer
17年5月31日在1:26

我不确定我的想法,也许anon函数提供的作用域(本地名称空间)不是C / C ++中的三元运算符。查看使用此示波器的示例

–狼
17年5月31日在12:09



在这种情况下,“简单”看起来更像是“复杂”

– micahhoover
20 Dec 10'在19:50

#4 楼

映射三进制很容易阅读,没有括号:

c := map[bool]int{true: 1, false: 0} [5 > 4]


评论


不能完全确定为什么会得到-2 ...是的,这是一种解决方法,但它可以工作并且类型安全。

–亚历山大·桑蒂尼(Alessandro Santini)
15年8月3日,11:52

是的,它有效,类型安全,甚至具有创造力;但是,还有其他指标。三元操作在运行时等效于if / else(例如参见此S / O帖子)。此响应不是因为1)两个分支都被执行,2)创建映射3)调用哈希。所有这些都是“快速的”,但没有if / else快。另外,我认为如果条件{r = foo()} else {r = bar()},它不比var r T更具可读性。

–骑士
2015年12月1日20:55



在其他语言中,当我有多个变量并且带有闭包或函数指针或跳转时,我会使用这种方法。随着变量数量的增加,编写嵌套if变得容易出错,而例如{(0,0,0)=> {code1},(0,0,1)=> {code2} ...} [(x> 1,y> 1,z> 1)](伪代码)越来越多并且随着变量数量的增加而更具吸引力。闭合使该模型保持快速。我希望类似的权衡取舍。

–马克斯·墨菲(Max Murphy)
16-6-29在11:10



我想您会在该模型中使用一个开关。我喜欢go开关自动断开的方式,即使有时不方便。

–马克斯·墨菲(Max Murphy)
16年6月29日在11:14

正如Cassy Foesch所指出的那样:简单明了的代码胜于创意代码。

–狼
16 Dec 13'7:52



#5 楼

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


如果/否则,它不会胜过其他情况,需要强制转换,但可以。仅供参考:

BenchmarkAbsTernary-8 100000000 18.8 ns / op

BenchmarkAbsIfElse-8 2000000000 0.27 ns / op

评论


恭喜,这是最好的解决方案!一条线处理所有可能的情况

–亚历山大·奥利维拉(Alexandro de Oliveira)
19年9月12日在14:10

我不认为这可以处理条件评估,还是可以?带有自由副作用的分支无关紧要(就像您的示例中一样),但是如果它具有副作用,您将遇到问题。

–阿什顿·维尔斯多夫(Ashton Wiersdorf)
19-09-26在12:34

#6 楼

前言:在不争论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
}

如何使用它?
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)

看起来很酷,简单,优雅且高效(也可以内联)。
与“真正的”三元运算符:它始终对所有操作数求值。
要实现延迟和仅在需要时求值,唯一的选择是使用函数(声明的函数或方法,或函数文字),仅在以下情况下调用/如果需要:
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上尝试以下示例。

#7 楼

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

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程序会打印一个非正值而它不应该打印! (类似地,如果反转分支,则会通过不必要地调用慢函数来引入开销。)

评论


有趣的读物,但我对您对Gustavo方法的批评并不真正理解。我在原始代码中看到了(某种)吸收函数(好吧,我将<=更改为<)。在您的示例中,我看到一个初始化,这在某些情况下是多余的,并且可能是扩展的。您能否澄清一下:多解释一下您的想法?

–狼
16 Dec 12'在10:29

主要区别在于,即使不应该采用该分支,在任一分支之外调用函数也会产生副作用。在我的情况下,仅会打印正数,因为函数printPositiveAndReturn仅被调用为正数。相反,始终执行一个分支,然后通过执行另一个分支来“固定”值并不能消除第一个分支的副作用。

–old
16/12/13在0:27



我知道,但是经验丰富的程序员通常都知道副作用。在那种情况下,即使编译的代码可能相同,我还是希望Cassy Foesch为嵌入式函数提供明显的解决方案:它更短并且对于大多数程序员而言都是显而易见的。不要误会我的意思:我真的很喜欢Go的闭包;)

–狼
16 Dec 13'7:12

“经验程序员通常会意识到副作用”-不会。避免对术语求值是三元运算符的主要特征之一。

–乔纳森·哈特利
18-09-14在16:12

#8 楼

eold的答案很有趣而且很有创造力,甚至可能很聪明。

但是,建议改为:

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


是的,他们俩编译成基本上相同的程序集,但是,此代码比调用匿名函数仅返回返回可能首先写入变量的值更加清晰。

基本上,简单且

此外,任何使用地图文字的代码都不是一个好主意,因为地图在Go中根本不是轻量级的。从Go 1.3开始,可以保证小地图的随机迭代顺序,并且要强制执行,对于小地图,它在内存方面的效率要低得多。

,制作和删除了许多小地图。既浪费空间又费时。我有一段使用小地图的代码(可能使用两个或三个键,但是常见的用例只是一个条目),但是代码速度很慢。我们谈论的速度至少比使用双切片键[index] => data [index]映射重写的同一代码慢3个数量级。而且可能更多。由于某些操作以前需要花费几分钟才能运行,因此开始在毫秒内完成。\

评论


简单清晰的代码比创意代码更好-我非常喜欢这个代码,但是在慢了一点之后,我在上一节中有些困惑,也许这也可能会使其他人感到困惑?

–狼
16 Dec 12'在10:42

因此,基本上...我有一些代码正在创建带有一个,两个或三个条目的小地图,但是代码运行非常缓慢。因此,很多m:= map [string] interface {} {a:42,b:“ stuff”},然后在另一个函数中对其进行迭代:对于键,val:= range m {code here}切换后到两片式系统:keys = [] string {“ a”,“ b”},data = [] interface {} {42,“ stuff”},然后像i一样遍历key,=:range keys { val:= data [i];此处的代码加速了1000倍。

–Cassy Foesch
16年12月13日在17:33

我明白了,谢谢您的澄清。 (也许在这一点上答案本身可以得到改善。)

–狼
16 Dec 13'在17:57

-.- ...touché,逻辑...touché...我最终会继续...;)

–Cassy Foesch
16 Dec 14'在21:34

#9 楼

单行代码,尽管被创建者所避开,但它们有自己的位置。

此解决了懒惰的评估问题,方法是让您(必要时)传递要评估的函数:

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所示那样包装它。


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

评论


即使这绝对不是学术性的,这也相当不错。

–法比恩
20年1月28日在13:09

#10 楼

正如其他人指出的那样,golang没有三元运算符或任何等效的运算符。这是一个出于可读性的刻意决定。
最近,这使我想到了一种以非常有效的方式构造位掩码的场景,在惯用语言编写时变得难以阅读,因为它占用了大量屏幕行,非常当封装为一个函数或同时封装为两个函数时效率低下,因为代码会产生分支:
package lib

func maskIfTrue(mask uint64, predicate bool) uint64 {
  if predicate {
    return mask
  }
  return 0
}

        text    "".maskIfTrue(SB), NOSPLIT|ABIInternal, 
func zeroOrOne(predicate bool) (result int) {
  if predicate {
    result = 1
  }
  return
}
-24 funcdata
    movblzx "".predicate+8(SP), AX
    movq    AX, "".result+16(SP)
    ret
, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) funcdata , 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
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
}
, "".~r2+24(SP) ret

我从中学到的是更多地利用了Go;在函数(result int)中使用命名结果可以节省我在函数中声明它的行(并且您可以对捕获执行相同的操作),但是编译器也可以识别该惯用法(仅分配值IF),并在可能的情况下将其替换为
    movq    "".v1+8(SP), AX
    cmpq    AX, 
    seteq   AL
    xorps   X0, X0
    movss   "".v1+16(SP), X1
    ucomiss X1, X0
    sethi   CL
    movblzx AL, AX
    shlq    , AX
    movblzx CL, CX
    shlq    , CX
    orq     CX, AX
    movblzx "".v1+20(SP), CX
    shlq    , CX
    orq     AX, CX
    movq    CX, "".result+24(SP)
    ret

q4312078q
然后可以自由地内联。
q4312078q q4312078q