?年學 go 2:控制流

上一篇咱們瞭解了golang 的變量、函數和基本類型,這一篇將介紹一下控制流

如今咱們看一個複雜點的例子:html

fibonacci(遞歸版)

package main
import "fmt"

func main() {
    result := 0
    for i := 0; i <= 10; i++ {
         result = fibonacci(i)
         fmt.Printf("fibonacci(%d) is: %d\n", i, result)
      }
}

func fibonacci(n int) (res int) {
    if n <= 1 {
        res = 1
       } else {
           res = fibonacci(n-1) + fibonacci(n-2)
       }
    return
}

    // outputs

    fibonacci(0) is: 1
    fibonacci(1) is: 1
    fibonacci(2) is: 2
    fibonacci(3) is: 3
    fibonacci(4) is: 5
    fibonacci(5) is: 8
    fibonacci(6) is: 13
    fibonacci(7) is: 21
    fibonacci(8) is: 34
    fibonacci(9) is: 55
    fibonacci(10) is: 89
  • for i := 0; i <= 10; i++ {} 第7行是一個循環結構 這裏for 循環是一個控制流

控制流

For

Go 只有一種循環接口-- for 循環linux

For 支持三種循環方式,包括類 while 語法git

1 基本for循環 支持初始化語句

s := "abc"
for i, n := 0, len(s); i < n; i++ {
    // i, n 爲定義的變量 只在for 循環內做用
    println(s[i])
}

基本的 for 循環包含三個由分號分開的組成部分:github

  • 初始化語句:在第一次循環執行前被執行
  • 循環條件表達式:每輪迭代開始前被求值
  • 後置語句:每輪迭代後被執行

2 替代 while (n > 0) {}

C 的 while 在 Go 中叫作 forgolang

n := len(s)
// 循環初始化語句和後置語句都是可選的。
for n > 0 { // 等同於 for (; n > 0;) {}
    println(s[n])
    n--
}

3 死循環

for { // while true
        println(s)
    }

IF…ELSE

就像 for 循環同樣,Go 的 if 語句也不要求用 ( ) 將條件括起來,同時, { } 仍是必須有的
  • 條件表達式必須是布爾類型,可省略條件表達式括號
  • 支持初始化語句,可定義代碼塊局部變量
  • 代碼塊左大括號必須在條件表達式尾部
x := 0

// if x > 10  // Error: missing condition in if statement(左大括號必須在條件表達式尾部)
// {
// }

if x > 10{
    ...
}else{
    ...
}

if n := "abc"; x > 0 {  // 初始化語句(在這裏是定義變量)
    println(n[2])
} else if x < 0 {
    println(n[1])
} else {
    println(n[0])     // 局部變量 n 有效範圍是 整個 if/else 塊
}
if 語句定義的變量做用域僅在if範圍以內(包含else語句)
不支持三元操做符 "a > b ? a : b"

以上是上段代碼出現的兩個控制流,剩下的控制流還有數據庫

  • Switch
  • Range
  • Goto, Break, Continue, defer

Switch

switch 語句用於選擇執行,語法以下:express

switch optionalStatement; optionalExpression{
        case expressionList1: block1
        ...
        case expressionListN: blockN
        default: blockD
    }

先看一個例子:小程序

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os { // 將 os 與 case 條件匹配
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    case "plan9", "openbsd": // 多個條件命中其一便可(OR)
        fmt.Println("plan9 | openbsd")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}
  • 若是有可選語句聲明, 分號是必要的, 不管後邊的可選表達式語句是否出現(若是可選語句沒有出現默認爲true)
  • 每個case 語句必需要有一個表達式列表,多個用分號隔開
  • switch 語句自上而下執行,當匹配成功後執行case分支的代碼塊,執行結束後退出switch
switch i {
case 0: // 空分支,只有當 i == 0 時纔會進入分支 至關於 "case 0: break;"
case 1:
    f() // 當 i == 0 時函數不會被調用
}
  • 若是想要在執行完每一個分支的代碼後還繼續執行後續的分支代碼,可使用fallthrough 關鍵字達到目的
package main

import "fmt"

func switch1(n int) {
    switch { // 這裏用的是沒有條件的switch 語句會直接執行
    case n == 0:
        fmt.Println(0)
        fallthrough  // fallthrough 需放在 case 塊結尾,可用 break 阻止
    case n == 1: // 若是匹配到0 這裏會繼續執行
        fmt.Println(1)
    case n == 2: // fallthrough 不會對這裏有做用
        fmt.Println(2)
    default:
        fmt.Println("default")
    }
}

func main() {
    switch1(0)
}
# output
0
1
  • 用 default 能夠指定當其餘全部分支都不匹配的時候的行爲
switch i {
        case 0:
        case 1:
            f()
        default:
            g()  // 當i不等於0 或 1 時調用
    }

Range

Range 相似迭代器的操做,返回(索引,值)或(健,值)

它能夠迭代任何一個集合(包括字符串、數組、數組指針、切片、字典、通道)windows

基本語法以下:設計模式

coll := 3string["a", "b", "c"]
for ix, val := range coll {
   ...
}

// 容許返回單值

for ix := range coll {
    println(ix, coll[ix])
}

// 也可使用 _ 忽略

for _, val := range coll {
    println(val)
}

// 也能夠只迭代,不返回。可用來執行清空 channel 等操做

for range coll {
    ...
}

val 始終爲集合中對應索引的值拷貝,所以它通常只具備只讀性質,對它所作的任何修改都不會影響到集合中原有的值(譯者注:若是 val 爲指針,則會產生指針的拷貝,依舊能夠修改集合中的原值
一個字符串是 Unicode 編碼的字符(或稱之爲 rune)集合,所以您也能夠用它迭代字符串

下面是每種數據類型使用range時 ix和val 的值

date type ix value 值類型
string index s[index] unicode, rune
array/slice index s[index]
map key m[index]
channel element
range 會複製目標數據。字符串、切片基本結構是個很小的結構體,而字典、通道自己是指針封裝,複製成本很小,無需專門優化。

若是是數組,可改爲數組指針或者切片類型。

Break continue

break 和 continue 均可在多級嵌套循環中跳出

break 可用於 for、switch、select語句,終止整個語句塊執行

continue 僅能 於 for 循環,終止後續操做,當即進入下一輪循環。

goto

goto 語句能夠配合標籤(label)形式的標識符使用,即某一行第一個以冒號:結尾的單詞,標籤區分大小寫。

package main

func main() {
    i:=0
    HERE:
        print(i)
        i++
        if i==5 {
            return
        }
        goto HERE
}
# output 01234
使用標籤和 goto 語句是不被鼓勵的:它們會很快致使很是糟糕的程序設計,並且總有更加可讀的替代方案來實現相同的需求。

for、switch 或 select 語句均可以配合標籤(label)形式的標識符使用

package main

import "fmt"

func main() {

LABEL1:
    for i := 0; i <= 5; i++ {
        for j := 0; j <= 5; j++ {
            if j == 4 {
                continue LABEL1
            }
            fmt.Printf("i is: %d, and j is: %d\n", i, j)
        }
    }
}
continue 語句指向 LABEL1,當執行到該語句的時候,就會跳轉到 LABEL1 標籤的位置

defer

defer 語句會延遲函數的執行直到上層函數返回

延遲調用的參數會馬上生成,可是在上層函數返回前函數都不會被調用

package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}

// output
hello
world

defer 棧

延遲的函數調用被壓入一個棧中。當函數返回時, 會按照後進先出的順序調用被延遲的函數調用。
defer 經常使用來定義簡單的方法

package main

import "fmt"

func main() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
            defer fmt.Println(i)
        }

        fmt.Println("done")
}
// 能夠想一下會輸出什麼
// 代碼執行 https://tour.go-zh.org/flowcontrol/13

關鍵字 defer 容許咱們進行一些函數執行完成後的收尾工做,例如:

  • 關閉文件流:

    // open a file defer file.Close()

  • 解鎖一個加鎖的資源

    mu.Lock() defer mu.Unlock()

  • 打印最終報告

    printHeader() defer printFooter()

  • 關閉數據庫連接

    // open a database connection defer disconnectFromDB()

合理使用 defer 語句可以使得代碼更加簡潔。

下面的代碼展現了在調試時使用 defer 語句的手法

package main

import (
    "io"
    "log"
)

func func1(s string) (n int, err error) {
    defer func() {
            log.Printf("func1(%q) = %d, %v", s, n, err)
        }()
    return 7, io.EOF
}

func main() {
    func1("Go")
}

// 輸出
Output: 2016/04/25 10:46:11 func1("Go") = 7, EOF

更多defer 的用法(https://blog.go-zh.org/defer-panic-and-recover)

參考連接

Go 指南
The way to go -- 控制結構
Effective Go

到這裏簡單的控制流用法講解就結束了

下節將會是golang 數據結構部分, 會用到的代碼爲

fibonacci(內存版)

package main

 import (
     "fmt"
     "time"
 )

 const LIM = 41

 var fibs [LIM]uint64

 func main() {
     var result uint64 = 0
     start := time.Now()
     for i := 0; i < LIM; i++ {
         result = fibonacci(i)
         fmt.Printf("fibonacci(%d) is: %d\n", i, result)
     }
     end := time.Now()
     delta := end.Sub(start)
     fmt.Printf("longCalculation took this amount of time: %s\n", delta)
 }
 func fibonacci(n int) (res uint64) {
     // memoization: check if fibonacci(n) is already known in array:
     if fibs[n] != 0 {
         res = fibs[n]
         return
     }
     if n <= 1 {
         res = 1
     } else {
         res = fibonacci(n-1) + fibonacci(n-2)
     }
     fibs[n] = res
     return
 }

最後,感謝女友支持和包容,比❤️

想了解如下內容能夠在公號輸入相應關鍵字獲取歷史文章: 公號&小程序 | 設計模式 | 併發&協程

相關文章
相關標籤/搜索