go语言学习笔记(十二)——结构体

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_32023305/article/details/82711769
  • 前言

结构体类型表示的是数据结构,可以包含若干个具有确切的名字和类型的字段。我们可以为这些类型关联上一些方法,这里可以把方法看做函数的特殊版本。函数可以没有名字,可以当作值来看待,而方法没有这两个特性,最重要的是方法必须隶属于某一个方法。方法所属的类型会通过其声明中的接收者声明体现出来。

我们可以把结构体类型(struct)中的一个字段看做是该结构体类型的一个属性或一项数据,再把隶属于它的一个方法看作是附加在其中数据之上的一项操作。

将属性及操作封装在一起,是面向对象编程(OOP)的一个主要原则。

Go语言推荐使用这种封装的做法。

  • 声明

如果结构体类型的某个字段声明中只有一个类型名,那么该字段代表了什么?

type AnimalCategory struct {
	kingdom string // 界。
	phylum  string // 门。
	class   string // 纲。
	order   string // 目。
	family  string // 科。
	genus   string // 属。
	species string // 种。
}


type Animal struct {
	scientificName string // 学名。
	AnimalCategory        // 动物基本分类。
}

上述示例中,Animal类型中的字段声明AnimalCategory代表什么呢?

字段声明AnimalCategory代表了Animal类型的一个“嵌入字段”。

如果一个字段的声明中只有字段的类型而没有名称,那么它就是一个嵌入字段,或者叫匿名字段。

我们可以通过此类型(Animal)变量的名称后跟“.”,再后跟嵌入字段类型的方式引用到该字段,如:

a.AnimalCategory.String()

从这里可以看出,嵌入字段的类型既是类型,也是名称(可以直接引用)。

这里这种用法很像实现了继承

那么,Go语言是用嵌入字段实现了OOP的继承吗?

Go语言通过嵌入字段的方式实现了类型间的组合,而没有所谓OOP的继承概念。

OOP中的继承,其实是通过牺牲代码的简洁性来换取可扩展性,而且这种可扩展性是通过侵入父类的方式来实现的。

类型之间的组合采用的是非声明的方式,我们不需要显式地声明某个类型实现了某个接口,或者一个类型继承了另一个类型。

类型之间的组合也是非侵入式(不同于OOP)的,不会破坏类型的封装或加重类型之间的耦合。

类型间的组合也是灵活的,可以通过嵌入字段的方式把一个类型的属性和能力(操作)“嫁接”给另一个类型。这样,被嵌入类型就实现了嵌入字段所实现的接口。

Go原因的类型组合要比OOP中的继承更加简洁和清晰,可以轻松地通过嵌入多个字段来实现功能强大的类型,却不会有多重继承那样复杂的层次结构和可观的管理成本。

接口类型之间也可以组合,而且非常常见,我们常常以此来扩展接口定义的行为或者标记接口的特征。

  • 值方法和指针方法

type user struct {
 name string
 email string 
} 
// 值接收者 
func (u user) notify() {
 log.Printf("sending User Email to %s<%s>\n", u.name, u.email) 
} 
// 引用接收者 
func (u *user) notifyPointer() { 
log.Printf("sending User Email to %s<%s>\n", u.name, u.email) 
}


与普通的函数相比,又有一些不同:值接收者声明的方法,调用时会使用这个值的一个副本去执行,而指针接收者在调用者会共享调用方法时接收者所指向的值,即可以修改指向的值。
在使用时,值类型的接收者也可以使用指针类型的调用,如下:

func (u user) notify() {
  log.Printf("sending User Email to %s<%s>\n", u.name, u.email)
} 
func main() { 
  tom := &user{"tom", "[email protected]"} tom.notify() 
}

其实在Go的代码背后,已经对该类型进行了转换

(*tom).notify()

所以有如下的对照关系

方法接收者 实际可用类型
(t T) T and *T
(t *T) *T

因此在如下的代码执行时,会报错,因为interface声明了notify方法,而方法接收者使用的是指针类型,因而只有*user实现了notify方法,user并没有实现,所以sendNotification的参数应该是&u,而不是u。

package main

import (
    "log"
)

type notifier interface {
    notify()
}

type user struct {
    name  string
    email string
}

func (u *user) notify() {
    log.Printf("Sending user email to %s", u.name)
}

func main() {
    u := user{"Bill", "[email protected]"}

    sendNotification(u)
}
func sendNotification(n notifier) {
    n.notify()
}

执行结果

# command-line-arguments
./main.go:23: cannot use u (type user) as type notifier in argument to sendNotification:
        user does not implement notifier (notify method has pointer receiver)
  • 思考题

1、我们可以在结构体类型中嵌入某个类型的指针类型吗?如果可以,有哪些注意事项?

答:我们可以在结构体中嵌入某个类型的指针类型, 它和普通指针类似,默认初始化为nil,因此在用之前需要人为初始化,否则可能引起错误

2、子目录struct{}代表了什么?又有什么用处?

答:空结构体不占用内存空间,但是具有结构体的一切属性,如可以拥有方法,可以写入channel。所以当我们需要使用结构体而又不需要具体属性时可以使用它。

猜你喜欢

转载自blog.csdn.net/sinat_32023305/article/details/82711769