欢迎访问我的博客,目前从事Machine Learning,欢迎交流

【读书笔记&个人心得】第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 外层

不要使用 goto

posted @ 2023-03-01 11:54  有蚊子  阅读(18)  评论(0编辑  收藏  举报