Go 之旅四: 方法與接口篇

原文鏈接 http://ironxu.com/701

本文是學(xué)習(xí) A Tour of Go (中文參考 Go 之旅中文 ) 整理的筆記,介紹Go 語(yǔ)言方法,接口,類型的基本概念和使用。

1. 方法

$GOPATH/src/go_note/gotour/methods/method/method.go 源碼如下:

/**
 * go 語(yǔ)言 方法
 */

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

/**
 * 方法名: Abs_method
 * 方法接收者: Vertex
 */
func (v Vertex) Abs_method() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 傳指針
func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

// 函數(shù)
func Abs_function(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 為非結(jié)構(gòu)體聲明方法
type MyFloat float64

func (f MyFloat) Abs_myfloat() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    v := Vertex{3, 4}
    v.Scale(10) // 此時(shí) v.Scale(10) 會(huì)隱式轉(zhuǎn)為 (&v).Scale(10)
    fmt.Println(v.Abs_method())
    fmt.Println(Abs_function(v)) // 方法只是個(gè)帶接收者參數(shù)的函數(shù)

    f := MyFloat(-2)
    fmt.Println(f.Abs_myfloat())
}

Go 沒(méi)有類。不過(guò)你可以為結(jié)構(gòu)體類型定義方法。

方法是一類帶特殊的 接收者 參數(shù)的函數(shù),方法接收者位于方法 func 關(guān)鍵字和方法名之間。

1.1 非結(jié)構(gòu)體類型聲明方法

只能為在同一包內(nèi)定義的類型添加方法, 而不能為其它包內(nèi)定義的類型(包括 int 之類的內(nèi)建類型)的接收者聲明方法。即接收者的類型定義和方法聲明必須在同一包內(nèi);不能為內(nèi)建類型聲明方法。

type MyFloat float64
func (f MyFloat) Abs_myfloat () float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

1.2 指針接收者

為指針接收者聲明方法:對(duì)于某類型 T ,指針接收者的類型可以用 *T 表示。(T 不能是像 *int 這樣的指針。)

指針接收者的方法可以修改接收者指向的值。 由于方法經(jīng)常需要修改它的接收者,指針接收者比值接收者更常用。若使用值接收者,方法只會(huì)對(duì)原始值的副本進(jìn)行操作。

1.3 方法與指針重定向(隱式轉(zhuǎn)換)

以指針為接收者的方法被調(diào)用時(shí),接收者既能為值又能為指針:

func (v *Vertex) Scale(f float64) {}

v.Scale(5)
(&v).Scale(5)

由于 Scale 方法有一個(gè)指針接收者,為方便起見(jiàn),Go 會(huì)將語(yǔ)句 v.Scale(5) 解釋為 (&v).Scale(5)

而以值為接收者的方法被調(diào)用時(shí),接收者既能為值又能為指針:

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

這種情況下,方法調(diào)用 p.Abs() 會(huì)被解釋為 (*p).Abs()。函數(shù)必須接受與定義相同的類型,不會(huì)隱式轉(zhuǎn)換

1.4 選擇值或指針作為接收者

使用指針接收者的原因有二:

  • 方法能夠修改接收者指向的值。
  • 避免在每次調(diào)用方法時(shí)復(fù)制該值,若值的類型為大型結(jié)構(gòu)體時(shí),這樣做會(huì)更加高效。

通常來(lái)說(shuō),所有給定類型的方法都應(yīng)該有值或指針接收者,但并不應(yīng)該二者混用。

2. 接口

$GOPATH/src/go_note/gotour/methods/interface/interface.go 源碼如下:

/**
 * go 接口
 */

package main

import (
    "fmt"
    "math"
)

// 定義接口
type Abser interface {
    Abs() float64
}

func main() {
    // 使用接口
    var a Abser
    f := MyFloat(-math.Sqrt2)
    a = f
    fmt.Println(a.Abs())

    v := Vertex{3, 4}
    a = &v
    fmt.Println(a.Abs())

    var i I
    var t *T
    i = t
    i.M()

    i = &T{"hello"}
    i.M()

    // 空接口
    var inter_empty interface{}
    inter_empty = 42
    fmt.Printf("%v, %T\n", inter_empty, inter_empty)
    inter_empty = "hello"
    fmt.Printf("%v, %T\n", inter_empty, inter_empty)

    // 類型斷言
    var j interface{} = "hello"
    s := j.(string)
    fmt.Println(s)

    s, ok := j.(string)
    fmt.Println(s, ok)

    inter_float, ok := j.(float64)
    fmt.Println(inter_float, ok)

    // 類型選擇
    do(21)
    do("hello")
    do(true)
}

type MyFloat float64

// 實(shí)現(xiàn)接口
func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

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

type I interface {
    M()
}

type T struct {
    S string
}

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

接口類型 是由一組方法簽名定義的集合, 接口類型的值可以保存任何實(shí)現(xiàn)了這些方法的值。

類型通過(guò)實(shí)現(xiàn)一個(gè)接口的所有方法來(lái)實(shí)現(xiàn)該接口, 既然無(wú)需專門顯式聲明,也就沒(méi)有“implements“關(guān)鍵字。隱式接口將接口的實(shí)現(xiàn)與定義解耦,這樣接口的實(shí)現(xiàn)可以出現(xiàn)在任何包中,無(wú)需提前定義。

2.1 接口值

在內(nèi)部,接口值可以看做包含值和具體類型的元組:

(value, type)

接口值保存了一個(gè)具體底層類型的具體值,接口值調(diào)用方法時(shí)會(huì)調(diào)用具體類型的的同名方法。

2.2 底層值為 nil 的接口值

即便接口內(nèi)的具體值為 nil,方法仍然會(huì)被 nil 接收者調(diào)用。保存了 nil 具體值的接口其自身并不為 nil

但是接口值nil時(shí),由于此時(shí)接口值既不保存值也不保存具體類型,調(diào)用方法會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤,因?yàn)榻涌诘脑M內(nèi)并未包含能夠指明該調(diào)用哪個(gè)具體類型的方法。

func main() {
    var i I
    i.M() // panic: runtime error
    
    var t *T
    i = t
    i.M() // <nil>
}

type I interface {
    M()
}

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

2.3 空接口

指定了零個(gè)方法的接口值被稱為空接口:

interface{}

因?yàn)槊總€(gè)類型都至少實(shí)現(xiàn)了零個(gè)方法,空接口可保存任何類型的值。

2.4 類型斷言

類型斷言提供了訪問(wèn)接口值底層具體值的方式。

t := i.(T)

該語(yǔ)句斷言接口值 i 保存了具體類型 T ,并將其底層類型為 T 的值賦予變量 t 。如果 i 并未保存 T 類型的值,該語(yǔ)句就會(huì)觸發(fā)一個(gè)錯(cuò)誤。

為了判斷一個(gè)接口值是否保存了一個(gè)特定的類型, 類型斷言可返回兩個(gè)值:其底層值和判斷斷言是否成功的布爾值。

t, ok := i.(T)

i 保存了一個(gè) T ,那么 t 將會(huì)是其底層值,而 ok 為 true 。否則, ok 將為 falset 將為 T 類型的零值,程序并不會(huì)產(chǎn)生錯(cuò)誤。

2.5 類型選擇

類型選擇是一種按順序從幾個(gè)類型斷言中選擇分支的結(jié)構(gòu)。

類型選擇與一般的 switch 語(yǔ)句相似,不過(guò)類型選擇中的 case 為類型(而非值),它們針對(duì)給定接口值所存儲(chǔ)值的類型進(jìn)行比較

switch v := i.(type) {
case T:
    // v 的類型為 T
case S:
    // v 的類型為 S
default:
    // 沒(méi)有匹配,v 與 i 的類型相同
}

類型選擇中的聲明與類型斷言 i.(T) 的語(yǔ)法相同,只是具體類型 T 被替換成了關(guān)鍵字 type

此選擇語(yǔ)句判斷接口值 i 保存的值類型是 T 還是 S。 在 TS 的情況下,變量 v 會(huì)分別按 TS 類型取保存在 i 中的值。在默認(rèn)(沒(méi)有匹配)的情況下,變量 vi 的接口類型和值相同。

3. Stringer

$GOPATH/src/go_note/gotour/methods/stringer/stringer.go 源碼如下:

/**
 * go String
 */
package main

import (
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person{"Author", 42}
    z := Person{"Modifier", 1989}
    fmt.Println(a, z)
}

fmt 包中定義的 Stringer 是最普遍的接口之一。

type Stringer interface {
    String() string
}

Stringer 是一個(gè)可以用字符串描述自己的類型。fmt 包(還有很多包)都通過(guò)此接口來(lái)打印值。

4. 錯(cuò)誤

$GOPATH/src/go_note/gotour/methods/error/error.go 源碼如下:

/**
 * go語(yǔ)言 error
 */

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s", e.When, e.What)
}

func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

Go 程序使用 error 值來(lái)表示錯(cuò)誤狀態(tài)。與 fmt.Stringer 類似, error 類型是一個(gè)內(nèi)建接口:

type error interface {
    Error() string
}

通常函數(shù)會(huì)返回一個(gè) error 值,調(diào)用的它的代碼應(yīng)當(dāng)判斷這個(gè)錯(cuò)誤是否等于 nil 來(lái)進(jìn)行錯(cuò)誤處理。

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

errornil 時(shí)表示成功;非 nilerror 表示失敗。

5. Reader

$GOPATH/src/go_note/gotour/methods/reader/reader.go 源碼如下:

/**
 * go read
 */

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, workd!")
    b := make([]byte, 4)

    for {
        n, err := r.Read(b)
        fmt.Printf("n = %v, err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        if err == io.EOF {
            break
        }
    }
}

io 包指定了 io.Reader 接口, 它表示從數(shù)據(jù)流的末尾進(jìn)行讀取。Go 標(biāo)準(zhǔn)庫(kù)包含了該接口的許多實(shí)現(xiàn),包括文件、網(wǎng)絡(luò)連接、壓縮和加密等等。

io.Reader 接口有一個(gè) Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 用數(shù)據(jù)填充給定的字節(jié)切片并返回填充的字節(jié)數(shù)和錯(cuò)誤值。 在遇到數(shù)據(jù)流的結(jié)尾時(shí),它會(huì)返回一個(gè) io.EOF 錯(cuò)誤。

參考

可以關(guān)注我的微博了解更多信息:

@剛剛小碼農(nóng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 方法和接口 第四篇包含了方法和接口,可以用它們來(lái)定義對(duì)象和其行為;以及如何將所有內(nèi)容貫通起來(lái)。 方法 Go 沒(méi)有類...
    張洋銘Ocean閱讀 1,494評(píng)論 2 0
  • 出處---Go編程語(yǔ)言 歡迎來(lái)到 Go 編程語(yǔ)言指南。本指南涵蓋了該語(yǔ)言的大部分重要特性 Go 語(yǔ)言的交互式簡(jiǎn)介,...
    Tuberose閱讀 18,501評(píng)論 1 46
  • 方法 Go中沒(méi)有類,但是可以為結(jié)構(gòu)體定義方法,方法就是一類帶有特殊的接受者參數(shù)的函數(shù)。方法接受者在它自己的參數(shù)列表...
    EvansChang閱讀 359評(píng)論 0 0
  • 作者:Alon Zakai 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/pos...
    胡子大哈閱讀 904評(píng)論 0 1
  • ?1??0??? ??沒(méi)??有??和??白??羊??座??談??過(guò)??的??戀??愛(ài)??不??叫??戀??愛(ài)???...
    c夢(mèng)蕾c閱讀 355評(píng)論 1 0