【读书笔记&个人心得】第5章:控制结构
if-else
Go 的 if 不用写括号
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
if 与{要在同一行,} else { 这三者要同一行
不建议同时在 if-else 结构的两个分支里都使用 return 语句
if condition {
return x
}
return y
判断一个字符串是否为空
if str == "" { ... }
if len(str) == 0 {...}
判断系统环境
var prompt = "Enter a digit, e.g. 3 "+ "or %s to quit."
func init() {
if runtime.GOOS == "windows" {
prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter")
} else { //Unix-like
prompt = fmt.Sprintf(prompt, "Ctrl+D")
}
}
if 可以包含一个初始化语句
在 if 初始化的语句,只在该 if-else 中有效
一般写法
val := 10
if val > 12 {
// do something
}
使用 if 包含初始化,先写初始化,然后加;号,再写 if 的条件,如果变量在 if 结构之前就已经存在,那么在 if 结构中,该变量原来的值会被隐藏(覆盖原理)
package main
import "fmt"
func main() {
val := 1
fmt.Println(val)// 1
if val := 10; val > 5 {
fmt.Println(val) //5
}
}
package main
import "fmt"
func main() {
if val := GetFood(); val == "apple" {
fmt.Println("the food is " + val) //the food is apple
}
}
func GetFood() string {
return "apple"
}
测试多返回值函数的错误
其实就个
try{}catch(E e){}
一个函数会发生错误时,会返回两个值,一个是执行结果,一个是执行是否成功,对于后者,有两种类型:
1 返回是否成功:true 表示成功,0、false 和 nil 表示失败
2 返回是否出错:成功的话,error 为 nil,失败的话,error 包含相应的错误信息。error: var err error
习惯用法:
value, err := pack1.Function1(param1)
if err != nil {
fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
return err
}
// 未发生错误,继续执行:
根据我的试验,return 时,在子函数时才 return,main 函数不 return,否则会提示 too many return values
发现错误终止程序的运行
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
value, err := strconv.Atoi("123")
if err != nil {
fmt.Printf("Program stopping with error %v", err)
os.Exit(1)
}
fmt.Printf(strconv.Itoa(value))
}
获取结果并立即判断错误
// 返回err
if err := file.Chmod(0664); err != nil {
fmt.Println(err)
return err
}
// 返回是否成功 true、false
if value, ok := readData(); ok {
…
}
没有为多返回值的函数准备足够的变量来存放结果。编译将会报错,若想避免编译出错,可以用 _ 舍弃掉返回值,若能保证执行一定成功,那就加一层封装永久解决问题。
func atoi (s string) (n int) {
n, _ = strconv.Atoi(s)
return
}
实际上,fmt 包(第 4.4.3 节)最简单的打印函数也有 2 个返回值:
count, err := fmt.Println(x) // number of bytes printed, nil or 0, error
当打印到控制台时,可以将该函数返回的错误忽略;但当输出到文件流、网络流等具有不确定因素的输出对象时,应该始终检查是否有错误发生(另见练习 6.1b)。
switch
switch var1 {
case val1:
...
case val2:
...
default:
...
}
var1 可以时任意类型
支持单分支多匹配值
case val1, val2, val3
自动 break
一旦成功地匹配到某个分支,在执行完相应代码后就会退出整个 switch 代码块,还希望继续执行后续分支的代码,可以使用 fallthrough 关键字来达到目的
switch i {
case 0: // 空分支,只有当 i == 0 时才会进入分支
case 1:
f() // 当 i == 0 时函数不会被调用
}
switch i {
case 0: fallthrough
case 1:
f() // 当 i == 0 时函数也会被调用
}
package main
import (
"fmt"
)
func main() {
val := 3
switch val {
case 0:
case 1, 2:
case 3:
fmt.Println("0没有匹配")
fmt.Println("1没有匹配")
return
fmt.Printf("第三句不会被执行,因为使用了return提前结束整个函数的执行")
}
}
输出:
0 没有匹配
1 没有匹配
注意:由于 switch 是分支结构,所以在代码块内的 return 不一定会执行,所以要在 switch 外也写 switch 来保证函数始终会返回(我觉得或许写在 default 中也可以)
default
当其他分支都无法匹配成功时,会匹配 default,default 可以放在任何位置,建议放在最后
链式的 if-else 的替代
switch {
case i < 0:
f1()
case i == 0:
f2()
case i > 0:
f3()
}
任何支持进行相等判断的类型都可以作为测试表达式的条件,包括 int、string、指针等
package main
import (
"fmt"
)
func main() {
i := 11
var ptr *int = &i
ptr2 := ptr
fmt.Println(ptr, ptr2)
switch {
case i < 0:
fmt.Println("执行f1()")
case i == 0:
fmt.Println("执行f2()")
case i > 0:
fmt.Println("执行f3()")
fallthrough //继续往下执行
case ptr == ptr2:
fmt.Println("执行f4()")
}
}
输出
0xc000018030 0xc000018030
执行 f3()
执行 f4()
switch 包含初始化
switch result := calculate(); {
case result < 0:
...
case result > 0:
...
default:
// 0
}
switch a, b := x[i], y[j]; {
case a < b: t = -1
case a == b: t = 0
case a > b: t = 1
}
实现一个季节计算器
package main
import (
"fmt"
"strconv"
)
func main() {
month := -1
fmt.Scanf("%d", &month)
switch month {
case 12, 1, 2:
fmt.Println("冬季")
case 3, 4, 5:
fmt.Println("春季")
}
switch {
case month >= 6 && month <= 8:
fmt.Println("夏季")
case strconv.Itoa(month) == "9" || strconv.Itoa(month) == "10" || strconv.Itoa(month) == "11":
fmt.Println("秋季")
}
}
for
在 Go 中,循环用 for,for 很灵活,和 if 一样,for 省略了括号
先声明 i 并初始化,然后判断 i<5,然后执行 i++,判断 i<5,重复以上步骤
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
}
多个计数器
for i, j := 0, N; i < j; i, j = i+1, j-1 {}
使用按位取反取|1|到|10|
package main
import "fmt"
func main() {
for i := 0; i <= 10; i++ {
fmt.Printf("%2d|", ^i)
}
fmt.Printf("\n")
for i := 0; i >= -10; i-- {
fmt.Printf("%2d|", ^i)
}
}
使用按位补码从 0 到 10
package main
import "fmt"
func main() {
for i := 0; i <= 10; i++ {
fmt.Printf("the complement of %b is: %b | %d is: %d\n", i, ^i, i, ^i)
}
}
/* Output:
the complement of 0 is: -1
the complement of 1 is: -10
the complement of 10 is: -11
the complement of 11 is: -100
the complement of 100 is: -101
the complement of 101 is: -110
the complement of 110 is: -111
the complement of 111 is: -1000
the complement of 1000 is: -1001
the complement of 1001 is: -1010
the complement of 1010 is: -1011
*/
和 switch 连用
写一个从 1 打印到 100 的程序,但是每当遇到 3 的倍数时,不打印相应的数字,但打印一次 "Fizz"。遇到 5 的倍数时,打印 Buzz 而不是相应的数字。对于同时为 3 和 5 的倍数的数,打印 FizzBuzz(提示:使用 switch 语句)
package main
import "fmt"
const (
FIZZ = 3
BUZZ = 5
FIZZBUZZ = 15
)
func main() {
for i := 0; i <= 100; i++ {
switch {
case i%FIZZBUZZ == 0:
fmt.Println("FizzBuzz")
case i%FIZZ == 0:
fmt.Println("Fizz")
case i%BUZZ == 0:
fmt.Println("Buzz")
default:
fmt.Println(i)
}
}
}
把 for 当成 while 用
就像 switch 后面不提供任何被判断的值(实际上默认为判断是否为 true),for 也可以没有初始化语句和修饰语句结构(但是有条件语句),因此 ;; 便是多余的了
package main
import "fmt"
func main() {
var i int = 5
for i >= 0 {
i = i - 1
fmt.Printf("The variable i is now: %d\n", i)
}
}
无限循环(死循环)
条件语句是可以被省略的,如 i:=0; ; i++ 或 for { } 或 for ;; { }(;; 会在使用 gofmt 时被移除)
如果 for 循环的头部没有条件语句,那么就会认为条件永远为 true,变成 while 是只有条件语句
想要直接退出循环体,可以使用 break 语句(第 5.5 节)或 return 语句直接返回(函数的 return)(第 6.1 节)。这两者之间有所区别,break 只是退出当前的循环体,而 return 语句提前对函数进行返回,不会执行后续的代码。
无限循环的经典应用是服务器,用于不断等待和接受新的请求。
for t, err = p.Token(); err == nil; t, err = p.Token() {
...
}
// 我的写法
for {
t, err = p.Token()
if err!=nil {
break;
}
}
for-range 结构
这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合(包括数组和 map,详见第 7 和 8 章)。语法上很类似其它语言中的 foreach 语句,但您依旧可以获得每次迭代所对应的索引(很智能,可以智能化提供下一个索引,而不用借助迭代器)
for ix, val := range coll { }
val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(译者注:如果 val 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值)
一个字符串是 Unicode 编码的字符(或称之为 rune)集合,因此您也可以用它迭代字符串:
for pos, char := range str {
...
}
每个 rune 字符和索引在 for-range 循环中是一一对应的。它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。
package main
import "fmt"
func main() {
str := "Go is a beautiful language!"
fmt.Printf("The length of str is: %d\n", len(str))
for pos, char := range str {
fmt.Printf("Character on position %d is: %c \n", pos, char)
}
fmt.Println()
str2 := "Chinese: 日本語"
fmt.Printf("The length of str2 is: %d\n", len(str2))
for pos, char := range str2 {
fmt.Printf("character %c starts at byte position %d\n", char, pos)
}
fmt.Println()
fmt.Println("index int(rune) rune char bytes")
for index, rune := range str2 {
fmt.Printf("%-2d %d %U '%c' % X\n", index, rune, rune, rune, []byte(string(rune)))
}
}
输出
The length of str is: 27
Character on position 0 is: G
Character on position 1 is: o
Character on position 2 is:
Character on position 3 is: i
Character on position 4 is: s
Character on position 5 is:
Character on position 6 is: a
Character on position 7 is:
Character on position 8 is: b
Character on position 9 is: e
Character on position 10 is: a
Character on position 11 is: u
Character on position 12 is: t
Character on position 13 is: i
Character on position 14 is: f
Character on position 15 is: u
Character on position 16 is: l
Character on position 17 is:
Character on position 18 is: l
Character on position 19 is: a
Character on position 20 is: n
Character on position 21 is: g
Character on position 22 is: u
Character on position 23 is: a
Character on position 24 is: g
Character on position 25 is: e
Character on position 26 is: !
The length of str2 is: 18
character C starts at byte position 0
character h starts at byte position 1
character i starts at byte position 2
character n starts at byte position 3
character e starts at byte position 4
character s starts at byte position 5
character e starts at byte position 6
character : starts at byte position 7
character starts at byte position 8
character 日 starts at byte position 9
character 本 starts at byte position 12
character 語 starts at byte position 15
index int(rune) rune char bytes
0 67 U+0043 'C' 43
1 104 U+0068 'h' 68
2 105 U+0069 'i' 69
3 110 U+006E 'n' 6E
4 101 U+0065 'e' 65
5 115 U+0073 's' 73
6 101 U+0065 'e' 65
7 58 U+003A ':' 3A
8 32 U+0020 ' ' 20
9 26085 U+65E5 '日' E6 97 A5
12 26412 U+672C '本' E6 9C AC
15 35486 U+8A9E '語' E8 AA 9E
U+65E5 的意思是这是一个 Unicode 字符,可以这样验证
package main
import (
"fmt"
)
func main() {
fmt.Printf("%s", "\u8A9E") // output 語
}
break 和 continue
break 只会退出最内层的循环
package main
func main() {
for i:=0; i<3; i++ {
for j:=0; j<10; j++ {
if j>5 {
break
}
print(j)
}
print(" ")
}
}
continue
continue 忽略剩余的循环体(剩余未执行的代码)而直接进入下一次循环的过程,但不是无条件执行下一次循环,执行之前依旧需要满足循环的判断条件
标签与 goto
使用全大写来对标签命名,如 LABEL1
例 1
package main
import "fmt"
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
输出:
j:0,i:0||
j:1,i:0||
j:2,i:0||
j:3,i:0||
j:0,i:1||
j:1,i:1||
j:2,i:1||
j:3,i:1||
j:0,i:2||
j:1,i:2||
j:2,i:2||
j:3,i:2||
j:0,i:3||
j:1,i:3||
j:2,i:3||
j:3,i:3||
j:0,i:4||
j:1,i:4||
j:2,i:4||
j:3,i:4||
j:0,i:5||
j:1,i:5||
j:2,i:5||
j:3,i:5||
例 2
package main
import "fmt"
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("j:%d,i:%d||", j, i)
fmt.Printf("\n")
}
fmt.Printf("这一句永远不会被执行\n")
}
}
例 1 中,continue 语句指向 LABEL1,当执行到该语句的时候,就会跳转到 LABEL1 标签的位置。
您可以看到当 j4 和 j5 的时候,没有任何输出:标签的作用对象为外部循环,因此 i 会直接变成下一个循环的值,而此时 j 的值就被重设为 0,即它的初始值
例 2 的第三个 Printf 因为内层循环每次变成 4 就会导致外层循环进入下一次,就像起了一个 continue 作业一样,第三个第三个 Printf 就被短路了
package main
import "fmt"
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
break LABEL1
}
fmt.Printf("j:%d,i:%d||", j, i)
fmt.Printf("\n")
}
fmt.Printf("这一句永远不会被执行\n")
}
}
输出:
j:0,i:0||
j:1,i:0||
j:2,i:0||
j:3,i:0||
由于标签的作用对象是外层循环,所以 break 也是 break 外层