go

方法

Posted by 云起 on 2022-09-17
Estimated Reading Time 6 Minutes
Words 1.5k In Total
Viewed Times

Method

随写

  1. Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
  2. 方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

基本使用

定义

1
2
3
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
1
2
3
1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
2.接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
3.方法名、参数列表、返回参数:具体格式与函数定义相同。

指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。

1
2
3
func (p *Person) SetAge(newAge int8) {
p.age = newAge
}

值类型的接收者

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

1
2
3
func (p Person) SetAge2(newAge int8) {
p.age = newAge
}

什么时候应该使用指针类型接收者

1
2
3
1.需要修改接收者中的值
2.接收者是拷贝代价比较大的大对象
3.保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

任意类型添加方法

在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法

1
2
3
4
5
6
7
8
9
10
11
12
type MyInt int

//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
fmt.Println("Hello, 我是一个int。")
}
func main() {
var m1 MyInt
m1.SayHello() //Hello, 我是一个int。
m1 = 100
fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt
}

注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

普通函数与方法的区别

1.对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。

2.对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。

匿名字段

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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())
}
1
2
Manager: 0xc42000a060
User: 0xc42000a060, &{1 Tom}

通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现 “override”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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())
}
1
2
Manager: 0xc420074180, &{{1 Tom} Administrator}
User: 0xc420074180, &{1 Tom}

方法集

Golang方法集 :每个类型都有与之关联的方法集,这会影响到接口实现规则。

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

表达式

Golang 表达式 :根据调用者不同,方法分为两种表现形式:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 立即复制 receiver,因为不是指针类型,不受后续修改影响。

mExpression := (*User).Test
mExpression(&u) // 显式传递 receiver
}
1
2
3
4
5
6
7
8
9
10
11
func main() {
u := User{1, "Tom"}
mValue := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。

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

mValue()
}
//{2 Jack}
//{1 Tom}

自定义error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
}

If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !