函數是可讓咱們將語句打包成一個單元,而後能夠屢次調用,其能夠提升應用的模塊性和代碼的重複利用率。數組
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
時按導入包的順序執行,以下圖按箭頭順序執行。
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
的個num1
和num2
關係。