天天看點

go語言學習:方法 一、方法定義二、匿名字段三、方法集四、表達式五、自定義error

一、方法定義

Golang 方法總是綁定對象執行個體,并隐式将執行個體作為第一實參 (receiver)。

• 隻能為目前包内命名類型定義方法。
• 參數 receiver 可任意命名。如方法中未曾使用 ,可省略參數名。
• 參數 receiver 類型可以是 T 或 *T。基類型 T 不能是接口或指針。 
• 不支援方法重載,receiver 隻是參數簽名的組成部分。
• 可用執行個體 value 或 pointer 調用全部方法,編譯器自動轉換。
           

一個方法就是一個包含了接受者的函數,接受者可以是命名類型或者結構體類型的一個值或者是一個指針。

所有給定類型的方法屬于該類型的方法集。

1.1.1. 方法定義:

func (recevier type) methodName(參數清單)(傳回值清單){}

    參數和傳回值可以省略
           
package main

type Test struct{}

// 無參數、無傳回值
func (t Test) method0() {

}

// 單參數、無傳回值
func (t Test) method1(i int) {

}

// 多參數、無傳回值
func (t Test) method2(x, y int) {

}

// 無參數、單傳回值
func (t Test) method3() (i int) {
    return
}

// 多參數、多傳回值
func (t Test) method4(x, y int) (z int, err error) {
    return
}

// 無參數、無傳回值
func (t *Test) method5() {

}

// 單參數、無傳回值
func (t *Test) method6(i int) {

}

// 多參數、無傳回值
func (t *Test) method7(x, y int) {

}

// 無參數、單傳回值
func (t *Test) method8() (i int) {
    return
}

// 多參數、多傳回值
func (t *Test) method9(x, y int) (z int, err error) {
    return
}

func main() {}
           

下面定義一個結構體類型和該類型的一個方法:

package main

import (
    "fmt"
)

//結構體
type User struct {
    Name  string
    Email string
}

//方法
func (u User) Notify() {
    fmt.Printf("%v : %v \n", u.Name, u.Email)
}
func main() {
    // 值類型調用方法
    u1 := User{"golang", "[email protected]"}
    u1.Notify()
    // 指針類型調用方法
    u2 := User{"go", "[email protected]"}
    u3 := &u2
    u3.Notify()
}
           

輸出結果:

golang : [email protected] 
    go : [email protected]
           

解釋: 首先我們定義了一個叫做 User 的結構體類型,然後定義了一個該類型的方法叫做 Notify,該方法的接受者是一個 User 類型的值。要調用 Notify 方法我們需要一個 User 類型的值或者指針。

在這個例子中當我們使用指針時,Go 調整和解引用指針使得調用可以被執行。注意,當接受者不是一個指針時,該方法操作對應接受者的值的副本(意思就是即使你使用了指針調用函數,但是函數的接受者是值類型,是以函數内部操作還是對副本的操作,而不是指針操作。

我們修改 Notify 方法,讓它的接受者使用指針類型:

package main

import (
    "fmt"
)

//結構體
type User struct {
    Name  string
    Email string
}

//方法
func (u *User) Notify() {
    fmt.Printf("%v : %v \n", u.Name, u.Email)
}
func main() {
    // 值類型調用方法
    u1 := User{"golang", "[email protected]"}
    u1.Notify()
    // 指針類型調用方法
    u2 := User{"go", "[email protected]"}
    u3 := &u2
    u3.Notify()
}
           

輸出結果:

golang : [email protected] 
    go : [email protected]
           

注意:當接受者是指針時,即使用值類型調用那麼函數内部也是對指針的操作。

方法不過是一種特殊的函數,隻需将其還原,就知道 receiver T 和 

*T

 的差别。

package main

import "fmt"

type Data struct {
    x int
}

func (self Data) ValueTest() { // func ValueTest(self Data);
    fmt.Printf("Value: %p\n", &self)
}

func (self *Data) PointerTest() { // func PointerTest(self *Data);
    fmt.Printf("Pointer: %p\n", self)
}

func main() {
    d := Data{}
    p := &d
    fmt.Printf("Data: %p\n", p)

    d.ValueTest()   // ValueTest(d)
    d.PointerTest() // PointerTest(&d)

    p.ValueTest()   // ValueTest(*p)
    p.PointerTest() // PointerTest(p)
}
           

輸出:

Data: 0xc42007c008
    Value: 0xc42007c018
    Pointer: 0xc42007c008
    Value: 0xc42007c020
    Pointer: 0xc42007c008
           

1.1.2. 普通函數與方法的差別

1.對于普通函數,接收者為值類型時,不能将指針類型的資料直接傳遞,反之亦然。

2.對于方法(如struct的方法),接收者為值類型時,可以直接用指針類型的變量調用方法,反過來同樣也可以。

package main

//普通函數與方法的差別(在接收者分别為值類型和指針類型的時候)

import (
    "fmt"
)

//1.普通函數
//接收值類型參數的函數
func valueIntTest(a int) int {
    return a + 10
}

//接收指針類型參數的函數
func pointerIntTest(a *int) int {
    return *a + 10
}

func structTestValue() {
    a := 2
    fmt.Println("valueIntTest:", valueIntTest(a))
    //函數的參數為值類型,則不能直接将指針作為參數傳遞
    //fmt.Println("valueIntTest:", valueIntTest(&a))
    //compile error: cannot use &a (type *int) as type int in function argument

    b := 5
    fmt.Println("pointerIntTest:", pointerIntTest(&b))
    //同樣,當函數的參數為指針類型時,也不能直接将值類型作為參數傳遞
    //fmt.Println("pointerIntTest:", pointerIntTest(&b))
    //compile error:cannot use b (type int) as type *int in function argument
}

//2.方法
type PersonD struct {
    id   int
    name string
}

//接收者為值類型
func (p PersonD) valueShowName() {
    fmt.Println(p.name)
}

//接收者為指針類型
func (p *PersonD) pointShowName() {
    fmt.Println(p.name)
}

func structTestFunc() {
    //值類型調用方法
    personValue := PersonD{101, "hello world"}
    personValue.valueShowName()
    personValue.pointShowName()

    //指針類型調用方法
    personPointer := &PersonD{102, "hello golang"}
    personPointer.valueShowName()
    personPointer.pointShowName()

    //與普通函數不同,接收者為指針類型和值類型的方法,指針類型和值類型的變量均可互相調用
}

func main() {
    structTestValue()
    structTestFunc()
}
           

輸出結果:

valueIntTest: 12
    pointerIntTest: 15
    hello world
    hello world
    hello golang
    hello golang
           

二、匿名字段

Golang匿名字段 :可以像字段成員那樣通路匿名字段方法,編譯器負責查找。

package main

import "fmt"

type User struct {
    id   int
    name string
}

type Manager struct {
    User
}

func (self *User) ToString() string { // receiver = &(Manager.User)
    return fmt.Sprintf("User: %p, %v", self, self)
}

func main() {
    m := Manager{User{1, "Tom"}}
    fmt.Printf("Manager: %p\n", &m)
    fmt.Println(m.ToString())
}
           

輸出結果:

Manager: 0xc42000a060
    User: 0xc42000a060, &{1 Tom}
           

通過匿名字段,可獲得和繼承類似的複用能力。依據編譯器查找次序,隻需在外層定義同名方法,就可以實作 "override"。

package main

import "fmt"

type User struct {
    id   int
    name string
}

type Manager struct {
    User
    title string
}

func (self *User) ToString() string {
    return fmt.Sprintf("User: %p, %v", self, self)
}

func (self *Manager) ToString() string {
    return fmt.Sprintf("Manager: %p, %v", self, self)
}

func main() {
    m := Manager{User{1, "Tom"}, "Administrator"}

    fmt.Println(m.ToString())

    fmt.Println(m.User.ToString())
}
           

輸出結果:

Manager: 0xc420074180, &{{1 Tom} Administrator}
    User: 0xc420074180, &{1 Tom}
           

三、方法集

Golang方法集 :每個類型都有與之關聯的方法集,這會影響到接口實作規則。

• 類型 T 方法集包含全部 receiver T 方法。
    • 類型 *T 方法集包含全部 receiver T + *T 方法。
    • 如類型 S 包含匿名字段 T,則 S 和 *S 方法集包含 T 方法。 
    • 如類型 S 包含匿名字段 *T,則 S 和 *S 方法集包含 T + *T 方法。 
    • 不管嵌入 T 或 *T,*S 方法集總是包含 T + *T 方法。
           

用執行個體 value 和 pointer 調用方法 (含匿名字段) 不受方法集限制,編譯器總是查找全部方法,并自動轉換 receiver 實參。

Go 語言中内部類型方法集提升的規則:

類型 T 方法集包含全部 receiver T 方法。

package main

import (
    "fmt"
)

type T struct {
    int
}

func (t T) test() {
    fmt.Println("類型 T 方法集包含全部 receiver T 方法。")
}

func main() {
    t1 := T{1}
    fmt.Printf("t1 is : %v\n", t1)
    t1.test()
}
           

輸出結果:

t1 is : {1}
    類型 T 方法集包含全部 receiver T 方法。
           

類型 

*T

 方法集包含全部 

receiver T + *T

 方法。

package main

import (
    "fmt"
)

type T struct {
    int
}

func (t T) testT() {
    fmt.Println("類型 *T 方法集包含全部 receiver T 方法。")
}

func (t *T) testP() {
    fmt.Println("類型 *T 方法集包含全部 receiver *T 方法。")
}

func main() {
    t1 := T{1}
    t2 := &t1
    fmt.Printf("t2 is : %v\n", t2)
    t2.testT()
    t2.testP()
}
           

輸出結果:

t2 is : &{1}
    類型 *T 方法集包含全部 receiver T 方法。
    類型 *T 方法集包含全部 receiver *T 方法。
           

給定一個結構體類型 S 和一個命名為 T 的類型,方法提升像下面規定的這樣被包含在結構體方法集中:

如類型 S 包含匿名字段 T,則 S 和 

*S

 方法集包含 T 方法。

這條規則說的是當我們嵌入一個類型,嵌入類型的接受者為值類型的方法将被提升,可以被外部類型的值和指針調用。

package main

import (
    "fmt"
)

type S struct {
    T
}

type T struct {
    int
}

func (t T) testT() {
    fmt.Println("如類型 S 包含匿名字段 T,則 S 和 *S 方法集包含 T 方法。")
}

func main() {
    s1 := S{T{1}}
    s2 := &s1
    fmt.Printf("s1 is : %v\n", s1)
    s1.testT()
    fmt.Printf("s2 is : %v\n", s2)
    s2.testT()
}
           

輸出結果:

s1 is : {{1}}
    如類型 S 包含匿名字段 T,則 S 和 *S 方法集包含 T 方法。
    s2 is : &{{1}}
    如類型 S 包含匿名字段 T,則 S 和 *S 方法集包含 T 方法。
           

如類型 S 包含匿名字段 

*T

,則 S 和 

*S

 方法集包含 

T + *T

 方法。

這條規則說的是當我們嵌入一個類型的指針,嵌入類型的接受者為值類型或指針類型的方法将被提升,可以被外部類型的值或者指針調用。

package main

import (
    "fmt"
)

type S struct {
    T
}

type T struct {
    int
}

func (t T) testT() {
    fmt.Println("如類型 S 包含匿名字段 *T,則 S 和 *S 方法集包含 T 方法")
}
func (t *T) testP() {
    fmt.Println("如類型 S 包含匿名字段 *T,則 S 和 *S 方法集包含 *T 方法")
}

func main() {
    s1 := S{T{1}}
    s2 := &s1
    fmt.Printf("s1 is : %v\n", s1)
    s1.testT()
    s1.testP()
    fmt.Printf("s2 is : %v\n", s2)
    s2.testT()
    s2.testP()
}
           

輸出結果:

s1 is : {{1}}
    如類型 S 包含匿名字段 *T,則 S 和 *S 方法集包含 T 方法
    如類型 S 包含匿名字段 *T,則 S 和 *S 方法集包含 *T 方法
    s2 is : &{{1}}
    如類型 S 包含匿名字段 *T,則 S 和 *S 方法集包含 T 方法
    如類型 S 包含匿名字段 *T,則 S 和 *S 方法集包含 *T 方法
           

四、表達式

Golang 表達式 :根據調用者不同,方法分為兩種表現形式:

instance.method(args...) ---> <type>.func(instance, args...)
           

前者稱為 method value,後者 method expression。

兩者都可像普通函數那樣指派和傳參,差別在于 method value 綁定執行個體,而 method expression 則須顯式傳參。

package main

import "fmt"

type User struct {
    id   int
    name string
}

func (self *User) Test() {
    fmt.Printf("%p, %v\n", self, self)
}

func main() {
    u := User{1, "Tom"}
    u.Test()

    mValue := u.Test
    mValue() // 隐式傳遞 receiver

    mExpression := (*User).Test
    mExpression(&u) // 顯式傳遞 receiver
}
           

輸出結果:

0xc42000a060, &{1 Tom}
    0xc42000a060, &{1 Tom}
    0xc42000a060, &{1 Tom}
           

需要注意,method value 會複制 receiver。

package main

import "fmt"

type User struct {
    id   int
    name string
}

func (self User) Test() {
    fmt.Println(self)
}

func main() {
    u := User{1, "Tom"}
    mValue := u.Test // 立即複制 receiver,因為不是指針類型,不受後續修改影響。

    u.id, u.name = 2, "Jack"
    u.Test()

    mValue()
}
           

輸出結果

{2 Jack}
    {1 Tom}
           

在彙編層面,method value 和閉包的實作方式相同,實際傳回 FuncVal 類型對象。

FuncVal { method_address, receiver_copy }
           

可依據方法集轉換 method expression,注意 receiver 類型的差異。

package main

import "fmt"

type User struct {
    id   int
    name string
}

func (self *User) TestPointer() {
    fmt.Printf("TestPointer: %p, %v\n", self, self)
}

func (self User) TestValue() {
    fmt.Printf("TestValue: %p, %v\n", &self, self)
}

func main() {
    u := User{1, "Tom"}
    fmt.Printf("User: %p, %v\n", &u, u)

    mv := User.TestValue
    mv(u)

    mp := (*User).TestPointer
    mp(&u)

    mp2 := (*User).TestValue // *User 方法集包含 TestValue。簽名變為 func TestValue(self *User)。實際依然是 receiver value copy。
    mp2(&u)
}
           

輸出:

User: 0xc42000a060, {1 Tom}
    TestValue: 0xc42000a0a0, {1 Tom}
    TestPointer: 0xc42000a060, &{1 Tom}
    TestValue: 0xc42000a100, {1 Tom}
           

将方法 "還原" 成函數,就容易了解下面的代碼了。

package main

type Data struct{}

func (Data) TestValue() {}

func (*Data) TestPointer() {}

func main() {
    var p *Data = nil
    p.TestPointer()

    (*Data)(nil).TestPointer() // method value
    (*Data).TestPointer(nil)   // method expression

    // p.TestValue()            // invalid memory address or nil pointer dereference

    // (Data)(nil).TestValue()  // cannot convert nil to type Data
    // Data.TestValue(nil)      // cannot use nil as type Data in function argument
}
           

五、自定義error

1.1. 抛異常和處理異常

1.1.1. 系統抛

package main

import "fmt"

// 系統抛
func test01() {
   a := [5]int{0, 1, 2, 3, 4}
   a[1] = 123
   fmt.Println(a)
   //a[10] = 11
   index := 10
   a[index] = 10
   fmt.Println(a)
}

func getCircleArea(radius float32) (area float32) {
   if radius < 0 {
      // 自己抛
      panic("半徑不能為負")
   }
   return 3.14 * radius * radius
}

func test02() {
   getCircleArea(-5)
}

//
func test03() {
   // 延時執行匿名函數
   // 延時到何時?(1)程式正常結束   (2)發生異常時
   defer func() {
      // recover() 複活 恢複
      // 會傳回程式為什麼挂了
      if err := recover(); err != nil {
         fmt.Println(err)
      }
   }()
   getCircleArea(-5)
   fmt.Println("這裡有沒有執行")
}

func test04()  {
   test03()
   fmt.Println("test04")
}

func main() {
   test04()
}
           

1.1.2. 傳回異常

package main

import (
   "errors"
   "fmt"
)

func getCircleArea(radius float32) (area float32, err error) {
   if radius < 0 {
      // 建構個異常對象
      err = errors.New("半徑不能為負")
      return
   }
   area = 3.14 * radius * radius
   return
}

func main() {
   area, err := getCircleArea(-5)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println(area)
   }
}
           

1.1.3. 自定義error:

package main

import (
    "fmt"
    "os"
    "time"
)

type PathError struct {
    path       string
    op         string
    createTime string
    message    string
}

func (p *PathError) Error() string {
    return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s", p.path,
        p.op, p.createTime, p.message)
}

func Open(filename string) error {

    file, err := os.Open(filename)
    if err != nil {
        return &PathError{
            path:       filename,
            op:         "read",
            message:    err.Error(),
            createTime: fmt.Sprintf("%v", time.Now()),
        }
    }

    defer file.Close()
    return nil
}

func main() {
    err := Open("/Users/5lmh/Desktop/go/src/test.txt")
    switch v := err.(type) {
    case *PathError:
        fmt.Println("get path error,", v)
    default:

    }

}
           

輸出結果:

get path error, path=/Users/pprof/Desktop/go/src/test.txt 
    op=read 
    createTime=2018-04-05 11:25:17.331915 +0800 CST m=+0.000441790 
    message=open /Users/pprof/Desktop/go/src/test.txt: no such file or directory