golang筆記——函數與方法


如果你遇到沒有函數體的函數聲明,表示該函數不是以Go實現的。

package math func Sin(x float64) float //implemented in assembly language

如果為函數的每一個返回值都設置變量名,則會以相應的零值初始化,且在該函數的return語句中省略操作數,這種用法稱之為 bare return。

go中的錯誤處理,習慣上是先進行一系列的初始化檢查,將處理失敗邏輯的代碼先行處理,然后才是函數的實際邏輯,這樣使得代碼更簡潔,避免過多的層級結構。

函數定義時,可以使用函數類型作為參數,也可以作為返回類型,是不是有點類似委托,從而實現閉包。此外還有匿名函數,是不是類似於lambda表達式。strings.Map 函數可以拿來試驗。
func squares() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}
func main() {
    f := squares()
    fmt.Println(f()) // "1"
    fmt.Println(f()) // "4"
    fmt.Println(f()) // "9"
    fmt.Println(f()) // "16"
}
 
        
 
        

匿名函數和squares中,存在變量引用。這就是函數值屬於引用類型和函數值不可比較的原因。Go使用閉包(closures)技術實現函數值,Go程序員也把函數值叫做閉包。

 注意golang聖經中匿名函數一節中的例子程序。

 

go語言的可變參函數非常好用,你可以傳遞多個同類型參數,也可以直接傳入一個該類型的切片(注意傳入切片時要使用...標記,我想應該是為了同切片參數區分吧,畢竟兩者還是有些不同的),如果想要使用不同類型的變參,那么使用萬能的 interfac{} ,函數體內像解析切片一樣解析這個變參就好了。

 

直到包含該defer語句的函數執行完畢時,defer后的函數才會被執行,不論包含defer語句的函數是通過return正常結束,還是由於panic導致的異常結束。你可以在一個函數中執行多條defer語句,它們的執行順序與聲明順序相反

 

var mu sync.Mutex var m = make(map[string]int) func lookup(key string) int { mu.Lock() defer mu.Unlock() return m[key] }



調試復雜程序時,defer機制也常被用於記錄何時進入和退出函數。

func bigSlowOperation() { defer trace("bigSlowOperation")() // don't forget the extra parentheses // ...lots of work… time.Sleep(10 * time.Second) // simulate slow operation by sleeping } func trace(msg string) func() { start := time.Now() log.Printf("enter %s", msg) return func() { log.Printf("exit %s (%s)", msg,time.Since(start)) } }


我們只需要首先命名double的返回值,再增加defer語句,我們就可以在double每次被調用時,輸出參數以及返回值。

func double(x int) (result int) { defer func() { fmt.Printf("double(%d) = %d\n", x,result) }() return x + x } _ = double(4) // Output: // "double(4) = 8" 

 

 

太利於調試了。。。defer語句還是要細看。

 

 

為了方便診斷問題,runtime包允許程序員輸出堆棧信息。在下面的例子中,我們通過在main函數中延遲調用printStack輸出堆棧信息。

gopl.io/ch5/defer2 func main() { defer printStack() f(3) } func printStack() { var buf [4096]byte n := runtime.Stack(buf[:], false) os.Stdout.Write(buf[:n]) }

不能為一個結構體定義同名的字段名和方法名,有點奇怪。
 

函數指針:go里其實也是有函數指針的,下面用go語言實現表驅動模式。
package main

import (
    "fmt"
)

func add(a int, b int) int {
    return a + b 
}

func sub(a int, b int) int {
    return a - b 
}

func main() {
    fm := make(map[int]func(int, int) int)
    fm[1001] = add 
    fm[1002] = sub 
    protocol := 2001
    i := 1
    j := 2
    if func_handle, ok := fm[protocol]; ok {
        println(func_handle(i, j)) 
    } else {
        fmt.Printf("protocol: %d not register!", protocol)
    }   
}
 
        

 

返回局部變量指針:

  不同於 C 語言,GO 的函數可以返回局部變化指針,且編譯器會通過逃逸分析(escape analysis)來決定是否在堆上分配內存。

 

 

編譯時可以通過 -gcflags "-l -m" 參數來禁用函數內聯,函數內聯會對內存分配有一些影響,具體不清楚。

 

函數參數沒有所謂的引用傳遞,都是值傳遞的,區別只是傳遞的是拷貝對象還是指針而已。在 C 語言中,一般推薦傳遞指針參數來避免復制對象提升效率,但在 go 中,被復制的指針會延長目標對象的生命周期,還可能導致它被分配到堆上,則性能消耗要加上堆內存分配和垃圾回收的成本,而在棧上復制小對象其實非常快,所以如果不是特別大的對象或確實需要修改原對象,一般不需要傳指針參數。在並發編程中,也提倡使用不可變對象(只讀或復制),可以消除數據同步的麻煩。

如下就會在堆上分配內存,編譯時通過 -gcflags "-m" 可查看匯編代碼:

func test(p *int) {
    go func() {
        println(p)
    }() 
}

func main() {
    x := 100 
    p := &x
    test(p)
}

 

 

使用傳出參數,推薦使用返回值,也可以使用二級指針:

func test(p **int) {
    x := 100
    *p = &x
}

func main() {
    var p *int
    test(&p)
    println(*p)
}

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM