Go 之旅四: 方法與接口篇

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

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

1. 方法

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

/**
 * go 語言 方法
 */

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) // 此時 v.Scale(10) 會隱式轉(zhuǎn)為 (&v).Scale(10)
    fmt.Println(v.Abs_method())
    fmt.Println(Abs_function(v)) // 方法只是個帶接收者參數(shù)的函數(shù)

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

Go 沒有類赫粥。不過你可以為結(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 指針接收者

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

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

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

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

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

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

由于 Scale 方法有一個指針接收者映企,為方便起見悟狱,Go 會將語句 v.Scale(5) 解釋為 (&v).Scale(5)

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

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

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

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

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

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

通常來說,所有給定類型的方法都應(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

// 實現(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)
}

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

類型通過實現(xiàn)一個接口的所有方法來實現(xiàn)該接口, 既然無需專門顯式聲明宣蔚,也就沒有“implements“關(guān)鍵字向抢。隱式接口將接口的實現(xiàn)與定義解耦,這樣接口的實現(xiàn)可以出現(xiàn)在任何包中胚委,無需提前定義挟鸠。

2.1 接口值

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

(value, type)

接口值保存了一個具體底層類型的具體值亩冬,接口值調(diào)用方法時會調(diào)用具體類型的的同名方法艘希。

2.2 底層值為 nil 的接口值

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

但是接口值nil時,由于此時接口值既不保存值也不保存具體類型营袜,調(diào)用方法會產(chǎn)生運行時錯誤撒顿,因為接口的元組內(nèi)并未包含能夠指明該調(diào)用哪個具體類型的方法。

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 空接口

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

interface{}

因為每個類型都至少實現(xiàn)了零個方法荚板,空接口可保存任何類型的值凤壁。

2.4 類型斷言

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

t := i.(T)

該語句斷言接口值 i 保存了具體類型 T 跪另,并將其底層類型為 T 的值賦予變量 t 拧抖。如果 i 并未保存 T 類型的值,該語句就會觸發(fā)一個錯誤免绿。

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

t, ok := i.(T)

i 保存了一個 T ,那么 t 將會是其底層值淌哟,而 ok 為 true 迹卢。否則, ok 將為 falset 將為 T 類型的零值绞绒,程序并不會產(chǎn)生錯誤婶希。

2.5 類型選擇

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

類型選擇與一般的 switch 語句相似蓬衡,不過類型選擇中的 case 為類型(而非值),它們針對給定接口值所存儲值的類型進行比較

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

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

此選擇語句判斷接口值 i 保存的值類型是 T 還是 S缴啡。 在 TS 的情況下壁晒,變量 v 會分別按 TS 類型取保存在 i 中的值。在默認(rèn)(沒有匹配)的情況下业栅,變量 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 是一個可以用字符串描述自己的類型碘裕。fmt 包(還有很多包)都通過此接口來打印值携取。

4. 錯誤

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

/**
 * go語言 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 值來表示錯誤狀態(tài)。與 fmt.Stringer 類似帮孔, error 類型是一個內(nèi)建接口:

type error interface {
    Error() string
}

通常函數(shù)會返回一個 error 值雷滋,調(diào)用的它的代碼應(yīng)當(dāng)判斷這個錯誤是否等于 nil 來進行錯誤處理。

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

errornil 時表示成功文兢;非 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ù)流的末尾進行讀取姆坚。Go 標(biāo)準(zhǔn)庫包含了該接口的許多實現(xiàn)澳泵,包括文件、網(wǎng)絡(luò)連接兼呵、壓縮和加密等等兔辅。

io.Reader 接口有一個 Read 方法:

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

Read 用數(shù)據(jù)填充給定的字節(jié)切片并返回填充的字節(jié)數(shù)和錯誤值。 在遇到數(shù)據(jù)流的結(jié)尾時萍程,它會返回一個 io.EOF 錯誤幢妄。

參考

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

@剛剛小碼農(nóng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市茫负,隨后出現(xiàn)的幾起案子蕉鸳,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潮尝,死亡現(xiàn)場離奇詭異榕吼,居然都是意外死亡,警方通過查閱死者的電腦和手機勉失,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門羹蚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乱凿,你說我怎么就攤上這事顽素。” “怎么了徒蟆?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵胁出,是天一觀的道長。 經(jīng)常有香客問我段审,道長全蝶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任寺枉,我火速辦了婚禮抑淫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姥闪。我一直安慰自己始苇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布甘畅。 她就那樣靜靜地躺著埂蕊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疏唾。 梳的紋絲不亂的頭發(fā)上蓄氧,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音槐脏,去河邊找鬼喉童。 笑死,一個胖子當(dāng)著我的面吹牛顿天,可吹牛的內(nèi)容都是我干的堂氯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼牌废,長吁一口氣:“原來是場噩夢啊……” “哼咽白!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸟缕,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤晶框,失蹤者是張志新(化名)和其女友劉穎排抬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體授段,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蹲蒲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了侵贵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片届搁。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖窍育,靈堂內(nèi)的尸體忽然破棺而出卡睦,到底是詐尸還是另有隱情,我是刑警寧澤漱抓,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布么翰,位于F島的核電站,受9級特大地震影響辽旋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜檐迟,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一补胚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧追迟,春花似錦溶其、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廓块,卻和暖如春厢绝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背带猴。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工昔汉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拴清。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓靶病,卻偏偏與公主長得像,于是被迫代替她去往敵國和親口予。 傳聞我的和親對象是個殘疾皇子娄周,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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

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