07GO語言函數

go語言函數

函數特性:java

  • Go 函數 不支持 嵌套、重載和默認參數
  • 函數無需聲明原型、支持不定長度變參、多返回值、命名返回值參數
  • 支持匿名函數、閉包
  • 函數也能夠做爲一種類型使用
  • 函數是一等公民,可做爲參數傳遞
  • 函數傳遞是值的拷貝或者是指針的拷貝,區別在於會不會影響原始值
  • 不定長變參,若是傳入slice,必定要用 ...展開傳入

一、函數定義

定義函數使用關鍵字 func,且左大括號不能另起一行編程

func A(a int,b string) int{
   // 參數 a, b 名在前,類型在後
   // 若是隻有一個返回值,直接寫個int就行。表示返回值是一個int型
}
func A(a int,b string) (int,string){
   //有多個返回值的時候,小括號括起來,用逗號隔開
}
func A(a,b,c int){
   //若是 多個參數類型同樣,能夠這樣寫
}
func A()(a,b,c int){    //對於返回值也能夠這樣寫,簡寫必定要命名返回的是誰,如a,b,c
   a,b,c=1,2,3             //在返回值的時候已經命名了a,b,c 已經分配好內存地址,
                   //因此在這裏直接進行賦值就能夠, 不須要加 :=
   return  a,b,c  // 返回的時候不用說明返回的是誰,由於函數已經命名返回的名稱類型。
   //能夠直接寫一個return 就能夠,可是爲了代碼可讀性,仍是寫全
}
func B()(int,int,int){ // 這樣寫不命名返回值,可是return時要注意
   a,b,c :=1,2,3 //沒有開闢內存控件,因此要用 :=
   return a,b,c //因爲沒有命名返回值,因此必需要把a,b,c返回
}

函數是一種類型,建議將複雜簽名定義爲函數類型,以便於閱讀。數組

func test(fn func() int) int {
    return fn()
}
type FormatFunc func(s string, x, y int) string // 定義函數類型。
func format(fn FormatFunc, s string, x, y int) string {
    return fn(s, x, y)
}
func main() {
    s1 := test(func() int { return 100 }) // 直接將匿名函數當參數。
    s2 := format(func(s string, x, y int) string {
        return fmt.Sprintf(s, x, y)
    }, "%d, %d", 10, 20)
    println(s1, s2)
}

二、參數

實參:在調用有參函數時,函數名後面括號中的參數稱爲「實際參數」,實參能夠是常量、變量或表達式。閉包

形參:自定義函數中的「形參」全稱爲"形式參數" 因爲它不是實際存在變量,因此又稱虛擬變量。實參和形參能夠重名。形參的做用域是整個函數體就像定義在函數體內的局部變量函數式編程

變參函數

go語言的變參本質上就是 slice。只能有⼀個,且必須是最後⼀個。性能

func test(s string, n ...int) string {//使用 變量名  ...類型聲明可變參,只能是最後一個
    var x int
    for _, i := range n {
        x += i
    }
    return fmt.Sprintf(s, x)
}
func main() {
    println(test("sum: %d", 1, 2, 3))
}

注意:在參數賦值時能夠不用用一個一個的賦值,能夠直接傳遞一個數組或者切片,特別注意的是在參數後加上「…」。即:給可變參數傳一個slice時,必須用...展開指針

func main() {
    s := []int{1, 2, 3}
    println(test("sum: %d", s...))//test函數接收int型可變參,這裏傳入int型slice, s...展開了
}

參數傳遞:code

(1)值傳遞:orm

​ 傳遞的是值得拷貝,函數中對傳入的變量修改,不會影響到實際參數。

func sum(x, y int) int {  //接收int型 的x,y   實際上是出入實參的拷貝
    return x + y
}

(2)引用傳遞

​ 將實際參數的地址傳遞到函數中,好比傳入指針,在函數中修改會改變實際參數。

func swap(x, y *string) {//接受的是指針類型,經過指針直接操做內存
    var temp string
    temp = *x
    *x = *y
    *y = temp
}

(3)固定類型可變參數

​ 就是最後一個參數是可變參數,能夠傳入不固定的參數,可是類型相同。

func variable(str string, source ...string){//可變參數,固定類型
    fmt.Println("可變參數的長度:",len(source))
}

(4)任意類型的不定參數

​ 函數和每一個參數的類型都不固定。 形參用 interface{}類型的可變參數聲明

func variable(values ...interface{}) {//接收一個可變參數,空接口類型(任何類型都實現了空接口)
    for _, val := range values {
        switch v := val.(type) {
        case int:
            fmt.Println("val type is int ", v)
        case float64:
            fmt.Println("val type is float ", v)
        case string:
            fmt.Println("val type is string ", v)
        case bool:
            fmt.Println("val type is bool ", v)
        default:
            fmt.Println("val type is unknow ", v)
        }
    }
}

(5)函數類型參數

​ 函數類型賦值給變量,做爲參數傳遞引用

// 定義函數類型--這個函數接收兩個string的參數
type myfunc func(string, string)

func addperfix(perfix, name string) {//這個函數正好符合myfunc這個函數類型
    fmt.Println(perfix, name)
}
// 第二個參數接收的是一個 myfunc類型的參數
func sayhello(name string, f myfunc) {
    f("hello", name)
}

func main() {
    sayhello("helloworld", addperfix)//將對應類型的參數傳入
}

小結:

一、Go語言函數中的參數不支持默認值。

二、不管是值傳遞,仍是引用傳遞,傳遞給函數的都是變量的副本,不過,值傳遞是值的拷貝。引用傳遞是地址的拷貝,通常來講,地址拷貝更爲高效。而值拷貝取決於拷貝的對象大小,對象越大,則性能越低。

三、map、slice、chan、指針、interface默認以引用的方式傳遞。

四、函數的可變參數只能有一個,且必須是最後一個。

五、在參數賦值時能夠不用用一個一個的賦值,能夠直接傳遞一個數組或者切片,特別注意的是在參數後加上「…」便可

三、返回值

(1)有返回值的函數必須寫return,不然報錯

(2)能夠返回多個返回值,聲明的時候用括號括起來

(3)返回值能夠被命名,命名返回參數可看作與形參相似的局部變量,最後由 return 隱式返回。 命名返回參數

​ 可被同名局部變量遮蔽,此時須要顯式返回

(4)不能⽤容器對象接收多返回值。只能⽤多個變量,或 "_" 忽略。

func test() (int, int) {
    return 1, 2
}
func main() {
    // s := make([]int, 2)
    // s = test() // Error: multiple-value test() in single-value context
    x, _ := test() //有返回值必須用多個變量接。
    println(x)
}

(5)多返回值可直接做爲其餘函數調⽤實參。

func test() (int, int) {
    return 1, 2
}
func add(x, y int) int {
    return x + y
}
func sum(n ...int) int {
    var x int
    for _, i := range n {
        x += i
    }
    return x
}
func main() {
    println(add(test()))
    println(sum(test()))
}

(6)命名返回參數容許 defer 延遲調⽤經過閉包讀取和修改。

func add(x, y int) (z int) {
    defer func() {
        z += 100
    }()
    z = x + y
    return
}
func main() {
    println(add(1, 2)) // 輸出: 103
}

顯式 return 返回前,會先修改命名返回參數。

//這段代碼,命名返回值z,就至關於局部變量,返回z+200,即z=z+200
func add(x, y int) (z int) {
    defer func() {
    println(z) // 輸出: 203
    }()
    z = x + y
    return z + 200 // 執⾏順序: (z = z + 200) -> (call defer) -> (ret)
}
func main() {
    println(add(1, 2)) // 輸出: 203
}

defer、return、返回值三者的執行順序應該是:return最早給返回值賦值;接着defer開始執行一些收尾工做;最後RET指令攜帶返回值退出函數。

四、匿名函數

匿名函數可賦值給變量,作爲結構字段,或者在 channel ⾥傳送。

匿名函數由一個不帶函數名的函數聲明和函數體組成。匿名函數的優越性在於能夠直接使用函數內的變量,沒必要聲明

fn := func() { println("Hello, World!") }
fn()
// --- function collection ---
fns := [](func(x int) int){
    func(x int) int { return x + 1 },
    func(x int) int { return x + 2 },
    }
println(fns[0](100))
// --- function as field ---
d := struct {
    fn func() string
}{
    fn: func() string { return "Hello, World!" },
}
println(d.fn())
// --- channel of function ---
fc := make(chan func() string, 2)
fc <- func() string { return "Hello, World!" }
println((<-fc)())

五、閉包

閉包的概念:

閉包是能夠包含自由(未綁定到特定對象)變量的代碼塊,這些變量不在這個代碼塊內或者任何全局上下文中定義,而是在定義代碼塊的環境中定義。要執行的代碼塊(因爲自由變量包含在代碼塊中,因此這些自由變量以及它們引用的對象沒有被釋放)爲自由變量提供綁定的計算環境(做用域)。

我的理解

也就是,函數返回值是一個函數,返回的函數能作一些事情,經過主函數獲得這個返回值函數,而後就能夠屢次調用去作這個函數能夠作的事情。這個函數應用改的全部的局部變量都將成爲閉包中的變量,持續引用。

能夠把 閉包理解成,返回值是一個對象,能夠作一些操做。 咱們傳值獲得這個對象(實際上是函數),而後就能夠傳值作這個函數能作的事情了。函數式編程的概念這裏不深究,大概瞭解閉包的使用。

注意:閉包引用的原局部變量也是拷貝,是對指針的拷貝。

func main(){
    f:=closure(10)
    fmt.Println(f(1))
    fmt.Println(f(2))
    fmt.Println(f(3))
    fmt.Println(f(4))
}
func closure(x int) func(int)int{
    return func(y int) int{
        return x+y
    }
}

六、遞歸

遞歸,就是在運行的過程當中調用本身。一個函數調用本身,就叫作遞歸函數。

構成遞歸需具有的條件:
一、子問題須與原始問題爲一樣的事,且更爲簡單。
二、不能無限制地調用自己,須有個出口,化簡爲非遞歸情況處理。

遞歸函數能夠解決許多數學問題,如計算給定數字階乘、產生斐波系列等;

package main
import "fmt"
//菲波那切數列
func fibonaci(i int) int {
    if i == 0 {
        return 0
    }
    if i == 1 {
        return 1
    }
    return fibonaci(i-1) + fibonaci(i-2)
}

func main() {
    var i int
    for i = 0; i < 10; i++ {
        fmt.Printf("%d\n", fibonaci(i))
    }
}

七、延遲調用-defer

  • defer的執行方式相似其它語言中的析構函數,在函數體執行結束後按照調用順序的相反順序逐個執行
  • 即便函數發生嚴重錯誤也會執行
  • 支持匿名函數的調用
  • 經常使用於資源清理、文件關閉、解鎖以及記錄時間等操做
  • 經過與匿名函數配合可在return以後修改函數計算結果
  • 若是函數體內某個變量做爲defer後的語句參數,則在定義defer時即已經得到了拷貝,若是是匿名函數的參數,則是引用某個變量的地址
  • Go 沒有異常機制,但有 panic/recover 模式來處理錯誤
  • Panic 能夠在任何地方引起,但recover只有在defer調用的函數中有效
func main(){            //defer 逆序調用
    for i:=0;i<10;i++{
        defer fmt.Println(i)     //值的拷貝
    }
}
//執行結果是: 從9 到0

看一個有趣的例子:

func main(){
    for i:=0;i<10;i++{
        defer func(){
            fmt.Println(i)  //引用變量地址
        }()
    }
}
//執行結果   所有都是10

當defer 的是個匿名函數的時候,這裏就會出現閉包。每次執行循環體,都只是 對i的引用,能夠理解成 把defer 存入棧,可是存的是func這個匿名函數並無賦值i,等到執行完循環體開始執行defer 語句的時候,就會給i賦值,這個時候的i就是10,因此輸出了10個10

八、錯誤處理

這裏簡單介紹下go語言錯誤處理,(會有一篇詳解錯誤處理的)。

  • defer 在函數發聲嚴重錯誤的時候也會執行,相似java中 try..catch 中的finally 。
  • go 語言沒有異常機制,但有 panic/recover 模式來處理錯誤
  • Panic 能夠在任何地方引起,但recover只有在defer調用的函數中有效
func main() {
      A()
      B()
      C()
}

func A(){
   fmt.Println("func  A")
}

func B()  {
   defer func() {                //注意defer 要在panic以前註冊函數
      if err:=recover();err!=nil{   //調用recover(),返回一個panic的信息,
                    // 若是返回nil,說明沒錯,若是返回不是nil,說明引起了panic,在進行recover操做
         fmt.Println("Recover in B")
      }
   }()
   panic("Panic in B")

}

func C(){
   fmt.Println("func C")
}
//打印結果
//func  A
//Recover in B
//func C
相關文章
相關標籤/搜索