前言
Go中對函數的使用非常普遍,Go語言中沒有默認參數這個概念。
函數格式
func 函數名(參數1,參數2,......)(返回值1,返回值2,....){ }
package main
import (
"fmt"
)
//函數
//函數的定義:Go是強類型語言必須給 參數、和返回值指定數據類型
//返回值使用和參數各使用()分開
func sum(x1 int, x2 int) (ret int) {
return x1 + x2
}
//沒有返回值的函數
func f1(x1 int, x2 int) {
println(x1 + x2)
}
//沒有參數的函數,也沒有返回值的函數
func f3() {
fmt.Println("hello world")
}
//沒有參數,但是有返回值的函數
func f4() string {
return "Hello World"
}
//函數的返回值可以命名也不可不命名
func f5(x int, y int) (ret int) {
ret = x + y
return //使用命名返回值,為ret,return后面可省略
}
//返回多個返回值
func f6() (int, string) {
return 1, "2"
}
//參數的類型簡寫:當函數有多個參數並且類型一致時,我們可以簡寫前面的參數類型
func f7(x, y, z int, n, o string, i, j bool) int {
return x + y
}
//可變長的參數:可變長參數必須放在最后
func f8(x string,y...int){
fmt.Println(x)
fmt.Println(y) //切片類型[1 2 3]
}
func main() {
ret := sum(1, 1)
fmt.Println(ret)
f1(2, 3)
str01 := f4()
fmt.Println(str01)
n, s := f6()
fmt.Println(n, s)
f8("下雨了",1,2,3)
}
函數參數
在Go語言中函數參數 通過 值類型傳遞!也就是說Go函數中的參數都是副本!
package main
import "fmt"
var s1 []string
func modify(s1 []string) {
s1[0] = "Zero" //修改了s1副本和s1本身對應的底層數組的同1個位置
}
func expansion(s2 []string) {
s2 = append(s2, "3")//擴展了、不算修改底層數組原處!
fmt.Println("我append數據了啊:",s2)
}
func main() {
s1 = []string{"1", "2"}
s1[0] = "一"
fmt.Println(s1)
modify(s1)
fmt.Println(s1)
expansion(s1)
fmt.Println("增加了沒有",s1)
}
可變參數
可變長的參數
package main
import "fmt"
func f1(args ...interface{}) {
//args空接口類型的slice類型
fmt.Printf("類型:%T,可變參數值:%v\n", args, args)
}
func main() {
f1()
f1(1)
f1(2, 3, 4)
//拆開傳值
slice1 := []interface{}{5, 6, 7}
f1(slice1...)
}
參數總結
//1.可選參數:調用f1函數是option可以不傳,傳參之后函數接收到1個slice。 func f1(option ...string) { fmt.Println(option) } //2.必須傳參:參數可以是任意類型,如果沒有參數可以傳nil func f2(option interface{}) { fmt.Println(option) } //3.兼容以上2種情況option可選參數,參數可以是任意類型 func f3(option ...interface{}) { fmt.Println(option) }
閉包函數
1個函數嵌套了另1個函數,內層的函數引用了外層函數的參數或者變量之后,這個外層函數就行包一樣包住了內層的函數,外層函數就叫閉包函數。
值得注意得是在Go的函數里面,你只能定義1個匿名函數,不能像Python 一樣在函數內部在聲明1個有名函數。
Python versus Golang 
閉包的原理
1.函數A可以作為函數B的1個參數傳到函數B。
2.函數查找變量的順序為 首先在自己內部找,找不到再去外層函數找。
3.函數A也可以作為函數B的返回值返回。
package main
import "fmt"
//1個簡單的閉包函數 結束1個 int參數x,返回1個參數為int和返回值為int的函數
func adder(x int) func(int) int {
//內部的匿名函數引用了外部adder函數的變量x就稱為閉包
return func(y int) int {
x += y
return x
}
}
func main() {
ret := adder(10)
fmt.Println(ret(100))
}
package main
import "fmt"
//1.需求在不改變CEO和CTO寫得代碼的情況下,把CTO寫得的函數傳到CEO函數中執行
//CEO寫得函數(代碼不能動)
func ceo(f func()) {
fmt.Println("This is CEO 函數")
f()
}
//CTO寫的函數
func cto(y...int) {
var sum int
for _,v:=range(y){
sum+=v
}
fmt.Println("This is CTO 函數",sum)
}
//我寫得函數
func wraper(f func(...int), a ...int) func() {
tmp := func() {
f(a...)
}
fmt.Println("This is function code by me")
return tmp
}
func main() {
// //匿名函數
// var f1 = func(x, y int) {
// fmt.Println(x + y)
// }
// f1(10, 20)
// //立即執行函數
// func(x, y int) {
// fmt.Println(x + y)
// }(10, 200)
//閉包
w := wraper(cto, 2020, 4,4,10,41)
ceo(w)
}
閉包面試題
package main
import "fmt"
func cal(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add, sub
}
func main() {
f1, f2 := cal(10)
//由於 內部函數一直引用外部函數的變量i,所以外部函數的變量i一直在變。
fmt.Println(f1(1), f2(2)) //11 9
fmt.Println(f1(3), f2(4)) //12 8
fmt.Println(f1(5), f2(6)) //13 7
}
遞歸函數
遞歸。遞 、 歸 所說的就是 遞和歸2個不同動作過程。有遞也有歸才叫遞歸。遞就是壓棧的過程,歸就是出棧的過程。
遞歸函數就是自己調用自己
應用場景:問題相同但是每次問題規模都減小
必須有1個退出條件:遞 、歸 。遞 、歸不能無限得傳遞(壓棧)達到了1臨界值(達到了目的) 就得歸(出棧)。

package main
import "fmt"
func test(n int){
fmt.Println("--->n=",n)
if n<3{
n++
test(n)//從這里進去的,就從這里出來
}
fmt.Println("<----n=",n)
}
func main() {
test(1)
}
/*
main函數開始執行
test(1):棧(一)
1.fmt.Println("--->n=",n) 打印:--->n= 1
2.if n<3{n=1所以條件成立}n++之后 n=2
3.遇到函數自己調用自己,開辟新棧(二)也就是test(2)
###############################################################
13.出棧二來到在棧一步驟3位置,代碼往下執行fmt.Println("<----n=",n)棧一中此時變量n=3啊,所以打印:<----n= 2
14.main函數結束
test(2) 棧(二)
4.fmt.Println("--->n=",n) 打印:--->n= 2
5.if n<3{n=2所以條件成立}n++之后 n=3
6.遇到函數自己調用自己,開辟新棧(三)也就是test(3)
###############################################################
11.出棧三 來到在棧二步驟6的位置,代碼往下執行fmt.Println("<----n=",n)棧二中此時變量n=3啊,所以打印:<----n= 3
12.棧(一)步驟3給我壓棧的棧,我出棧就到棧(一)步驟3
test(3)棧(三)
7.fmt.Println("--->n=",n) 打印:--->n= 3
8.if n<3{n=3所以條件不成立了} n++不執行 test()也不會調用自己進行壓棧
9.不壓棧了就出棧,看到fmt.Println("<----n=",n)就執行所以打印:<----n= 3
10.然后出棧,步驟6給我壓棧的棧,我出棧了就從步驟6 出去
*/
文件夾&遞歸思想
遞歸思想對我們影響深遠,每天打開電腦進入文件夾、查找子文件、進入子文件 也算得上是遞歸操作
遞:進入1個目錄 ------>進入這個目錄的子目---------> 進入子目錄的子目錄......
臨界值:找到/看到自己想要的
歸:再逐步退出來
rm -rf /* 中的 -r 選項是什么意思?
-r, -R, --recursive remove directories and their contents recursively
import os
file_info=[]
def recursion_files(inital_path="D:\goproject"):
files = os.listdir(inital_path)
if not files:
return
for item in files:
fileAbs = os.path.join(inital_path,item)
if os.path.isfile(fileAbs):
file_info.append(fileAbs)
else:
#如果有文件夾就把該文件夾下的子目錄保存進行壓棧操作
recursion_files(inital_path=fileAbs)
recursion_files()
print(file_info)
階乘
package main
import "fmt"
func f(n int) int {
if n <= 1 {
return 1
}
return n*f(n-1)
}
func main() {
ret:=f(5)
fmt.Println(ret)
}
有N個台階,1次可以走1步,也可以走2步,請問有多少種走法?
package main
import "fmt"
func f(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 2
}
return f(n-1) + f(n-2)
}
func main() {
ret := f(3)
fmt.Println(ret)
}
錯誤處理recover()
Python中的異常、錯誤信息都屬於Exception類,都可以通過try except Exceptiong語句進行攔截捕捉到。
Golang中的錯誤信息屬於error 類型,它和panic是2種不同的東西。
Golang中的錯誤信息是由於函數作為變量return出來的變量,而recover攔截的是Panic異常不是error類型的變量。所以我們寫了太多的if err!=nill{}。
recover函數可以幫助我們攔截到Golang程序執行過程中出現的panic,例如:引用了空指針/數組越界..然后進行現場的recover(恢復),確保程序不會崩潰無法繼續執行下面的代碼。
既然是攔截就一定需要在合理的位置和時機進行panic的攔截。
通常recover會和defer延遲執行結合使用,在程序執行之前進行defer注冊、在程序執行完畢之后 進行錯誤捕捉、攔截、處理。
var numberList []int func main() { data := make(map[string]interface{}, 10) //defer+函數調用:在main函數結束之后再調用 //注意:defer recover()一定要在錯誤發生之前進行注冊,可以未雨綢繆不能亡羊補牢! defer func() { //recover返回的是1個空接口類型的變量,我們可以對這個變量進行記錄! if p := recover(); p != nil { data["data"] = nil data["err_msg"] = p fmt.Println(data) //攔截可能出現的錯誤,返回給前端。 marshal, err := json.Marshal(data) if err != nil { fmt.Println(err) } fmt.Println(marshal) } }() //模擬數組越界的錯誤! index := 10 item := numberList[index] data["data"] = item }
defer關鍵字
代碼大多情況下是按照由上至下的順序執行的,而Python中的 yeild、send(上下切換執行=協程)還有Golang中的defer關鍵字可以改變程序的執行順序。
在Go的函數里可以聲明defer關鍵字+函數,有延遲調用函數的效果,多用於釋放sync.Mutex、file句柄、socket資源、數據庫事務操作。
這是1個經典的例子我使用了defer+recover檢測事務執行過程中可能出現的err和panic再進行RollBACK。
//sqlx事務操作:transactionsn: Noun(一筆)交易,業務,買賣;辦理;處理 func sqxTransaction() (err error) { var transaction *sqlx.Tx transaction, err = db.Beginx() //開啟事務 if err != nil { fmt.Println("開始事務失敗", err) return err } //defer延遲執行函數,最后檢查程序執行過程中是否出現panic?是否返回錯誤? defer func() { //程序執行出現panic之后回滾 if p := recover(); p != nil { transaction.Rollback() panic(p) //程序執行返回錯誤之后回滾。 } else if err != nil { fmt.Println("事務回滾:", err) transaction.Rollback() //程序執行過程沒出現panic、也沒有返回err信息,提交事務。 } else { err = transaction.Commit() fmt.Println("事務執行成功") } }() //執行SQL1 sqlStr1 := "update user set age=20 where id=?" ret, err := transaction.Exec(sqlStr1, 1) if err != nil { fmt.Println(err) return err } affectedRow, err := ret.RowsAffected() if err != nil { return err } if affectedRow != 1 { return errors.New("執行SQL1時未對數據庫進行修改!\n") } //執行SQL1成功之后開始執行SQL2 sqlStr2 := "update user set age=19 where id=?" ret, err = transaction.Exec(sqlStr2, 2) if err != nil { return err } affectedRow, err = ret.RowsAffected() if err != nil { return err } if affectedRow != 1 { return errors.New("執行SQL2時未對數據庫進行修改!\n") } return err }
聲明1個defer 就是開辟1層獨立的棧針進行逐層壓棧操作,在函數體內語句執行完畢之后,按照先進后出(后進先出)的順序出棧。
defer關鍵字執行時機
Go語言的函數中return語句在底層並不是原子操作。
它分為2部執行:
1.返回值賦值
2.執行RET指令兩步。
defer語句執行的時機就在返回值賦值操作后,RET指令執行前。
具體如下圖所示:

defer的執行步驟如下:
1.先執行函數中語句內容
2.遇到defer關鍵字 開辟獨立的defer棧空間(不同於函數)逐一壓棧(不執行)
3.給函數中return值=賦值
4.按先入后出的順序 執行defer棧中的語句內容
package main
import "fmt"
func f1() int {
x := 5
defer func() {
x++ //
}()
return x
}
func f2() (x int) {
defer func() {
x++
}()
return 5 //1.先給返回值賦值 x=5 2.defer壓棧x=5 3.執行x=5+1 4.return x=6
}
func f3() (y int) {
x := 5
defer func() { //壓棧時開辟了獨立的空間x=5 x+1
x++
}()
return x //這里是x=y=5
}
func f4() (x int) {
defer func(x int) {
x++ //參數改寫的是副本
}(x)
return 5 //1.先給返回值賦值x=5 2.defer開辟獨立的棧 壓棧x=5 3.執行x++ 4.returm
}
func main() {
fmt.Println(f1()) //5
fmt.Println(f2()) //6
fmt.Println(f3()) //5
fmt.Println(f4()) //5
}
當defer的函數語句中遇到函數調用先執行函數然后再壓棧
defer func1("1", a, fun2("10", a, b)
defer壓棧時遇到函數調用,先把函數執行完畢之后,再執行壓棧。
package main
import "fmt"
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
//1.先執行calc("10", a, b)打印"10" 1 3 4
//2.壓棧1 defer calc("1", a, 4)
defer calc("1", a, calc("10", a, b))
a=0
//3.再執行calc("20", a, b)打印"20" 0 2 2
//4.壓棧2:defer calc("2", 0, 2)
defer calc("2", a, calc("20", a, b))
//棧2出棧:2 0 2 2
//棧1出棧:1 1 3 4
b=1
}
如何在嵌套函數內修改函數外部的變量?指針啊!
package main import "fmt" func outerFunc()(n int) { defer func(n *int ){ *n+=10 fmt.Println(*n) }(&n) n=900 return n } func main() { n := outerFunc() fmt.Println(n) }
defer里面再嵌套defer會是什么效果?遞歸嗎?
package main import "fmt" func f1() { fmt.Println("f1開始") defer func() { fmt.Println("f2開始") defer func() { fmt.Println("f3開始") defer func() { fmt.Println("f4開始") defer func() { fmt.Println("f4結束") }() }() fmt.Println("f3結束") }() fmt.Println("f2結束") }() fmt.Println("f1結束") } func outerFunc() { fmt.Println("outerFunc開始") defer f1() fmt.Println("outerFunc結束") } func main() { outerFunc() }
defer、recover the panic
defer還有1個作用我們可以在程序執行結束之前,在defer的函數中執行recover捕捉到程序的panic進行處理,保證程序執行時遇到panic不會異常終止。
相當於Python里面的 Try Except功能。
func outerFunc() { //使用defer函數設置recover來捕捉panic defer func(){ if p := recover(); p != nil { fmt.Println(p) } }() dsn := "zhanggen:ne3thd8p0s@tcp(192.168.56.18:3306)/web?charset=utf8mb4&parseTime=True" db,err:=sqlx.Connect("mysql", dsn) if err!=nil{ //拋出err信息,recover就會接收到 panic(err) } if db==nil{ panic("數據庫連接未建立!") } }
分金幣
package main
import "fmt"
var (
coins = 50
users = []string{
"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
}
distribution = make(map[string]int, len(users))
)
/*
你有50枚金幣,需要分配給以下幾個人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配規則如下:
a. 名字中每包含1個'e'或'E'分1枚金幣
b. 名字中每包含1個'i'或'I'分2枚金幣
c. 名字中每包含1個'o'或'O'分3枚金幣
d: 名字中每包含1個'u'或'U'分4枚金幣
寫一個程序,計算每個用戶分到多少金幣,以及最后剩余多少金幣?
程序結構如下,請實現 ‘dispatchCoin’ 函數
*/
func dispatchCoins() {
for _, user := range users {
for _, c := range user {
switch c {
case 'e', 'E':
distribution[user]++
coins--
case 'i', 'I':
distribution[user] += 2
coins -= 2
case 'o', 'O':
distribution[user] += 3 //分金幣
coins -= 3 //分出金幣之后需要從總金幣數量扣除
case 'u', 'U':
distribution[user] += 4
coins -= 4
}
}
}
fmt.Println(distribution) //打印每人分到的金幣
fmt.Println("剩余", coins) //剩余的金幣
}
func main() {
dispatchCoins()
}
