func 函數名(參數類型 參數,參數類型 參數)(返回值類型,返回值類型){ 左花邊號必須和func同行}python
func anet(a int,b string) (int,string) { }
也能夠同時定義多個一樣類型的返回值和參數,代碼以下所示:算法
func anet(a ,b,c int) (a,b,c int) { // abc都是int類型的 }
因爲a,b,c是咱們在返回值那個小括號內定義好了,因此咱們在函數內部的代碼裏面不須要再次定義,代碼以下所示:數組
func anet(a, b, c int) (a, b, c int) { a, b, c = 1, 2, 3 // 在返回值那個小括號內定義好了,因此不須要定義了 return a, b, c }
可變長度參數,咱們在傳入參數的時候有時候只知道這個參數是int型,殊不知道有多少個,因此這個時候須要使用可變長度參數,用三個點來替代,這個可變長度參數等同於python裏面的**kwargs或者*args,都只能放在參數裏面的最後一位閉包
func anet(a ...int) { // ...表示可變長度參數,參數類型全爲int型 a, b, c = 1, 2, 3 return a, b, c } func main() { anet1("a", 1, 2, 3, 4, 5) } func anet1(b string, a ...int) { // 可變長度參數只能放在最後面使用 fmt.Println(b, a) }
對於函數參數傳遞方式來講,有兩種:app
引用傳遞
不管是值傳遞,仍是引用傳遞,傳遞給函數的都是變量的副本,不過,值傳遞是值的拷貝。引用傳遞是地址的拷貝,通常來講,地址拷貝更爲高效。而值拷貝取決於拷貝的對象大小,對象越大,則性能越低。函數
多返回值的時候,咱們並不須要從新定義變量,直接等於號賦值就好了。性能
咱們傳入一個數組到函數裏面,再在這個函數裏面去修改這個數組的值,看會不會對原始數組發生改變:
先看例子:測試
package main import ( "fmt" ) func main() { s2 := []int{1, 2, 3, 4, 5} fmt.Println(s2) anet(s2) fmt.Println(s2) } func anet(s []int) { s[0] = 7 s[1] = 8 fmt.Println(s) } // 打印結果以下 [1 2 3 4 5] [7 8 3 4 5] [7 8 3 4 5]
由打印結果可得知,若是函數的參數是一個數組,那麼傳入到這個函數的數組在函數內部進行了操做,那麼其原始數組也會變更,因傳入參數的時候是直接傳入的一個內存地址進去讓函數使用的,因此可以對原始數組進行更改。指針
那麼看看輸入普通的數字有沒有這樣的效果code
package main import ( "fmt" ) func main() { a, b := 1, 2 fmt.Println(a, b) anet(a, b) fmt.Println(a, b) } func anet(s ...int) { s[0] = 7 s[1] = 8 fmt.Println(s) } // day
多返回值
多返回值的時候不須要從新定義了
func returnName(a, b int) (total, sub int) { total = a + b // 不須要從新定義了,直接使用=賦值便可 sub = a / b return } func main() { t, s := returnName(10, 34) fmt.Println(t, s) }
把函數當作參數傳入到另外一個函數
把某一個函數做爲參數傳入到另外一個函數裏面
// func1 package main import ( "fmt" ) type add_func func(int, int) int func add(a, b int) int { return a + b } func operator(op add_func, a, b int) int { return op(a, b) } func main() { c := add fmt.Println(c) su := c(10, 20) fmt.Println(su) ss := operator(add, 12, 30) // 把函數當作參數傳入進去 fmt.Println(ss) }
不定長參數
// func1 package main import ( "fmt" ) func add2(arg ...int) int { s := 0 for _, v := range arg { s += v } return s } func add3(a, b int, args ...int) int { s := a + b for _, v := range args { s += v } return s } func addStr(a string, args ...string) string { s := a for _, v := range args { s += v } return s } func main() { fmt.Println(add2(1, 2, 3, 4, 5, 6, 10)) // 多個參數 fmt.Println(add3(2, 3, 111, 333, 444, 555)) // 傳入2個或者多個參數 fmt.Println(addStr("asdf", "adfuwe", "123", "siw", "sdc")) // 傳入一個或者多個參數 }
沒有給這個函數命名的函數叫作匿名函數,代碼例子以下:
func main() { a := func() { // 直接把函數賦值給a,未對這個函數進行命名 fmt.Println("Func A") } a() }
閉包的做用就是返回一個匿名函數,請看代碼
func main() { f := closure(10) fmt.Println(f(1)) fmt.Println(f(2)) } func closure(x int) func(int) int { // 其實是這麼寫的func closure(x int) (func(int) int) //只不過在編譯的時候自動給你轉換了,實際是說返回值是函數,int型的 return func(y int) int { fmt.Printf("%p", &x) return x + y } }
咱們看下這個代碼例子
func main() { for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() } } // 打印結果: 3 3 3
爲何打印結果都是3呢,由於defer是最後執行的,在for循環結束後才執行,然而在for循環結束後i是等於3了,因此打印結果是3
咱們利用defer在panic觸發異常的時候進行糾正,使程序依舊可以正常運行,代碼以下:
// heh1 package main import ( "fmt" ) func main() { A() B() C() } func A() { fmt.Println("Func B") } func B() { defer func() { // 必須在panic以前定義這個,不然會報錯找不到 if err := recover(); err != nil { fmt.Println("recover in B") } }() panic("Func B") } func C() { fmt.Println("Func C") }
咱們若是須要對某個目錄下的壓縮包進行解壓,壓縮包的格式有不少種,有zip,gz,rar,tar,tar.gz等等之類的格式,針對這些後綴名,若是咱們使用if條件去判斷類型後再去執行代碼,顯然目前是可行的,代碼效果以下:
if 後綴名=="zip" { unzip 文件名 }else if 後綴名=="rar" { 解壓文件 }else if 後綴名=="tar" { 解壓文件 }else if 後綴名=="targ.gz" { 解壓文件 }
顯然針對幾種文件類型使用if判斷仍是能夠的,可是一旦文件類型成千上萬的時候,你以爲if語句寫下來那豈不是N多行了啊,這樣的if語句你受到了嗎?一樣的switch語句也是同樣,在面對不少條件的時候一樣顯得很臃腫,那麼該如何使用?
其實咱們能夠考慮使用map的映射關係來解決,這樣的話咱們只須要考慮在map添加文件類型與對應的解壓方法便可,不須要在寫if/switch條件語句來判斷了。
代碼以下:
package main import ( "fmt" ) // 使用映射和函數引用來製造分支 var ff = map[string]func(string) ([]string, error){ ".gz": GzipFile, ".zip": ZipFile, ".tar.gz": TarGz} func GzipFile(file string) ([]string, error) { fmt.Println(file) xxx := []string{"a", "b"} return xxx, nil } func TarGz(file string) ([]string, error) { fmt.Println(file) xxx := []string{"a", "c"} return xxx, nil } func ZipFile(file string) ([]string, error) { fmt.Println(file) xxx := []string{"a", "b"} return xxx, nil } func ss(file string) { // 總調用函數 if function, ok := ff[file]; ok { function(file) } } func main() { ss(".tar.gz") }
解釋下:
在運行時動態地選擇函數的另外一個場景即是,當咱們有兩個或者多個的函數實現了相同的功能時,好比使用了不一樣的算法等,咱們不但願在程序編譯時綁定到其中任何一個函數,例如容許咱們動態地選擇他們來作性能測試或者回歸測試等。
舉個例子:
// package main import ( "os" ) var IsPalindrome func(string) bool // func init() { if len(os.Args) > 1 && (os.Args[1] == "-a" || os.Args[1] == "--ascii") { os.Args = append(os.Args[:1], os.Args[2:]...) IsPalindrome = func(s string) bool { // simple ascii version if len(s) <= 1 { return true } if s[0] != s[len(s)-1] { return false } return IsPalindrome(s[1 : len(s)-1]) } } else { IsPalindrome = func(s string) bool { // UTF8 version if len(s) <= 1 { return true } if s[0] != s[len(s)-1] { return false } return IsPalindrome(s[1 : len(s)-1]) } } } func main() { }
代碼解釋下:
所謂高階函數就是將一個或者多個其餘函數做爲本身的參數,並在函數裏調用他們。能夠看看下面最簡單的例子:
func SliceIndex(limit int,predicate func(i int) bool) int{ for i:= 0;i < limit;i++{ if predicate(i) { return i } } return -1 }
上面的函數很簡單,返回predicate()爲真時的索引值。它只知道一個長度信息和它的第二個參數,也是是對於任意給定索引值返回一個布爾值的函數,代表這個索引是否調用者所指望的。下面看看如何調用這個函數
func main() { xs := []int{1, 2, 3, 4} fmt.Println(SliceIndex(len(xs), func(i int) bool { return xs[i] == 3 })) }