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

news/2025/2/26 10:21:12

目录

  • 4. struct 结构体
    • 4.1 初始化
    • 4.2 内嵌字段
    • 4.3 可见性
    • 4.4 方法与函数
      • 4.4.1 区别
      • 4.4.2 闭包
    • 4.5 Tag 字段标签
      • 4.5.1定义
      • 4.5.2 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无法正常释放,导致变量逃逸

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>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}
	 */
}

http://www.niftyadmin.cn/n/5868538.html

相关文章

Milvus x DeepSeek 搭建低成本高精度 RAG 实战

为什么手握海量数据&#xff0c;却用不出真正的“智能”&#xff1f;要么 AI 模型学艺不精&#xff0c;答非所问&#xff1b;要么技术门槛太高&#xff0c;让普通开发者望而却步。现在&#xff0c;使用阿里云 Milvus 向量检索服务、DeepSeek 大模型和 PAI LangStudio 开发工具&…

【大模型应用之智能BI】基于 Text2SQL 的 GenBI 技术调研和深度分析(包含案例)

1. 引言:GenBI 的崛起与 Text2SQL 的核心地位 随着数据驱动决策在企业中日益普及,商业智能(Business Intelligence,BI)工具的需求持续增长。传统的 BI 工具通常依赖于用户手动创建数据查询、仪表盘和报表,这对于非技术用户来说存在较高的门槛。生成式 BI(Generative BI…

【前端】react+ts 轮播图的实现

一、场景描述 在很多网站的页面中都有轮播图&#xff0c;所以我想利用react.js和ts实现一个轮播图。自动轮播图已经在前面实现过了&#xff0c;如&#xff1a;https://blog.csdn.net/weixin_43872912/article/details/145622444?sharetypeblogdetail&sharerId145622444&a…

SQL进阶实战技巧:汽车转向次数分析 | 真实场景案例

目录 0 问题描述 1 数据准备 2 问题分析 3 小结 关键技术总结 0 问题描述 现有一组实际汽车在平整路面安全行驶数据,每秒记录一次汽车的车头绝对指向,车头方向记为[0-360)度,部分数据如下,完整数据后附文件。

Docker基础-常见命令

docker images -查看所有的本地镜像。 docker pull -把远端镜像拉取到本地。 docker rmi -删除镜像。 docker push -推到镜像仓库。 docker run -创建并运行容器&#xff08;自动化&#xff0c;如果发现镜像不存在会先去拉取&#xff0c; 拉取完了以后再去自动创建容器&am…

langchain-go调用deepseek

1.查看官网 发现只有ollama,openai,Mistral于是查看代码 2.代码查看 先从llm, err : openai.New(url, model, token)开始 发现New方法可以传option参数&#xff0c;再看一下option参数 const (tokenEnvVarName "OPENAI_API_KEY" //nolint:gosecmodelE…

HITCON2017SSRFME-学习复盘

代码审计 192.168.122.15 <?phpif (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$http_x_headers explode(,, $_SERVER[HTTP_X_FORWARDED_FOR]);//用逗号分割多个IP$_SERVER[REMOTE_ADDR] $http_x_headers[0];}echo $_SERVER["REMOTE_ADDR"];//给第一个IP发送请…

Linux主机用户登陆安全配置

Linux主机用户登陆安全配置 在Linux主机上进行用户登录安全配置是一个重要的安全措施&#xff0c;可以防止未经授权的访问。以下是如何创建用户hbu、赋予其sudo权限&#xff0c;以及禁止root用户SSH登录&#xff0c;以及通过ssh key管理主机用户登陆。 创建用户hbu 使用具有…