Go語言中支持函數、匿名函數和閉包,而且函數在Go語言中屬於「一等公民」golang
一等(頭等)函數、支持頭等函數(First Class Function)的編程語言,能夠把函數賦值給變量,也能夠把函數做爲其它函數的參數或者返回值編程
func function_name( [參數列表-能夠寫多個參數] ) (返回類型列表-能夠返回多個類型結果]){ 函數體 }
// ageInfo 返回年齡相關的信息 func ageInfo(name string, age int) (string, int) { nextAget := age + 1 return fmt.Sprintf("%s今年%d了", name, age), nextAget }
函數的參數和返回值都是可選的,咱們能夠僅僅封裝一個從上到下執行的代碼把它放到函數內部閉包
// sayHello 僅僅是輸出一段文字 func sayHello(){ fmt.Println("你好啊明天") }
package main import "fmt" func main() { // 經過函數名加括號就能夠執行函數 sayHello() } func sayHello() { // 重複的代碼能夠用函數封裝 fmt.Println("這是重複的代碼xxxxxxxxx") }
函數能夠接受參數,而後函數內部的代碼能夠根據參數來動態計算產出不一樣的結果app
package main import "fmt" func main() { // 經過函數名加括號就能夠執行函數,括號內能夠傳入參數 result := sayHello("eson") fmt.Println(result) // 輸出結果: Hello eson } func sayHello(name string) string { // 重複的代碼能夠用函數封裝 return fmt.Sprintf("Hello %s", name) }
func sayHello(name string, add string) string { // 重複的代碼能夠用函數封裝 return fmt.Sprintf("Hello %s", name) }
上面函數接收了兩個相同類型的參數,能夠經過簡寫來優化下編程語言
func sayHello(name, address string) string { // 重複的代碼能夠用函數封裝 return fmt.Sprintf("Hello %s, address is :%s", name, address) }
package main import "fmt" func main() { ret := countNumber("eson", 1, 2, 3, 4, 5) fmt.Printf("%s", ret) // eson count number :[1 2 3 4 5] } // 可變長參數注意只能用在最後一個 func countNumber(name string, number ...int) string { // 重複的代碼能夠用函數封裝 return fmt.Sprintf("%s count number :%v", name, number) }
Go語言中經過return關鍵字向外輸出返返回值函數
package main import "fmt" func main() { result := calculate(1, 10) fmt.Printf("result is:%v", result) // result is:11 } // 單個返回值 func calculate(x, y int) int { return x + y }
package main import "fmt" func main() { sum, sub := calculate(10, 1) fmt.Printf("sum is:%v, sub is: %v\n", sum, sub) // sum is:11, sub is: 9 } // 返回多個值 func calculate(x, y int) (int, int) { // 固然這裏的值能夠返回不一樣類型的看實際狀況 return x + y, x - y }
package main import "fmt" func main() { sum, result := calculate(10, 1) fmt.Printf("sum is:%v, %v\n", sum, result) // sum is:11, calculate sum is:11 } // 返回多個值命名 func calculate(x, y int) (sum int, result string) { // 固然這裏的值能夠返回不一樣類型的看實際狀況 sum = x + y result = fmt.Sprintf("calculate sum is:%d", sum) return sum, result }
函數內部能夠引用全局變量,可是函數內定義的變量只在函數內部有效優化
package main import "fmt" func main() { } // 全局變量 var num int = 18 func sum() { fmt.Printf("全局變量的值是:%d", num) // 函數內部能夠直接使用全局變量 sumRet := num + 1 fmt.Printf("%v\n", sumRet) } func referSum() { // 這裏想引用sum函數的sumRet的值? No 不能夠函數內部的值只能在函數內部使用 }
邏輯運算塊內部是一個獨立的做用域url
// 全局變量 var num int = 18 func sum() { fmt.Printf("全局變量的值是:%d", num) // 函數內部能夠直接使用全局變量 sumRet := num + 1 // if是邏輯運算從if開始到結束它nebula定義的值只能在內部使用,可是它能夠向上引用sumA // 函數內部是一個局部做用域能夠被它下層的做用域所調用 if sumRet > 1 { sumA := 10 fmt.Printf("sumA的值是:%v\n", sumA) } // fmt.Println(sumA) 可是這裏函數做用域想調用本身包含的子做用域代碼塊是不能夠的 }
循環邏輯內部也是一個獨立的做用域,而且能夠向上引用它上層的做用域指針
// 全局變量 var num int = 18 func sum() { fmt.Printf("全局變量的值是:%d", num) // 函數內部能夠直接使用全局變量 sumRet := num + 1 for i := 0; i < sumRet; i++ { // 這個i每次循環的時候都會被賦值新的值 // 在循環內定義的值只有在本次循環內有效 cycle := i + num fmt.Printf("i is:%d\n", cycle) } }
什麼狀況下用值傳遞?code
package main import "fmt" type person struct { name string age int } func main() { p1 := person{name: "John", age: 18} showInfo(p1) } // 只想展現的時候能夠傳值 func showInfo(student person) { fmt.Printf("學生:%s的年齡是:%d", student.name, student.age) }
什麼狀況下用指針傳遞?
package main import "fmt" type person struct { name string age int } func main() { p1 := person{name: "John", age: 18} // 過了1年年齡加1 happyNewYear(&p1) fmt.Printf("name:%s, age is:%d", p1.name, p1.age) } func happyNewYear(p *person) { p.age++ }
固然結構體能夠直接使用指針變量
package main import "fmt" type person struct { name string age int } func main() { p1 := &person{name: "John", age: 18} // 初始化一個person的結構體指針變量 // 過了1年年齡加1 happyNewYear(p1) fmt.Printf("name:%s, age is:%d", p1.name, p1.age) } func happyNewYear(p *person) { p.age++ }
package main import "fmt" func main() { // sum 這個函數的類型 fmt.Printf("%T", sum) // func(int, int) int } func sum(x, y int) int { return x + y }
從上面能夠看出來每一個函數都是有一個類型的,那咱們在函數裏傳函數的時候就能夠這麼寫
package main import "fmt" func main() { // sum 這個函數的類型 fmt.Printf("%T\n", sum) // func(int, int) int fmt.Printf("%v\n", calculate(100, 200, sum)) // 300 } func sum(x, y int) int { return x + y } // 定義了一個計算函數,它接收了3個參數 // x,y int類型 // op 是一個函數類型 func(x,y int) int func calculate(x, y int, op func(x, y int) int) int { return op(x, y) }
package main import "fmt" func main() { // sum 這個函數的類型 fmt.Printf("%T\n", sum) // func(int, int) int fmt.Printf("%v\n", calculate(100, 200, sum)) // 300 } func sum(x, y int) int { return x + y } // 定義一個函數類型並使用它 type fType func(int, int) int // 定義了一個計算函數,它接收了3個參數 // x,y int類型 // op 是一個函數類型 ftype func calculate(x, y int, op fType) int { return op(x, y) }
package main import ( "errors" "fmt" ) func main() { a, b := 100, 200 var method string // 獲取用戶輸入 _, _ = fmt.Scanln(&method) // 獲取放方法 job, err := calculate(method) // 執行 if err != nil { fmt.Printf("%v\n", err) } else { fmt.Printf("%v\n", job(a, b)) } } func sum(x, y int) int { return x + y } func sub(x, y int) int { return x - y } // 定義一個函數類型並使用它 type fType func(int, int) int func calculate(op string) (fType, error) { switch op { case "+": return sum, nil case "-": return sub, nil default: return nil, errors.New("沒法識別的方法") } }
當咱們須要臨時在函數內使用一個函數的時候不能像定義普通函數那樣使用了,這個時候就用到了匿名函數(沒有名稱的函數就是匿名函數)
// 匿名函數沒有名稱 func(參數)(返回值){ 函數體 }
package main import "fmt" func main() { // 匿名函數賦值變量並賦值 sum := func(x, y int) int { return x + y } fmt.Printf("%v\n", sum(100, 200)) }
package main import "fmt" func main() { // 定義一個匿名變量後面跟擴韓直接傳值並運行 func(x, y int) { fmt.Printf("sum : %d + %d result is :%d\n", x, y, x+y) }(100, 200) // 結果: sum : 100 + 200 result is :300 }
匿名函數多用於實現回調函數和閉包
閉包就是:
package main import ( "errors" "fmt" ) // 這個函數返回一個func(int)類型的函數 func wapper() func(arg int) { x := 0 return func(y int) { x += y fmt.Println(x) } } func main() { // 當這個時候外部函數銷燬了,可是x這個變量並無,由於它被內部函數所引用了 f := wapper() f(10) f(10) f(10) } // 結果: 10 20 30
簡單例子
package main import ( "fmt" "time" ) func main() { // 經過timed函數能夠獲取函數的運行時間 timed(run)() } // 這個函數接收一個函數,並返回一個函數 func timed(f func()) func() { return func() { start := time.Now() f() fmt.Printf("這個函數運行須要:%dms\n", time.Since(start)/1000000) } } func run() { time.Sleep(time.Second * 3) }
實際應用例子
package main import ( "fmt" "net/http" "time" ) func main() { http.HandleFunc("/hello", timed(hello)) http.ListenAndServe(":3000", nil) } func timed(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() f(w, r) end := time.Now() fmt.Println("The request took", end.Sub(start)) } } func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "<h1>Hello!</h1>") }
正常來講要在函數內使用一個變量,要不就定義一個全局變量,在函數內可使用,或者經過閉包的方式調用
簡單例子
package main import ( "errors" "fmt" ) // 這個函數返回一個func(int)類型的函數 func wapper() func(arg int) { x := 0 return func(y int) { x += y fmt.Println(x) } } func main() { // 當這個時候外部函數銷燬了,可是x這個變量並無,由於它被內部函數所引用了 f := wapper() f(10) f(10) f(10) } // 結果: 10 20 30
應用例子
package main import ( "fmt" "net/http" ) type Database struct { Url string } func NewDatabase(url string) Database { return Database{url} } func main() { db := NewDatabase("localhost:5432") http.HandleFunc("/hello", hello(db)) http.ListenAndServe(":3000", nil) } func hello(db Database) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, db.Url) } }
defer是go語言裏的延遲函數,它定義在一個函數內defer後面跟的表達式將會延遲執行
package main import ( "fmt" "time" ) func main() { runTasks() } func runTasks() int { start := time.Now() // 直接在這裏定義一個延遲函數 // 用來統計這個函數執行了多久 defer func(stime time.Time) { fmt.Printf("這個函數運行須要:%dms\n", time.Since(start)/1000000) }(start) // 開始運行函數邏輯 fmt.Println("函數內邏輯執行1") time.Sleep(time.Second * 2) fmt.Println("函數內邏輯執行2") return 666 // defer fmt.Println("若是在return語句後面加延遲語句是不能夠的") }
golang目前沒有像其餘語言異常處理相似:try catch,可是能夠經過defer來捕獲 異常舉例來講!~
在go函數內經過panic來觸發異常並退出程序,這裏須要注意不要濫用:panic,panic會讓整個程序掛掉
若是一個函數出現了一個未知的異常後,它的處理邏輯是根據調用鏈,不斷向上返回直到碰到recover函數
package main import ( "fmt" ) func main() { f1() } func f1() { defer func() { if err := recover(); err != nil { fmt.Printf("f1函數捕獲到異常了,異常報錯是:%v\n", err) } }() fmt.Println("這個是第1層函數") f2() } func f2() { panic("f2 觸發了panic") fmt.Println("這個是第2層函數") // panic後這裏是不執行的 }
總結下