Golang基础语法(7)

72 阅读3分钟

28.方法

匿名字段

可以像字段成员那样访问匿名字段方法,编译器负责查找。

package main
​
import "fmt"type User struct {
    id   int
    name string
}
​
type Manager struct {
    User //只定义类型
}
//用self来占位,也可以用其他表示,都会被编译器进行识别访问
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: 0xc000010030
User: 0xc000010030, &{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} 

方法集

• 类型 T 方法集包含全部 receiver T 方法。
• 类型 *T 方法集包含全部 receiver T + *T 方法。
• 如类型 S 包含匿名字段 T,则 S 和 *S 方法集包含 T 方法。 
• 如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法。 
• 不管嵌入 T*T*S 方法集总是包含 T + *T 方法。

注:当类型调用自己声明的方法时,不需要考虑receiver是值语义还是指针语义,可以调用全部方法。因为在调用时,编译器自动做了转换。

// 使用值语义声明变量u
u := user{}
// notify方法的receiver为指针语义
u.notify()  // 编译器自动转换为 (&u).notify()// 使用指针语义声明变量u2
u2 := &user{}
// showUserInfo方法的receiver为值语义
u2.showUserInfo()   // 编译器自动转换为 (*u2).showUserInfo()
// notifier 是一个定义了notify行为的接口
type notifier interface {
    notify()
}
​
// user 使用指针语义实现了notifier接口
type user struct {
    name  string
    email string
}
​
// receiver为指针语义
func (u *user) notify() {
    fmt.Printf("Sending User Email To %s<%s>\n", u.name, u.email)
}
​
// receiver为值语义
func (u user) showUserInfo() {
    fmt.Printf("Name:%s\n Email:<%s>\n", u.name, u.email)
}
​
func main() {
​
    u := user{"Han", "han@email.com"}
​
    // 发生编译错误!!!!!!!!!!!,此时T类型不包含*T的方法,所以报错
    sendNotification(u)
    //解决防范  sendNotification(&u)
}
​
// sendNotification函数接收一个实现notifier接口的变量
func sendNotification(n notifier) {
    n.notify()
}

表达式

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

前者称为 method value,后者 method expression。

两者都可像普通函数那样赋值和传参,区别在于 method value 绑定实例,而 method expression 则须显式传参。

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() //{2 Jack}
​
    mValue()//{1 Tom}
} 
​

自定义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:
​
        }
​
}