讀《Go併發編程實戰》第4章 流程控制方式

       說實話,該書前面講的枯燥冗長,看的有點打瞌睡,而我本身又是有一個有強迫症的人,喜歡一個字一個字地摳,最終結果是一看就困,而後轉天再看再困,依次循環......。編程

       這就總會讓我本身有點遐想,本身也寫一本關於Go的書算了,但由於平時真的太忙了,稍有時間時又貢獻給我女兒。我想後面我錄一些視頻,幫助那些想學習編程的人快速入門,打消非計算機專業人員的入門門檻。編程語言

       好了,廢話有點多,仍是耐着性子把做者郝林的這本書看完。ide


       Go語言與其它編程語言相似,也有if語句、switch語句、for循環語句、goto語句,但不一樣之處在於Go語言的循環語句只有for,沒有平時咱們見的while、do-while;在其它編程語言中咱們常常聽到儘可能避免使用goto語句,而讀過謝孟軍的beego代碼的人應該能看到謝大神使用了很多goto語句;此外,Go語言還有defer語句、針對異常處理引入了panic和recover語句等,本章就基本圍繞着這些展開。函數


4.1 基本流程控制學習

首先看一個源代碼:字體

package main

import (
    "fmt"
)

func main(){
    v := []int{1, 2, 3}
    if v != nil{
         var v int = 123
         fmt.Printf("%v\n", v)
    }
    fmt.Printf("%v\n", v)
}

       第一次看Go代碼的人可能會有點不舒服,不過沒有關係,看的多了就舒服了,掌握編程語言最大的訣竅就是多寫,隨便解釋一下:動畫

  • package和import不用多說,屬於工程化思想中的體系部分ui

  • v := []int{1,2,3},上看下看左看右看,也沒有看到v的定義呢?其實這裏:=就是定義加賦值spa

       好,偏離原文太多了,原文在這裏主要說代碼塊和做用域。視頻


4.1.1 代碼塊和做用域

       所謂代碼塊就是一個由花括號「{」和「}」括起來的若干表達式和語句的序列。固然,代碼塊中也能夠不包含任何代碼 ,即爲空代碼塊。就上面源代碼爲例,代碼塊就是main()函數中的內容。

       那麼這個程序運行結果是什麼呢?結果以下:

       123

       [1  2  3]

       之因此兩次打印內容不一樣,就是因爲做用域的緣由,先採用做者的原話。

       「咱們能夠在某個代碼塊中對一個已經在包含它的外層代碼塊中聲明過的標識符進行重聲明。而且,當咱們在內層代碼塊中使用這個標識符的時候,它表明的老是它在內層代碼塊中被重聲明時與它綁定在一塊兒的那個程序實體。也就是說,在這種狀況下,在外層代碼中聲明的那個同名標識符被屏蔽了。

       看懂了嗎?是否是有點繞死了?腦子稍短路一下就死機了,用人話來講是這樣的:   

       main()函數是一個大的代碼塊,它裏面又包括了一個小的代碼塊(對應的if語句),這至關於一間大房子(main)裏面有一個小臥室(if{}),大房子客廳裏有一個叫郝林的人,小臥室中也有一個叫郝林的人,且房子隔音效果很是好。當你進入小臥室時輕輕地喊一聲「郝林」,那麼是小臥室的郝林會迴應你,由於大房子客廳中的那個郝林聽不到;一樣地,當你進入大房子客廳時,你輕輕地喊一聲「郝林」,那麼是大房子客廳的郝林會迴應你,由於小房子中的那個郝林聽不到。

       若是您學過程序編譯原理的話,就很容易明白其中的原理,由於在編譯時,這根本就是兩個不一樣的變量,此處再也不展開,因此有時候想一想視頻有市場是應當的,若是是視頻兩句話就能交待清楚,但文字就得囉嗦不少。


if 100 < number{
     number++
}

這個是一個if語句,固然也符合代碼塊的定義,由花括號括起來的若干表達式和語句的序列。

{
}

這個也是一個代碼塊,儘快沒有任何內容,由花括號括起來的若干表達式和語句的序列,這裏的若干包括0。

for i :=0; i <100; i++ {
    i = i + 4
}

一樣,這也是一個代碼塊。


做者又說,在3.3.3節講過,基本數據類型的值都沒法與空值nil進行判等,而上面源代碼if v != nil { }就沒有編譯錯誤,是由於這裏的v表明的是一個切片類型而不是一個string類型值。這裏稍有點要講解的內容,通常語言是沒有切片的,這是一個特別的類型,回頭我作成一個演示動畫一看就很清楚明白。


4.1.2 if語句

      「Go語言的if語句老是以關鍵字if開始,在這以後,能夠跟一條簡單語句(這裏能夠的意思是說,也能夠沒有),而後是一個做爲條件判斷的布爾類型的表達式以及一個用花括號「{」和「}」括起來的代碼塊」。

       上面這句話比較常,簡單地理解,就是if語句必須是這種形式「if +條件判斷 + {}」,舉兩個粟子:

var i int = 0

if i < 10 {
     i++
}

這個if語句就是典型的if+條件判斷+{ },其中條件判斷爲i<10。固然這個if語句還能夠這樣寫:

if i:=0; i <10 {
     i++
}

這個if語句就是上面藍色字體中說的,在if以後能夠跟一條簡單語句i:=0,即變量i的定義和賦值語句。固然這個if語句還能夠改寫爲:

if i:=0; i <10; i++{

}

這裏把i++也放到了if和「{」之間。引用一下原書內容:

經常使用的簡單語句包括短變量聲明、賦值語句和表達式語句。除了特殊的內建函數和代碼包unsafe中的函數,針對其餘函數和方法的調用表達式和針對通道類型值的接收表達式均可以出如今語句上下文中。換名話說,它們均可以稱爲表達式語句。在必要時,咱們還可使用圓括號「(」和「)」將它們括起來。其餘的簡單語句還包括髮送語句、自增/自減語句和空語句」。

看懂沒有?若沒有看懂就算了,本書做者定義嚴謹,形成沒有接觸過的人很難理解 :)


       下面引用做者的一個例子:

if 100 < number{
    number++
} else {
    number--
}

       可能沒有接觸過的編程語言的小夥伴會問,怎麼還有else呀?這還算不算if語句,哥告訴你,這纔是正宗的,別無二家,回到原書內容。「可能讀者已經注意到了,其中的條件表達式100<number並無被圓括號括起來。實際上,這也是Go語言的流程控制語句的特色之一。另外,跟在條件表達式和else關鍵以後的兩個代碼塊必須由「{」和「}」括起來。這一點是強制的,不論代碼包含幾條語句以及是否包含語句都是如此」。

       這些東西沒有必要特別記憶,多寫幾個例子,天然而然您就知道什麼是正確的什麼是錯誤的。

       「上面示例中,有兩個特殊符號:++和--。它們分別表明了自增語句和自減語句。注意它們並非操做符。++的做用是把它左邊的標識符表明的值與無類型常量1相加並將結果再賦給左邊的標識符,而--的做用則是把它左邊的標識符表明的值與無類型常量1相減並將結果再賦給左邊的標識符。也就是說,自增語句number++與賦值語句number=number+1具備相同的語義,而自減語句number--則與賦值語句number=number-1具備相同的語義。另外,在++和--左邊的並不只限於標識符,還能夠是任何可被賦值的表達式,好比應用在切片類型值或字典類型值之上的索引表達式"。


【更多慣用法】:

這有點相似英語經常使用對話300句。

func Open(name string) (file *File, err error)

       這個就是常見的函數慣用用法,該函數來自標準庫中os包。

       因爲在Go語言中一個函數能夠返回多個結果,所以咱們經常會把函數執行的錯誤也做爲返回結果之一,該函數表達意思是說,您指定一個文件路徑讓Go語言幫您把文件內容讀出來,爲了讀出文件內容,該函數返回您文件的句柄(即第一個參數),同時也返回一個錯誤(即第二個參數),用以表達在打開文件時是否發生了錯誤。具體怎麼用呢?

f, err := os.Open(name)

if err != nil {
     return err
}

繞了半天后,仍是回到if語句上。「總之,if語句常被用來常規錯誤」。

       「在一般狀況下,咱們應該先雲檢查變量err的值是否爲nil,若是變量err的值不爲nil,那麼就說明os.Open函數在被執行過程當中發生了錯誤。這時的f變量的值確定是不可用的。這已是一個約定俗成的規則了」。

        「另外,if語句常被做爲衛述語句。衛述語句是指被用來檢查關鍵的先決條件的合法性並在檢查未經過的狀況下當即終止當前代碼塊的執行的語句。其實,在上一個示例中的if語句就是衛述語句中的一種。它在有錯誤發生的時候當即終止了當前代碼塊的執行並將錯誤返回給外層代碼塊」。

       經過理解一下這段藍色的文字,一般咱們寫程序是這樣的:

func update(id int, deptment string) bool {
     if id <=0 {
          return false
     }
     // 省略若干行
     return true
}

這個沒毛病,update函數開始處的那個if語句就是衛述語句。該函數能夠稍改造一下:

func update(id int, deptment string) error {
     if id <=0 {
         return errors.New("The id is INVALID!")
     }
     // 省略若干行
     return nil
}

下面這個update返回結果再也不是bool值,而是error值,它能夠表示在函數執行期間是否發生了錯誤,並且還能夠體現出錯誤的具體描述。


4.1.3 switch語句

       switch語句與if語句相似,都是一種多分支執行語句,剛接觸編程的人可能有疑惑,爲何要提供兩種呢?我是否只用一個就能夠了?

       固然,您只用其中之一就足夠了,爲何要提供兩種呢?簡單理解仍是慣用習慣吧。

§1.  組成和編寫方法

    「switch可使用表達式或者類型說明符做爲case斷定方法。所以switch語句也就能夠分爲兩類:表達式switch語句和類型switch語句。在表達式switch語句中,每個case攜帶的表達式都會與switch語句要斷定的那個表達式(也稱爲switch表達式)相比較。而在類型switch語句中,每一個case所攜帶的不是表達式而是類型字面量,而且switch語句要斷定的目標也變成了一個特殊的表達式。這個特殊表達式的結果是一個類型而不是一個類型值。

       在女兒的哭聲中我讀這句話真的好吃力,靜下心來也不難理解,看例子就好:


§2.  表達式switch語句

switch 2*3+5{
    default:
         fmt.Println("運算錯誤!")
    case 5 + 5:
         fmt.Println("結果爲10.")
    case 5 + 6:
         fmt.Println("結果爲11.")
}

       快看,快看,switch後面的2*3+5是一個表達式,第一個case後面的5+5也是一個表達式,第二個case後面的5+6也是一個表達式,因此這是一個典型的表達式switch語句。

       在表達式switch語句中,switch表達式和case攜帶的表達式都會被求值。

       程序運行時,先找計算第一個case後面的表達式獲得10,而後與switch的表達式值11進行比較,發現10≠11,接着計算第二個case後面的表達式獲得11,而後與switch的表達式值11進行比較,發現二者相同,打印出「結果爲11.」後就退出該代碼塊。

switch content {
    default:
        fmt.Println("Unknown Language.")
    case "Python":
        fmt.Println("An Interpreted Language.")
    case "Go":
        fmt.Println("A Compiled Language.")
}

       這也是一個表達式switch語句,您可能會想這都沒有計算,怎麼是一個表達式呢?

       姐,表達式不只僅是數學運算,字符串也是喲,若是您實在感受不順眼,改造一下:

switch content := getContent(); content {
    default:
        fmt.Println("Unknown Language.")
    case "Python":
        fmt.Println("An Interpreted Language.")
    case "Go":
        fmt.Println("A Compiled Language.")
}

      在這個示例中,switch語句先調用getConent()函數,而且把它的結果賦給了新聲明的變量content,後面緊接着的就是對content的值進行斷定。看着像是表達式switch嗎?


     「如今來看case語句。一條case語句由一個case表達式和一個語句列表組成,而且這二者之間須要用冒號「:」分隔,在上例的switch語句中,一共有3個case語句,注意default case是一種特殊的case語句。

     「一個case表達式由一個case關鍵字和一個表達式列表組成。注意,這裏說的是一個表達式列表,而不是一個表達式。這意味着,一個case表達式中能夠包含多個表達式。如今,咱們利用這一特性來改造一下上面的switch語句:

switch content := getContent(); content {
    default:
        fmt.Println("Unknown Language.")
    case "Python", "Ruby":
        fmt.Println("An Interpreted Language.")
    case "Go", "Java", "C":
        fmt.Println("A Compiled Language.")
}

其中"Python"和"Ruby"造成一個表達式列表放到了case後面,同理"Go"、"Java"和"C"也造成一個表達式列表放到了另外一個case後面。


因爲Go語言有一個fallthrough關鍵字,因此上面示例可改造以下:

switch content := getContent(); content {
    default:
        fmt.Println("Unknown Language.")
    case "Python":
        fallthrough
    case "Ruby":
        fmt.Println("An Interpreted Language.")
    case "Go", "Java", "C":
        fmt.Println("A Compiled Language.")
}

當content內容爲"Python"時,儘管會匹配"Python"對應的case語句,但因爲fallthrough關鍵字的存在,它讓程序穿越它而向下執行,因此會打印「An Interpreted Language.」,但必定要記住的是fallthrough只能穿越一次。


§3.  類型switch語句

先看個示例吧:

switch v.(type){
    case string:
        fmt.Printf("The string is '%s'.\n", v.(string))
    case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
        fmt.Printf("The integer is %d.\n", v)
    default:
        fmt.Printf("Unsupported value. (type=%T)\n", v)
}

仔細看case後面的表達式,都是string, int, int8, int16等Go語言的類型,因此所謂類型switch語句就是對類型進行斷定,而不是對值進行斷定,其餘方面與表達式switch通常無二。


把這個代碼跑通,須要補充點關於v的內容:

var v interface{} = "aaabbb"

注意這裏是把v定義爲接口,而非string,即var v string = "aaabbb",若是真的這樣定義了v,那麼Go的編譯器就會拋個異常給你看,並說:「我靠,你都知道是什麼類型了,還讓switch判斷,這是耍我玩呀!」。


如今咱們來具體分析這段示例代碼,這個switch語句共包含了3條case語句。

> 第一條case語句的表達式包含了類型string字面量,這意味着若是v的類型是string類型,那麼該分支就會被執行。在這個分支中,咱們使用類型斷言表達式v.(string)把v的值轉換成了string類型的值,並以特定格式打印出來;

> 第二條case語句中的類型字面量有多個,包括了全部的整數類型,這就意味着只要v的類型屬於整數類型,該分支就會被執行。在這個分支中,咱們並無使用類型斷言表達式把v的值轉換成任何一個整數類型的值,而是利用fmt.Printf函數直接打印出了v所表示的整數值;

> 若是v的類型既不是string類型也不是整數類型,那麼default case的分支將會被執行,並使用標準輸出打印v的動態類型(%T)。


須要特別注意的是:fallthrough語句不容許出如今類型switch語句中的任何case語句的語句列表中


最後,值得特別提出的是,類型switch語句的switch表達式還有一種變形寫法。

var v interface{} = "aaabbb"
switch i := v.(type) {
    case string:
        fmt.Printf("The string is '%s'.\n", i)
    case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
        fmt.Printf("The integer is %d.\n", i)
    default:
        fmt.Printf("Unsupport value. (type=%T)\n", i)
}

請注意switch表達式位置上的i := v.(type),這其實是一個短變量聲明,當存在這這種形式的switch表達式的時候,就至關於這個變量i被聲明在了每一個case語名的語句列表的開始處。在每一個case語句中,變量i的類型都是不一樣的,它的類型會和與它處於同一個case語句的case表達式包含的類型字面量所表明的那個類型相等。例如,上面的示例中第一個case語句至關於:

case string:

        i := v.(string)

        fmt.Printf("The string is '%s'.\n", i)


是否是再次被做者的富有九曲十折的表達方式折服?其實做者想表達的意思,簡單來講是這樣的:

switch v.(type){

     case string:

            fmt.Printf("The string is '%s'.\n", v.(string))

}

若是switch表達式只是取變量v的類型,那麼在case語句中必須把變量v進行強制類型轉換;


switch i := v.(type){

     case string:

            fmt.Printf("The string is '%s'.\n", i)

}

若是switch表達式中有對變量v的類型賦值給i,那麼當進入某個case語句中時,至關於變量i在每一個case語句中都有一次具體的類型轉換,switch那麼能夠把它理解爲模板。


好吧,若是越說越胡塗,請您暫時記住這兩種用法就好,隨着代碼寫的愈來愈多,就會逐步明白的。


§4. 更多慣用法

好了,又到了經常使用英語300句了 :)

在很多狀況下switch表達式是缺省掉的,即:

switch{
     case number < 100:
          number++
     case number < 200:
          number--
     default:
          number -= 2
}

看這裏的switch表達式消失了,此種狀況下該switch語句的斷定目標被視爲布爾值true,也就是說,全部case表達式的結果值都應該是布爾類型,因此纔有switch代碼塊中每一個case語句都是number在與數值進行比較,以得到布爾值。

相關文章
相關標籤/搜索