作為一門高階語言,Go同樣提供了流程控制的支援。在瞭解了基礎結構之後,繼續學習Go的流程控制,裡面涉及到的基礎結構的內容還能對其有更多的瞭解。
說流程控制之前先說一下interface,因為後續在流程控制中會穿插著對interface的使用。
interface
interface是一切型別的基型別,類似於Java中的基類Obejct
,所有的結構都是interface的實現,因為interface基型別沒有定義任何的函式,所以其他任何結構都認為是interface的實現。當然,也可以自己定義interface自己去實現相應的函式,這個下期物件導向的時候會詳細解釋。這裡先簡單說明interface作為基型別時的使用。
在Java中,所有的型別都是Object的子類,所以宣告物件時可以將物件的型別宣告為Object,在賦值時給一個子型別,在Go中同樣可以,但僅限於針對interface宣告的使用(還是會牽涉到物件導向的東西),也就是說,宣告時可以將變數宣告為interface型別,賦值時給一個其他基礎型別的值,這是最簡單的interface作為基型別的使用。
var hello interface{} = "hello world"
fmt.Println(hello)
複製程式碼
例子中宣告hello時,宣告的型別是interface{}型別,並不是string型別,但是賦值時給的是string型別,說明hello實際型別還是string型別。具體的型別轉換下面會詳細說明。
if-else
Go中的if-else結構的使用者與Java中的特別的類似,僅僅區別在兩者的語法上面,Go的語法為:
if 條件1 {
...
} else if 條件2 && 條件3 {
...
} else {
...
}
複製程式碼
Go對語法的要求沒有Java那麼嚴格,對於括號可以帶,也可以不帶。同樣的,Go也支援&&
、||
、!
這樣的運算子進行多個條件的關聯判斷
func max(a, b int) (max int) {
if a > b {
max = a
} else if a == b {
max = a
} else {
max = b
}
return
}
複製程式碼
斷言
斷言在Go中是一種型別轉換的語法,能否方便的進行型別的轉換。Go語言中簡單的斷言語法為 value := element.(type)
//value := element.(type) //type為要轉換的型別
var hello interface{} = "helloworld"
fmt.Println(hello.(string))
fmt.Println(hello.(int))//該行會報錯,因為hello實際型別是string型別
複製程式碼
稍微不注意,直接轉換的話就會出現異常,所以一般不推薦使用簡單的語法,而是用高階語法 value, ok := element.(type)
,這也是在if-else結構中講解的原因。
// value, ok := element.(type) //type為要轉換的型別,ok為是否成功轉換,型別為bool,value為實際轉換的值
var hello interface{} = "helloworld"
helloS, ok := hello.(string)
if ok {
fmt.Println("hello tranfer successfully : ", helloS)
} else {
fmt.Println("hello transfer failed")
}
複製程式碼
使用高階語法能保證在執行的時候不會出現錯誤,保證程式的持續執行,這是比較推薦的做法。
map斷言是map的一種高階用法。
//map的斷言
// value, ok := m[key] //這裡的OK不再是簡單的成功或者失敗,理解成是否存在更合適
var m = make(map[string]interface{})//建立map的方式,具體make的用法後續會講解
m["key1"] = "value1"
value1, ok := m["key1"]
if ok {
fmt.Println("map m contain 'key1' ", value1)
} else {
fmt.Println("map m contain 'key1'")
}
複製程式碼
map在斷言的使用上好像是天生支援似的,不需要進行Contains函式的校驗等,直接使用,平時在程式碼中使用的也是非常多。簡直不要太好用。
switch
switch感覺像是if-else的高階版,同樣是進行條件判斷的結構,不同的條件執行不同的語句。語法類似Java,Java中只能使用byte、int、short、char和string,在Go中可沒有這些限制。
從上至下的判斷,直到找到匹配的case或者執行default語句,case結尾也不需要break進行跳出流程操作,執行完自動跳出。相反,如果想執行下一個case的話,需要使用fallthrough
關鍵字進行下沉操作,
這時候下一條case的條件將被忽略。
switch value1 { //大括號必須與switch保持一行
case value1:
...
case value2, value3://多個條件使用逗號隔開
...
default://沒有符合的條件執行預設
...
}
複製程式碼
語法規定 switch後跟的value1可以是任意型別(甚至是不寫),但是case後的條件必須和switch後的value保持相同型別
grade := 10
switch grade {
//case code < 60://code為int型別,不能使用code < 60作為case條件
case 10:
fmt.Println("不及格")
case 70:
fmt.Println("及格")
default:
fmt.Println("無效的分數")
}
//用於型別斷言
switch hello.(type) {
case string:
fmt.Println("hello is string")
case int:
fmt.Println("hello is int")
default:
fmt.Println("hello is unknown type")
}
switch {//直接判斷case
case a < b:
fmt.Println("a less than b")
fallthrough //緊接著執行下一個case,不需要進行判斷
case a > b:
fmt.Println("a bigger than b")
}
複製程式碼
for
說到迴圈、重複執行等首先想到的就是for,Go同樣提供了支援,相對於Java,Go中for的使用更靈活。
同樣的,想跳出for迴圈時使用break
關鍵字。
//語法一
for init;條件;賦值{//左側大括號必須與for同行
...
}
//語法二
for 條件 {//左側大括號必須與for同行
...
}
//語法三
//這是個死迴圈
for {//左側大括號必須與for同行
...
}
//語法四
for index, value := range slice/array/map {//range是關鍵字
...
}
複製程式碼
上手就是一個排序來介紹最基本的for結構
a := []int{1, 3, 9, 4, 1, 4, 6, 132, 1, 29, 43, 55, 89, 46}
for i := 0; i < len(a); i++ {//len為Go內建函式
for j := i + 1; j < len(a); j++ {
if a[i] > a[j] {
a[i], a[j] = a[j], a[i]
}
}
}
fmt.Println(a)//結果:[1 1 1 3 4 4 6 9 29 43 46 55 89 132]
複製程式碼
只寫條件的for迴圈,類似Java中的while
var i = 0
for i < len(a) {
fmt.Print(a[i]," ")
i++
}//結果: 1 1 1 3 4 4 6 9 29 43 46 55 89 132
複製程式碼
死迴圈寫法更簡單了,不過需要注意使用break進行跳出,否則電腦就該嗡嗡嗡~響不停了
i = 0
for{
if i < len(a) {
fmt.Print(a[i], " ")
i++
} else {
break
}
}//結果: 1 1 1 3 4 4 6 9 29 43 46 55 89 132
複製程式碼
最牛的語法四就是為slice和array使用的,能遍歷所有的集合。當遍歷slice和array時,index指的是其中的索引位置;遍歷map時指的就是key了。請看下面的例子
for index, value := range a {
fmt.Printf("index: %d, value: %d \n", index, value)
}
/*
結果:
index: 0, value: 1
index: 1, value: 1
index: 2, value: 1
index: 3, value: 3
index: 4, value: 4
index: 5, value: 4
index: 6, value: 6
index: 7, value: 9
index: 8, value: 29
index: 9, value: 43
index: 10, value: 46
index: 11, value: 55
index: 12, value: 89
index: 13, value: 132
*/
m := map[string]string{}
m["hello"] = "world"
m["hey"] = "bye"
for key, value := range m {
fmt.Printf("key: %s, value: %s \n", key, value)
}
/*
結果:
key: hello, value: world
key: hey, value: bye
*/
複製程式碼
select
select 第一眼看到可能會想到SQL中的選擇,但是它也是Go中的一個流程控制關鍵字。
select的使用主要是結合channel來使用,所以這裡要是講解channels會設計到很多東西,我們後期會做詳細的講解,這裡先做select的介紹。
select的語法跟switch類似,用於選擇合適的條件進行執行相應的邏輯,但牽涉到channel,所以select中的case都是對channel的操作,只能是往channel中讀或者寫。
select {
case channel讀操作:
...
case channel寫操作:
...
default:
...
}
複製程式碼
注意點:
channel包含讀和寫兩種操作,case中必須包含一種操作
case的執行是無序的、隨機的,select會執行任意一個可執行的case
沒有可執行的case時會執行default,沒有default的話就會阻塞,等待可執行的channel
下面是一個簡單的例子實現,先不要深究內容含義,瞭解select語法即可
c := make(chan int, 1)
select {
case c <- 1:
fmt.Println("push into channel")
case <-c:
fmt.Println("get from channel")
default:
fmt.Println("default")
}
//結果:push into channel
複製程式碼
...
不要懷疑標題,標題就是三個英文點,這裡要說一下這三個點的問題,以此來解釋一下為什麼在使用fmt.Println()和fmt.Printf()函式時使用逗號將引數隔開的問題。
我們先看一下fmt.Println()和fmt.Printf()的原始碼
// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
複製程式碼
這裡看到Println()和Printf()這兩個函式其實就一個入參,為什麼我能用逗號分隔從而給多個引數呢?
原因是這樣的,a ...interface{}
這個其實是slice的一個特殊用法,說明這定義的是一個可變引數,可以接收不定數量的統一型別的引數,定義為...interfaec{}就可以接收不定數量的任意基礎型別。定義可變引數時的語法就是在型別前面加上這三個點,這裡使用interface就說明可以接收任何型別
想使用這可變引數的語法也很簡單,可以將其作為slice使用,也可以繼續將其作為可變引數使用。使用可變引數的語法就是在定義的後面加上這三個點。下面看例子
func main(){
definedThreeDot("jack", "rose", "tom", "jerry")//定義多個引數來使用可變引數
}
func definedThreeDot(source ...string) {//定義可變引數,定義時在型別前面加上三個點
useThreeDot(source...)//將可變引數作為可變引數使用,使用時在定義後面加上三個點
useThreeDotAsSlice(source)//將可變引數作為slice使用
}
func useThreeDotAsSlice(ss []string) {//定義slice來接收可變引數
fmt.Println(ss)//直接列印slice
}
func useThreeDot(ss ...string) {//定義可變引數,定義時在型別前面加上三個點
for index, s := range ss {//作為slice來遍歷可變引數
fmt.Printf("index : %d, value : %s \n", index, s)//index和s都作為可變引數來使用
}
}
/*
結果:
index : 0, value : jack
index : 1, value : rose
index : 2, value : tom
index : 3, value : jerry
[jack rose tom jerry]
*
/
複製程式碼
總結
Go 中的流程控制大致上就這麼多,平時專案中使用的也是非常多的,特別是對便利集合時,非常的方便。相信你親自體驗後也會讚不絕口的。
同時也順帶解釋了一下可變引數,結合著slice和流程控制也能對這個可變引數有一個更深的瞭解。
原始碼可以通過'github.com/souyunkutech/gosample'獲取。
關注我們的「微信公眾號」
首發微信公眾號:Go技術棧,ID:GoStack
版權歸作者所有,任何形式轉載請聯絡作者。
作者:搜雲庫技術團隊 出處:gostack.souyunku.com/2019/04/29/…