简单易懂,解析Go语言中的struct结构体

目录

  • [4. struct 结构体](#4. struct 结构体)
    • [4.1 初始化](#4.1 初始化)
    • [4.2 内嵌字段](#4.2 内嵌字段)
    • [4.3 可见性](#4.3 可见性)
    • [4.4 方法与函数](#4.4 方法与函数)
      • [4.4.1 区别](#4.4.1 区别)
      • [4.4.2 闭包](#4.4.2 闭包)
    • [4.5 Tag 字段标签](#4.5 Tag 字段标签)
      • 4.5.1定义
      • [4.5.2 Tag规范](#4.5.2 Tag规范)
      • [4.5.3 Tag意义](#4.5.3 Tag意义)

4. struct 结构体

go的结构体类似于其他语言中的class,主要区别就是go的结构体没有继承这一概念,但可以使用类型嵌入来实现相似功能。

4.1 初始化

使用type关键字来定义一个新的类型,struct将新类型限定为结构体类型。

结构体中的字段可以为任何类型,但是包含一些特殊类型 如:接口,管道,函数,指针的时候要格外注意

go 复制代码
//type定义一个新类型
type newInt int

//type定义一个简单的结构体
type base struct{
   value int   
}

//type定义一个复杂的结构体
type student struct {
   Name string
   age  int
   c    interface{}
   d    func() int
   e    func()
   base     //将base类型嵌入到了student类型中
}

4.2 内嵌字段

内嵌字段大体上有两种方式:显式指定(m1)和隐式指定(m2)

  • 显式指定就相当于把目标结构体当作字段,调用时需要先调用这个字段,在调用目标结构体中的信息
  • 隐式指定相当于把目标结构体中的所有字段都在新结构体中创建了一次,并且指向嵌入结构体内部。同时创建同名嵌入结构体对象[指与base同名]
  • 显式创建同名结构体字段 ≠ 隐式指定
go 复制代码
type base struct {
	Value int
}
//显式指定
type m1 struct {
	b base
}

//隐式指定
type m2 struct {
	base
}

//显式指定同名字段
type m3 struct {
   base base
}

对上述结构体进行调用:

  • 只有隐式指定直接操作被嵌入结构体内的数据;
  • 隐式指定后,直接操作嵌入结构体中的数据和通同名结构体操作作用一样
go 复制代码
func main() {
	a1 := m1{}
	a2 := m2{}
	a3 := m3{}
	//显式指定只能通过嵌入结构体进行操作
	// a1.Value = 1 //a1.Value undefined (type m2 has no field or method Value)
	a1.b.Value = 2
	//隐式指定两种操作数据方法操作的是同一个变量
	a2.Value = 2
	a2.base.Value = 3
	fmt.Println(a2.Value) //3
	//显式指定同名变量 ≠ 隐式指定 
	// a3.Value = 3 //a3.Value undefined (type m3 has no field or method Value)
	a3.base.Value = 4
}

当内嵌字段中的字段与结构体中得字段同名时:

  • 直接调用时是指定当前结构体中显式定义的字段,但嵌入结构体中的字段仍可通过嵌入类型进行调用
  • 方法同理
go 复制代码
//数据
func main() {
	a1 := m1{}
	a1.Value = "hello world"
	a1.base.Value = 1
	fmt.Println(a1)
	//获取a1中的所有字段类型
	t := reflect.TypeOf(a1)
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldType := field.Type
		fmt.Printf("Field: %s, Type: %s\n", field.Name, fieldType.Name())
	}
}

type base struct {
	Value int
}

type m1 struct {
	Value string
	base
}
/*
{hello world {1}}
Field: Value, Type: string
Field: base, Type: base
 */
go 复制代码
//方法
func main() {
   a1 := m1{}
   a1.test()
   a1.base.test()
   fmt.Println(a1)
   //获取a1中的所有字段类型
   t := reflect.TypeOf(a1)
   for i := 0; i < t.NumField(); i++ {
      field := t.Field(i)
      fieldType := field.Type
      fmt.Printf("Field: %s, Type: %s\n", field.Name, fieldType.Name())
   }
}

type base struct {
   Value int
}

func (b *base) test() {
   b.Value = 1
}
func (m *m1) test() {
   m.Value = "hello world"
}

type m1 struct {
   Value string
   base
}
/*
{hello world {1}}
Field: Value, Type: string
Field: base, Type: base
 */

4.3 可见性

  • 首字母大写表示该字段/方法/结构体为可导出的,反之为不可导出的
  • 在同一个包内,不区分是否可导出,都可访问;包外只能访问可导出的字段/方法/结构体
  • 不可导出的字段不可以进行序列化(转化为json)
  • 可通过可导出的方法去操作不可导出的字段
go 复制代码
// test/test1.go
package test

type User struct {
   Name string
   age  int
}

func (u *User) Test() {
   u.Name = "hello"
   u.age = 18
}

func (u *User) test() {
   u.Name = "world"
   u.age = 81
}

type student struct {
   Name string
   age  int
}
go 复制代码
//main.go
package main

import (
   "fmt"
   "test/test"
)

func main() {
   a := test.User{}
   //b := test.student{} // 不能在包外访问未导出结构体
   a.Name = "123"
   //a.age = 123 // 不能在包外访问未导出字段
   a.Test()
   //a.test() // 不能在包外访问未导出方法
   fmt.Println(a)
   /*
      {hello 18}
    */
}

4.4 方法与函数

4.4.1 区别

  • 方法定义是必须有一个接收器(receiver);函数不需要
  • 大部分情况下,方法的调用需要有一个对象;函数不需要
  • 由于go中的所有传递都是值传递,也就是将数据复制一份再调用,所以如果想要修改原本对象的值,就要传递指针,进行引用传递
go 复制代码
func 函数名 (参数) 返回值类型 {函数体} //函数定义
func (接收器) 方法名  (参数) 返回值类型 {函数体} // 方法定义
go 复制代码
func main() {
	a := User{}
	a.setName() // 方法调用
	fmt.Println(a)
	setName(&a) // 函数调用
	fmt.Println(a)
}
/*
   {world 0}
   {hello 0}
 */

type User struct {
	Name string
	age  int
}
//函数
func setName(u *User) {
	u.Name = "hello"
}
//方法
func (u *User) setName() {
	u.Name = "world"
}

值传递时可以通过返回值的方式修改目标对象

go 复制代码
func main() {
	a := User{}
	a = a.setName() //需要显式给原变量赋值
	fmt.Println(a)
	a = setName(a)  //需要显式给原变量赋值
	fmt.Println(a)
}

type User struct {
	Name string
	age  int
}

func setName(u User) User {
	u.Name = "hello"
	return u
}
func (u User) setName() User {
	u.Name = "world"
	return u
}

4.4.2 闭包

说到函数和方法,就必须说一下闭包

什么是闭包?

简单来说,就是函数内部引用函数外部变量,导致变量生命周期发生变化。这样的函数就叫做闭包

常见于函数返回值为另一个函数时

go 复制代码
package main

import "fmt"

func main() {
   b := test()
   fmt.Println(b())
   fmt.Println(b())
}

func test() func() int {
   a := 1
   return func() int {
      a++
      return a
   }
}

上面的函数导致变量a无法正常释放,导致变量逃逸

shell 复制代码
go build -gcflags="-m" main.go
# command-line-arguments
./main.go:11:6: can inline test
./main.go:13:9: can inline test.func1
./main.go:6:11: inlining call to test
./main.go:13:9: can inline main.test.func1
./main.go:7:15: inlining call to main.test.func1
./main.go:7:13: inlining call to fmt.Println
./main.go:8:15: inlining call to main.test.func1
./main.go:8:13: inlining call to fmt.Println
./main.go:6:11: func literal does not escape
./main.go:7:13: ... argument does not escape
./main.go:7:15: ~R0 escapes to heap
./main.go:8:13: ... argument does not escape
./main.go:8:15: ~R0 escapes to heap
./main.go:12:2: moved to heap: a
./main.go:13:9: func literal escapes to heap

4.5 Tag 字段标签

4.5.1定义

在reflect包中提供了获取字段名称、类型、Tag的方法(上文展示过获取名称和类型)

结构体StructField表示结构体的一个字段(reflect/type.go)

go 复制代码
// A StructField describes a single field in a struct.
type StructField struct {
	// Name is the field name.
	Name string

	// PkgPath is the package path that qualifies a lower case (unexported)
	// field name. It is empty for upper case (exported) field names.
	// See https://golang.org/ref/spec#Uniqueness_of_identifiers
	PkgPath string

	Type      Type      // field type
	Tag       StructTag // field tag string
	Offset    uintptr   // offset within struct, in bytes
	Index     []int     // index sequence for Type.FieldByIndex
	Anonymous bool      // is an embedded field
}

type StructTag string 

4.5.2 Tag规范

StructTag本质上就是字符串,理论上任何形式都符合规范。但通常情况下约定,Tag的格式应该是key:"value"

  • key:非空字符串,不能包含控制字符,空格,引号,冒号
  • value:双引号包围的字符串
  • 冒号前后不能有空格,多个value用逗号隔开,key之间用空格隔开
  • key一般表示用途,value表示控制指令;

4.5.3 Tag意义

  • Go语言反射机制可以给结构体成员赋值,用Tag可以决定赋值的动作
  • 可以使用定义好的Tag规则,参考规则就可以继续不同的操作
go 复制代码
//仅对Tag值为true的字段赋值(Tag决定赋值动作)
type Person struct {
	Name string `assign:"true"`
	Age  int    `assign:"false"`
}

func assignValues(v interface{}) {
	val := reflect.ValueOf(v).Elem() // 获取指针指向的值
	typ := val.Type()

	for i := 0; i < val.NumField(); i++ {
		field := val.Field(i)
		tag := typ.Field(i).Tag.Get("assign") // 获取字段的tag

		if tag == "true" {
			// 根据字段类型赋值
			switch field.Kind() {
			case reflect.String:
				field.SetString("Default Name")
			case reflect.Int:
				field.SetInt(25)
			}
		}
	}
}

func main() {
	p := &Person{}
	assignValues(p)
	fmt.Printf("Person: %+v\n", p)
}
/*
Person: &{Name:Default Name Age:0}
 */

下方例子使用了json:"kind,omitempty",这个tag规定了字段为空不进行序列化;

go 复制代码
import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name  string `json:"kind,omitempty"` //为空时不进行序列化
	Value int
	age   int
}

func main() {
	// 创建一个 Person 实例
	p := Person{Name: "", Value: 100, age: 100}

	// 序列化为 JSON
	jsonData, _ := json.Marshal(p)
	fmt.Println(string(jsonData)) 
	/*
	{"Value":100}
	 */
}
相关推荐
M_Lin_1 分钟前
C++继承 多态
开发语言·c++
猿周LV8 分钟前
多线程进阶 : 八股文面试题 一 [Java EE 多线程 锁和死锁相关问题]
java·开发语言·java-ee
enyp8029 分钟前
qt QTreeWidget`总结
开发语言·数据库·qt
啾啾Fun2 小时前
[java基础-JVM篇]2_垃圾收集器与内存分配策略
java·开发语言·jvm
计算机毕设指导62 小时前
基于Springboot的游戏分享网站【附源码】
java·spring boot·后端·mysql·spring·游戏·maven
羊小猪~~2 小时前
基于C++“简单且有效”的“数据库连接池”
java·开发语言·前端·数据库·c++·后端·adb
belong_to_you2 小时前
【Python模块】——pymysql
开发语言·python
Hello__nibuhao3 小时前
C 语言中控制文件的读取或写入光标
c语言·开发语言·算法
别惹CC3 小时前
Spring Boot 3 整合 Spring Cloud Gateway 工程实践
java·spring boot·后端·spring cloud·gateway
思茂信息3 小时前
CST直角反射器 --- A求解器, 远场源, 距离像, 逆ChirpZ变换(ICZT)
开发语言·javascript·人工智能·算法·ai·软件工程·软件构建