【跟著我們學Golang】流程控制

搜雲庫技術團隊發表於2019-05-13

作為一門高階語言,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'獲取。

關注我們的「微信公眾號」

【跟著我們學Golang】流程控制


首發微信公眾號:Go技術棧,ID:GoStack

版權歸作者所有,任何形式轉載請聯絡作者。

作者:搜雲庫技術團隊 出處:gostack.souyunku.com/2019/04/29/…

相關文章