结构体及其方法的使用法门

结构体表示的是实实在在的数据结构,一个结构体类型能够包含若干个字段,每一个字段一般都须要有确切的名字和类型。结构体也能够不包含任何字段,即便这样的结构体也是有意义的,由于,咱们能够为结构体关联上一些方法。编程

函数和结构体的区别

方法能够看作是函数的特殊版本。数据结构

函数是独立的程序实体。 咱们能够声明有名字的函数,也能够声明没有名字的函数,还能够把函数当作普通的值来传递。咱们还能够把具备相同签名的函数抽象成独立的函数类型。dom

方法须要有名字,不能被当作值来看待,最重要的是:方法必须隶属于某一个类型。 方法所述的类型会经过方法声明中的接收者(receiver)声明体现出来。函数

方法声明

func (接收者名称 接收者类型) methodName(参数列表)(结果列表)优化

举个例子指针

// AnimalCategory 表明动物分类学中的基本分类法。
type AnimalCategory struct {
	kingdom string // 界。
	phylum string // 门。
	class  string // 纲。
	order  string // 目。
	family string // 科。
	genus  string // 属。
	species string // 种。
}

func (ac AnimalCategory) String() string {
	return fmt.Sprintf("%s%s%s%s%s%s%s",ac.kingdom, ac.phylum, ac.class, ac.order,ac.family, ac.genus, ac.species)
}

func main {
	category := AnimalCategory{species: "cat"}
	fmt.Printf("The animal category: %s\n", category)
}

从String方法的声明中能够得知,String方法隶属于AnimalCategory类型。code

在Go语言中,咱们能够经过为一个类型编写String方法,来自定义该类型的字符串表示形式。对象

调用fmt.Printf函数时,使用占位符%s和category值自己就能够打印出category的字符串表示形式,无需显示调用category的String方法。fmt.Printf函数会自动就寻找String函数。即便一个类型没有实现String方法,fmt.Printf也能按照默认的格式打印出该类型的字符串表示。继承

方法隶属的类型其实并不局限于结构体类型,可是必须是某个自定义的数据类型,而且不能是任何接口类型。接口

一个数据类型关联的全部方法,共同组成了该类型的方法集合。同一个方法集合中的方法不能出现重名。而且,若是它们所属的是一个结构体类型,那么它们的名称与该类型中任何字段的名称也不能重复。

咱们能够把结构体类型中的一个字段看做是它的一个属性或者一项数据,再把隶属于它的一个方法看做是附加在其中数据之上的一个能力或者一项操做。将属性及其能力(或者说数据及其操做)封装在一块儿,是面向对象编程(object-oriented programming)的一个主要原则。

Go 语言摄取了面向对象编程中的不少优秀特性,同时也推荐这种封装的作法。从这方面看,Go语言实际上是支持面向对象编程的,但它选择摒弃了一些在实际运用过程当中容易引发程序开发者困惑的特性和规则。

匿名字段

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

Go语言规范规定,若是一个字段的声明中只有字段的类型名而没有字段的名称,那么它就是一个嵌入字段,也能够被称为匿名字段。咱们能够经过此类型变量的名称后跟“.”,再后跟嵌入字段类型的方式引用到该字段。也就是说,嵌入字段的类型既是类型也是名称。

在某个表明变量的标识符的右边加“.”,再加上字段名或方法名的表达式被称为选择表达式,它用来表示选择了该变量的某个字段或者方法。

嵌入字段的方法集合会被无条件地合并进被嵌入类型的方法集合中。

只要名称相同,不管这两个方法的签名是否一致,被嵌入类型的方法都会“屏蔽”掉嵌入字段的同名方法。

相似的,因为咱们一样能够像访问被嵌入类型的字段那样,直接访问嵌入字段的字段,因此若是这两个结构体类型里存在同名的字段,那么嵌入字段中的那个字段必定会被“屏蔽”

多层嵌入的问题

在这种状况下,“屏蔽”现象会以嵌入的层级为依据,嵌入层级越深的字段或方法越可能被“屏蔽”。

若是处于同一个层级的多个嵌入字段拥有同名的字段或方法,那么从被嵌入类型的值那里,选择此名称的时候就会引起一个编译错误,由于编译器没法肯定被选择的成员究竟是哪个

Go 语言是用嵌入字段实现了继承吗?

强调一下,Go 语言中根本没有继承的概念,它所作的是经过嵌入字段的方式实现了类型之间的组合。

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

类型组合也是非侵入式的,它不会破坏类型的封装或加剧类型之间的耦合。咱们要作的只是把类型当作字段嵌入进来,而后不劳而获地使用嵌入字段所拥有的一切。若是嵌入字段有哪里不合心意,咱们还能够用“包装”或“屏蔽”的方式去调整和优化。

类型间的组合也是灵活的,咱们老是能够经过嵌入字段的方式把一个类型的属性和能力“嫁接”给另外一个类型。这时候,被嵌入类型也就天然而然地实现了嵌入字段所实现的接口。

组合要比继承更加简洁和清晰,Go 语言能够垂手可得地经过嵌入多个字段来实现功能强大的类型,却不会有多重继承那样复杂的层次结构和可观的管理成本。

接口类型之间也能够组合。在 Go 语言中,接口类型之间的组合甚至更加常见,咱们经常以此来扩展接口定义的行为或者标记接口的特征。

值方法和指针方法都是什么意思?有什么区别?

方法的接收者类型必须是某个自定义的数据类型,并且不能是接口类型或接口的指针类型。所谓的值方法,就是接收者类型是非指针的自定义数据类型的方法。

func (cat *Cat) SetName(name string) {
	cat.name = name
}

func (cat Cat) SetNameOfCopy(name string) {
	cat.name = name
}

方法setName的接收者类型是Cat。表示的Cat类型的指针类型。这时,Cat能够叫作Cat的基本类型。指针类型的值表示的是指向某个基本类型值的指针。

值方法和指针方法之间有什么不一样点呢?

值方法的接收者是该方法所属的那个类型值的一个副本。咱们在该方法内对该副本的修改通常都不会体如今原值上,除非这个类型自己是某个引用类型(好比切片或字典)的别名类型。

而指针方法的接收者,是该方法所属的那个基本类型值的指针值的一个副本。咱们在这样的方法内对该副本指向的值进行修改,却必定会体如今原值上。

一个自定义数据类型的方法集合中仅会包含它的全部值方法,而该类型的指针类型的方法集合却囊括了前者的全部方法,包括全部值方法和全部指针方法

咱们在这样的基本类型的值上只能调用到它的值方法。可是,Go 语言会适时地为咱们进行自动地转译,使得咱们在这样的值上也能调用到它的指针方法。

若是一个基本类型和它的指针类型的方法集合是不一样的,那么它们具体实现的接口类型的数量就也会有差别,除非这两个数量都是零。

以上结论能够结合下面的例子进行体会。

package main

import "fmt"

type Cat struct {
	name           string // 名字。
	scientificName string // 学名。
	category       string // 动物学基本分类。
}

func New(name, scientificName, category string) Cat {
	return Cat{
		name:           name,
		scientificName: scientificName,
		category:       category,
	}
}

func (cat *Cat) SetName(name string) {
	cat.name = name
}

func (cat Cat) SetNameOfCopy(name string) {
	cat.name = name
}

func (cat Cat) Name() string {
	return cat.name
}

func (cat Cat) ScientificName() string {
	return cat.scientificName
}

func (cat Cat) Category() string {
	return cat.category
}

func (cat Cat) String() string {
	return fmt.Sprintf("%s (category: %s, name: %q)",
		cat.scientificName, cat.category, cat.name)
}

func main() {
	cat := New("little pig", "American Shorthair", "cat")
	cat.SetName("monster") // (&cat).SetName("monster")
	fmt.Printf("The cat: %s\n", cat)

	cat.SetNameOfCopy("little pig")
	fmt.Printf("The cat: %s\n", cat)

	type Pet interface {
		SetName(name string)
		Name() string
		Category() string
		ScientificName() string
	}

	_, ok := interface{}(cat).(Pet)
	fmt.Printf("Cat implements interface Pet: %v\n", ok)
	_, ok = interface{}(&cat).(Pet)
	fmt.Printf("*Cat implements interface Pet: %v\n", ok)
}

思考题

  1. 咱们能够在结构体类型中嵌入某个类型的指针类型吗?若是能够,有哪些注意事项?

咱们能够在结构体中嵌入某个类型的指针类型, 它和普通指针相似,默认初始化为nil,所以在用以前须要人为初始化,不然可能引发错误

  1. 字面量struct{}表明了什么?又有什么用处?

空结构体不占用内存空间,可是具备结构体的一切属性,如能够拥有方法,能够写入channel。因此当咱们须要使用结构体而又不须要具体属性时可使用它。

相关文章
相关标签/搜索