golang快速入门:结构体

结构体

Go 语言的面向对象编程与我们之前所熟悉的 PHP、Java 那一套完全不同,没有 ​​class​​、​​extends​​、​​implements​​ 之类的关键字和相应的概念,而是借助结构体来实现类的声明。

go 复制代码
type Person struct {
    name string //名字
    male bool   //性别
}

可以理解为结构体 ​​Person​​,并且包含了 ​​​​​​name​​、​​male​​ 两个属性。

​可以通过定义形如 ​​NewXXX​​ 这样的全局函数(首字母大写)作为结构体的初始化函数:

go 复制代码
func NewPerson(name string, male bool) *Persion {
    return &Person{name, male}
}


person := NewPerson("结构体", false)
fmt.Println(person)

成员方法

值方法

要为 Go 结构体定义成员方法,需要在 ​​func​​ 和方法名之间声明方法所属的结构体(有的地方将其称之为接收者声明),以 Person​​​​ 结构体为例,要为其定义获取 ​​name​​ 值的方法,可以这么做:

go 复制代码
func (s Person) GetName() string  {
    return s.name
}

可以在初始化 ​​Person​​ 结构体后,通过 ​​GetName()​​ 方法获取 ​​name​​ 值:

css 复制代码
person := NewPerson("结构体", false)
fmt.Println("Name:", person.GetName())

通过在函数中增加接收者声明的方式定义了函数所归属的类型,函数就不再是普通的函数,而是类的成员方法了。

指针方法

如果​需要在函数内部修改成员变量的值,并且该修改要作用到该函数作用域以外,那么就需要传入指针类型(结构体是值类型,不是引用类型,所以需要显式传入指针)。在 ​​​​GetName​​ 方法中,由于不需要对类的成员变量进行修改,所以不需要传入指针。

go 复制代码
func (s *P) SetName(name string) {
    s.name = name
}

​初始化 ​​​​Person​​ 结构体之后,通过 ​​​SetName​​​ 方法修改 ​​​name​​​ 值,然后再通过 ​​​GetName​​​ 将其打印出来:

css 复制代码
person := NewPerson("结构体", false)
person.SetName("结构体测试")
fmt.Println("Name:", person.GetName())

值方法和指针方法的区别

在 Go 语言中,当我们将成员方法 ​​SetName​​ 所属的类型声明为指针类型时,严格来说,该方法并不属于 ​​Person​​ 结构体,而是属于指向 ​​Person​​ 的指针类型。

当我们有如下情形的考量时,需要将成员方法定义为指针方法:

  1. 数据一致性:方法需要修改传入的类型实例本身;
  2. 方法执行效率:如果是值方法,在方法调用时一定会产生值拷贝,而大对象拷贝代价很大。

通过组合实现结构体的继承和方法重写

继承

Go 没有直接提供继承相关的语法实现,但是我们通过组合的方式间接实现类似功能,所谓组合,就是将一个结构体型嵌入到另一个结构体,从而构建新的结构体。 现在有一个 Animal 结构体类型,它有一个属性 Name 用于表示该动物的名称,以及三个成员方法。

go 复制代码
type Animal struct {
    Name string
}

func (a Animal) Call() string {
    return "动物的叫声..."
}

func (a Animal) FavorFood() string {
    return "爱吃的食物..."
}

func (a Animal) GetName() string  {
    return a.Name
}

定义一个继承自该类型的子结构体 ​​Cat​

go 复制代码
type Cat struct {
    Animal
}

在 ​​Cat​​ 结构体类型中,嵌入了 ​​Animal​​ ,可以在 ​​Cat​​ 实例上访问所有 ​​Animal​​ 类型包含的属性和方法:

go 复制代码
func main() {
    animal := Animal{"布偶猫"}
    cat := Cat{animal}

    fmt.Println(cat.GetName())
    fmt.Println(cat.Call())
    fmt.Println(cat.FavorFood())
}

通过组合实现了结构体与结构体之间的继承功能。

方法重写

通过在子结构体中定义同名方法来覆盖父类方法的实现,在面向对象编程中这一术语叫做方法重写,比如 ​​Cat​​ 结构体中,可以重写 ​​Call​​ 方法和 ​​FavorFood​​​ 方法的实现如下:

go 复制代码
func (c Cat) FavorFood() string {
    return "鱼和老鼠"
}

func (c Cat) Call() string {
    return "喵喵喵"
}


animal := Animal{"布偶猫"}
cat := Cat{animal}

fmt.Print(cat.Animal.Call())
fmt.Println(cat.Call())
fmt.Print(cat.Animal.FavorFood())
fmt.Println(cat.FavorFood())

组合的实现方式更加灵活,不用考虑单继承还是多继承,想要继承哪个类型的方法,直接组合进来就可以了。

继承指针类型的属性和方法

在 Go 语言中,还可以通过指针方式继承某个类型的属性和方法:

go 复制代码
type Cat struct { 
    *Animal
}

在调用时,传入 ​​Animal​​ 实例的指针引用就可以了

matlab 复制代码
func main() {
    animal := Animal{"布偶猫"}
    cat := Cat{&animal}

    fmt.Println(cat.Animal.GetName())
    fmt.Print(cat.Animal.Call())
    fmt.Println(cat.Call())
    fmt.Print(cat.Animal.FavorFood())
    fmt.Println(cat.FavorFood())
}

当我们通过组合实现结构体之间的继承时,由于结构体实例本身是值类型,如果传入值字面量的话,实际上传入的是结构体实例的副本,对内存耗费更大,所以组合指针类型性能更好。

为组合类型设置别名

go 复制代码
type Cat struct {
    animal *Animal
}

...

func main() {
    animal := Animal{"布偶猫"}
    cat := Cat{&animal}

   // 通过 animal 引用 Animal 类型实例 
    fmt.Println(cat.animal.GetName())
    fmt.Print(cat.animal.Call())
    fmt.Println(cat.Call())
    fmt.Print(cat.animal.FavorFood())
    fmt.Println(cat.FavorFood())
}

结构体属性和方法的可见性

在 Go 语言中,无论是变量、函数还是结构体属性和成员方法,可见性都是以包为维度的。Go 语言没有提供​​​private​​​​​、​​​​protected​​​​ 和 ​​​​public​​​关键字。可见性都是根据其首字母的大小写来决定的,如果变量名、属性名、函数名或方法名首字母大写,就可以在包外直接访问,否则只能在包内访问。

相关推荐
武子康几秒前
大数据-14-Hive HQL 表连接查询 HDFS导入导出 逻辑运算 函数查询 全表查询
大数据·后端·apache hive
星辰大海的精灵几秒前
轻松玩转 Kubernetes 集群的工具包
后端·架构·kubernetes
import_random14 分钟前
[python]Flask(介绍+应用)
后端
林太白32 分钟前
Rust项目搭建
前端·后端·rust
江小北40 分钟前
今天去面试了,遇到一个面试题,spring单例bean是线程安全的吗?
java·后端·spring
天天摸鱼的java工程师41 分钟前
设计一个多租户 SaaS 系统,如何实现租户数据隔离(数据库级别 / 表级别)与资源配额控制?
java·后端·面试
用户67570498850242 分钟前
Go语言切片,使用技巧与避坑指南
后端
Nero181 小时前
代码随想录二刷第三天 | 203.移除链表元素、707.设计链表、206.反转链表
后端
木西1 小时前
Nest.js实战:构建聊天室的群聊与私聊模块
前端·后端·nestjs
数字人直播1 小时前
跨境电商如何选择高转化率的AI数字人直播平台?
前端·后端