1、if-else
条件语句模型:
if 条件 1 { 分支 1 } else if 条件 2 { 分支 2 } else if 条件 ... { 分支 ... } else { 分支 else }
Go编译器,对于 { 和 } 的位置有严格的要求,它要求 else if (或 else)和 两边的花括号,必须在同一行。
由于 Go是 强类型,所以要求你条件表达式必须严格返回布尔型的数据(nil 和 0 和 1 都不行
这里有一个高级写法,可以允许先运行一个表达式,获取变量后再进行判断
func main() { if age := 20;age > 18 { fmt.Println("已经成年了") } }
2、switch-case
语句模型
switch 表达式 { case 表达式1: 代码块 case 表达式2: 代码块 case 表达式3: 代码块 case 表达式4: 代码块 case 表达式5: 代码块 default: 代码块 }
拿 switch 后的表达式分别和 case 后的表达式进行对比,只要有一个 case 满足条件,就会执行对应的代码块,然后直接退出 switch - case ,如果不相等就判断下一个case,如果 一个都没有满足,才会执行 default 的代码块。
一个case后面可以接多个条件
case 后可以接多个条件,多个条件之间是 或 的关系,用逗号相隔。
func main() { month := 2 switch month { case 3, 4, 5: fmt.Println("春天") case 6, 7, 8: fmt.Println("夏天") case 9, 10, 11: fmt.Println("秋天") case 12, 1, 2: fmt.Println("冬天") default: fmt.Println("输入有误...") } } //output 冬天
case 条件常量不能重复
当 case 后接的是常量时,该常量只能出现一次,如果出现多次,在编译的时候会报错
switch 后可接函数
switch 后面可以接一个函数,只要保证 case 后的值类型与函数的返回值 一致即可。
// 判断一个同学是否有挂科记录的函数 // 返回值是布尔类型 func getResult(args ...int) bool { for _, i := range args { if i < 60 { return false } } return true } func main() { chinese := 80 english := 50 math := 100 switch getResult(chinese, english, math) { // case 后也必须 是布尔类型 case true: fmt.Println("该同学所有成绩都合格") case false: fmt.Println("该同学有挂科记录") } }
switch 可不接表达式
switch 后可以不接任何变量、表达式、函数。
当不接任何东西时,switch - case 就相当于 if - else if - else
score := 30 switch { case score >= 95 && score <= 100: fmt.Println("优秀") case score >= 80: fmt.Println("良好") case score >= 60: fmt.Println("合格") case score >= 0: fmt.Println("不合格") default: fmt.Println("输入有误...") }
switch 的穿透能力
正常情况下 switch - case 的执行顺序是:只要有一个 case 满足条件,就会直接退出 switch - case ,如果 一个都没有满足,才会执行 default 的代码块。
但是有一种情况是例外。
那就是当 case 使用关键字 *fallthrough *开启穿透能力的时候。
s := "hello" switch { case s == "hello": fmt.Println("hello") fallthrough case s != "world": fmt.Println("world") } //output hello world
需要注意的是,fallthrough 只能穿透一层,意思是它让你直接执行下一个case的语句,而且不需要判断条件。
3、for循环
基本模型
for [condition | ( init; condition; increment ) | Range] { statement(s); }
for 后面,可以接四种类型的表达式:
1、接一个条件表达式
a := 1 for a <= 5 { fmt.Println(a) a ++ } //output 1 2 3 4 5
2、接三个表达式
(1)第一个表达式:初始化控制变量,在整个循环生命周期内,只运行一次;
(2)第二个表达式:设置循环控制条件,当返回true,继续循环,返回false,结束循环;
(3)第三个表达式:每次循完开始(除第一次)时,给控制变量增量或减量。
func main() { for i := 1; i <= 5; i++ { fmt.Println(i) } } //output 1 2 3 4 5
3、接一个 range 表达式
遍历一个可迭代的对象
range 后可接数组、切片,字符串等
由于 range 会返回两个值:索引和数据,若你后面的代码用不到索引,需要使用 _ 表示
func main() { myarr := [...]string{"world", "python", "go"} for _, item := range myarr { fmt.Printf("hello, %s\n", item) } } //output hello, world hello, python hello, go
如果你用一个变量来接收的话,接收到的是索引
func main() { myarr := [...]string{"world", "python", "go"} for i := range myarr { fmt.Printf("hello, %v\n", i) } } //output hello, 0 hello, 1 hello, 2
4、不接表达式
类似于C语言中的while循环,不添加表达式就代表一直是true
但是一般我们并不会让程序处于死循环,在满足一定的条件下,可以使用关键字 break 退出循环体,也可以使用 continue 直接跳到下一循环。
这两种都是不添加表达式的写法:
for { 代码块 } // 等价于 for ;; { 代码块 }
func main() { var i int = 1 for { if i > 5 { break } fmt.Printf("hello, %d\n", i) i++ } } //output hello, 1 hello, 2 hello, 3 hello, 4 hello, 5
4、goto无条件跳转
goto 后接一个标签,这个标签的意义是告诉 Go程序下一步要执行哪里的代码。
所以这个标签如何放置,放置在哪里,是 goto 里最需要注意的。
goto 标签; ... ... 标签: 表达式;
goto 可以打破原有代码执行顺序,直接跳转到某一行执行代码。
func main() { goto flag fmt.Println("B") flag: fmt.Println("A") } //执行结果,并不会输出 B ,而只会输出 A A
goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。
循环功能
func main() { i := 1 flag: if i <= 5 { fmt.Println(i) i++ goto flag } } //output 1 2 3 4 5
使用 goto 实现 类型 break 的效果。
func main() { i := 1 for { if i > 5 { goto flag } fmt.Println(i) i++ } flag: } //output 1 2 3 4 5
使用 goto 实现 类型 continue的效果
func main() { i := 1 flag: for i <= 10 { if i%2 == 1 { i++ goto flag } fmt.Println(i) i++ } } //output 2 4 6 8 10
注意事项
goto语句与标签之间不能有变量声明,否则编译错误。
func main() { fmt.Println("start") goto flag var say = "hello oldboy" fmt.Println(say) flag: fmt.Println("end") } //编译错误 .\main.go:7:7: goto flag jumps over declaration of say at .\main.go:8:6
4、defer 延迟语句
- 延迟调用
defer 的用法很简单,只要在后面跟一个函数的调用,就能实现将这个 xxx 函数的调用延迟到当前函数执行完后再执行。defer xxx()
func myfunc() { fmt.Println("B") } func main() { defer myfunc() fmt.Println("A") } //output A B
即时求值的变量快照
使用 defer 只是延时调用函数,此时传递给函数里的变量,不应该受到后续程序的影响。func main() { name := "go" defer fmt.Println(name) // 输出: go name = "python" fmt.Println(name) // 输出: python } //output python go
可见给 name 重新赋值为 python,后续调用 defer 的时候,仍然使用未重新赋值的变量值,就好在 defer 这里,给所有的这是做了一个快照一样。
多个defer 反序调用--多个defer 是反序调用的,有点类似栈一样,后进先出
func main() { name := "go" defer fmt.Println(name) // 输出: go name = "python" defer fmt.Println(name) // 输出: python name = "java" fmt.Println(name) } //output java python go
defer 与 return 孰先孰后
var name string = "go" func myfunc() string { defer func() { name = "python" }() fmt.Printf("myfunc 函数里的name:%s\n", name) return name } func main() { myname := myfunc() fmt.Printf("main 函数里的name: %s\n", name) fmt.Println("main 函数里的myname: ", myname) } //output myfunc 函数里的name:go main 函数里的name: python main 函数里的myname: go
来一起理解一下这段代码,第一行很直观,name 此时还是全局变量,值还是go
第二行也不难理解,在 defer 里改变了这个全局变量,此时name的值已经变成了 python
重点在第三行,为什么输出的是 go ?
解释只有一个,那就是 defer 是return 后才调用的。所以在执行 defer 前,myname 已经被赋值成 go 了。
5. 为什么要有 defer?
若是没有 defer,你可以写出这样的代码
func f() { r := getResource() //0,获取资源 ...... if ... { r.release() //1,释放资源 return } ...... if ... { r.release() //2,释放资源 return } ...... if ... { r.release() //3,释放资源 return } ...... r.release() //4,释放资源 return }
使用了 defer 后,代码就显得简单直接,不管你在何处 return,都会执行 defer 后的函数。
func f() { r := getResource() //0,获取资源 defer r.release() //1,释放资源 ...... if ... { ... return } ...... if ... { ... return } ...... if ... { ... return } ...... return }