您好,登錄后才能下訂單哦!
文章轉載請注明出處www.leexide.com
希望每一位尋求轉載的朋友都能夠按照要求進行,鼓勵原創,尊重原創。
微信公眾號:DevOps運維運營之家
QQ號碼:1045884038
E-mail:leexide@126.com
如有問題或建議,請關注微信公眾號
流程控制是順序編程中必不可少的一部分,它是整個編程基礎的重要一環。在順序編程的流程控制部分,Go語言和其他主流語言有一些差別,主要體現在Go語言沒有do-while
語句,因此for
語句擁有更廣泛的含義與用途。另一方面switch
語句也有一些擴展,例如支持類型判斷和初始化子語句等。
除了這些常見的流程控制語法的關鍵字,Go語言還有三個特殊的關鍵字,分別是:
defer
:用于捕獲異常和資源回收等工作;select
:用于多分支選擇(配合通道使用);go
:用于異步啟動goroutine并執行特定函數。例子:
package main
import "fmt"
func main() {
a := 1
if a < 20 {
fmt.Printf("a小于20\n")
}
fmt.Printf("a的值是:%d\n", a)
}
例子:
package main
import "fmt"
func main() {
a := 100
if a < 20 {
fmt.Printf("a小于20\n")
} else {
fmt.Printf("a大于20\n")
}
fmt.Printf("a的值是:%d\n", a)
}
例子:
package main
import "fmt"
func main() {
a := 100
if a < 20 {
fmt.Printf("a小于20\n")
if a > 10 {
fmt.Printf("a大于10\n")
} else {
fmt.Printf("a小于10\n")
}
} else {
fmt.Printf("a大于20\n")
}
fmt.Printf("a的值是:%d\n", a)
}
}
說明:
上面的代碼嵌套了一層if-else
語句,這在編程規范中可以說大忌,特別是在邏輯復雜的情況下,嵌套if語句將非常影響性能,因此就有了else-if
語句。
else-if
語句是在前面if-else
語句之上再度擴展的,為了解決多重判斷的問題。例如:
package main
import "fmt"
func main() {
a := 11
if a > 20 {
fmt.Printf("a大于20\n")
} else if a < 10 {
fmt.Printf("a小于10\n")
} else {
fmt.Printf("a大于10\n")
fmt.Printf("a小于20\n")
}
fmt.Printf("a的值是:%d\n", a)
}
else-if
語句可以連續使用多個else if
關鍵字,例如判斷一個數字是否大于10小于20且不等于11:
package main
import "fmt"
func main() {
a := 13
if a > 20 {
fmt.Printf("a大于20\n")
} else if a < 10 {
fmt.Printf("a小于10\n")
} else if a == 11 {
fmt.Printf("a等于11\n")
} else {
fmt.Printf("a大于10\n")
fmt.Printf("a小于20\n")
fmt.Printf("a不等于11\n")
}
fmt.Printf("a的值是:%d\n", a)
}
if語句可以有一個子語句,用于初始化局部變量,例如:
package main
import "fmt"
func main() {
if a := 10; a < 20 {
fmt.Printf("a小于20\n")
} else {
fmt.Printf("a的值是:%d\n", a)
}
}
注意1:
子語句只能有一個表達式,比如下面的例子是無法編譯的。
func main() {
if b := 10; a := 10; a < 20 { //編譯出錯,初始化子語句只能有一個表達式
fmt.Printf("a小于20\n")
} else {
fmt.Printf("a的值是:%d\n", a)
}
}
注意2:
a的值是在if代碼塊中定義的,所以不能在代碼塊之外調用,例如下面代碼也是無法編譯的:
func main() {
if a := 10; a < 20 {
fmt.Printf("a小于20\n")
}
//編譯出錯,a是在if代碼塊中定義的,所以不能再函數中調用
fmt.Printf("a的值是:%d\n", a)
}
在上面使用if條件語句時,一般用于二元判斷,對于多元判斷使用條件語句就會顯得煩瑣,所以就有了選擇語句。例如判斷一個數字屬于哪個區間就可以用選擇語句輕松實現,避免條件語句煩瑣的嵌套過程。
在Go語言中,switch
表示選擇語句的關鍵字,switch
語句會根據初始化表達式得出一個值,然后根據case
語句的條件,執行相應的代碼塊,最終返回特定內容。每個case
被稱為一種情況,只有當初始化語句的值符合case
的條件語句時,case
才會被執行。
如果沒有遇到符合的case
,則可以使用默認的case
(default case
),如果己經遇到了
符合的case
,那么后面的case
都不會被執行。
與其他編程語言不同的是,在Go語言編程中,switch
有兩種類型。
switch
:在表達式switch
中,case
包含與switch
表達式的值進行比較的表達式。switch
:在類型switch
中,case
包含與特殊注釋的switch
表達式的類型進行比較的類型。例子:
package main
import "fmt"
func main() {
grade := "B"
marks := 90
switch marks {
case 90:
grade = "A"
case 80:
grade = "B"
case 60, 70:
grade = "C"
default:
grade = "D"
}
fmt.Printf("你的成績為%s\n", grade)
}
這種方式雖然簡單,但一般不這樣寫,因為不容易擴展這個選擇語句,例如把分數改為100或者91時,也會返回“你的成績為D”,這明顯不符合實際情況。這是因為選擇語句中沒有考慮值的范圍的問題,現在假設90分到100分為A, 80分到89分為B, 60分到79分為C ,低于60分則為D 。這種情況下上面的單值判斷己經不能滿足我們此時的需求。
因此就需要完整的switch 表達式寫法了:
package main
import "fmt"
func main() {
grade := "E"
marks := 90
switch {
case marks >= 90:
grade = "A"
case marks >= 80:
grade = "B"
case marks >= 70:
grade = "C"
case marks >= 60:
grade = "D"
default:
grade = "E"
}
switch {
case grade == "A":
fmt.Printf("你的成績優秀!\n")
case grade == "B":
fmt.Printf("表現良好!\n")
case grade == "C", grade == "D": //case表達式可以有多個
fmt.Printf("再接再厲!\n")
default:
fmt.Printf("成績不合格!\n")
}
fmt.Printf("你的成績為%s\n", grade)
}
上面例子中有兩個switch
相互關聯,第一個switch
語句判斷成績所屬區間,并得出grade
的值, 然后第二個switch
根據grade
的值返回相應的話語。
注意,每一個case
都可以擁有多個表達式,它的含義與fallthrough
一樣,例如下面兩種寫法是一樣的意思:
/* 多表達式寫法 */
case grade == "C", grade == "D": //case表達式可以有多個
fmt.Printf("再接再厲!\n")
/* fallthrough寫法 */
case grade == "C":
fallthrough
case grade == "D":
fmt.Printf("再接再厲!\n")
fallthrough
關鍵詞可以把當前case
控制權交給下一個case
語句判斷。
類型switch
語句針對變量類型判斷執行哪個case
代碼塊,下面是一個簡單的例子:
package main
import "fmt"
var x interface{} //空接口
func main() {
x = 1
switch i := x.(type) { //這里表達式只有一句初始化子語句
case nil:
fmt.Printf("這里是nil,x的類型是%T", i)
case int:
fmt.Printf("這里是int,x的類型是%T", i)
case float64:
fmt.Printf("這里是float64,x的類型是%T", i)
case bool:
fmt.Printf("這里是bool,x的類型是%T", i)
case string:
fmt.Printf("這里是string,x的類型是%T", i)
default:
fmt.Printf("未知類型")
}
}
類型switch
的初始化子語句中需要判斷的變量必須是具有接口類型的變量(如果是固定類型的變量就沒有判斷的意義了)。在語法上類型switch
與表達式switch
沒有太大區別。
switch
語句同樣擁有初始化子語句,和if
一樣均是寫在關鍵字后面,只能有一句語句,例如:
/* 單值判斷寫法 */
switch marks := 90; marks {
case 90:
grade = "A"
case 80:
grade = "B"
case 70:
grade = "C"
case 60:
grade = "D"
default:
grade = "E"
}
/* 范圍表達式寫法 */
switch marks := 90; { //這里的分號不能省略
case marks >= 90:
grade = "A"
case marks >= 80:
grade = "B"
case marks >= 70:
grade = "C"
case marks >= 60:
grade = "D"
default:
grade = "E"
}
在Go語言中,除了switch
語句,還有一種選擇語句一-select
,這種選擇語句用于配合通道(channel
)的讀寫操作,用于多個channel
的并發讀寫操作。
switch
是按順序從上到下依次執行的,而select
是隨機選擇一個case
來判斷,直到匹配其中的一個case
,舉個例子:
package main
import "fmt"
func main() {
a := make(chan int, 1024)
b := make(chan int, 1024)
for i := 0; i < 10; i++ {
fmt.Printf("第%d次", i)
a <- 1
b <- 1
select {
case <-a:
fmt.Println("from a")
case <-b:
fmt.Println("from b")
}
}
}
上述代碼的意思是,同時在a
和b
中選擇,哪個有內容就從哪個讀,由于channel
的讀寫操作是阻塞操作,使用select
語句可以避免單個channel
的阻塞。此外select
同樣可以使用default
代碼塊,用于避免所有channel
同時阻塞。
循環語句是編程中常使用的流程控制語句之一,在Go語言中,循環語句的關鍵字是for
,沒有while
關鍵字。for
語句可以根據指定的條件重復執行其內部的代碼塊,這個判斷條件一般是由for
關鍵字后面的子語句給出的。
例如一個簡單的for
循環例子:
package main
import "fmt"
func main() {
for a := 0; a < 5; a++ {
fmt.Printf("a的值是:%d\n", a)
}
}
上面for
關鍵字后面有三個子語句,初始化變量a
為0
,并判斷當a
小于5
時執行下面代碼塊的內容,每次判斷a
的值都加l
,直到不符合初始化語句的判斷條件,進而退出循環。
for
語句后面的三個子語句我們稱為:
這三者不能顛倒順序,其中條件子語句是必需的,條件子語句會返回一個布爾型,true
則執行代碼塊,false
則跳出循環。
例如以下示例代碼中就省略了初始化子語句和后置子語句:
package main
import "fmt"
func main() {
a := 0
b := 5
for a < b {
a++
fmt.Printf("a的值是:%d\n", a)
}
}
在上面的例子中,for
關鍵字后面只有一個a< b
的判斷語句,這是典型的條件判斷語
句,它實際上是三個子語句的簡寫:
for ; a < b ;
//Go語言編譯器會自動判斷三個子語句中是否存在條件子語句
//例如寫成這樣就會報錯
for ; ; a < b
后置子語句的意思就是先進行條件子語句判斷,for
代碼塊執行之后再對條件變量操作的語句進行判斷,上面例子中的a++
就是一個后置子語句。
每一個for
語句都可以使用一個特殊的range
子語句,其作用類似于迭代器,用于輪詢數組或者切片值中的每一個元素,也可以用于輪詢字符串的每一個字符,以及字典值中的每個鍵值對,甚至還可以持續讀取一個通道類型值中的元素。
例子:
package main
import "fmt"
func main() {
str := "abcz"
for i, char := range str {
fmt.Printf("字符串第%d個字符的值為%d\n", i, char)
}
for _, char := range str { //忽略第一個值(忽略index)
println(char)
}
for i := range str { //忽略第二個值
fmt.Println(i)
}
for range str { //忽略全部返回值,只執行下面代碼塊
println("執行成功")
}
}
range
關鍵宇右邊是range
表達式,表達式一般寫在for
語句前面,以便提高代碼易讀性。像上面的例子可以寫成:
for i := range "abcz"
//把原來的str := "abcz"表達式寫到range關鍵字后面
這樣不僅降低了可讀性,也不容易管理后續的循環代碼塊。
range
關鍵宇左邊表示的是一對索引-值對,根據不同的表達式返回不同的結果,詳見下表。
右邊表達式返回的類型 | 第一個值 | 第二個值 |
---|---|---|
string | index | str[index],返回類型為rune |
array/slice | index | str[index] |
map | key | m[key] |
channel | element |
從上表可以看出,除了輪詢字符串,還支持其他類型,例如數組,切片,字典甚至通道等等。
例子:
package main
import "fmt"
func main() {
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
println(k, v)
}
numbers := []int{1, 2, 3, 4}
for i, x := range numbers {
fmt.Printf("第%d次,x的值為%d。\n", i, x)
}
}
返回第一個值為索引值(鍵值),有時候并不是我們所需要的,因此可以使用“_
”空標識符表示忽略第一個返回值。對于空字典或切片、空數組、空字符串等情況,for
語句會直接結束,不會循環。
但如果需要指定for
執行循環的次數,例如需要獲取數組(或者其他支持的類型)里面的值,而數組中有一個值是空字符串,則可以指定數組(或其他支持類型)長度,強制讓for
循環執行相應次數,例如將上面的例子稍微改一下:
package main
import "fmt"
func main() {
numbers := [5]int{1, 2, 3, 4}
for i, x := range numbers {
fmt.Printf("第%d次,x的值為%d。\n", i, x)
}
}
由于定義了numbers
長度為5
,但numbers
中只有4
個值,因此最后一個為空值,從for
循環返回的信息可以看到第5
次x
的值為0
,代碼塊的確執行了5
次。
defer
用于延遲調用指定函數,defer
關鍵字只能出現在函數內部。
例子:
package main
import "fmt"
func main() {
defer fmt.Println("World")
fmt.Println("Hello")
}
上面例子會首先打印Hello
,然后再打印World
,因為第一句使用了defer
關鍵字, defer
語句會在函數最后執行,被延遲的操作是defer
后面的內容。
defer
后面的表達式必須是外部函數的調用,上面的例子就是針對fmt.Println
函數的延遲調用。defer
有兩大特點:
defer
語句全部執行,defer
所在函數才算真正結束執行。defer
語句時,需要等待所有defer
語句執行完畢,才會執行return
語句。因為defer
的延遲特點,可以把defer
語句用于回收資源、清理收尾等工作。使用defer
語句之后,不用糾結回收代碼放在哪里,反正都是最后執行。
這里需要注意defer
的執行時機,例如下面的例子:
package main
import "fmt"
var i = 0
func print() {
fmt.Println(i)
}
func main() {
for ; i < 5; i++ {
defer print()
}
}
上面例子返回的是5
個5
,這是因為每個defer
都是在函數輪詢之后,最后才執行,此時i
的值當然就是5
了。如果要正確反向打印數字則應該這樣寫:
package main
import "fmt"
var i = 0
func print(i int) {
fmt.Println(i)
}
func main() {
for ; i < 5; i++ {
defer print(i)
}
}
上面例子引入了函數參數,雖然還沒介紹函數的概念,但是不妨礙理解這里面的defer
關鍵知識。這里之所以是一個反序的數字列表,是因為defer
其實是一個棧,遵循先入后出,或者理解為后進先出。
當i
等于0 時,defer
語句第一次被壓棧,此時defer
后面的函數返回0
; i
不斷自增,一直到i
等于4
時,defer
語句第5
次入棧,defer
后的函數返回4
;此時i
的自增不再滿足for
條件,于是跳出循環,在結束之前,Go語言會根據defer
后進先出原則逐條打印棧內的數值,于是就出現現在看到的結果了。
簡單來說就是當一個函數內部有多個defer
語句時,最后面的defer
語句最先執行(當然是指在所有defer
語句中) 。
在Go語言中,有一個特殊的概念就是標簽,可以給for
、switch
或select
等流程控制代碼塊打上一個標簽,配合標簽標識符可以方便跳轉到某一個地方繼續執行,有助于提高編程效率。標簽格式如下:
L1:
for i := 0; i <= 5; i++ {
//代碼塊
}
//下面寫法也可以,不過Go語言編譯器會自動格式化為上面的格式
L2:switch i {
//代碼塊
}
標簽的名稱是區分大小寫的,為了提高代碼易讀性,建議標簽名稱使用大寫字母和數字。標簽可以標記任何語句, 并不限定于流程控制語句,未使用的標簽會引發錯誤。
break
語句意思是打斷,這個語句表示打斷當前流程控制。
例子:
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Printf("i的值是:%d\n", i)
if i > 4 {
break
}
}
}
上面例子中break
的用法與主流編程語言并無差別,當i >4
時,break
語句打斷了后續的循環操作,跳出循環。
但是對于嵌套流程控制語句,例如下面這種情況:
package main
import "fmt"
func main() {
for {
x := 1
switch {
case x > 0:
fmt.Println("A")
break
case x == 1:
fmt.Println("B")
default:
fmt.Println("C")
}
}
}
上面的例子會一直返回A
,無限循環。上面代碼出現的break
語句只是跳出了switch
流程,并沒有跳出for
循環,所以這個程序會一直執行下去。
為了跳出指定的流程控制代碼塊,就需要標簽出場了:
package main
import "fmt"
func main() {
LOOP1:
for {
x := 1
switch {
case x > 0:
fmt.Println("A")
break LOOP1
case x == 1:
fmt.Println("B")
default:
fmt.Println("C")
}
}
}
上面代碼中的break LOOPl
表示跳出到標簽為LOOPl
的代碼塊之外。
與break
相反,continue
用于跳轉到指定代碼塊位置繼續執行任務,continue
僅能用于for
循環。例如與上面幾乎一樣的代碼,只是改動了一個關鍵宇:
package main
import "fmt"
func main() {
LOOP1:
for i := 0; i <= 5; i++ {
switch {
case i > 0:
fmt.Println("A")
continue LOOP1
case i == 1:
fmt.Println("B")
default:
fmt.Println("C")
}
fmt.Printf("i is:%d\n", i)
}
}
上面代碼中因為使用了continue
語句跳轉到了外圍for
循環中,與break
不同的是,continue
表示跳轉后繼續執行操作。
Go語言中的goto
語句可以無條件跳轉到相同函數中的帶標簽語句。標簽、goto
等關鍵字都并非Go語言獨創,Go語言可以說是一門大量參考了其他語言優點的編程語言,在流程控制上做了一些擴增(如類型switch
),同時也減少了一些關鍵宇(如while
) 。
package main
func main() {
var i int
for {
println(i)
i++
if i > 2 {
goto BREAK
}
}
BREAK:
println("break")
}
上面代碼中,goto
語句指向了BREAK
標簽,所以當循環中遇到i>2
時,直接跳轉打印break
。goto
只能在同一個函數中跳轉。
下面的是我的公眾號二維碼,歡迎關注。文章轉載請注明出處www.leexide.com
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。