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​​​关键字。可见性都是根据其首字母的大小写来决定的,如果变量名、属性名、函数名或方法名首字母大写,就可以在包外直接访问,否则只能在包内访问。

相关推荐
leobertlan7 小时前
2025年终总结
前端·后端·程序员
面向Google编程7 小时前
从零学习Kafka:数据存储
后端·kafka
易安说AI8 小时前
Claude Opus 4.6 凌晨发布,我体验了一整晚,说说真实感受。
后端
易安说AI8 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
易安说AI8 小时前
用 Claude Code 远程分析生产日志,追踪 Claude Max 账户被封原因
后端
颜酱9 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
Coder_Boy_12 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
掘金者阿豪13 小时前
关系数据库迁移的“暗礁”:金仓数据库如何规避数据完整性与一致性风险
后端
ServBay13 小时前
一个下午,一台电脑,终结你 90% 的 Symfony 重复劳动
后端·php·symfony
sino爱学习13 小时前
高性能线程池实践:Dubbo EagerThreadPool 设计与应用
java·后端