在C/C++(以及许多类似语言)中,常用的一种声明和初始化变量的习惯用法是使用三元条件运算符:
int index = val > 0 ? val : -val
Go语言没有三目运算符。实现与上述代码相同的功能最惯用的方式是什么?我想到了下面的解决方案,但它似乎过于冗长。
var index int
if val > 0 {
index = val
} else {
index = -val
}
有更好的选择吗?
在C/C++(以及许多类似语言)中,常用的一种声明和初始化变量的习惯用法是使用三元条件运算符:
int index = val > 0 ? val : -val
Go语言没有三目运算符。实现与上述代码相同的功能最惯用的方式是什么?我想到了下面的解决方案,但它似乎过于冗长。
var index int
if val > 0 {
index = val
} else {
index = -val
}
有更好的选择吗?
正如所指出的(也许并不令人惊讶),在Go中使用if+else
确实是条件语句的惯用方式,详情请参阅官方文档。
除了完整的var+if+else
代码块外,这种简写形式也经常被使用:
index := val
if val <= 0 {
index = -val
}
如果您有一段重复的代码块,例如等效于int value = a <= b ? a : b
的代码块,则可以创建一个函数来保存它:
并且如果你有足够重复的代码块,如等同于int value = a <= b ? a : b
,你可以创建一个函数持有它:
func min(a, b int) int {
if a <= b {
return a
}
return b
}
...
value := min(a, b)
编译器将内联这种简单函数,因此它更快、更清晰、更短。c := (map[bool]int{true: a, false: a - 1})[a > b]
是一个混淆代码的例子,尽管它能运行。 - Rick-777if/else
是惯用的方法,那么Golang或许可以考虑让if/else
语句返回一个值:x = if a {1} else {0}
。这种做法并非Golang独有,Scala等主流编程语言也支持类似写法。参考链接:http://alvinalexander.com/scala/scala-ternary-operator-syntax - Max MurphyGo语言没有三元运算符,使用if/else语法是惯用方法。
为什么Go语言没有 ?: 运算符?
在Go语言中没有三元测试操作。您可以使用以下语法来实现相同的结果:
if expr {
n = trueVal
} else {
n = falseVal
}
Go语言中不包括?:
的原因是,该语言的设计者已经看到过太多使用此操作符创建难以理解的复杂表达式的情况。虽然if-else
形式更长,但无疑更清晰。一种语言只需要一种条件控制流结构。
if
成为表达式,并允许像Rust或Scala那样使用诸如if cond { value1 } else { value3 }
这样的结构呢? - user14611663int a = test ? 1 : 2;
if
块:var a int
if test {
a = 1
} else {
a = 2
}
a := func() int { if test { return 1 } return 2 }()
expr1 ? expr2 : expr3
。如果expr1
的评估结果为true
,则评估expr2
并将其作为表达式的结果。否则,评估expr3
并将其作为结果提供。这段话出自K&R的《ANSI C编程语言》第2.11节。我的Go解决方案保留了这些特定的语义。 - Peter Boyera := func() int { if test { return 1 } return 2 }()
应该可以工作,还是我错了? - Olivier Ponselse
块。谢谢。我还删除了“simply”,因为它听起来有些学究气。 - undefined前言:虽然我们不能否认if else
这种方式的优越性,但我们仍然可以尝试使用语言特性来实现代码构造,从而找到乐趣。
Go 1.18 泛型更新:Go 1.18 加入了泛型支持,现在可以像这样创建一个通用的 If()
函数。请注意:此功能可在github.com/icza/gog
中使用,方法名为gog.If()
(声明:作者本人)。
func If[T any](cond bool, vtrue, vfalse T) T {
if cond {
return vtrue
}
return vfalse
}
您可以像这样使用:
min := If(i > 0, i, 0)
以下是1.18版本之前的答案:
在我的github.com/icza/gox
库中有许多其他方法,其中包括以下If
结构: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()
}
使用方法:假设我们有以下函数来计算a
和b
:
func calca() int { return 3 }
func calcb() int { return 4 }
然后:
i := If(someCondition).Fint(calca, calcb)
i := If(time.Now().Year() > 2020).Fint(calca, calcb)
如果我们想要使用函数字面量:
i := If(time.Now().Year() > 2020).Fint(
func() int { return 3 },
func() int { return 4 },
)
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上尝试这些示例。
If
不能与 nil 检查和解引用一起使用,例如 If(p != nil, *p, defaultValue)
,因为参数(包括 *p
)会立即被评估。 - blackgreen三元运算符不需要括号即可轻松阅读:
c := map[bool]int{true: 1, false: 0} [5 > 4]
fmt.Println("Operation %s; reverting to normal form.", (map[bool]string{true: "skipped", false: "failed"})[opkip])
?可以,以下是较为通俗易懂的重写版本:var status string
if opkip {
status = "skipped"
} else {
status = "failed"
}
fmt.Printf("Operation %s; reverting to normal form.", status)
- kavadiasfunc 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语句高效,并且需要进行类型转换,但是可以正常工作。FYI:
BenchmarkAbsTernary-8 100000000 18.8 ns/op
BenchmarkAbsIfElse-8 2000000000 0.27 ns/op
test = function1(); if condition {test = function2()}
,这将是相同的,并且不需要类型断言(更快)。至于涉及返回的答案,我不知道。还要看两个评估中是否都很昂贵。还是感谢您的回答!尽管如此,这似乎是一个好主意。 - Edw590正如其他人所提到的,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)
中使用命名返回值可以节省一行在函数内部声明(你也可以对捕获变量采用相同的方式),编译器还可以识别这种惯用法(只有当条件满足时才赋值),并在可能的情况下用条件语句替换它。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
index := func() int {
if val > 0 {
return printPositiveAndReturn(val)
} else {
return slowlyReturn(-val) // or slowlyNegate(val)
}
}(); # exactly one branch will be evaluated
index := printPositiveAndReturn(val);
if val <= 0 {
index = slowlyReturn(-val); // or slowlyNegate(val)
}
如果你得到的程序表现不同,那么当 val <= 0
时,程序会输出一个非正值,而实际上它不应该这样做!(类似地,如果你颠倒了分支,调用缓慢的函数将会增加额外开销。)
abs
函数(好吧,我会将<=
改为<
)。在你的例子中,我看到了一个初始化,在某些情况下是多余的,并且可能很昂贵。您能否请澄清一下:更详细地解释您的想法? - WolfprintPositiveAndReturn
仅对正数进行调用。相反,始终执行一个分支,然后通过执行另一个分支来“修复”值,并不能撤消第一个分支的副作用。 - eold现在随着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语句如果是false则不会调用该部分)
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 在未来添加箭头函数)
playground
感谢这个答案提供者已经回答过了
虽然被创建者避免使用,但单行代码也有它的用处。
这个代码通过让你可选地传递需要评估的函数来解决惰性求值问题:
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
一样对其进行包装。这里提供的独立解决方案也很好,但对于某些用途可能不够清晰。
三元运算符
。 - Eric