程序的流程结构 程序的流程控制结构一共有三种:顺序结构,选择结构,循环结构。
顺序结构:从上向下,逐行执行。
选择结构:条件满足,某些代码才会执行。0-1次
分支语句:if,switch,select
循环结构:条件满足,某些代码会被反复的执行多次。0-N次
循环语句:for
条件语句 If语句 语法格式:
1 2 3 4 5 if 布尔表达式 { } else { }
1 2 3 4 5 6 7 if 布尔表达式1 { } else if 布尔表达式2 { } else { }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a int = 10 if a < 20 { fmt.Printf("a 小于 20\n" ) } fmt.Printf("a 的值为 : %d\n" , a) }
如果其中包含一个可选的语句组件(在评估条件之前执行),则还有一个变体。它的语法是
1 2 3 4 5 if statement; condition { } if condition{ }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func main () { if 7 %2 == 0 { fmt.Println("7 is even" ) } else { fmt.Println("7 is odd" ) } if 8 %4 == 0 { fmt.Println("8 is divisible by 4" ) } if num := 9 ; num < 0 { fmt.Println(num, "is negative" ) } else if num < 10 { fmt.Println(num, "has 1 digit" ) } else { fmt.Println(num, "has multiple digits" ) } }
需要注意的是,num的定义在if里,那么只能够在该if..else语句块中使用,否则编译器会报错的。
switch语句:“开关” switch是一个条件语句,它计算表达式并将其与可能匹配的列表进行比较,并根据匹配执行代码块。它可以被认为是一种惯用的方式来写多个if else子句。
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。 switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加break 。
而如果switch没有表达式,它会匹配true
Go里面switch默认相当于每个case最后带有break ,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。
变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。 可以同时测试多个可能符合条件的值,使用逗号分割它们 ,例如:case val1, val2, val3。
1 2 3 4 5 6 7 8 switch var1 { case val1: ... case val2: ... default : ... }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "fmt" "time" ) func main () { var grade = "B" var marks = 90 switch marks { case 90 : grade = "A" case 80 : grade = "B" case 50 , 60 , 70 : grade = "C" default : grade = "D" } switch { case grade == "A" : fmt.Printf("优秀!\n" ) case grade == "B" , grade == "C" : fmt.Printf("良好\n" ) case grade == "D" : fmt.Printf("及格\n" ) case grade == "F" : fmt.Printf("不及格\n" ) default : fmt.Printf("差\n" ) } fmt.Printf("你的等级是 %s\n" , grade) t := time.Now() switch { case t.Hour() < 12 : fmt.Println("It's before noon" ) default : fmt.Println("It's after noon" ) } }
输出:
1 2 3 优秀! 你的等级是 A It's before noon
fallthrough 如需贯通后续的case,就添加fallthrough
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" ) func main () { switch x := 5 ; x { default : fmt.Println(x) case 5 : x += 10 fmt.Println(x) fallthrough case 6 : x += 20 fmt.Println(x) } }
运行结果:
解释:
根据switch语句的逻辑,首先会执行default分支,但是由于我们没有在default分支中写入任何代码,所以会直接跳过。在case 5分支的最后,我们使用了fallthrough关键字。fallthrough关键字的作用是强制执行下一个case分支的代码,而不进行条件判断。
在switch
语句中,default
用于处理没有匹配到任何case
的情况。当switch
表达式的值与所有的case
都不匹配时,程序会执行default
分支中的代码。
default
分支是可选的,也就是说可以选择是否在switch
语句中包含它。如果没有default
分支,而且没有任何一个case
匹配到switch
表达式的值,那么switch
语句将不会执行任何代码。
default
分支通常被用作最后一个分支,用于处理不常见或者意外的情况。它可以用来提供一个默认的处理逻辑,或者给用户一个错误提示。
以下是一个示例,展示了default
的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { num := 10 switch num { case 1 , 2 , 3 : fmt.Println("小数" ) case 4 , 5 , 6 : fmt.Println("中数" ) case 7 , 8 , 9 : fmt.Println("大数" ) default : fmt.Println("未知数" ) } }
在上面的示例中,如果num
的值不属于1到9之间的任何一个数,那么就会执行default
分支,并输出”未知数”。default
分支可以用来处理一些边缘情况或者未预料到的情况,确保程序的健壮性。
case中的表达式是可选的,可以省略。如果该表达式被省略,则被认为是switch true,并且每个case表达式都被计算为true,并执行相应的代码块 。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" ) func main () { num := 75 switch { case num >= 0 && num <= 50 : fmt.Println("num is greater than 0 and less than 50" ) case num >= 51 && num <= 100 : fmt.Println("num is greater than 51 and less than 100" ) case num >= 101 : fmt.Println("num is greater than 100" ) } }
输出:
1 num is greater than 51 and less than 100
switch的注意事项
case后的常量值不能重复
case后可以有多个常量值
fallthrough应该是某个case的最后一行。如果它出现在中间的某个地方,编译器就会抛出错误。
Type Switch switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
1 2 3 4 5 6 7 8 9 switch x.(type ){ case type : statement(s); case type : statement(s); default : statement(s); }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { var x interface {} switch i := x.(type ) { case nil : fmt.Printf(" x 的类型 :%T" ,i) case int : fmt.Printf("x 是 int 型" ) case float64 : fmt.Printf("x 是 float64 型" ) case func (int ) float64 : fmt.Printf("x 是 func(int) 型" ) case bool , string : fmt.Printf("x 是 bool 或 string 型" ) default : fmt.Printf("未知型" ) } }
运行结果:
解释:
在这段代码中,变量x
的类型被声明为interface{}
,它是一个空接口。空接口可以表示任意类型的值,包括nil
。当我们使用类型断言x.(type)
来获取x
的具体类型时,如果x
的值是nil
,那么case nil
分支会匹配成功。这里需要注意的是,nil
是一个特殊的值,表示一个指针类型或接口类型的零值,表示该指针或接口不指向任何具体的对象。在这种情况下,我们可以将其视为一种特殊的类型。因此,case nil
分支被用来处理x
为nil
的情况。
循环语句 循环语句表示条件满足,可以反复的执行某段代码。
for是唯一的循环语句。(Go没有while循环)
for语句 语法结构:
1 for init; condition; post { }
初始化语句只执行一次。在初始化循环之后,将检查该条件。如果条件计算为true,那么{}中的循环体将被执行,然后是post语句。post语句将在循环的每次成功迭代之后执行。在执行post语句之后,该条件将被重新检查。如果它是正确的,循环将继续执行,否则循环终止。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" ) func main () { for i := 1 ; i <= 10 ; i++ { fmt.Printf(" %d" ,i) } }
在for循环中声明的变量仅在循环范围内可用。因此,i不能在外部访问循环。
所有的三个组成部分,即初始化、条件和post都是可选的。
效果与while相似
效果与for(;;) 一样
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环
1 2 3 for key, value := range oldMap { newMap[key] = value }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func main () { var b int = 9 var a int numbers := [6 ]int {1 , 2 , 3 , 5 } for a := 0 ; a < 5 ; a++ { fmt.Printf("a 的值为: %d\n" , a) } for a < b { a++ fmt.Printf("a 的值为: %d\n" , a) } for i,x:= range numbers { fmt.Printf("第 %d 位 x 的值 = %d\n" , i,x) } }
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 a 的值为: 0 a 的值为: 1 a 的值为: 2 a 的值为: 3 a 的值为: 4 a 的值为: 1 a 的值为: 2 a 的值为: 3 a 的值为: 4 a 的值为: 5 a 的值为: 6 a 的值为: 7 a 的值为: 8 a 的值为: 9 第 0 位 x 的值 = 1 第 1 位 x 的值 = 2 第 2 位 x 的值 = 3 第 3 位 x 的值 = 5 第 4 位 x 的值 = 0 第 5 位 x 的值 = 0
多层for循环 for循环中又有循环嵌套,就表示多层循环了。
跳出循环的语句 break语句 break:跳出循环体。break语句用于在结束其正常执行之前突然终止for循环
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" ) func main () { for i := 1 ; i <= 10 ; i++ { if i > 5 { break } fmt.Printf("%d " , i) } fmt.Printf("\nline after for loop" ) }
输出:
1 2 1 2 3 4 5 line after for loop
continue语句 continue:跳出一次循环。continue语句用于跳过for循环的当前迭代。在continue语句后面的for循环中的所有代码将不会在当前迭代中执行。循环将继续到下一个迭代。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" ) func main () { for i := 1 ; i <= 10 ; i++ { if i%2 == 0 { continue } fmt.Printf("%d " , i) } }
输出:
goto语句 goto:可以无条件地转移到过程中指定的行。
语法结构:
1 2 3 4 goto label;.. .. label: statement;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func main () { var a = 10 LOOP: for a < 20 { if a == 15 { a = a + 1 goto LOOP } fmt.Printf("a的值为 : %d\n" , a) a++ } }
输出:
1 2 3 4 5 6 7 8 9 a的值为 : 10 a的值为 : 11 a的值为 : 12 a的值为 : 13 a的值为 : 14 a的值为 : 16 a的值为 : 17 a的值为 : 18 a的值为 : 19
统一错误处理 多处错误处理存在代码重复时是非常棘手的,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 err := firstCheckError() if err != nil { goto onExit } err = secondCheckError() if err != nil { goto onExit } fmt.Println("done" ) return onExit: fmt.Println(err) exitProcess()
生成随机数 示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" "math/rand" "time" ) func main () { num1 := rand.Int() fmt.Println(num1) for i := 0 ; i < 10 ; i++ { num := rand.Intn(10 ) fmt.Println(num) } rand.Seed(1000 ) num2 := rand.Intn(10 ) fmt.Println("--->" , num2) t1 := time.Now() fmt.Println(t1) fmt.Printf("%T\n" , t1) timeStamp1 := t1.Unix() fmt.Println(timeStamp1) timestamp2 := t1.UnixNano() fmt.Println(timestamp2) rand.Seed(time.Now().UnixNano()) for i := 0 ; i < 10 ; i++ { fmt.Println("--->" , rand.Intn(100 )) } num3 := rand.Intn(46 ) + 3 fmt.Println(num3) num4 := rand.Intn(62 ) + 15 fmt.Println(num4) }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 2359980755445512729 6 0 9 3 2 ---> 5 2023-06-02 12:00:50.6796224 +0800 CST m=+0.001542201 time.Time 1685678450 1685678450679622400 ---> 74 ---> 79 ---> 18 ---> 21 ---> 68 ---> 41 ---> 33 ---> 31 ---> 26 ---> 37 15
解释:
在随机数生成中,种子(seed)是用于初始化随机数生成器的值。种子决定了随机数序列的起始点。在某种程度上,相同种子会生成相同的随机数序列。
在上述代码中,rand.Seed()
方法用于设置随机数生成器的种子。如果没有显式地设置种子,Go语言的math/rand
包默认使用一个固定的种子,这意味着每次程序运行时都会生成相同的随机数序列。这在某些情况下可能不是我们所期望的。
通过调用rand.Seed()
方法并传入一个不同的种子值,我们可以改变随机数生成器的起始点,从而产生不同的随机数序列。通常情况下,我们会使用当前时间的纳秒级时间戳作为种子,以确保每次运行程序时都能生成不同的随机数序列。
在代码中,rand.Seed(1000)
将种子设置为固定值1000,因此后续生成的随机数序列将始终相同。而rand.Seed(time.Now().UnixNano())
使用当前时间的纳秒级时间戳作为种子,可以产生不同的随机数序列。
但是自从Go 1.2 0以来,rand.Seed就已经被弃用了 !