函數(shù)
Golang函數(shù)特點
- 無需聲明原型
- 支持多返回值
- 不定參數(shù)傳參 也就是函數(shù)的參數(shù)個數(shù)不是固定的 但是后面的類型是固定的
- 支持命名返回參數(shù)
- 支持匿名函數(shù)和閉包
- 函數(shù)也是一種類型 可以賦值給一個變量
- 不支持 嵌套 一個包不能有2個名字一樣的函數(shù)
- 不支持重載
- 不支持默認參數(shù)
函數(shù)聲明
函數(shù)聲明包含一個函數(shù)名裁蚁,參數(shù)列表钝鸽,返回值列表和函數(shù)體磅摹,如果函數(shù)沒有返回值米酬,則返回列表可以省略瞬浓。函數(shù)從第一條語句開始執(zhí)行旋讹,直到執(zhí)行return語句或者執(zhí)行函數(shù)的最后一條語句序厉。
func test(str,x string,y int)(int,string){ //相同的2個類型 可以省略type合并稱一個 //(int,string)返回參數(shù) } //函數(shù)是第一類對象 可以作為參數(shù)傳遞
參數(shù)
函數(shù)定義時指出邻辉,函數(shù)定義時有參數(shù)溪王,該變量可被稱為函數(shù)的形參,形參就像定義在函數(shù)的局部變量值骇。但是當(dāng)調(diào)用函數(shù)的時候莹菱,傳遞過來的變量就是函數(shù)的實參,函數(shù)可以通過2種方式傳遞參數(shù)
- 值傳遞:在 在調(diào)用函數(shù)的時候?qū)嶋H參數(shù)復(fù)制一份傳遞到函數(shù)中吱瘩,這樣在函數(shù)中如果對參數(shù)修改道伟,不會影響到實際參數(shù)
func swap(x ,y int)int{ }
引用傳遞: 引用傳遞是指在調(diào)用函數(shù)時把實際參數(shù)的地址傳遞到函數(shù)中,那么函數(shù)中對參數(shù)進行修改就是對實際參數(shù)修改使碾,會影響到實際參數(shù)蜜徽。
func swap(x,y *int){//傳入指針 var temp int //創(chuàng)建一個臨時變量 temp =*x //x的地址的值賦值給temp臨時變量 *x=*y //y的地址的值給x *y=temp //temp的地址給到y(tǒng) }
在默認情況下Go語言使用的是值傳遞,也就是在調(diào)用過程中不會影響到實際參數(shù)
無論是值傳遞票摇,還是引用傳遞拘鞋,傳遞給函數(shù)的都是變量的副本,不過兄朋,值傳遞是值得拷貝掐禁。引用傳遞是地址得拷貝怜械,一般來說,地址拷貝更加高效傅事。而值拷貝取決于對象得大小缕允,對象越大 性能越低
map\slice\array\chan\指針\interface 默認都是以引用的方式傳遞
Golang可變參數(shù)本質(zhì)上就是slice。只能有一個蹭越,且必須是最后一個障本。在參數(shù)賦值的時候不用一個個的賦值∠炀椋可以直接傳遞一個數(shù)組或者切片驾霜,注意最后一個參數(shù)要加上args
...
int返回值
"_"標(biāo)識符,用來忽略函數(shù)的某個返回值买置。Go的返回值可以被命名粪糙,并且像在函數(shù)開頭聲明的變量那樣使用。返回值的名稱應(yīng)當(dāng)具有一定的意義忿项,可以作為文檔使用蓉冈。沒有參數(shù)的return語句返回各個變量的當(dāng)前值。這種用法被稱作"裸返回"轩触。直接返回語句僅應(yīng)用在像下面這樣的短函數(shù)中寞酿,在長的函數(shù)中它們會影響代碼的可讀性。
//直接返回sum func add(x, y int) (sum int) { sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum)//21 } //命名返回參數(shù)允許defer延遲調(diào)用通過閉包讀取和修改 //直接返回sum //直接返回sum func add(x, y int) (sum int) { defer func() { fmt.Println("加100") sum += 100 }() sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum) //加100 121 //顯示return返回前脱柱,會先修改命名返回參數(shù) }
匿名函數(shù)
匿名函數(shù)是指不需要定義函數(shù)名的一種函數(shù)實現(xiàn)方式伐弹。在GO里面,函數(shù)可以像普通變量一樣被傳遞或使用榨为,Go語言支持隨時在代碼里定義匿名函數(shù)惨好。匿名函數(shù)由一個不帶函數(shù)聲明和函數(shù)體組成。匿名函數(shù)的優(yōu)越性可以直接使用函數(shù)內(nèi)的變量不必聲明随闺。
func main() { var sum = func() int { return 100 * 100 > } fmt.Println(sum())//10000 } //Golang匿名函數(shù)可賦值給變量昧狮,作為結(jié)構(gòu)字段,或者在channel里傳送 func main() { //創(chuàng)建一個函數(shù)變量 fun := func() { fmt.Println("111") } fun() //111 //fun 數(shù)組 創(chuàng)建多個函數(shù) 函數(shù)的格式為接受一個參數(shù)返回一個int類型 funs := [](func(x int) int){ func(x int) int { return x + 1 }, func(x int) int { return x + 2 }, } fmt.Println(funs[0](100)) //101 //作為結(jié)構(gòu)體的一個field d := struct { fn func(x int) int }{ //創(chuàng)建一個函數(shù)變量 fn: func(x int) int { //函數(shù)變量賦值 return 1 }, } fmt.Println(d.fn(1)) //1 //channel c := make(chan func() string, 2) c <- func() string { return "hello world" } fmt.Println((<-c)())//hello world }
閉包
閉包可以理解為一種保存函數(shù)狀態(tài)的方法板壮,當(dāng)我們調(diào)用一個函數(shù),或者執(zhí)行操作合住,或者返回結(jié)果绰精,函數(shù)運行結(jié)束之后,隨即消亡透葛,因為函數(shù)一般都是在堆上笨使,當(dāng)系統(tǒng)檢測當(dāng)前內(nèi)存空間沒有被引用就會回收
閉包的作用就是保存函數(shù)的運行狀態(tài) 避免函數(shù)被回收。
- 訪問所在的作用域
- 函數(shù)嵌套
- 在所在作用域外被調(diào)用
func bb() func(i int) int { var s int = 0 fmt.Println("ccccc") fmt.Println("ccccc") b := func(i int) int { s = s + 1 fmt.Printf("地址%#v", &s) return s > } > return b > } > func main() { > a := bb() > b := bb() > fmt.Println(a(1)) > fmt.Println(a(1)) > fmt.Println(b(1)) > fmt.Println(b(1)) > } > //ccccc > //ccccc > //ccccc > //ccccc > //地址(*int)(0xc00000a0a8)1 > //地址(*int)(0xc00000a0a8)2 > //地址(*int)(0xc00000a0c0)1 > //地址(*int)(0xc00000a0c0)2 > //可以看到a和b變量的調(diào)用 s被存在了不同的內(nèi)存當(dāng)中 > //當(dāng)采用a調(diào)用的時候 s的變量被保存到了0xc00000a0a8當(dāng)中 > //當(dāng)采用b調(diào)用的時候 s變量被保存到了0xc00000a0c0 中 因為這是在調(diào)用bb()方法的時候 都申請了一塊內(nèi)存 > > //把s定義成全局變量 > var s int = 0 > func bb() func(i int) int { > b := func(i int) int { > s = s + 1 > fmt.Printf("地址%#v", &s) > return s > } > return b > } > func main() { > a := bb() > b := bb() > fmt.Println(a(1)) > fmt.Println(a(1)) > fmt.Println(b(1)) > fmt.Println(b(1)) > } > //ccccc > //ccccc > //ccccc > //ccccc > //地址(*int)(0x85dc68)1 > //地址(*int)(0x85dc68)2 > //地址(*int)(0x85dc68)3 > //地址(*int)(0x85dc68)4 > //可以看到當(dāng)s提升為全局變量 申請的一塊地址 用于a和b調(diào)用的時候 是共享的內(nèi)存變量 > ``` > > 通過以上案例我們可以得出在a:=bb()的時候執(zhí)行了bb函數(shù)僚害,實際上這里的指向是直接指向的bb()函數(shù)的內(nèi)部子函數(shù)硫椰。 > > 當(dāng)我們調(diào)用a()的時候 直接執(zhí)行的也是內(nèi)部的子函數(shù)。 > > 通過局部變量和全局變量可以發(fā)現(xiàn),申請的內(nèi)存方式是不同的
遞歸函數(shù)
遞歸就是在運行的過程中調(diào)用自己靶草。一個函數(shù)調(diào)用自身叫做遞歸函數(shù)
構(gòu)成遞歸函數(shù)得條件:
子問題必須與原始問題為同樣的事蹄胰,且更為簡單
不能無限制調(diào)用必須有個出口,化簡為分遞歸狀態(tài)處理
func factorial(i int) int { if i <= 1 { return 1 } return i * factorial(i-1) } func main() { fmt.Println(factorial(7))//5040 }
延遲調(diào)用(defer)
defer特性: 1.關(guān)鍵字defer用于注冊延遲調(diào)用 2. 這些調(diào)用直到return前才會被執(zhí)行奕翔。因此可以用來做資源清理 3.多個defer裕寨,按照先進后出的方式執(zhí)行 4.defer語句中的變量,在defer聲明時就決定了 defer用途: 1.關(guān)閉文件句柄 2.鎖資源釋放 3.數(shù)據(jù)庫連接釋放 func main() { for i := 0; i < 10; i++ { //defer是先進后出的 defer 調(diào)用的函數(shù)參數(shù)的值 defer 被定義時就確定了. //defer fmt.Print(strconv.Itoa(i) + "\t") //9 8 7 6 5 4 3 2 1 0 //defer 碰上閉包 defer func() { fmt.Println(&i) //defer func內(nèi)部所使用的變量的值需要在這個函數(shù)運行時才確定 //也就是講這在閉包用到的時候這個變量已經(jīng)變成了10,所以輸出全都是10 }() } }
defer 與return
//defer 與return func foo() (i int) { i = 0 defer func() { fmt.Println(i) }() //在具名返回函數(shù)中派继,執(zhí)行return 2的時候已經(jīng)將i的值賦值為2了宾袜。所以輸出的時候結(jié)果為2不是0 return 2 } func main() { foo() }
defer nil報錯
//defer nil函數(shù)報錯 func test() { //聲明的時候未被調(diào)用 var run func() = nil //被調(diào)用 報錯 defer run() fmt.Println("runs") } func main() { //捕捉異常 defer func() { if err := recover(); err != nil { fmt.Println("錯誤日志") fmt.Println(err) } }() //調(diào)用 runtime error: invalid memory address or nil pointer dereference //main從上到下開始執(zhí)行。defer 延遲執(zhí)行 //執(zhí)行test方法驾窟。test方法調(diào)用的時候,出test的時候進行報錯庆猫。 當(dāng)test調(diào)用完成的時候 defer run才會被調(diào)用 test() }
在錯誤的地方使用defer
func do() error { res, err := http.Get("http://www.google.com") defer res.Body.Close() if err != nil { return err } return nil } func main() { do() //panic: runtime error: invalid memory address or nil pointer dereference //因為google無法訪問因為網(wǎng)絡(luò)原因 defer 直接使用了res變量 res為nill未判斷 //可以在defer 的時候加上一層判斷解決該錯誤 if res!=nil{ defer res.Body.Close() } }
不檢查錯誤
//對于f.Close()可能會返回一個錯誤,但是這個錯誤會被我們忽略掉 func do() error { f, err := os.Open("book.txt") if err != nil { return err } if f != nil { defer f.Close() } return nil } func main() { do() } // 改進 if f!=nill{ defer func(){ if err:=f.Close();err!=nill{ //code } } }
釋放相同的資源
func do() error { f, err := os.Open("book.txt") if err != nil { return err } if f != nil { defer func() { if err := f.Close(); err != nil { fmt.Printf("defer close book.txt err %v\n", err) } }() } f, err = os.Open("another-book.txt") if err != nil { return err } if f != nil { defer func() { if err := f.Close(); err != nil { fmt.Printf("defer close another-book.txt err %v\n", err) } }() } return nil } func main() { do()//輸出結(jié)果: defer close book.txt err close ./another-book.txt: file already closed //當(dāng)延遲函數(shù)執(zhí)行時候绅络,只有最后一個變量會被用到月培,因此f會成為最后那個資源。而且2個資源都將這一個資源作為關(guān)閉對象 } //解決方案 //可以把f當(dāng)作參數(shù)傳遞進去 defer func(f io.Closer){ }(f)
異常處理
Golang沒有結(jié)構(gòu)化異常昨稼,使用panic拋出異常节视,recover捕獲錯誤。
異常的使用場景很簡單:Go可以拋出一個panic異常假栓,然后在defer中通過recover捕獲這個異常寻行。然后正常處理
panic: 1.內(nèi)置函數(shù) 2.加入函數(shù)F中書寫了panic語句 ,會終止其后要執(zhí)行得代碼匾荆,在panic所在函數(shù)F內(nèi)如果存在要執(zhí)行得defer函數(shù)列表拌蜘。按照defer先進后出執(zhí)行 3.返回函數(shù)F得調(diào)用者G,在G中牙丽,調(diào)用函數(shù)F語句之后得代碼不會執(zhí)行简卧,加入函數(shù)G在存在要執(zhí)行得defer先進后廚執(zhí)行 4.直到goroutine整個退出,并報告錯誤 recover: 1.內(nèi)置函數(shù) 2.用來控制一個goroutine得panicking行為烤芦,捕獲panic举娩,從而影響應(yīng)用得行為 3.一般的調(diào)用建議 1.在defer函數(shù)中,通過recever來終止一個goroutine的panicking過程构罗,從而恢復(fù)正常代碼的執(zhí)行 2.可以獲取通過panic傳遞的error 注: 1.利用recover處理panic指令铜涉,defer必須放在panic之前定義,另外recover只有在defer調(diào)用的函數(shù)中才有效遂唧,否則當(dāng)panic芙代,receover無法捕捉到panic,無法防止panic擴散 2.recover處理異常后盖彭,邏輯并不會恢復(fù)到panic那個點去纹烹,函數(shù)跑到defer之后的那個點 3.多個defer會形成defer棧页滚,后定義的defer語句最先被調(diào)用。
延遲調(diào)用
//延遲調(diào)用中引發(fā)錯誤铺呵,可被后續(xù)異常調(diào)用捕獲裹驰,但僅最后一個錯誤可被捕獲 func test() { defer func() { fmt.Println("最后調(diào)用") fmt.Println(recover()) }() defer func() { fmt.Println("第一次調(diào)用") panic("defer panic") }() panic("test panic") } func main() { test() } //捕獲函數(shù)recover 只有在延遲調(diào)用內(nèi)直接調(diào)用才會終止錯誤,否則總是返回nill陪蜻。任何未捕獲的錯誤都會沿調(diào)用堆棧向外傳遞 package main import ( "fmt" ) func test() { defer func() { fmt.Println(recover()) //有效 }() defer recover() //無效邦马! defer fmt.Println(recover()) //無效! defer func() { func() { println("defer inner") recover() //無效宴卖! }() }() panic("test panic") } func main() { test() }
Go實現(xiàn)類似try catch的異常處理
func Try(fn func(), handler func(interface{})) { defer func() { if err := recover(); err != nil { handler(err) } }() fn() } func main() { Try(func() { fmt.Println("測試") }, func(err interface{}) { }) }