Go的结构体类型

Go的结构体类型

一、结构体类型的基础知识

在Go语言中,我们可以通过为一个类型编写名为String的方法,来自定义该类型的字符串表示形式。这个String方法不需要任何参数声明,但需要一个string类型的的结果声明。

package main

import "fmt"

type Animal struct {
	name   string
	a_type string
}

func (a Animal) String() string {
	return fmt.Sprintf("%s,%s", a.name, a.a_type)
}

func main() {
	one_a := Animal{name: "aa", a_type: "tt"}
	fmt.Printf("%s\n", one_a)
}

  • 方法隶属的类型其实并不局限于结构体类型,但必须是某个自定义的数据类型,并且不是任何接口类型;
  • 一个数据类型关联的所有方法,共同组成了该类型的方法集合。同一个方法集合中的方法不能出现重名。并且,如果它们所属的是一个结构体,那么它们的名称于该类型的任何字段的名称也不能重复;

二、嵌入字段:如果结构体类型的某个字段声明中只有一个类型名,这个字段代表嵌入字段

2.1 嵌入字段

Go语言规范规定,如果一个字段的声明中只有字段的类型名而没有字段的名称,那么它就是一个嵌入字段,也可以称为匿名字段。嵌入字段的类型既是类型也是名称。

type AnimalCategory struct {
	c01 string
	c02 string
}

type Animal struct {
	name string
	AnimalCategory
}

2.2 嵌入字段的特性

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

可以直接访问嵌入字段的方法,可以可以直接访问嵌入字段的字段。

package main

import "fmt"

type AnimalCategory struct {
	c01 string
	c02 string
}

func (a AnimalCategory) String() string {
	return fmt.Sprintf("%s, %s", a.c01, a.c02)
}

type Animal struct {
	name string
	AnimalCategory
}

func main() {
	ac := AnimalCategory{c01: "aa", c02: "bb"}
	one_a := Animal{name: "1", AnimalCategory: ac}
	fmt.Printf("%s, %s\n", one_a, one_a.c01) // aa, bb
}

2.2 屏蔽

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

由于我们同样可以像访问被嵌入类型的方法那样,直接访问嵌入字段的字段。所以,如果这两个结构体类型里存在同名的字段,那么嵌入字段中的那个字段一定会被“屏蔽”。

正因为嵌入字段的字段和方法都可以“嫁接”到被嵌入类型上,所以即使在两个相同同名的成员一个是字段,另一个是方法的情况下,这种“屏蔽”现象依然会存在。

如果处于同一个层级的多个嵌入字段拥有同名的字段或方法,那么被嵌入类型的值那里,选择此名称的时候会引发一个编译错误。

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

GO语言中,根本没有继承的概念。它所做的是通过嵌入字段的方式实现了类型之间的组合。

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

Go官网对于为什么没有继承的解释

四、值方法和指针方法

方法的接收者类型必须是某个自定义的数据类型,而不能是接口类型或接口的指针类型。

所谓值方法,就是接收者类型是非指针的自定义数据类型的方法。

所谓指针方法,就是接收者类型是指针类型的方法。

区别:

  1. 值方法的接收者是该方法所属的那个类型值的一个副本。我们在该方法内对该副本的修改一般都不会体现在原值上,除非这个类型本身是某个引用类型(比如切片或字典)的别名类型。而指针方法的接收者,是该方法所属的那个基本类型值的指针值的一个副本。我们在这样的方法内对该副本指向的值进行修改,却一定会体现在原值上。

  2. 一个自定义数据类型的方法集合中仅会包含它的所有值方法,而该类型的指针类型的方法集合却囊括了前者的所有方法,包括所有值方法和所有指针方法。严格来讲,我们在这样的基本类型的值上只能调用到它的值方法。但是,Go 语言会适时地为我们进行自动地转译,使得我们在这样的值上也能调用到它的指针方法。

    比如,在Cat类型的变量cat之上,之所以我们可以通过cat.SetName(“monster”)修改猫的名字,是因为 Go 语言把它自动转译为了(&cat).SetName(“monster”),即:先取cat的指针值,然后在该指针值上调用SetName方法。

  3. 一个指针类型实现了某某接口类型,但它的基本类型却不一定能够作为该接口的实现类型。

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) ScientificName() string {
	return cat.scientificName
}

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

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("a001", "ShangHai", "cat")
	// 该类型的指针类型的方法集合囊括了前者的所有方法,包括所有值方法和指针方法。
	// 严格来讲,我们在这样的基本类型的值上只能调用它的值方法,
	// 但是,GO语言会适时地为我们进行自动转换,使我们的值也可以调用它的指针方法。
	cat.SetName("name02") // (&cat).SetName("name02")
	fmt.Printf("cat : %s\n", cat)

	// 值方法的接收者是该方法所属的那个类型值的一个副本,对该副本的修改不会体现到原值上
	cat.SetNameOfCopy("copy name03")
	fmt.Printf("cat : %s \n", cat)

	// 一个指针类型实现了某某接口类型,但它的基本类型却不一定能够作为该接口的实现类型
	type Pet interface {
		SetName(name string)
		Name() string
		Category() string
		ScientificName() string
	}
	_, ok := interface{}(cat).(Pet)
	fmt.Printf("cat 实现了Pet接口:%v\n", ok)
	_, ok = interface{}(&cat).(Pet)
	fmt.Printf("*cat 实现了Pet接口:%v\n", ok)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值