Go基礎編程:函數

函數是可讓咱們將語句打包成一個單元,而後能夠屢次調用,其能夠提升應用的模塊性和代碼的重複利用率。數組

Go是編譯型語言,因此函數編寫的順序可有可無;爲了可讀性,每每把main() 函數寫在前面,其餘函數按照必定邏輯順序進行編寫(例如函數被調用的順序)。網絡

函數聲明閉包

使用關鍵字func聲明;一個函數包括:函數名、形式參數列表、返回值、及{}函數主體,固然這些均可以省略,可是(){}不能省。app

func 函數名(參數列表) 返回值{
     主體
}

函數名函數

函數實質上也是一個引用型變量;全部命名規則和變量同樣,函數名大小寫對應包外是否可見,函數名也不能和常量或變量同名this

const NAME  = "老王"
​
func NAME()  {} // NAME redeclared in this block previous declaration

參數spa

參數是在函數主體能夠直接使用的變量3d

func name(x string){
    fmt.Println(x) //打印x
}

參數個數沒有限制,類型也沒有限制,形式參數有點類型變量的聲明,以下:code

func name(x int){} // 一個int
func name1(x string) //一個string
func name2(x int,y string)//一個int,一個string
func name2(x , y string)//2個string

不定長參數協程

不定長參數是前面參數已肯定,後面必須同類型的不肯定長度用變量名 ... 類型表示,實質上是切片

func name(a... int){ } //不肯定長度的int型 變量a 操做和切片同樣
​
func name3(a string,b... int){}

形式參數不須要和普通變量同樣必需要使用

func test(a string,b int){
    fmt.Println(b) // a 沒有使用,能正常運行
}

調用

在須要調用的地方使用函數名和參數便可

package main
​
import (
    "fmt"
)
​
func main()  {
     test("哈哈哈",2) //此處調用
     test("嘿嘿嘿",3) //重複調用,使用不一樣的參數
}
​
func test(a string,b int){
    fmt.Println(a,b)
}

默認值

Go語言的函數沒有默認值,函數調用是參數必需要和定義的參數一致

package main
​
import (
    "fmt"
)
​
func main()  {
     //參數給少了
     test("嘿嘿嘿")  //not enough arguments in call to test
     //have (string)
     //want (string, int)
     //參數給多了
     test("嘿嘿嘿",3,5477)// too many arguments in call to test
     //have (string, number, number)
     //want (string, int)
     //參數順序不一致
     test(3,"嘿嘿嘿") //cannot use 3 (type int) as type string in argument to test
     // cannot use "嘿嘿嘿" (type string) as type int in argument to test

}
​
func test(a string,b int){
    fmt.Println(a,b)
}

返回值

函數返回值是在參數括號後面,Go語言中函數返回值能夠是多個

返回值能夠定義類型,不定義具體名稱

func test() string{
    return "返回值"
}

返回值也能夠能夠具體名稱,超過2個字符必須加上括號()包起來

func test() (name string){
     name = "返回值"
     return name
}

多個返回值

func test1() (name string,age int){
     name = "返回值"
     age = 23
     return name,age
}

若是有多個返回值時,定義有具體名稱的必須所有都要名稱:

func test1() (name string, int){
     name = "返回值"
     return name,23
}
//syntax error: mixed named and unnamed function parameters

做用域

對於整個程序來講,函數參數、返回值和函數主體裏定義的參數是局部變量,函數結束後,內存會被GC回收。但GC回收是看變量是否有被引用,若是引用就不回收。以下,經常用來生成結構體變量:

package main
​
import (
     "fmt"
)
​
type User struct {
     Name string
}
​
func NewUser() *User {
     return &User{}
}
​
func main()  {
     user := NewUser() //此處返回局部變量,但變量不會被回收,由於還沒還在引用
     user.Name="嘿嘿"
     fmt.Println(user)
}

終止

有時候咱們須要運行到某個地方必須終止本函數,不須要等函數所有運行完畢,使用關鍵字return來終止。

特殊函數

Go語言有一些內置函數,能直接調用的函數

函數名 說明
append、copy 切片的添加元素和複製切片
close 關閉管道通訊
complex、real 、imag 建立和操做複數
len、cap len 返回長度或數量(字符串、數組、切片、map 和管道);cap 返回容量(只能用於切片和 map)
new、make 用於分配內存
panic、recover 錯誤處理機制
print、println 底層打印函數

main()函數

main函數是程序的入口,一個程序只能有一個main函數

init()函數

init函數是在編譯時自動調用的函數,無需(也不能)咱們調用,一個包內能夠有多個init函數,不衝突;有多個init時按導入包的順序執行,以下圖按箭頭順序執行。

image.png

defer

關鍵字defer用來延遲調用函數的執行,他是在函數結束前調用,以下:

func main()  {
     fmt.Println("函數開始。。。。")
     defer test()
     fmt.Println("函數結束。。。。")
}
​
func test() {
    fmt.Println("函數結束了,我執行。。。。")
}
//函數開始。。。。
//函數結束。。。。
//函數結束了,我執行。。。。

函數結束包括整個函數執行完畢和異常都觸發

func main()  {
     fmt.Println("函數開始。。。。")
     defer test()
     panic("異常")
     fmt.Println("函數結束。。。。")
}
​
func test() {
    fmt.Println("函數結束了,我執行。。。。")
}
//函數開始。。。。
//函數結束了,我執行。。。。
//panic:異常

能夠有多個defer,執行順序是先進後出,即堆棧

package main
​
import (
    "fmt"
)
​
func main()  {
    fmt.Println("函數開始。。。。")
    defer test1()
    defer test2()
    fmt.Println("函數結束。。。。")
}
​
func test1() {
    fmt.Println("函數結束了,test1 執行。。。。")
}
​
func test2() {
    fmt.Println("函數結束了,test2 執行。。。。")
}
​
//函數開始。。。。
//函數結束。。。。
//函數結束了,test2 執行。。。。
//函數結束了,test1 執行。。。。

應用:defer 通常用於須要手動關閉的資源,如:文件的關閉、網絡的關閉;還應用於處理互斥鎖和錯誤處理。

package main
​
import (
     "net/http"
     "sync"
)
var mu sync.Mutex
func main()  {
     defer func() {
         if err := recover();err!=nil{
            //... 錯誤處理
         }
     }()
     resp, err := http.Get("http://www.example.com")
     if err != nil {
        panic(err)
     }
     defer resp.Body.Close()  //網絡關閉
     //....
}
​
func Lock()  {
     mu.Lock()
     defer mu.Unlock()
     //.....  臨界資源的操做
}

defer 只做用於語句的最後一個函數,以下:

package main
​
import (
    "fmt"
)
​
type User struct {
     Name string
}
​
func NewUser() *User {
    return &User{}
}
​
func (u *User) Show(name string) *User {
     fmt.Println(name)
     return u
}
​
func main()  {
     user := NewUser() 
     defer user.Show("1").Show("2").Show("3") //Show("1").Show("2") 和defer無關
     user.Show("結束了。。。")
}
//1
//2
//結束了。。。
//3

defer 參數是函數時

package main
​
import (
    "fmt"
)
​
func main()  {
     x,y:=1,2
     defer  call(x,call(x,y)) //做爲參數的call(x,y)和defer 無關,先執行
     call(x,y)
}
​
func call(x ,y int) int {
     fmt.Println(x,y)
     return x+y
}
//1 2
//1 2
//1 3

匿名函數

匿名函數就是沒有名字的函數,使用方式有兩種,一種是直接調用,沒有返回值;還有一種是賦值給一個變量,而後能夠屢次調用,能夠有返回值。以下:

package main
​
import (
    "fmt"
)
​
func main()  {
     func() {
        fmt.Println("直接定義而且調用的匿名函數")  //直接定義而且調用的匿名函數
     }() // 加上()直接調用
    ​
     a:= func() {
         fmt.Println("定義匿名函數並負責給a變量")
     }
     a() // 調用匿名函數  定義匿名函數並負責給a變量
     a() // 調用匿名函數  定義匿名函數並負責給a變量
    ​
     b:= func() int {
     fmt.Println("定義匿名函數並負責給b變量,有返回值")
        return 1
     }
     i := b() //定義匿名函數並負責給b變量,有返回值
     fmt.Println(i) //1
}
​

匿名函數的使用很是方便,經常應用於 形式參數、啓動新goroutine和defer 配合處理錯誤等,下面簡單示例

package main
​
import (
     "fmt"
)
func show(y string)  {
     fmt.Println(y)
}
func test(x string, a func(y string))  {
     a(x)
}
func main()  {
     test("形式參數",show) //形式參數
    ​
     ch := make(chan struct{})
     go func() {
     fmt.Println("啓動協程。。。")  //啓動協程。。。
     ch <- struct{}{}
     }()
     <- ch
}

遞歸

遞歸函數簡單來講就是本身調用本身,Go語言是支持遞歸函數的。下面遞歸求1~5數列的和

package main
​
import (
    "fmt"
)
var sum int
func main()  {
     test(5)
     fmt.Println(sum) //15
}
​
func test(x int)  {
     if x <=0 {
         return
     }
     sum += x
     x--
     test(x)  //本身調用本身
}

遞歸函數應用普遍,好比樹的遍歷、無限菜單等。雖然支持無效遞歸,可是遞歸比較消耗內存, 使用時不該該無限遞歸下去,應該有個打破條件跳出。

回調

回調函數參數是另外一個函數。

package main
​
import (
    "fmt"
)
​
func main()  {
     cabblak(test) //參數爲函數
}
​
func test()  {
     fmt.Println("參數爲函數")
}
​
func cabblak(a func())  {
     a()
}

閉包

閉包函數是把函數做爲返回值

package main
​
import (
     "fmt"
)
​
func main()  {
     i := test(1)
     i() //2
     i() //3
    ​
     j:=test(1)
     j() //2
     j() //3
}
​
func test(n int) func()  {
     return func() {
        n++
        fmt.Println(n)
     }
}
​

須要注意的是,函數本質上是引用類型變量,閉包函數返回函數即變量,這個變量還被引用狀態,GC沒有回收,故里面定義的各類變量內存地址沒有發生變化,在後面不停重複調用i() ,地址沒發生變化,全部n的值在不斷累計。j是另外一個變量了,和i沒什麼聯繫,就像var num1,num2 int的個num1num2關係。

相關文章
相關標籤/搜索