10-Go語言函式
函式
- Go語言和C語言一樣也有函式的概念, Go語言中函式除了定義格式和不用宣告以外,其它方面幾乎和C語言一模一樣
- 格式:
func 函式名稱(形參列表)(返回值列表){ 函式體; }
- 無引數無返回值函式
func say(){ fmt.Println("Hello World!!!") }
- 有引數無返回值函式
func say(name string){ fmt.Println("Hello ", name) }
- 無引數有返回值函式
func sum() int { // 只有一個返回值時,返回值列表的()可以省略 return 1 + 1 }
- 有引數有返回值函式
func sum(a int, b int) int { return a + b }
和C語言函式差異
- 和C語言不同的是,Go語言中可以給函式的返回值指定名稱
// 給返回值指定了一個名稱叫做res, return時會自動將函式體內部res作為返回值 // 其實本質就是提前定義了一個區域性變數res, 在函式體中使用的res就是這個區域性變數,返回的也是這個區域性變數 func sum() (res int) { res = 1 + 1 return }
- 和C語言不同的是,Go語言中的函式允許有多個返回值函式
func calculate(a int, b int) (sum int, sub int) { sum = a + b sub = a - b return }
- 相鄰同類型形參OR返回值型別可以合併, 可以將資料型別寫到最後一個同類型形參OR返回值後面
// a, b都是int型別, 所以只需要在b後面新增int即可 func calculate(a, b int) (sum, sub int) { sum = a + b sub = a - b return }
- 和C語言不同的是Go語言中的函式不需要先宣告在使用
package main import "fmt" func main() { say(); } func say(){ // 在後面定義也可以在前面使用 fmt.Println("Hello World!!!") }
- 和C語言不同的是Go語言中的函式不支援巢狀定義
func test1(){ func test2(){ } }
值傳遞和引用傳遞
- Go語言中
值型別
有: int系列、float系列、bool、string、陣列、結構體- 值型別通常在棧中分配儲存空間
- 值型別作為函式引數傳遞, 是拷貝傳遞
- 在函式體內修改值型別引數, 不會影響到函式外的值
package main import "fmt" func main() { num := 10 change(num) fmt.Println(num) // 10 } func change(num int){ num = 998 }
package main import "fmt" func main() { arr := [3]int{1, 3, 5} change(arr) fmt.Println(arr) // 1, 3, 5 } func change(arr [3]int){ arr[1] = 8 }
package main import "fmt" type Person struct { name string age int } func main() { p := Person{"lnj", 33} change(p) fmt.Println(p.name) // lnj } func change(p Person){ p.name = "zs" }
- Go語言中
引用型別
有: 指標、slice、map、channel- 引用型別通常在堆中分配儲存空間
- 引用型別作為函式引數傳遞,是引用傳遞
- 在函式體內修改引用型別引數,會影響到函式外的值
package main import "fmt" func main() { num := 10 change(#) fmt.Println(num) // 998 } func change(num *int){ *num = 998 }
package main import "fmt" func main() { arr := []int{1, 3, 5} change(arr) fmt.Println(arr) // 1, 8, 5 } func change(arr []int){ arr[1] = 8 }
package main import "fmt" func main() { mp := map[string]string{"name":"lnj", "age":"33"} change(mp) fmt.Println(mp["name"]) // zs } func change(mp map[string]string){ mp["name"] = "zs" }
匿名函式
- 匿名函式也是函式的一種, 它的格式和普通函式一模一樣,只不過沒有名字而已
- 普通函式的函式名稱是固定的, 匿名函式的函式名稱是系統隨機的
- 匿名函式可以定義在函式外(全域性匿名函式),也可以定義在函式內(區域性匿名函式), Go語言中的普通函式不能巢狀定義, 但是可以通過匿名函式來實現函式的巢狀定義
- 全域性匿名函式
package main import "fmt" // 方式一 var a = func(){ fmt.Println("hello world1") } // 方式二 var ( b= func(){ fmt.Println("hello world2") } ) func main() { a() b() }
- 一般情況下我們很少使用全域性匿名函式, 大多數情況都是使用區域性匿名函式, 匿名函式可以直接呼叫、儲存到變數、作為引數或者返回值
- 直接呼叫
package main import "fmt" func main() { func(s string){ fmt.Println(s) }("hello lnj") }
- 儲存到變數
package main import "fmt" func main() { a := func(s string) { fmt.Println(s) } a("hello lnj") }
- 作為引數
package main import "fmt" func main() { test(func(s string) { fmt.Println(s) }) } func test(f func(s string)){ f("hello lnj") }
- 作為返回值
package main import "fmt" func main() { res := test() res(10, 20) } func test() func(int, int) { return func(a int, b int) { fmt.Println(a + b) } }
- 匿名函式應用場景
- 當某個函式只需要被呼叫一次時, 可以使用匿名函式
- 需要執行一些不確定的操作時,可以使用匿名函式
package main import "fmt" func main() { // 專案經理的一天 work(func() { fmt.Println("組織部門開會") fmt.Println("給部門員工分配今天工作任務") fmt.Println("檢查部門員工昨天提交的程式碼") fmt.Println("... ...") }) // 程式員的一天 work(func() { fmt.Println("參加部門會議") fmt.Println("修改測試提交的BUG") fmt.Println("完成老大今天安排的任務") fmt.Println("... ...") }) } // 假設我們需要編寫一個函式,用於描述一個人每天上班都需要幹嘛 // 職場中的人每天上班前,上班後要做的事幾乎都是相同的, 但是每天上班過程中要做的事確實不確定的 // 所以此時我們可以使用匿名函式來解決, 讓上班的人自己覺得自己每天上班需要幹什麼 func work(custom func()){ // 上班前 fmt.Println("起床") fmt.Println("刷牙") fmt.Println("洗臉") fmt.Println("出門") fmt.Println("上班打卡") fmt.Println("開電腦") // 上班中 custom() // 上班後 fmt.Println("關電腦") fmt.Println("下班打卡") fmt.Println("出門") fmt.Println("到家") fmt.Println("吃飯") fmt.Println("睡覺") }
+為了提升程式碼的可讀性,我們還可以將這個大函式拆解為獨立的匿名函式
func work(custom func()){ // 這種寫法的好處是程式碼層次清晰,並且如果有一些變數 // 只需要在上班前或上班後使用,還可以將這些變數隔離,不對外界造成汙染 // 上班前 func(){ fmt.Println("起床") fmt.Println("刷牙") fmt.Println("洗臉") fmt.Println("出門") fmt.Println("上班打卡") fmt.Println("開電腦") }() // 上班中 custom() // 上班後 func(){ fmt.Println("關電腦") fmt.Println("下班打卡") fmt.Println("出門") fmt.Println("到家") fmt.Println("吃飯") fmt.Println("睡覺") }() }
func work(custom func()){ // 前提條件是這個函式只在work函式中使用, 兩者有較強的關聯性, 否則建議定義為普通函式 pre := func(){ fmt.Println("起床") fmt.Println("刷牙") fmt.Println("洗臉") fmt.Println("出門") fmt.Println("上班打卡") fmt.Println("開電腦") } latter := func(){ fmt.Println("關電腦") fmt.Println("下班打卡") fmt.Println("出門") fmt.Println("到家") fmt.Println("吃飯") fmt.Println("睡覺") } // 上班前 pre() // 上班中 custom() // 上班後 latter() }
閉包
- 閉包是一個特殊的匿名函式, 它是匿名函式和相關引用環境組成的一個整體
- 也就是說只要匿名函式中用到了外界的變數, 那麼這個匿名函式就是一個閉包
package main import "fmt" func main() { num := 10 a := func() { num++ // 在閉包中用到了main函式中的num, 所以這個匿名函式就是一個閉包 fmt.Println(num) // 11 } a() }
- 閉包中使用的變數和外界的變數是同一個變數, 所以可以閉包中可以修改外界變數
package main import "fmt" func main() { num := 10 a := func() { num = 6 // 在閉包中用到了main函式中的num, 所以這個匿名函式就是一個閉包 fmt.Println(num) // 6 } fmt.Println("執行閉包前", num) // 10 a() fmt.Println("執行閉包後", num) // 6 }
- 只要閉包還在使用外界的變數, 那麼外界的變數就會一直存在
package main import "fmt" func main() { res := addUpper() // 執行addUpper函式,得到一個閉包 fmt.Println(res()) // 2 fmt.Println(res()) // 3 fmt.Println(res()) // 4 fmt.Println(res()) // 5 } func addUpper() func() int { x := 1 return func() int { x++ // 匿名函式中用到了addUpper中的x,所以這是一個閉包 return x } }
延遲呼叫
- Go語言中沒有提供其它面嚮物件語言的解構函式, 但是Go語言提供了defer語句用於實現其它面嚮物件語言解構函式的功能
- defer語句常用於
釋放資源
、解除鎖定
以及錯誤處理
等- 例如C語言中我們申請了一塊記憶體空間,那麼不使用時我們就必須釋放這塊儲存空間
- 例如C語言中我們打開了一個檔案,那麼我們不使用時就要關閉這個檔案
- 例如C語言中我們打開了一個數據庫, 那麼我們不使用時就要關閉這個資料庫
- 這一類的操作在Go語言中都可以通過defer語句來完成
- 無論你在什麼地方註冊defer語句,它都會在所屬函式執行完畢之後才會執行, 並且如果註冊了多個defer語句,那麼它們會按照
後進先出
的原則執行- 正是因為defer語句的這種特性, 所以在Go語言中關閉資源不用像C語言那樣用完了再關閉, 我們完全可以開啟的同時就關閉, 因為無論如何defer語句都會在所屬函式執行完畢之後才會執行
package main import "fmt" func main() { defer fmt.Println("我是第一個被註冊的") // 3 fmt.Println("main函式中呼叫的Println") // 1 defer fmt.Println("我是第二個被註冊的") // 2 }
init函式
- golang裡面有兩個保留的函式:
- init函式(能夠應用於所有的package)
- main函式(只能應用於package main)
- 這兩個函式在定義時不能有任何的引數和返回值
- go程式會自動呼叫init()和main(),所以你
不能
在任何地方呼叫這兩個函式 - package main必須包含一個main函式, 但是每個package中的init函式都是可選的
- 一個package裡面可以寫任意多個init函式,但這無論是對於可讀性還是以後的可維護性來說,我們都強烈建議使用者在一個package中每個檔案只寫一個init函式
- 單個包中程式碼執行順序如下
-
main包-->常量-->全域性變數-->init函式-->main函式-->Exit
-
package main import"fmt" const constValue= 998 // 1 var gloalVarValue int = abc() // 2 func init() { // 3 fmt.Println("執行main包中main.go中init函式") } func main() { // 4 fmt.Println("執行main包中main.go中main函式") } func abc() int { fmt.Println("執行main包中全域性變數初始化") return 998 }
-
多個包之間程式碼執行順序如下
-
init函式的作用
- init函式用於處理當前檔案的初始化操作, 在使用某個檔案時的一些準備工作應該放到這裡