<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 2.3 流程和函數 這小節我們要介紹Go里面的流程控制以及函數操作。 ## 流程控制 流程控制在編程語言中是最偉大的發明了,因為有了它,你可以通過很簡單的流程描述來表達很復雜的邏輯。Go中流程控制分三大類:條件判斷,循環控制和無條件跳轉。 ### if `if`也許是各種編程語言中最常見的了,它的語法概括起來就是:如果滿足條件就做某事,否則做另一件事。 Go里面`if`條件判斷語句中不需要括號,如下代碼所示 if x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") } Go的`if`還有一個強大的地方就是條件判斷語句里面允許聲明一個變量,這個變量的作用域只能在該條件邏輯塊內,其他地方就不起作用了,如下所示 // 計算獲取值x,然后根據x返回的大小,判斷是否大于10。 if x := computedValue(); x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") } //這個地方如果這樣調用就編譯出錯了,因為x是條件里面的變量 fmt.Println(x) 多個條件的時候如下所示: if integer == 3 { fmt.Println("The integer is equal to 3") } else if integer < 3 { fmt.Println("The integer is less than 3") } else { fmt.Println("The integer is greater than 3") } ### goto Go有`goto`語句——請明智地使用它。用`goto`跳轉到必須在當前函數內定義的標簽。例如假設這樣一個循環: func myFunc() { i := 0 Here: //這行的第一個詞,以冒號結束作為標簽 println(i) i++ goto Here //跳轉到Here去 } >標簽名是大小寫敏感的。 ### for Go里面最強大的一個控制邏輯就是`for`,它即可以用來循環讀取數據,又可以當作`while`來控制邏輯,還能迭代操作。它的語法如下: for expression1; expression2; expression3 { //... } `expression1`、`expression2`和`expression3`都是表達式,其中`expression1`和`expression3`是變量聲明或者函數調用返回值之類的,`expression2`是用來條件判斷,`expression1`在循環開始之前調用,`expression3`在每輪循環結束之時調用。 一個例子比上面講那么多更有用,那么我們看看下面的例子吧: package main import "fmt" func main(){ sum := 0; for index:=0; index < 10 ; index++ { sum += index } fmt.Println("sum is equal to ", sum) } // 輸出:sum is equal to 45 有些時候需要進行多個賦值操作,由于Go里面沒有`,`操作符,那么可以使用平行賦值`i, j = i+1, j-1` 有些時候如果我們忽略`expression1`和`expression3`: sum := 1 for ; sum < 1000; { sum += sum } 其中`;`也可以省略,那么就變成如下的代碼了,是不是似曾相識?對,這就是`while`的功能。 sum := 1 for sum < 1000 { sum += sum } 在循環里面有兩個關鍵操作`break`和`continue` ,`break`操作是跳出當前循環,`continue`是跳過本次循環。當嵌套過深的時候,`break`可以配合標簽使用,即跳轉至標簽所指定的位置,詳細參考如下例子: for index := 10; index>0; index-- { if index == 5{ break // 或者continue } fmt.Println(index) } // break打印出來10、9、8、7、6 // continue打印出來10、9、8、7、6、4、3、2、1 `break`和`continue`還可以跟著標號,用來跳到多重循環中的外層循環 `for`配合`range`可以用于讀取`slice`和`map`的數據: for k,v:=range map { fmt.Println("map's key:",k) fmt.Println("map's val:",v) } 由于 Go 支持 “多值返回”, 而對于“聲明而未被調用”的變量, 編譯器會報錯, 在這種情況下, 可以使用`_`來丟棄不需要的返回值 例如 for _, v := range map{ fmt.Println("map's val:", v) } ### switch 有些時候你需要寫很多的`if-else`來實現一些邏輯處理,這個時候代碼看上去就很丑很冗長,而且也不易于以后的維護,這個時候`switch`就能很好的解決這個問題。它的語法如下 switch sExpr { case expr1: some instructions case expr2: some other instructions case expr3: some other instructions default: other code } `sExpr`和`expr1`、`expr2`、`expr3`的類型必須一致。Go的`switch`非常靈活,表達式不必是常量或整數,執行的過程從上至下,直到找到匹配項;而如果`switch`沒有表達式,它會匹配`true`。 i := 10 switch i { case 1: fmt.Println("i is equal to 1") case 2, 3, 4: fmt.Println("i is equal to 2, 3 or 4") case 10: fmt.Println("i is equal to 10") default: fmt.Println("All I know is that i is an integer") } 在第5行中,我們把很多值聚合在了一個`case`里面,同時,Go里面`switch`默認相當于每個`case`最后帶有`break`,匹配成功后不會自動向下執行其他case,而是跳出整個`switch`, 但是可以使用`fallthrough`強制執行后面的case代碼。 integer := 6 switch integer { case 4: fmt.Println("The integer was <= 4") fallthrough case 5: fmt.Println("The integer was <= 5") fallthrough case 6: fmt.Println("The integer was <= 6") fallthrough case 7: fmt.Println("The integer was <= 7") fallthrough case 8: fmt.Println("The integer was <= 8") fallthrough default: fmt.Println("default case") } 上面的程序將輸出 The integer was <= 6 The integer was <= 7 The integer was <= 8 default case ## 函數 函數是Go里面的核心設計,它通過關鍵字`func`來聲明,它的格式如下: func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { //這里是處理邏輯代碼 //返回多個值 return value1, value2 } 上面的代碼我們看出 - 關鍵字`func`用來聲明一個函數`funcName` - 函數可以有一個或者多個參數,每個參數后面帶有類型,通過`,`分隔 - 函數可以返回多個值 - 上面返回值聲明了兩個變量`output1`和`output2`,如果你不想聲明也可以,直接就兩個類型 - 如果只有一個返回值且不聲明返回值變量,那么你可以省略 包括返回值 的括號 - 如果沒有返回值,那么就直接省略最后的返回信息 - 如果有返回值, 那么必須在函數的外層添加return語句 下面我們來看一個實際應用函數的例子(用來計算Max值) package main import "fmt" // 返回a、b中最大值. func max(a, b int) int { if a > b { return a } return b } func main() { x := 3 y := 4 z := 5 max_xy := max(x, y) //調用函數max(x, y) max_xz := max(x, z) //調用函數max(x, z) fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在這直接調用它 } 上面這個里面我們可以看到`max`函數有兩個參數,它們的類型都是`int`,那么第一個變量的類型可以省略(即 a,b int,而非 a int, b int),默認為離它最近的類型,同理多于2個同類型的變量或者返回值。同時我們注意到它的返回值就是一個類型,這個就是省略寫法。 ### 多個返回值 Go語言比C更先進的特性,其中一點就是函數能夠返回多個值。 我們直接上代碼看例子 package main import "fmt" //返回 A+B 和 A*B func SumAndProduct(A, B int) (int, int) { return A+B, A*B } func main() { x := 3 y := 4 xPLUSy, xTIMESy := SumAndProduct(x, y) fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) } 上面的例子我們可以看到直接返回了兩個參數,當然我們也可以命名返回參數的變量,這個例子里面只是用了兩個類型,我們也可以改成如下這樣的定義,然后返回的時候不用帶上變量名,因為直接在函數里面初始化了。但如果你的函數是導出的(首字母大寫),官方建議:最好命名返回值,因為不命名返回值,雖然使得代碼更加簡潔了,但是會造成生成的文檔可讀性差。 func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B Multiplied = A*B return } ### 變參 Go函數支持變參。接受變參的函數是有著不定數量的參數的。為了做到這點,首先需要定義函數使其接受變參: func myfunc(arg ...int) {} `arg ...int`告訴Go這個函數接受不定數量的參數。注意,這些參數的類型全部是`int`。在函數體中,變量`arg`是一個`int`的`slice`: for _, n := range arg { fmt.Printf("And the number is: %d\n", n) } ### 傳值與傳指針 當我們傳一個參數值到被調用函數里面時,實際上是傳了這個值的一份copy,當在被調用函數中修改參數值的時候,調用函數中相應實參不會發生任何變化,因為數值變化只作用在copy上。 為了驗證我們上面的說法,我們來看一個例子 package main import "fmt" //簡單的一個函數,實現了參數+1的操作 func add1(a int) int { a = a+1 // 我們改變了a的值 return a //返回一個新值 } func main() { x := 3 fmt.Println("x = ", x) // 應該輸出 "x = 3" x1 := add1(x) //調用add1(x) fmt.Println("x+1 = ", x1) // 應該輸出"x+1 = 4" fmt.Println("x = ", x) // 應該輸出"x = 3" } 看到了嗎?雖然我們調用了`add1`函數,并且在`add1`中執行`a = a+1`操作,但是上面例子中`x`變量的值沒有發生變化 理由很簡單:因為當我們調用`add1`的時候,`add1`接收的參數其實是`x`的copy,而不是`x`本身。 那你也許會問了,如果真的需要傳這個`x`本身,該怎么辦呢? 這就牽扯到了所謂的指針。我們知道,變量在內存中是存放于一定地址上的,修改變量實際是修改變量地址處的內存。只有`add1`函數知道`x`變量所在的地址,才能修改`x`變量的值。所以我們需要將`x`所在地址`&x`傳入函數,并將函數的參數的類型由`int`改為`*int`,即改為指針類型,才能在函數中修改`x`變量的值。此時參數仍然是按copy傳遞的,只是copy的是一個指針。請看下面的例子 package main import "fmt" //簡單的一個函數,實現了參數+1的操作 func add1(a *int) int { // 請注意, *a = *a+1 // 修改了a的值 return *a // 返回新值 } func main() { x := 3 fmt.Println("x = ", x) // 應該輸出 "x = 3" x1 := add1(&x) // 調用 add1(&x) 傳x的地址 fmt.Println("x+1 = ", x1) // 應該輸出 "x+1 = 4" fmt.Println("x = ", x) // 應該輸出 "x = 4" } 這樣,我們就達到了修改`x`的目的。那么到底傳指針有什么好處呢? - 傳指針使得多個函數能操作同一個對象。 - 傳指針比較輕量級 (8bytes),只是傳內存地址,我們可以用指針傳遞體積大的結構體。如果用參數值傳遞的話, 在每次copy上面就會花費相對較多的系統開銷(內存和時間)。所以當你要傳遞大的結構體的時候,用指針是一個明智的選擇。 - Go語言中`channel`,`slice`,`map`這三種類型的實現機制類似指針,所以可以直接傳遞,而不用取地址后傳遞指針。(注:若函數需改變`slice`的長度,則仍需要取地址傳遞指針) ### defer Go語言中有種不錯的設計,即延遲(defer)語句,你可以在函數中添加多個defer語句。當函數執行到最后時,這些defer語句會按照逆序執行,最后該函數返回。特別是當你在進行一些打開資源的操作時,遇到錯誤需要提前返回,在返回前你需要關閉相應的資源,不然很容易造成資源泄露等問題。如下代碼所示,我們一般寫打開一個資源是這樣操作的: func ReadWrite() bool { file.Open("file") // 做一些工作 if failureX { file.Close() return false } if failureY { file.Close() return false } file.Close() return true } 我們看到上面有很多重復的代碼,Go的`defer`有效解決了這個問題。使用它后,不但代碼量減少了很多,而且程序變得更優雅。在`defer`后指定的函數會在函數退出前調用。 func ReadWrite() bool { file.Open("file") defer file.Close() if failureX { return false } if failureY { return false } return true } 如果有很多調用`defer`,那么`defer`是采用后進先出模式,所以如下代碼會輸出`4 3 2 1 0` for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } ### 函數作為值、類型 在Go中函數也是一種變量,我們可以通過`type`來定義它,它的類型就是所有擁有相同的參數,相同的返回值的一種類型 type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) 函數作為類型到底有什么好處呢?那就是可以把這個類型的函數當做值來傳遞,請看下面的例子 package main import "fmt" type testInt func(int) bool // 聲明了一個函數類型 func isOdd(integer int) bool { if integer%2 == 0 { return false } return true } func isEven(integer int) bool { if integer%2 == 0 { return true } return false } // 聲明的函數類型在這個地方當做了一個參數 func filter(slice []int, f testInt) []int { var result []int for _, value := range slice { if f(value) { result = append(result, value) } } return result } func main(){ slice := []int {1, 2, 3, 4, 5, 7} fmt.Println("slice = ", slice) odd := filter(slice, isOdd) // 函數當做值來傳遞了 fmt.Println("Odd elements of slice are: ", odd) even := filter(slice, isEven) // 函數當做值來傳遞了 fmt.Println("Even elements of slice are: ", even) } 函數當做值和類型在我們寫一些通用接口的時候非常有用,通過上面例子我們看到`testInt`這個類型是一個函數類型,然后兩個`filter`函數的參數和返回值與`testInt`類型是一樣的,但是我們可以實現很多種的邏輯,這樣使得我們的程序變得非常的靈活。 ### Panic和Recover Go沒有像Java那樣的異常機制,它不能拋出異常,而是使用了`panic`和`recover`機制。一定要記住,你應當把它作為最后的手段來使用,也就是說,你的代碼中應當沒有,或者很少有`panic`的東西。這是個強大的工具,請明智地使用它。那么,我們應該如何使用它呢? Panic >是一個內建函數,可以中斷原有的控制流程,進入一個令人恐慌的流程中。當函數`F`調用`panic`,函數F的執行被中斷,但是`F`中的延遲函數會正常執行,然后F返回到調用它的地方。在調用的地方,`F`的行為就像調用了`panic`。這一過程繼續向上,直到發生`panic`的`goroutine`中所有調用的函數返回,此時程序退出。恐慌可以直接調用`panic`產生。也可以由運行時錯誤產生,例如訪問越界的數組。 Recover >是一個內建的函數,可以讓進入令人恐慌的流程中的`goroutine`恢復過來。`recover`僅在延遲函數中有效。在正常的執行過程中,調用`recover`會返回`nil`,并且沒有其它任何效果。如果當前的`goroutine`陷入恐慌,調用`recover`可以捕獲到`panic`的輸入值,并且恢復正常的執行。 下面這個函數演示了如何在過程中使用`panic` var user = os.Getenv("USER") func init() { if user == "" { panic("no value for $USER") } } 下面這個函數檢查作為其參數的函數在執行時是否會產生`panic`: func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() //執行函數f,如果f中出現了panic,那么就可以恢復回來 return } ### `main`函數和`init`函數 Go里面有兩個保留的函數:`init`函數(能夠應用于所有的`package`)和`main`函數(只能應用于`package main`)。這兩個函數在定義時不能有任何的參數和返回值。雖然一個`package`里面可以寫任意多個`init`函數,但這無論是對于可讀性還是以后的可維護性來說,我們都強烈建議用戶在一個`package`中每個文件只寫一個`init`函數。 Go程序會自動調用`init()`和`main()`,所以你不需要在任何地方調用這兩個函數。每個`package`中的`init`函數都是可選的,但`package main`就必須包含一個`main`函數。 程序的初始化和執行都起始于`main`包。如果`main`包還導入了其它的包,那么就會在編譯時將它們依次導入。有時一個包會被多個包同時導入,那么它只會被導入一次(例如很多包可能都會用到`fmt`包,但它只會被導入一次,因為沒有必要導入多次)。當一個包被導入時,如果該包還導入了其它的包,那么會先將其它包導入進來,然后再對這些包中的包級常量和變量進行初始化,接著執行`init`函數(如果有的話),依次類推。等所有被導入的包都加載完畢了,就會開始對`main`包中的包級常量和變量進行初始化,然后執行`main`包中的`init`函數(如果存在的話),最后執行`main`函數。下圖詳細地解釋了整個執行過程: ![](images/2.3.init.png?raw=true) 圖2.6 main函數引入包初始化流程圖 ### import 我們在寫Go代碼的時候經常用到import這個命令用來導入包文件,而我們經常看到的方式參考如下: import( "fmt" ) 然后我們代碼里面可以通過如下的方式調用 fmt.Println("hello world") 上面這個fmt是Go語言的標準庫,其實是去`GOROOT`環境變量指定目錄下去加載該模塊,當然Go的import還支持如下兩種方式來加載自己寫的模塊: 1. 相對路徑 import “./model” //當前文件同一目錄的model目錄,但是不建議這種方式來import 2. 絕對路徑 import “shorturl/model” //加載gopath/src/shorturl/model模塊 上面展示了一些import常用的幾種方式,但是還有一些特殊的import,讓很多新手很費解,下面我們來一一講解一下到底是怎么一回事 1. 點操作 我們有時候會看到如下的方式導入包 import( . "fmt" ) 這個點操作的含義就是這個包導入之后在你調用這個包的函數時,你可以省略前綴的包名,也就是前面你調用的fmt.Println("hello world")可以省略的寫成Println("hello world") 2. 別名操作 別名操作顧名思義我們可以把包命名成另一個我們用起來容易記憶的名字 import( f "fmt" ) 別名操作的話調用包函數時前綴變成了我們的前綴,即f.Println("hello world") 3. _操作 這個操作經常是讓很多人費解的一個操作符,請看下面這個import import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" ) _操作其實是引入該包,而不直接使用包里面的函數,而是調用了該包里面的init函數。 ## links * [目錄](<preface.md>) * 上一章: [Go基礎](<02.2.md>) * 下一節: [struct類型](<02.4.md>)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看