老虞要學GoLang-函數(上)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

 

不可或缺的函數,在Go中定義函數的方式如下:

func (p myType ) funcName ( a, b int , c string ) ( r , s int ) {    return}

通過函數定義,我們可以看到Go中函數和其他語言中的共性和特性

共性

  • 關鍵字——func
  • 方法名——funcName
  • 入參——— a,b int,b string
  • 傳回值—— r,s int
  • 函數體—— {}

特性

Go中函數的特性是非常酷的,給我們帶來不一樣的編程體驗。

為特定類型定義函數,即為類型對象定義方法

在Go中通過給函數標明所屬類型,來給該類型定義方法,上面的 p myType 即表示給myType聲明了一個方法, p myType 不是必須的。如果沒有,則純粹是一個函數,通過包名稱訪問。packageName.funcationName

如:

//定義新的類型double,主要目的是給float64類型擴充方法type double float64//判斷a是否等於bfunc (a double) IsEqual(b double) bool {    var r = a - b    if r == 0.0 {        return true    } else if r < 0.0 {        return r > -0.0001    }    return r < 0.0001}//判斷a是否等於bfunc IsEqual(a, b float64) bool {    var r = a - b    if r == 0.0 {        return true    } else if r < 0.0 {        return r > -0.0001    }    return r < 0.0001}func main() {    var a double = 1.999999    var b double = 1.9999998    fmt.Println(a.IsEqual(b))    fmt.Println(a.IsEqual(3))    fmt.Println( IsEqual( (float64)(a), (float64)(b) ) )}

上述樣本為 float64 基本類型擴充了方法IsEqual,該方法主要是解決精度問題。 其方法調用方式為: a.IsEqual(double) ,如果不擴充方法,我們只能使用函數IsEqual(a, b float64)

入參中,如果連續的參數類型一致,則可以省略連續多個參數的類型,只保留最後一個型別宣告。

func IsEqual(a, b float64) bool 這個方法就只保留了一個型別宣告,此時入參a和b均是float64資料類型。 這樣也是可以的: func IsEqual(a, b float64, accuracy int) bool

變參:入參支援變參,即可接受不確定數量的同一類型的參數

func Sum(args ...int) 參數args是的slice,其元素類型為int 。經常使用的fmt.Printf就是一個接受任意個數參數的函數 fmt.Printf(format string, args ...interface{})

支援多傳回值

前面我們定義函數時傳回值有兩個r,s 。這是非常有用的,我在寫C#代碼時,常常為了從已有函數中獲得更多的資訊,需要修改函數簽名,使用out ,ref 等方式去獲得更多返回結果。而現在使用Go時則很簡單,直接在傳回值後面添加返回參數即可。

如,在C#中一個字串轉換為int類型時邏輯代碼

int v=0; if ( int.TryPase("123456",out v) ){    //code}

而在Go中,則可以這樣實現,邏輯精簡而明確

if v,isOk :=int.TryPase("123456") ; isOk {    //code}

同時在Go中很多函數充分利用了多傳回值

  • func (file *File) Write(b []byte) (n int, err error)
  • func Sincos(x float64) (sin, cos float64)

那麼如果我只需要某一個傳回值,而不關心其他傳回值的話,我該如何辦呢? 這時可以簡單的使用符號底線”_“ 來忽略不關心的傳回值。如:

_, cos = math.Sincos(3.1415) //只需要cos計算的值

命名傳回值

前面我們說了函數可以有多個傳回值,這裡我還要說的是,在函數定義時可以給所有的傳回值分別命名,這樣就能在函數中任意位置給不同傳回值複製,而不需要在return語句中才指定傳回值。同時也能增強可讀性,也提高godoc所產生文檔的可讀性

如果不支援命名傳回值,我可能會是這樣做的

func ReadFull(r Reader, buf []byte) (int, error) {    var n int    var err error    for len(buf) > 0  {        var nr int        nr, err = r.Read(buf)         n += nr        if err !=nil {            return n,err        }        buf = buf[nr:]    }    return n,err}

但支援給傳回值命名後,實際上就是省略了變數的聲明,return時無需寫成return n,err 而是將直接將值返回

func ReadFull(r Reader, buf []byte) (n int, err error) {    for len(buf) > 0 && err == nil {        var nr int        nr, err = r.Read(buf)        n += nr        buf = buf[nr:]    }    return}

函數也是“值”

和Go中其他東西一樣,函數也是值,這樣就可以聲明一個函數類型的變數,將函數作為參數傳遞。

聲明函數為值的變數(匿名函數:可賦值個變數,也可直接執行)

//賦值fc := func(msg string) {    fmt.Println("you say :", msg)}fmt.Printf("%T \n", fc)fc("hello,my love")//直接執行func(msg string) {    fmt.Println("say :", msg)}("I love to code")

輸出結果如下,這裡表明fc 的類型為:func(string)

func(string) you say : hello,my lovesay : I love to code

將函數作為入參(回呼函數),能帶來便利。如Tlog,為了統一處理,將資訊均通過指定函數去記錄日誌,且是否記錄日誌還有開關

func Log(title string, getMsg func() string) {    //如果開啟日誌記錄,則記錄日誌    if true {        fmt.Println(title, ":", getMsg())    }}//---------調用--------------count := 0msg := func() string {    count++    return "您沒有即使提醒我,已觸犯法律"}Log("error", msg)Log("warring", msg)Log("info", msg)fmt.Println(count)

這裡輸出結果如下,count 也發生了變化

error : 您沒有即使提醒我,已觸犯法律warring : 您沒有即使提醒我,已觸犯法律info : 您沒有即使提醒我,已觸犯法律3

函數也是“類型”

你有沒有注意到上面樣本中的 fc := func(msg string)... ,既然匿名函數可以賦值給一個變數,同時我們經常這樣給int賦值 value := 2 ,是否我們可以聲明func(string) 類型 呢,當然是可以的。

//一個記錄日誌的類型:func(string)type saveLog func(msg string)//將字串轉換為int64,如果轉換失敗調用saveLogfunc stringToInt(s string, log saveLog) int64 {    if value, err := strconv.ParseInt(s, 0, 0); err != nil {        log(err.Error())        return 0    } else {        return value    }}//記錄日誌訊息的具體實現func myLog(msg string) {    fmt.Println("Find Error:", msg)}func main() {    stringToInt("123", myLog) //轉換時將調用mylog記錄日誌    stringToInt("s", myLog)}

這裡我們定義了一個類型,專門用作記錄日誌的標準介面。在stringToInt函數中如果轉換失敗則調用我自己定義的介面函數進行Tlog,至於最終執行的哪個函數,則無需關心。

defer 延遲函數

defer 又是一個創新,它的作用是:順延強制,在聲明時不會立即執行,而是在函數return後時按照後進先出的原則依次執行每一個defer。這樣帶來的好處是,能確保我們定義的函數能百分之百能夠被執行到,這樣就能做很多我們想做的事,如釋放資源,清理資料,記錄日誌等

這裡我們重點來說明下defer的執行順序

func deferFunc() int {    index := 0    fc := func() {        fmt.Println(index, "匿名函數1")        index++        defer func() {            fmt.Println(index, "匿名函數1-1")            index++        }()    }    defer func() {        fmt.Println(index, "匿名函數2")        index++    }()    defer fc()    return func() int {        fmt.Println(index, "匿名函數3")        index++        return index    }()}func main() {    deferFunc()}

這裡輸出結果如下,

0 匿名函數31 匿名函數12 匿名函數1-13 匿名函數2

有如下結論:

  • defer 是在執行完return 後執行
  • defer 後進先執行

另外,我們常使用defer去關閉IO,在正常開啟檔案後,就立刻聲明一個defer,這樣就不會忘記關閉檔案,也能保證在出現異常等不可預料的情況下也能關閉檔案。而不像其他語言:try-catch 或者 using() 方式進行處理。

file , err :=os.Open(file)if err != nil {    return err}defer file.Close() //dosomething with file

後續,我將討論: 範圍、傳值和傳指標 以及 保留函數init(),main()

本筆記中所寫代碼儲存位置:

  • defer.go
  • defineFunctionType.go
  • function.go

上篇-控制語句

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.