GO方法与自定义类型

在面向对象编程语言中,我们可以使用类(class)来模拟现实世界的实体,通过类的属性与方法,我们可以扩展自己想要的类型。

Go语言中并没有类的概念,不过Go支持定义方法(method),Go的方法不是定义在类中的,那Go的方法定义在哪里的呢?

在这篇文章中我们就来探讨一下!

自定义数据类型

要讲清楚Go的方法,先了解Go的自定义数据类型。

Go作为一个数据类型系统,内置许多的基础数据类型供我们使用,比如intunitstringmapslice等。

如果基础数据类型还不能满足我们的需求,或者我们想和面向对象编程语言一样,定义一个有多个属性与方法的数据实体,Go语言的结构体(struct)可以达到类似的效果:

 

go

代码解读

复制代码

type Car struct{ ID   int Band string Name string }

Go语言中,通过关键词type定义的数据类型,称为自定义类型,其语法为:

 

bash

代码解读

复制代码

type 自定义类型名称 基础数据名称

显然,结构体就是一种自定义数据类型,当然,除了结构体,我们也可以在其他内置类型的基础上创建任何的数据类型:

 

go

代码解读

复制代码

type Reason int type Month int

定义好数据类型之后,就可以像使用内置数据类型一样,用自定义类型定义变量或常量了:

 

ini

代码解读

复制代码

package main ​ func main(){  const(    Spring Reason = 1    Summer Reason = 2    Autumn Reason = 3    Winter Reason = 4 )  const (    January Month = 1 + iota    February    March    April    May    June    July    August    September    October    November    December ) }

方法的创建

Go语言的方法(method)本质是什么?简单来说就是函数(func)。

方法与函数的区别在于方法必须有一个自定义类型的接收器,在Go语言中,自定义数据类型可以通过方法来扩展功能。

方法的创建

方法本质上就是函数,所以其创建也与函数相似,只要在关键字func函数名中间加上一个用小括号括起来的接收器即可,如下图所示:

代码示例:

 

go

代码解读

复制代码

type User struct{  ID   int  Name string } ​ func (u User)Say(message string){ //... } ​ func (u *User)Run(){ //... }

接收器的数据类型只能是使用type创建的数据类型,Go内置的数据类型不能作为接收器:

 

kotlin

代码解读

复制代码

//报错,int,string等内置数据类型不能作为接收器 func (r int)String(){  if r == 1 { return "春天" } else if r == 2 { return "夏天" } else if r == 3 { return "秋天" } else { return "冬天" } }

同一个数据类型上不能两个相同名称的方法:

 

go

代码解读

复制代码

type Reason int ​ func (r Reason) String() string { if r == 1 { return "春天" } else if r == 2 { return "夏天" } else if r == 3 { return "秋天" } else { return "冬天" } } ​ //报错 func (r Reason) String() string {   }

方法的调用

要调用方法,必须先创建对应自定义数据类型的变量,然后使用变量名后跟上一个点号来调用对应的方法:

 

go

代码解读

复制代码

package main ​ import "fmt" ​ type Reason int ​ func (r Reason) String() string { if r == 1 { return "春天" } else if r == 2 { return "夏天" } else if r == 3 { return "秋天" } else { return "冬天" } } ​ type User struct { ID   int Name string } ​ func (u User) Say(message string) {    fmt.Println(message) } ​ func main() { ​ u := User{ID: 1, Name: "test"} //创建变量 u.Say("Hello World")           //调用方法 ​ var reason Reason = 1 ​ fmt.Println(reason.String()) //输出:春天 } ​

方法的可见性

在面向对象编程语言中,如果不想一个方法被外部调用,可以将方法定义可见性定义为private,这就是面向对象最重要特性之一:封装。

Go语言控制可见性是通过首字母是否大小写来实现的,方法名以大写字母开头的可在包外调用,方法名以小写字母开头,则只允许包内调用:

 

go

代码解读

复制代码

package cart ​ type Cart struct { } ​ func NewCart() *Cart { return &Cart{} } ​ func (c *Cart) Lock() error { //... return nil } ​ func (c *Cart) TotalPrice() (int, error) { //... return 0, nil } ​ func (c *Cart) delete() error { //... return nil } ​

main包中调用:

 

go

代码解读

复制代码

package main ​ import ( "app/cart" "fmt" "log" ) ​ func main() { myCart := cart.NewCart() totalPrice, err := myCart.TotalPrice() if err != nil { log.Printf("impossible to compute price of the cart: %s", err) return } fmt.Printf("TotalPrice:%d\n", totalPrice) //错误,该方法不可见 //myCart.delete() } ​

接收器

接收器可以看作是方法的一个参数,但不在方法的形参列表中,而是写在方法名前面,一个方法只能有一个接收器,当通过自定义类型的变量调用方法时,Go会将调用者复制给接收器。

 

go

代码解读

复制代码

type User struct{ ID   int FirstName string LastName string } ​ func (u User) GetFirstName(){ return u.FirstName //通过接收器访问当前接收器的字段 }

值接收器和指针接收器

方法的接收器有两种:值接收器和指针接收器。

前面我们的很多示例都是使用值接收器:

 

go

代码解读

复制代码

func (u User) GetLastName(){ return u.FirstName //通过接收器访问当前接收器的字段 }

指针接收器的写法就是在自定义类型前面加一个*号表示指向该类型的指针:

 

go

代码解读

复制代码

func (u *User) GetFirstName(){ return u.FirstName //通过接收器访问当前接收器的字段 }

值接收器与指针接收器有什么区别呢?

当通过类型变量调用方法时,会把调用者复制给接收器,无论是值接收器还是指针接收器,都会发生复制,所不同的是,使用值接收器时,会把调用者的值复制给接收器,使用指针接收器时,会把调用者的内存地址复制给接收器。

因此使用指针接收器有两个好处:

  • 当调用者变量本身数据比较大时,指针接收器可以避免大数据复制。
  • 指针接收器与调用者变量指向同一个内存地址,因此可以通过指针接收器修改调用者本身,这点值接收器是无法做到的。

下面我们通过一个示例来演示一下:

 

go

代码解读

复制代码

package main ​ import ( "fmt" "strconv" ) ​ type Student struct { ID   int Name string } ​ type ClassRoom struct { ID       string Name     string Students []Student } ​ func (c ClassRoom) ChangeName1(name string) { fmt.Printf("值接收器的内存地址:%p\n", &c) c.Name = name } ​ func (c *ClassRoom) ChangeName2(name string) { fmt.Printf("指针接收器的内存地址:%p\n", c) c.Name = name } ​ func main() { var students []Student for i := 1; i <= 100; i++ { students = append(students, Student{ID: i, Name: "同学" + strconv.Itoa(i)}) } classRoom := ClassRoom{ID: "001", Name: "高中一班", Students: students} ​ fmt.Printf("调用者本身的内存地址:%p\n", &classRoom) ​ classRoom.ChangeName1("高中二班") ​ fmt.Println(classRoom.Name) //输出:高中一班 ​ classRoom.ChangeName2("高中二班") fmt.Println(classRoom.Name) //输出:高中二班 }

在这个示例程序中,我们创建一个ClassRoom类型的变量表示一个教室,该教室包含100个学生(Student)的信息,ChangeName1()方法使用的是值接收器,ChangeName2()方法使用的是指针接收器。

上面的示例运行结果为:

 

代码解读

复制代码

调用者本身的内存地址:0xc00005c040 值接收器的内存地址:0xc00005c080 高中一班 指针接收器的内存地址:0xc00005c040 高中二班

通过运行结果我们可以发现,使用指针接收器,接收器与调用指向同一个内存地址,这样可以修改调用者自身的属性,也可以避免大量数据的复制。

接收器的命名惯例

指针接收器的作用类似面向对象编程类的this,用于引用对象自身,不过Go并不推荐将接收器命名为this,而是推荐使用接收器类型的首字母小写:

 

go

代码解读

复制代码

type Reason int ​ //不推荐 func (this Reason)String()string{ ​ } ​ type Car struct{ ID int Name string } ​ //推荐 func (c Car)Run(){ ​ }

小结

与其他面向对象编程语言不同,Go的方法并不是定义在类中,而是附加于自定义类型之上的,可以更加灵活地扩展自定义数据类型的功能与行为。

最后,总结一下,阅读完这篇文章后应该掌握的几个知识点:

  • 自定义类型是什么,如何自定义数据类型
  • 方法是什么,如何创建与调用方法。
  • 接收器是什么?什么是指针接收器,什么是值接收器。
  • 什么情况下要用指针接收器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值