GO方法与自定义类型

简介: 本文详细介绍了 Go 语言中的自定义数据类型与方法。不同于传统的面向对象编程语言,Go 通过结构体 (`struct`) 和方法 (`method`) 来扩展自定义类型的功能。文章解释了如何定义结构体、创建方法,并探讨了值接收器与指针接收器的区别及应用场景。此外,还介绍了方法的可见性以及接收器的命名惯例。通过具体示例,帮助读者更好地理解和应用这些概念。

在面向对象编程语言中,我们可以使用类(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的方法并不是定义在类中,而是附加于自定义类型之上的,可以更加灵活地扩展自定义数据类型的功能与行为。

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

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


转载来源:https://juejin.cn/post/7247540660395098149

相关文章
|
6月前
|
存储 设计模式 Cloud Native
云原生系列Go语言篇-类型、方法和接口 Part 1
通过前面章节的学习,我们知道Go是一种静态类型语言,包含有内置类型和用户定义类型。和大部分现代编程语言一样,Go允许我们对类型关联方法。它也具备类型抽象,可以编写没有显式实现的方法。
85 0
|
1月前
|
SQL 关系型数据库 MySQL
Go语言项目高效对接SQL数据库:实践技巧与方法
在Go语言项目中,与SQL数据库进行对接是一项基础且重要的任务
53 11
|
3月前
|
存储 Ubuntu Go
在Ubuntu 16.04上安装Go 1.6的方法
在Ubuntu 16.04上安装Go 1.6的方法
46 1
|
3月前
|
存储 Ubuntu Go
在Ubuntu 18.04上安装Go的方法
在Ubuntu 18.04上安装Go的方法
52 1
|
3月前
|
存储 Ubuntu Linux
在Ubuntu 14.04上安装Go 1.6的方法
在Ubuntu 14.04上安装Go 1.6的方法
51 1
|
3月前
|
SQL 安全 测试技术
[go 面试] 接口测试的方法与技巧
[go 面试] 接口测试的方法与技巧
|
6月前
|
存储 安全 Go
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
掌握Go语言:Go语言类型转换,无缝处理数据类型、接口和自定义类型的转换细节解析(29)
133 0
|
3月前
|
运维 监控 算法
[go 面试] 优化线上故障排查与性能问题的方法
[go 面试] 优化线上故障排查与性能问题的方法
|
3月前
|
JSON 测试技术 Go
Go Kit中读取原始HTTP请求体的方法
Go Kit中读取原始HTTP请求体的方法
|
3月前
|
缓存 算法 Go