Go語言_通神路之五耀篇(1)

一、Go方法

靈胎篇博客中講到函數,函數就是相似與java中的方法,然而go中還有一些升級版的函數,叫方法。java

只不過這種方法在方法名前還有一個括號加參數,只不過被稱呼爲接收者,方法名後面的括號沒有參數,方法接收者在它本身的參數列表內,位於 func 關鍵字和方法名之間。函數

以下:這個就被稱之爲方法,由於方法名前面有接收者spa

package main

import (
   "math"
   "fmt"
)

type Person struct {
   x,y float64
}

func (p Person) Test() float64 {
   return math.Sqrt(p.x*p.x+p.y*p.y)
}

func main()  {
   value := Person{3,4}
   fmt.Print(value.Test())
}

反而,方法名後有參數,通常被稱呼爲函數,只不過是參數順序不一樣而已,調用的方式也不一樣罷了指針

package main

import (
   "math"
   "fmt"
)

type Person struct {
   x,y float64
}

func Test(p Person) float64 {
   return math.Sqrt(p.x*p.x+p.y*p.y)
}

func main()  {
   value := Person{3,4}
   fmt.Print(Test(value))
}

不過這個接受者有必定的限制,對struct結構體類型沒有限制要求,可是非結構體類型聲明方法,必須是本包內!!!code

舉例說明:blog

因此方法的接收者對結構體沒有要求,非結構體如數據類型等都要求在同一個package下面才能夠做爲接收者。接口

二、指針接收者

    2.1 概念

在靈胎篇第五篇中講到指針,& 操做符會生成一個指向其操做數的指針,* 操做符表示指針指向的底層值,所一當你運行下面的函數的時候,結果是50,開發

package main

import (
   "math"
   "fmt"
)

type Person struct {
   x,y float64
}

func (v *Person) Scale(f float64) {
   v.x = v.y * f
   v.y = v.y * f
}

func (p Person)Test() float64 {
   return math.Sqrt(p.x*p.x+p.y*p.y)
}

func main()  {
   value := Person{3,4}
   value.Scale(10)
   fmt.Print(value.Test())
}

當你去掉 Scale方法中接收者的指針的時候,結果會發生變化,由於去掉指針的話,執行那個函數的接收者將是Person結構體的一個副本,並非真正意義上的Person結構體,因此咱們須要用指針‘*’來更改博客

    2.2 方法與指針重定向

注意點:string

當函數的參數中有指針的狀況下,

func ScaleFunc(v *Vertex, f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

這個狀況下,函數中指針做爲參數,必須用 類型前加&

ScaleFunc(v, 5)  // 編譯錯誤!
ScaleFunc(&v, 5) // OK

而對於方法,能夠忽略,以下:

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK

由於方法中的接收者v.Scale(5)自動轉換(&v).Scale(5)操做

    2.3 方法與指針重定向(反向)

那說一下什麼是反向,就是方法中的接收者和函數的參數不是指針了,而是正常類型,咱們接下來如何操做。也有要求

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func AbsFunc(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

函數狀況下:

var v Vertex
fmt.Println(AbsFunc(v))  // OK
fmt.Println(AbsFunc(&v)) // 編譯錯誤!

方法狀況下:

var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

這種狀況下,方法調用 p.Abs() 會被自動轉換爲(*p).ABs(),像極了java的自動裝箱功能和int long的自動轉換功能

因此咱們在開發中,爲何方法用的很是多,函數用的不多的緣由,其次,這樣能夠避免在每次調用方法時複製該值。若值的類型爲大型結構體時,這樣作會更加高效。

三、接口

    3.1 概念理解

接口就是方法的集合,和java中的接口同樣,都須要實現接口

package main

import (
   "math"
   "fmt"
)

type myFloat float64

type Base interface {
   test() float64
}

func (f myFloat)test() float64 {
   return math.Sqrt(float64(f))
}

func main()  {

   var base Base

   f := myFloat(25) 

   base = f  //f實現接口base

   fmt.Print(base.test())
}

接下來用指針的接收者操做下

package main

import (
   "math"
   "fmt"
)

type myFloat float64

type Base interface {
   test() float64
}

func (f *myFloat)test() float64 {
   return math.Sqrt(25)
}

func main()  {

   var base Base

   f := myFloat(25)

   base = &f

   fmt.Print(base.test())
}

一樣是結果5,不過咱們上面不該該用數據類型做爲指針接收者,由於須要用結構體,這樣才方便在方法中使用操做業務邏輯。

接口接收者會實現接口!

    3.2 底層值爲 nil 的接口值

package main

import "fmt"

type I interface {
    M()
}

type T struct {
    S string
}

func (t *T) M() {
    if t == nil {
        fmt.Println("<nil>")
        return
    }
    fmt.Println(t.S)
}

結果爲:

(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello

即使接口內的具體值爲 nil,方法仍然會被 nil 接收者調用,在java中會觸發一個空指針異常,但在 Go 中一般會寫一些方法來優雅地處理它

*注意:* 保存了 nil 具體值的接口其自身並不爲 nil。

    3.3 空接口

package main

import "fmt"

func main() {
    var i interface{}
    describe(i)

    i = 42
    describe(i)

    i = "hello"
    describe(i)
}

func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}

結果爲:

(<nil>, <nil>)
(42, int)
(hello, string)

%V 意思是value %T是Type

相關文章
相關標籤/搜索