在C/C++(以及该族的许多语言)中,一个常见的习惯用法,根据一个条件来声明和初始化一个变量,使用三元条件运算符:
1
| int index = val > 0 ? val : -val |
Go没有条件运算符。实现上述相同代码的最惯用方法是什么?我找到了下面的解决方案,但似乎很冗长
1 2 3 4 5 6 7
| var index int
if val > 0 {
index = val
} else {
index = -val
} |
有更好的吗?
- 您可以使用其他部分初始化该值,并且只检查您的条件是否要更改,但不确定这样更好。
- 不管怎样,很多if/thens都应该被删除。从35年前我写第一个基本程序的时候起,我们就一直这样做。你的例子可以是:int index = -val + 2 * val * (val > 0);。
- @hyc您的示例远不能像go的惯用代码那样可读,甚至不像使用三元运算符的c版本那样可读。无论如何,afaik,不可能在go中实现该解决方案,因为布尔值不能用作数值。
- 想知道为什么Go没有提供这样的接线员?
- @埃里克旺有两个原因,阿法克:1-你不需要它,他们想让语言尽可能小。2-它往往被滥用,即在复杂的多行表达式中使用,语言设计者不喜欢它。
正如所指出的(并且希望毫不意外地),使用if+else确实是在go中处理条件的惯用方法。
除了完整的var+if+else代码块外,这种拼写也经常使用:
1 2 3 4
| index := val
if val <= 0 {
index = -val
} |
如果您有一个足够重复的代码块,例如等效的int value = a <= b ? a : b,您可以创建一个函数来保存它:
1 2 3 4 5 6 7 8 9 10
| func min(a, b int) int {
if a <= b {
return a
}
return b
}
...
value := min(a, b) |
编译器将内联这样简单的函数,所以它更快、更清晰、更短。
- 嘿,伙计们,看!我刚把三元运算符移植到Golangs!play.golang.org/p/zglwc_dhm0。那么高效!
- @汤怀德很有趣,但不完全是口是心非。此外,使用映射而不是条件似乎效率较低。不过,我会说你的解决方案很有创意!
- @Tomwilde您的解决方案看起来很有趣,但它缺少三元运算符条件评估的主要功能之一。
- @vladimirmatveev将值包装在闭包中;)
- 我想你可以讨论一下if/else是否更清楚,但它是如何缩短的?
- c := (map[bool]int{true: a, false: a - 1})[a > b]是一个模糊imho的例子,即使它起作用。
- 如果if/else是惯用方法,那么golang可能会考虑让if/else子句返回一个值:x = if a {1} else {0}。Go绝不是唯一一种这样工作的语言。一个主流的例子是scala。参见:alvinalexander.com/scala/scala-ternary-operator-syntax
- 是的,我也会说同样的话。他们不知道他们错过了什么!当前"惯用方法"的缺点是不能用这种方法定义常量
- 是if-return构造(您的第二个代码块)惯用的,在我看来是这样的(参见go-by-example:recursion)。只是想确定一下:你能确认一下吗?
- 映射解决方案似乎无法正常工作,因为它计算两个分支,而实际的?:运算符只应计算适用于该条件的分支。
- 好吧,golang的所有好处和代码的乐趣。但是没有埃尔维斯!回到javascript!LOL,J/K
- 太长了,看起来很复杂。为了简单起见,我使用三元运算符
- 像这样的golang代码提醒我语言有多好,它如何反映我多年来写nodejs和javascript的方式。谢谢你的回答!
- 伙计。Clever?对。如果以前从未见过,需要花时间来弄清意图?对。如果我在紧急情况下被呼叫,我想在凌晨3点看到那个代码吗?不,这不干净,我也不主张在接吻原则上聪明。
no-go没有三元运算符,使用if/else语法是惯用的方法:http://golang.org/doc/faq go-u有三元形式吗?
假设您有以下三元表达式(在C中):
Go中的惯用方法是简单地使用if块:
1 2 3 4 5 6 7
| var a int
if test {
a = 1
} else {
a = 2
} |
但是,这可能不符合您的要求。在我的例子中,我需要一个用于代码生成模板的内联表达式。
我使用了一个立即评估的匿名函数:
1
| a := func() int { if test { return 1 } else { return 2 } }() |
这样可以确保两个分支也不会被评估。
- 很好地知道,只对内联anon函数的一个分支进行评估。但请注意,这样的情况超出了C的三元运算符的范围。
- C条件表达式(通常称为三元运算符)有三个操作数:expr1 ? expr2 : expr3。如果expr1计算为true,则expr2计算为表达式的结果。否则,评估并提供expr3。这来自K&R的ANSIC编程语言第2.11节。我的Go解决方案保留了这些特定的语义。@沃尔夫,你能解释一下你的建议吗?
- 我不确定我在想什么,也许ANON函数提供了一个范围(本地命名空间),而C/C++中的三元运算符不是这样的。请参阅使用此范围的示例
地图三元很容易阅读,无括号:
1
| c := map[bool]int{true: 1, false: 0} [5 > 4] |
- 不完全确定为什么会有-2…是的,这是一个解决方法,但它是有效的,而且是类型安全的。
- 是的,它工作,类型安全,甚至是创造性的;但是,还有其他的度量标准。三元操作是运行时等价于if/else(请参见本S/O日志)。此响应不是因为1)两个分支都被执行,2)创建映射3)调用哈希。所有这些都是"快"的,但速度不如if/else快。此外,我还认为,如果条件r=foo()else r=bar(),那么它的可读性不会比var r t高。
- 在其他语言中,当我有多个变量并且有闭包、函数指针或跳转时,我使用这种方法。随着变量数量的增加,编写嵌套的ifs变得容易出错,而例如(0,0,0)=>code1,(0,0,1)=>code2…[(x>1,y>1,z>1)](伪代码)随着变量数量的增加变得越来越有吸引力。闭包使这个模型保持快速。我希望类似的折衷在Go中也适用。
- 我想在Go中,您将使用该型号的开关。我喜欢自动切换开关,即使偶尔不方便。
- 正如卡西·弗什所指出的:埃多克斯1〔6〕。
- 你好,很好的synthax,但是每一个潜在的结果都会被分析,即使它们没有被触发。因此,如果您有:(map[bool]string true:referencetab[i],false:newtab[i])[newtab==nil],则实际条件将引发一个错误:超出范围…
1 2 3 4 5 6 7 8 9 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)
} |
这不会优于if/else,并且需要强制转换,但会起作用。FY:
基准戒备室-8 100000000 18.8 ns/op
基准ABSIFELSE-8200000000 0.27 ns/op
如果您的所有分支都会产生副作用或计算代价高昂,那么下面的代码将在语义上保留重构:
1 2 3 4 5 6 7
| index := func() int {
if val > 0 {
return printPositiveAndReturn(val)
} else {
return slowlyReturn(-val) // or slowlyNegate(val)
}
}(); # exactly one branch will be evaluated |
通常没有开销(内联的),而且最重要的是,它不会将命名空间与只使用一次的助手函数(这会妨碍可读性和维护)混淆在一起。活生生的例子
注意,如果你天真地运用古斯塔沃的方法:
1 2 3 4
| index := printPositiveAndReturn(val);
if val <= 0 {
index = slowlyReturn(-val); // or slowlyNegate(val)
} |
你会得到一个有不同行为的程序;以防val <= 0程序会打印一个非正值,而它不应该打印!(类似地,如果反向调用分支,则会通过不必要地调用一个慢函数来引入开销。)
- 有趣的阅读,但我并不真正理解你对古斯塔沃方法的批评。我在原始代码中看到一个(类)abs函数(好吧,我将<=改为<)。在您的示例中,我看到了一个初始化,在某些情况下是多余的,并且可以扩展。请你澄清一下:再解释一下你的想法好吗?
- 主要的区别是,在任何一个分支之外调用函数都会产生副作用,即使不应该使用该分支。在我的例子中,只会打印正数,因为函数printPositiveAndReturn只对正数调用。相反,总是执行一个分支,然后执行另一个分支来"修复"该值不会撤消第一个分支的副作用。
- 我明白了,但是经验程序员通常知道副作用。在这种情况下,我更喜欢CassyFoesch的明显的嵌入式函数解决方案,即使编译后的代码可能是相同的:它较短,对大多数程序员来说很明显。别误会我:我真的很喜欢围棋的闭幕式;)
- "经验程序员通常知道副作用"-不。避免术语评估是三元运算符的主要特征之一。
Eold的答案很有趣,很有创意,甚至可能很聪明。
但是,建议改为:
1 2 3 4 5 6
| var index int
if val > 0 {
index = printPositiveAndReturn(val)
} else {
index = slowlyReturn(-val) // or slowlyNegate(val)
} |
是的,它们都编译成基本上相同的程序集,但是,与调用匿名函数仅仅返回一个值(这个值本来可以写入变量)相比,这个代码更清晰。
基本上,简单清晰的代码比创造性代码更好。
此外,任何使用map文字的代码都不是一个好主意,因为map在go中根本不是轻量级的。自从Go1.3以来,小地图的随机迭代顺序得到了保证,并且为了实现这一点,对于小地图来说,它在内存方面的效率要低得多。
因此,制作和删除许多小地图既耗时又耗时。我有一段代码使用了一个小地图(可能有两个或三个键,但常见的用例只是一个条目),但代码太慢了。我们说的是至少3个数量级的慢于相同的代码重写以使用双片键[index]=>数据[index]映射。可能更多。由于以前运行几分钟的一些操作在几毫秒内就开始完成了。
- simple and clear code is better than creative code—我非常喜欢这个,但在dog slow之后的最后一节我有点困惑,也许这对其他人也会困惑?
- 所以,基本上…我有一些代码正在创建带有一个、两个或三个条目的小地图,但代码运行非常缓慢。所以,很多m := map[string]interface{} { a: 42, b:"stuff" },然后在另一个迭代它的函数中:for key, val := range m { code here }在切换到两片系统后:keys = []string{"a","b" }, data = []interface{}{ 42,"stuff" },然后像for i, key := range keys { val := data[i] ; code here }一样迭代,东西加速了1000倍。
- 我明白了,谢谢你的澄清。(也许在这一点上答案本身可以改进。)
- -…触摸&233;,逻辑…触摸和233;最后我会继续…;)