Golang 中的条件控制语句与 Clang 相似,但省去了许多不必要的附加符号,提供必要的精简方式。
条件控制语句
if-else 语句
- 与
Clang不同的是Golang中的表达式都省去了()。
Usage 1:
if <condition1> {
<block1>
} else if <condition2> {
<block2>
} else {
<block0>
}
Example:
if returnValue == -1 {
fmt.Println("Something Error.")
}
if weight < 240 {
fmt.Println("Weight OK!")
} else {
fmt.Println("Too weight.")
}
if score < 60 {
fmt.Println("F")
} else if score < 70 {
fmt.Println("P")
} else if (score < 80) {
fmt.Println("C")
} else if (score < 90) {
fmt.Println("B")
} else {
fmt.Println("A")
}
Usage 2:
if <init>; <conditional> {
<block>
}
Golang 没有三目运算符,因为官方认为其影响 Golang 的语法整洁性。
switch-case 语句
Usage 1:
switch <variable> {
case <value1>:
<block1>
case <value2>:
<block2>
...
default:
<block0>
}
Example:
switch level {
case 1:
fmt.Println("P")
case 2:
fmt.Println("C")
case 3:
fmt.Println("B")
case 4:
fmt.Println("A")
default:
fmt.Println("F")
}
Usage 2:
switch {
case <condition1>:
<block1>
case <condition2>:
<block2>
...
default:
<block0>
}
Example:
switch {
case score < 60:
fmt.Println("F")
case score < 70:
fmt.Println("P")
case score < 80:
fmt.Println("C")
case score < 90:
fmt.Println("B")
default:
fmt.Println("A")
}
select-case 语句
expression必须是一个通信操作,如x := <-c。select语句将随机抽取一个case,如果通信成功将运行block;如果通信失败并且存在default将运行block0。
select {
case <expression1>:
<block1>
case <expression2>:
<block2>
default:
<block0>
}
Example:
var c1 = make(chan string)
var c2 = make(chan string)
func Thread1() {
time.Sleep(time.Millisecond * 2100) // A1
c1 <- "Thread1 is ready."
}
func Thread2() {
time.Sleep(time.Millisecond * 2100) // A1
c2 <- "Thread2 is ready."
}
func ThreadMain() {
for i := 0; i < 10; i++ {
select {
case x := <-c1:
fmt.Println("Get:", x)
case x := <-c2:
fmt.Println("Get:", x)
default:
fmt.Println("Get Nothing.")
time.Sleep(time.Millisecond * 500) // A2
}
}
}
func main() {
go Thread1()
go Thread2()
ThreadMain()
}
Get Nothing.
Get Nothing.
Get Nothing.
Get Nothing.
Get Nothing.
Get: Thread1 is ready.
Get: Thread2 is ready.
Get Nothing.
Get Nothing.
Get Nothing.
- 注意在案例输出中,
Thread1 is ready.与Thread2 is ready.先后是随机的,因为到第五次循环(2500ms)Thread1与Thread2都准备就绪。 - 如果将
A1处参数修改为2000,则Thread1 is ready.在先概率更高,因为Thread1与Thread2理论上恰好同时在第四次循环(2000ms)准备就绪,而线程启动需要其他准备时间且Thread1先于Thread2启动。
循环控制语句
for 语句
| Golang | Clang |
|---|---|
for <init>; <condition>; <post> { <block> } | for (<init>; <condition>; <post>) { <block>; } |
for <condition> { <block> } | while (<condition>) { <block>; } |
for { <block> } | while (true) { <block>; } |
Golang中的for语句;间的语句也可以是空语句
Example:
for i := 0; i < 10; i++ {
fmt.Println(i)
}
var i int = 0
// for ; i < 10; {
// fmt.Println(i)
// i++
// }
for i < 10 {
fmt.Println(i)
i++
}
// for ;; {
// fmt.Println("Running...")
// time.Sleep(time.Millisecond * 500)
// }
for {
fmt.Println("Running...")
time.Sleep(time.Millisecond * 500)
}
跳转语句
-
continue:跳转到下一个for循环。for i := 0; i < 5; i++ { if i == 3 { continue } fmt.Println(i) } -
break:跳转到当前for循环外。for i := 0; i < 5; i++ { if i == 3 { break } fmt.Println(i) } -
goto:跳转到指定标签for i := 0; i < 5; i++ { for j := 0; j < 5; j++ { if i == 3 and j == 3 { goto tag } fmt.Println(i, j) } } tag:
for-range 语句
slice := []int{1, 2, 3, 4, 5}
for key, value := range slice {
fmt.Println(key, value)
}
- 数组、
string、slice、map、channel等都可以使用for-range语句遍历。其中channel没有key且阻塞。
channel := make(chan int, 10)
for value := range channel {
fmt.Println(value)
}
特殊控制语句
Golang从语法上支持并发,它有其特殊的控制语句。
defer 语句
defer 语句用法
defer语句用于将操作延迟到函数结束执行,其操作甚至迟于return操作。defer语句将延迟操作压栈,在结束时逆序执行。defer语句必须使用函数。可以使用匿名函数。
defer <functionCall>
func Echo() {
defer fmt.Println("Function Exited.")
defer fmt.Println("Echoed.")
fmt.Println("Hello World!")
}
Hello World!
Echoed.
Function Exited.
defer 语句怪用
-
谈及
defer原理,defer语句将延迟操作压栈,压栈数据包括:函数名、函数参数(临时)地址。后面的所有怪用都基于这一原理,defer语句可以修改和访问本该已卸载的内存。defer压栈后实际对地址又进行了一次引用,因此Golang的垃圾回收机制(GC)实际没有卸载这些内存。 -
修改函数返回值。该案例中使用匿名返回值得不到相同效果,个人猜测是因为匿名返回值返回时进行了拷贝。
func Demo() (s string) {
s = "Edit by Function"
defer func() { s = "Edit by Defer" }
return s
}
Edit by Defer
- 循环延迟返回值相同。该案例中,
Demo1中defer访问for循环定义的i,在程序结束时访问其值为3;Demo2中defer访问每次循环体内定义的i,可以认为是循环变量i的快照版本。
func Demo1() {
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
}
3
3
3
func Demo2() {
for i := 0; i < 3; i++ {
i := i
defer fmt.Println(i)
}
}
2
1
0
go 语句
go语句将创建一个gorountine,可以简单认为是一个线程或协程。go语句的对象是一个函数,将函数作为一个新的线程运行。- 关于什么是线程,不作赘述。
func thread() {
for i := 0; i < 5; i++ {
fmt.Println("Thread", i)
time.Sleep(time.Microsecond * 50)
}
}
func main() {
go thread()
for i := 0; i < 5; i++ {
fmt.Println("Main", i)
time.Sleep(time.Microsecond * 50)
}
}
Main 0
Thread 0
Thread 1
Main 1
Main 2
Thread 2
Main 3
Thread 3
Thread 4
Main 4
异常处理
error 接口
接口原型
type error interface {
Error() string
}
构造异常
err := errors.New("异常信息") // error: "异常信息"
err := fmt.Errorf("错误信息: %v", "异常信息") // errors.New(fmt.Sprintf(...))
捕捉异常
func tryInt(s string) {
i, err := strconv.ParseInt(s, 0, 64)
if err == nil {
fmt.Println("Value:", i)
} else {
fmt.Println("Error:", err)
}
}
func main() {
tryInt("123123")
tryInt("123a123")
}
Value: 123123
Error: strconv.ParseInt: parsing "123a123": invalid syntax
异常嵌套
从 go 1.13 开始支持异常嵌套,引入了 errors.Is、errors.As、errors.Unwrap、fmt.Errorf 中的 %w。
func main() {
err1 := errors.New("error 1")
err2 := errors.New("error 2")
err3 := errors.New("error 3")
err4 := errors.New("error 1")
fmt.Errorf("%w: %w", err3, err1) // %w 称 Wrap, 将多种错误嵌套, 与 Unwrap 对应
fmt.Println(errors.Is(err1)) // true
fmt.Println(errors.Is(err2)) // false
fmt.Println(errors.Is(err3)) // true
fmt.Println(errors.Is(err4)) // false
}
errors.Is 的本质是不断调用 errors.Unwrap 并检测错误信息指针是否一致,因此第 10 行为 false。
系统错误可以使用 fs.xxx 找到对应的系统错误。
panic() 与 recover()
Golang中没有try-catch语句,而使用panic-recover进行异常控制,两者具有一定区别。func panic(v any)(崩溃)将产生异常,如果异常不被捕捉,将抛出命令行错误并终止程序。func recover() any(恢复)用于捕捉异常,只在defer函数中生效。- 非不可挽回的错误,建议使用
error而非panic。
造成 panic 的场景
- 索引或指针无效或越界
- 向关闭的
channel发送消息 - 类型断言(不获取
ok) - 用户发送
panic
捕捉与处理异常
defer func() {
err := recover()
if err != nil {
fmt.Printf("捕捉异常: %T %v\n", err, err)
}
}()
// defer recover() 无效
// defer func() { recover() }() 有效
panic("抛出异常")
捕捉异常: string 抛出异常
- 由于
recover()只在defer函数中生效,只能在函数结束处理异常。