天天看點

讀《Go并發程式設計實戰》第4章 流程控制方式

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

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

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

       Go語言與其它程式設計語言類似,也有if語句、switch語句、for循環語句、goto語句,但不同之處在于Go語言的循環語句隻有for,沒有平時我們見的while、do-while;在其它程式設計語言中我們經常聽到盡量避免使用goto語句,而讀過謝孟軍的beego代碼的人應該能看到謝大神使用了不少goto語句;此外,Go語言還有defer語句、針對異常處理引入了panic和recover語句等,本章就基本圍繞着這些展開。

4.1 基本流程控制

首先看一個源代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<code>package main</code>

<code>import (</code>

<code>    </code><code>"fmt"</code>

<code>)</code>

<code>func main(){</code>

<code>    </code><code>v := []</code><code>int</code><code>{1, 2, 3}</code>

<code>    </code><code>if</code> <code>v != nil{</code>

<code>         </code><code>var v </code><code>int</code> <code>= 123</code>

<code>         </code><code>fmt.Printf(</code><code>"%v\n"</code><code>, v)</code>

<code>    </code><code>}</code>

<code>    </code><code>fmt.Printf(</code><code>"%v\n"</code><code>, v)</code>

<code>}</code>

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

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

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

       好,偏離原文太多了,原文在這裡主要說代碼塊和作用域。

4.1.1 代碼塊和作用域

       所謂代碼塊就是一個由花括号“{”和“}”括起來的若幹表達式和語句的序列。當然,代碼塊中也可以不包含任何代碼 ,即為空代碼塊。就上面源代碼為例,代碼塊就是main()函數中的内容。

       那麼這個程式運作結果是什麼呢?結果如下:

       123

       [1  2  3]

       之是以兩次列印内容不同,就是由于作用域的原因,先采用作者的原話。

       “我們可以在某個代碼塊中對一個已經在包含它的外層代碼塊中聲明過的辨別符進行重聲明。并且,當我們在内層代碼塊中使用這個辨別符的時候,它代表的總是它在内層代碼塊中被重聲明時與它綁定在一起的那個程式實體。也就是說,在這種情況下,在外層代碼中聲明的那個同名辨別符被屏蔽了。”

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

       main()函數是一個大的代碼塊,它裡面又包括了一個小的代碼塊(對應的if語句),這相當于一間大房子(main)裡面有一個小卧室(if{}),大房子客廳裡有一個叫郝林的人,小卧室中也有一個叫郝林的人,且房子隔音效果非常好。當你進入小卧室時輕輕地喊一聲“郝林”,那麼是小卧室的郝林會回應你,因為大房子客廳中的那個郝林聽不到;同樣地,當你進入大房子客廳時,你輕輕地喊一聲“郝林”,那麼是大房子客廳的郝林會回應你,因為小房子中的那個郝林聽不到。

       如果您學過程式編譯原理的話,就很容易明白其中的原理,因為在編譯時,這根本就是兩個不同的變量,此處不再展開,是以有時候想想視訊有市場是應當的,如果是視訊兩句話就能交待清楚,但文字就得啰嗦很多。

<code>if</code> <code>100 &lt; number{</code>

<code>     </code><code>number++</code>

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

<code>{</code>

這個也是一個代碼塊,盡快沒有任何内容,由花括号括起來的若幹表達式和語句的序列,這裡的若幹包括0。

<code>for</code> <code>i :=0; i &lt;100; i++ {</code>

<code>    </code><code>i = i + 4</code>

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

作者又說,在3.3.3節講過,基本資料類型的值都無法與空值nil進行判等,而上面源代碼if v != nil { }就沒有編譯錯誤,是因為這裡的v代表的是一個切片類型而不是一個string類型值。這裡稍有點要講解的内容,一般語言是沒有切片的,這是一個特别的類型,回頭我做成一個示範動畫一看就很清楚明白。

4.1.2 if語句

      “Go語言的if語句總是以關鍵字if開始,在這之後,可以跟一條簡單語句(這裡可以的意思是說,也可以沒有),然後是一個作為條件判斷的布爾類型的表達式以及一個用花括号“{”和“}”括起來的代碼塊”。

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

<code>var i </code><code>int</code> <code>= 0</code>

<code>if</code> <code>i &lt; 10 {</code>

<code>     </code><code>i++</code>

這個if語句就是典型的if+條件判斷+{ },其中條件判斷為i&lt;10。當然這個if語句還可以這樣寫:

<code>if</code> <code>i:=0; i &lt;10 {</code>

這個if語句就是上面藍色字型中說的,在if之後可以跟一條簡單語句i:=0,即變量i的定義和指派語句。當然這個if語句還可以改寫為:

<code>if</code> <code>i:=0; i &lt;10; i++{</code>

這裡把i++也放到了if和“{”之間。引用一下原書内容:

“常用的簡單語句包括短變量聲明、指派語句和表達式語句。除了特殊的内建函數和代碼包unsafe中的函數,針對其他函數和方法的調用表達式和針對通道類型值的接收表達式都可以出現在語句上下文中。換名話說,它們都可以稱為表達式語句。在必要時,我們還可以使用圓括号“(”和“)”将它們括起來。其他的簡單語句還包括發送語句、自增/自減語句和空語句”。

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

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

<code>    </code><code>number++</code>

<code>} </code><code>else</code> <code>{</code>

<code>    </code><code>number--</code>

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

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

       “上面示例中,有兩個特殊符号:++和--。它們分别代表了自增語句和自減語句。注意它們并不是操作符。++的作用是把它左邊的辨別符代表的值與無類型常量1相加并将結果再賦給左邊的辨別符,而--的作用則是把它左邊的辨別符代表的值與無類型常量1相減并将結果再賦給左邊的辨別符。也就是說,自增語句number++與指派語句number=number+1具有相同的語義,而自減語句number--則與指派語句number=number-1具有相同的語義。另外,在++和--左邊的并不僅限于辨別符,還可以是任何可被指派的表達式,比如應用在切片類型值或字典類型值之上的索引表達式"。

【更多慣用法】:

這有點類似英語常用對話300句。

<code>func Open(name string) (file *File, err error)</code>

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

       由于在Go語言中一個函數可以傳回多個結果,是以我們常常會把函數執行的錯誤也作為傳回結果之一,該函數表達意思是說,您指定一個檔案路徑讓Go語言幫您把檔案内容讀出來,為了讀出檔案内容,該函數傳回您檔案的句柄(即第一個參數),同時也傳回一個錯誤(即第二個參數),用以表達在打開檔案時是否發生了錯誤。具體怎麼用呢?

<code>f, err := os.Open(name)</code>

<code>if</code> <code>err != nil {</code>

<code>     </code><code>return</code> <code>err</code>

繞了半天後,還是回到if語句上。“總之,if語句常被用來正常錯誤”。

       “在通常情況下,我們應該先雲檢查變量err的值是否為nil,如果變量err的值不為nil,那麼就說明os.Open函數在被執行過程中發生了錯誤。這時的f變量的值肯定是不可用的。這已經是一個約定俗成的規則了”。

        “另外,if語句常被作為衛述語句。衛述語句是指被用來檢查關鍵的先決條件的合法性并在檢查未通過的情況下立即終止目前代碼塊的執行的語句。其實,在上一個示例中的if語句就是衛述語句中的一種。它在有錯誤發生的時候立即終止了目前代碼塊的執行并将錯誤傳回給外層代碼塊”。

       通過了解一下這段藍色的文字,通常我們寫程式是這樣的:

<code>func update(id </code><code>int</code><code>, deptment string) </code><code>bool</code> <code>{</code>

<code>     </code><code>if</code> <code>id &lt;=0 {</code>

<code>          </code><code>return</code> <code>false</code>

<code>     </code><code>}</code>

<code>     </code><code>// 省略若幹行</code>

<code>     </code><code>return</code> <code>true</code>

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

<code>func update(id </code><code>int</code><code>, deptment string) error {</code>

<code>         </code><code>return</code> <code>errors.New(</code><code>"The id is INVALID!"</code><code>)</code>

<code>     </code><code>return</code> <code>nil</code>

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

4.1.3 switch語句

       switch語句與if語句類似,都是一種多分支執行語句,剛接觸程式設計的人可能有疑惑,為什麼要提供兩種呢?我是否隻用一個就可以了?

       當然,您隻用其中之一就足夠了,為什麼要提供兩種呢?簡單了解還是慣用習慣吧。

§1.  組成和編寫方法

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

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

§2.  表達式switch語句

<code>switch</code> <code>2*3+5{</code>

<code>    </code><code>default</code><code>:</code>

<code>         </code><code>fmt.Println(</code><code>"運算錯誤!"</code><code>)</code>

<code>    </code><code>case</code> <code>5 + 5:</code>

<code>         </code><code>fmt.Println(</code><code>"結果為10."</code><code>)</code>

<code>    </code><code>case</code> <code>5 + 6:</code>

<code>         </code><code>fmt.Println(</code><code>"結果為11."</code><code>)</code>

       快看,快看,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.”後就退出該代碼塊。

<code>switch</code> <code>content {</code>

<code>        </code><code>fmt.Println(</code><code>"Unknown Language."</code><code>)</code>

<code>    </code><code>case</code> <code>"Python"</code><code>:</code>

<code>        </code><code>fmt.Println(</code><code>"An Interpreted Language."</code><code>)</code>

<code>    </code><code>case</code> <code>"Go"</code><code>:</code>

<code>        </code><code>fmt.Println(</code><code>"A Compiled Language."</code><code>)</code>

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

       姐,表達式不僅僅是數學運算,字元串也是喲,如果您實在感覺不順眼,改造一下:

<code>switch</code> <code>content := getContent(); content {</code>

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

     “現在來看case語句。一條case語句由一個case表達式和一個語句清單組成,并且這兩者之間需要用冒号“:”分隔,在上例的switch語句中,一共有3個case語句,注意default case是一種特殊的case語句。”

     “一個case表達式由一個case關鍵字和一個表達式清單組成。注意,這裡說的是一個表達式清單,而不是一個表達式。這意味着,一個case表達式中可以包含多個表達式。現在,我們利用這一特性來改造一下上面的switch語句:”

<code>    </code><code>case</code> <code>"Python"</code><code>, </code><code>"Ruby"</code><code>:</code>

<code>    </code><code>case</code> <code>"Go"</code><code>, </code><code>"Java"</code><code>, </code><code>"C"</code><code>:</code>

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

由于Go語言有一個fallthrough關鍵字,是以上面示例可改造如下:

<code>        </code><code>fallthrough</code>

<code>    </code><code>case</code> <code>"Ruby"</code><code>:</code>

當content内容為"Python"時,盡管會比對"Python"對應的case語句,但由于fallthrough關鍵字的存在,它讓程式穿越它而向下執行,是以會列印“An Interpreted Language.”,但一定要記住的是fallthrough隻能穿越一次。

§3.  類型switch語句

先看個示例吧:

<code>switch</code> <code>v.(type){</code>

<code>    </code><code>case</code> <code>string:</code>

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

<code>    </code><code>case</code> <code>int</code><code>, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:</code>

<code>        </code><code>fmt.Printf(</code><code>"The integer is %d.\n"</code><code>, v)</code>

<code>        </code><code>fmt.Printf(</code><code>"Unsupported value. (type=%T)\n"</code><code>, v)</code>

仔細看case後面的表達式,都是string, int, int8, int16等Go語言的類型,是以所謂類型switch語句就是對類型進行判定,而不是對值進行判定,其他方面與表達式switch一般無二。

把這個代碼跑通,需要補充點關于v的内容:

<code>var v interface{} = </code><code>"aaabbb"</code>

注意這裡是把v定義為接口,而非string,即var v string = "aaabbb",如果真的這樣定義了v,那麼Go的編譯器就會抛個異常給你看,并說:“我靠,你都知道是什麼類型了,還讓switch判斷,這是耍我玩呀!”。

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

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

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

&gt; 如果v的類型既不是string類型也不是整數類型,那麼default case的分支将會被執行,并使用标準輸出列印v的動态類型(%T)。

需要特别注意的是:fallthrough語句不允許出現在類型switch語句中的任何case語句的語句清單中。

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

<code>switch</code> <code>i := v.(type) {</code>

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

<code>        </code><code>fmt.Printf(</code><code>"The integer is %d.\n"</code><code>, i)</code>

<code>        </code><code>fmt.Printf(</code><code>"Unsupport value. (type=%T)\n"</code><code>, i)</code>

請注意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){

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

如果switch表達式中有對變量v的類型指派給i,那麼當進入某個case語句中時,相當于變量i在每個case語句中都有一次具體的類型轉換,switch那麼可以把它了解為模闆。

好吧,如果越說越胡塗,請您暫時記住這兩種用法就好,随着代碼寫的越來越多,就會逐漸明白的。

§4. 更多慣用法

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

在不少情況下switch表達式是預設掉的,即:

<code>switch</code><code>{</code>

<code>     </code><code>case</code> <code>number &lt; 100:</code>

<code>          </code><code>number++</code>

<code>     </code><code>case</code> <code>number &lt; 200:</code>

<code>          </code><code>number--</code>

<code>     </code><code>default</code><code>:</code>

<code>          </code><code>number -= 2</code>

看這裡的switch表達式消失了,此種情況下該switch語句的判定目标被視為布爾值true,也就是說,所有case表達式的結果值都應該是布爾類型,是以才有switch代碼塊中每個case語句都是number在與數值進行比較,以獲得布爾值。

     本文轉自qingkechina 51CTO部落格,原文連結:http://blog.51cto.com/qingkechina/1983173,如需轉載請自行聯系原作者