golang数据结构初探之结构体struct

本文深入探讨了Go语言中的struct,包括其字段、方法和Tag的使用。结构体struct类似类,但不支持继承,可以通过内嵌实现复用。内嵌字段使得其他结构体的方法和字段在当前结构体中可用。方法的接收者决定了方法是否能直接修改对象,Tag是结构体字段的额外属性,常用于序列化和反序列化等场景。Tag的获取和使用是通过反射库实现的。
摘要由CSDN通过智能技术生成

结构体struct

Go语言的struct与其他编程语言的class有些类似,可以定义字段和方法,但是不可以继承

内嵌字段

Go语言的结构体没有继承的概念,当需要"复用"其他结构体时,需要使用组合的方式将其他结构体嵌入进来。

如下所示:

type Animal struct {
	Name string
}

func (a *Animal) SetName(name string) {
	a.Name = name
}

type Cat struct {
	Animal	//隐式声明
}

func main() {
	cat :=&Cat{}
	cat.Animal.SetName("mimi")
	fmt.Println(cat.Name)	//等同于 cat.Animal.Name
	fmt.Println(cat.Animal.Name)
}

假如现在有另外一个结构体也要尝试组合Animal,如下:

type Dog struct {
	a Animal
}

那么结构体Cat 和Dog 有什么区别呢

结构体中的字段可以显式指定也可以隐式指定。在上面的例子中,Dog结构体中的字段a为显式指定,而Cat结构体中由于内嵌了Animal,从而产生了一个隐式的同名字段。

对于类型为结构体的字段,显式指定时与其他类型没啥区别,仅代表某种类型的字段,而隐式指定时,原结构体的字段和方法看起来像是被继承过来了一样。当结构体Cat中嵌入了另外一个结构体Animal时,相当于声明了一个名为Animal的字段,此时结构体Animal中的字段和方法会被提升到Cat中,看上去和Cat原生字段和方法一样。如图所示:

image-20210820143618725

方法受体

一般情况下我们不会严格区分函数和方法,但是在介绍结构体的时候就要区分了。

一般的函数声明如下:

func 函数名(参数) { 函数体 }

而方法的声明如下所示:

func (接收者)函数名(参数) {函数体}

可见方法的声明多了一个接收者(官方称之为receiver)的概念。

方法主要用于为类型扩展方法,例如

type Student struct {
  Name string
}
//作用于Student的拷贝对象,修改不会影响原对象
func (s Student) SetName(name string){
  s.Name = name
}

//作用于指针对象,修改会影响原对象
func (s *Student) UpdateName(name string){
  s.Name = name
}

上面为Student类型增加了两个方法,类似地也可以给其他非结构体类型增加方法。

方法SetName()的接收者为Student,而UpdateName()的接收者为*Student,那么二者有什么区别呢?下面通过一个例子来展示:

func Receiver(){
	s := Student{}
	s.SetName("佩奇")
	fmt.Printf("Name is : %s\n",s.Name)	//empty
	s.UpdateName("喜洋洋")
	fmt.Printf("Name is : %s\n",s.Name)	//喜洋洋
}

Name is :
Name is : 喜洋洋

可以看出,虽然SetName和UpdateName()执行逻辑是一样的,但是接收者为Student的SetName()方法并没有成功的设置名字。

接收者可以简单理解为方法的作用对象,即该方法是作用于对象还是对象指针。如果作用于对象指针,那么方法内可以修改对象的字段;而作用于与对象,那么相当于方法内修改的是对象副本。

还有一种理解,就是把接收者理解为方法的特殊参数,对于接收者为对象的方法,相当于参数传递时拷贝了一份对象,方法内部修改对象不会影响到原对象中,而当接收者为对象指针时,方法修改对象时会影响到原对象中。

字段标签

Go语言的struct声明中允许为字段标记Tag,如下所示:

type TypeMeta struct {
	Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
	APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
}

其中每个字段后面两个反单引号中间的字符串就是字段的Tag。

Tag的本质

Tag是struct中的一部分

Tag用于标识结构体字段的额外属性,有点类似于注释。标准库reflect包中提供了操作Tag的方法,在介绍方法前,有必要先了解一下结构体的字段是如何表示的。

在reflect包中,使用结构体StructField表示结构体的一个字段:

type StructField struct {
	Name string 	//字段名
  Type Type			//字段类型
  Tag	 StructTag	//Tag
  ...
}

可以看出的是,Tag也是字段的一个组成部分,Tag的类型为StructTag,实际上它是一个string类型的别名,如下所示:

type StructTag string
Tag约定

Tag本身是一个字符串,单从语义上讲,任意的字符串都是合法的。但它有一个约定的格式,那就字符串由key:"value"组成。

  • key:必须是非空字符串,字符串不能包含控制字符串、空格、引号、冒号
  • value:以双引号标记的字符串

需要注意的是,key和value之间使用冒号分割,冒号前后不能有空格,多个key:"value"之间由空格分开。

对于上面的例子:

Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`

Kind字段中的Tag包含两个key:value,分别是json:"kind,omitempty"和 protobuf:“bytes,1,opt,name=kind”。

key一般表示用途,比如json表示用于控制结构体类型与JSON格式数据之间的转换,protobuf表示用于控制序列化和反序列化。value一般表示控制指令,具体指令由不同的库指定,使用到的时候可以去参考具体的库的写法

获取Tag

StructTag提供了Get(key string) string 方法来根据Tag的key值获取value。比如获取上例子Tag字符串中key值为json的value,如下所示:

type TypeMeta struct {
	Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
	APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
}

func main() {
	t := TypeMeta{}
	ty := reflect.TypeOf(t)

	for i := 0; i < ty.NumField(); i++ {
		fmt.Printf("Field : %s , Tag : %s\n",ty.Field(i).Name,ty.Field(i).Tag.Get("json"))
	}
}

函数输出如下:

Field : Kind , Tag : kind,omitempty
Field : APIVersion , Tag : apiVersion,omitempty

实际上标准库json包中将结构体对象转成JSON字符串时也是使用的类似方法。

Tag的意义

Go语言的反射特性可以动态地给结构体成员赋值,正是因为有Tag,在赋值前可以使用Tag来决定赋值的动作。

比如,官方的encoding/json包可是将一个JSON数据"Unmarshal"进一个结构体中,此过程就使用了Tag。该包定义了一些Tag规则,只要参考该规则设置Tag,就可以将不同的JSON数据转换成结构体。

综上,对于struct而言,Tag仅仅是一个普通的字符串,而其他库(如标准库json)定义了字符串规则并据此演绎了各种丰富的应用方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董洪臣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值