0
点赞
收藏
分享

微信扫一扫

golang快速入门:结构体

结构体

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

type Person struct {
    name string //名字
    male bool   //性别
}

可以理解为类名 Person,并且包含了 namemale 两个属性。

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

func NewPerson(name string, male bool) *Persion {
    return &Person{name, male}
}


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

成员方法

值方法

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

func (s Person) GetName() string  {
    return s.name
}

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

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

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

指针方法

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

func (s *P) SetName(name string) {
    s.name = name
}

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

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

接收者类型为指针的成员方法叫做指针方法,接收者类型为非指针的成员方法叫做值方法。

值方法和指针方法的区别

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

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

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

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

继承

Go 没有直接提供继承相关的语法实现,但是我们通过组合的方式间接实现类似功能,所谓组合,就是将一个结构体型嵌入到另一个结构体,从而构建新的结构体。

现在有一个 Animal 结构体类型,它有一个属性 Name 用于表示该动物的名称,以及三个成员方法。

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

type Cat struct {
    Animal
}

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

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

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

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

方法重写

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

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 语言中,还可以通过指针方式继承某个类型的属性和方法:

type Cat struct { 
    *Animal
}

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

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())
}

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

为组合类型设置别名

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

举报

相关推荐

0 条评论