当前位置: 首页 > news >正文

【Go】P14 Go语言核心利器:全面解析结构体 (Struct)

目录

  • 什么是结构体 (Struct)?
    • 结构体定义
    • 结构体是值类型
  • 结构体的初始化与实例化
    • 方式一:var 声明(零值实例化)
    • 方式二:使用 new 关键字
    • 方式三:字面量初始化(获取值)
    • 方式四:字面量初始化(获取指针)【*推荐】
    • 方式五:顺序初始化(不推荐)
  • 结构体方法
    • 值接收者
    • 指针接收者
  • 结构体的嵌套与“继承”
    • 结构体嵌套(组合)
    • 匿名嵌套(“继承”)
  • 结构体与 JSON 转换
    • 序列化 (Marshal):结构体 -> JSON
    • 反序列化 (Unmarshal):JSON -> 结构体
    • 结构体标签 (Struct Tag)
  • 总结

在这里插入图片描述

在编程世界中,我们经常需要处理比简单的数字、字符串或布尔值更复杂的数据。例如,一个“用户”可能包含用户名、年龄、邮箱和地址。Go 语言提供了一种强大的方式来组织和封装这些相关数据,这就是结构体 (Struct)

Go 语言中虽然没有传统面向对象语言(如 Java 或 C++)中的 class(类) 概念。但是,Go 通过结构体来实现数据的封装,并通过方法来实现行为。这种设计赋予了 Go 语言同样的灵活性和可扩展性。

本文将带你深入探讨 Go 语言的结构体,从基础定义、实例化、方法,到高级的嵌套、JSON 转换,助你掌握这个 Go 语言的核心利器。


什么是结构体 (Struct)?

当我们需要处理复杂的事物或场景时,单一的基础数据类型(如 intstring)显然不够用。Go 语言允许我们自定义数据类型,将多个不同类型的基础数据封装在一起,这种自定义的数据类型就称为结构体

简而言之,结构体是一个字段的集合。

结构体定义

在定义结构体之前,我们先要理解 Go 语言中的自定义类型。在 Go 中,我们使用 typestruct 关键字来定义一个结构体:

type Person struct {Name stringAge  intSex  string
}

这里,我们定义了一个名为 Person 的新类型。它有三个字段:Name(字符串类型)、Age(整型)和 Sex(字符串类型)。

重要提示:字段的可见性

Go 语言使用首字母大小写来控制可见性(公有或私有):

  • 首字母大写:如 Name 表示该字段是公有的,支持在包外被访问和修改。
  • 首字母小写:如 name 表示该字段是私有的,只能在当前包内部使用。

这种规则适用于结构体本身以及结构体内参数字段的限制。

结构体是值类型

这是一个核心概念:Go 语言中的结构体是值类型

这意味着当一个结构体被赋值给另一个变量,或者作为参数传递给一个函数时,Go 会复制整个结构体,而不是复制该结构体的地址。

我们通过一个示例来证明:

package mainimport "fmt"type Person struct {Name stringAge  intSex  string
}func main() {p1 := Person{Name: "张三", Age: 20}// 将 p1 赋值给 p2,这里发生了值拷贝p2 := p1 // 修改 p2 的 Namep2.Name = "李四"// p1 的 Name 并没有改变fmt.Printf("p1: %v\n", p1) // 输出: p1: {张三 20 }fmt.Printf("p2: %v\n", p2) // 输出: p2: {李四 20 }
}

如上所示,修改 p2 丝毫没有影响 p1,因为 p2p1 的一个完整副本。这与 Java 或 Python 中的对象(它们是引用类型)的行为截然不同。


结构体的初始化与实例化

定义结构体只是一种蓝图,我们必须实例化它才会真正分配内存。有多种方法可以实例化结构体。

假设我们有以下结构体:

type Person struct {Name stringAge  intSex  string
}

方式一:var 声明(零值实例化)

我们可以像声明普通变量一样声明结构体,此时结构体的所有字段都会被初始化为其类型的零值string 的零值是 ""int 的零值是 0)。然后我们再对其中属性进行赋值。

func main() {var p1 Personp1.Name = "张三"p1.Age = 20p1.Sex = "男"fmt.Printf("值:%v 类型:%T\n", p1, p1)// 值:{张三 20 男} 类型:main.Personfmt.Printf("值:%#v 类型:%T\n", p1, p1)// 值:main.Person{Name:"张三", Age:20, Sex:"男"} 类型:main.Person
}

方式二:使用 new 关键字

new 关键字用于分配内存new(T) 会为类型 T 分配零值内存,并返回一个指向该内存地址的指针 (*T)。且 Go 语言为指针访问字段提供了语法糖,我们赋值时无需使用 (*p2).xx,如下述示例。

func main() {// p2 是一个指向 Person 结构体的指针var p2 *Person = new(Person) // Go 语言为指针访问字段提供了语法糖,// 我们不需要写 (*p2).Name,可以直接写 p2.Namep2.Name = "李四"p2.Age = 30fmt.Printf("p2 值:%#v 类型:%T\n", p2, p2)// p2 值:&main.Person{Name:"李四", Age:30, Sex:""} 类型:*main.Person
}

方式三:字面量初始化(获取值)

这是最常用的方式之一。我们可以在声明时直接使用键值对初始化字段。

func main() {// 实例化一个 Person 值p3 := Person{Name: "王五",Age:  40,Sex:  "男",}fmt.Printf("p3 值:%#v 类型:%T\n", p3, p3)// p3 值:main.Person{Name:"王五", Age:40, Sex:"男"} 类型:main.Person
}

注意:

  • 可以只初始化部分字段,未初始化的字段将是零值。
  • 字段顺序可以任意。

方式四:字面量初始化(获取指针)【*推荐】

这是 Go 中最推荐的实例化方式。它通过 & 操作符获取结构体字面量的地址,得到一个结构体指针。

func main() {// 实例化一个 *Person 指针p4 := &Person{Name: "赵六",Age:  50,// Sex 字段未初始化,将是零值 ""}fmt.Printf("p4 值:%#v 类型:%T\n", p4, p4)// p4 值:&main.Person{Name:"赵六", Age:50, Sex:""} 类型:*main.Person
}

这种方式结合了 new 的便利(获得指针)和字面量初始化的灵活(设置初始值)。

方式五:顺序初始化(不推荐)

Go 允许在初始化时不写字段名,而是按顺序提供值:

// 极不推荐
p5 := Person{"田七", 60, "男"}

强烈不推荐这种写法。它非常脆弱,如果未来结构体字段的顺序改变或增加了新字段,代码将编译失败或产生严重的 bug。


结构体方法

如果说结构体字段(属性)是数据,那么 方法 (Methods) 就是绑定到该数据的行为。这类似于 Java 中的类方法。Go 方法的定义格式如下:

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {// 方法体
}
  • 接收者 (Receiver): (接收者变量 接收者类型) 是方法与结构体绑定的关键。它类似于其他语言中的 thisself
  • 接收者可以是值类型,也可以是指针类型

值接收者

值接收者操作的是结构体的副本。这意味着在方法内部对结构体的修改不会影响原始结构体。

// (p Person) 是值接收者
func (p Person) printInfo() {fmt.Printf("Name: %s, Age: %d\n", p.Name, p.Age)
}func main() {p1 := Person{Name: "张三", Age: 20}p1.printInfo() // 输出: Name: 张三, Age: 20
}

值接收者通常用于只读操作。

指针接收者

指针接收者操作的是结构体的指针,方法内部对结构体的修改会影响原始结构体,这是我们修改结构体状态时最常用的方式。

// (p *Person) 是指针接收者
func (p *Person) setName(newName string) {p.Name = newName
}func main() {p1 := &Person{Name: "张三", Age: 20} // p1 是 *Personfmt.Println("修改前:", p1.Name) // 修改前: 张三p1.setName("张三丰")fmt.Println("修改后:", p1.Name) // 修改后: 张三丰
}

什么时候使用值接收者 vs 指针接收者?

  • 需要修改原始结构体吗?
    • 是: 必须使用指针接收者。
    • 否: 两者皆可。
  • 结构体很大吗?
    • 是: 使用指针接收者。因为值接收者会复制整个结构体,开销很大。指针接收者只复制一个指针。
    • 否(如结构体只有几个小字段): 使用值接收者更安全(避免意外修改)。

经验法则: 如果不确定,优先使用指针接收者。这更高效,也符合“方法修改对象”的直觉。


结构体的嵌套与“继承”

Go 语言没有继承,但它通过 结构体嵌套(组合) 提供了更灵活的功能。

结构体嵌套(组合)

一个结构体的字段可以是另一个结构体。

// 收货地址
type Address struct {Province stringCity     string
}// 用户
type User struct {Name stringAge  intAddr Address // 嵌套 Address 结构体
}func main() {user := User{Name: "Alice",Age:  25,Addr: Address{Province: "广东",City:     "深圳",},}fmt.Println(user.Name)         // 输出: Alicefmt.Println(user.Addr.City)    // 输出: 深圳
}

我们也可以嵌套结构体指针、切片或 map:

type Post struct {Title  stringTags   []string           // 切片Meta   map[string]string  // MapAuthor *User              // 结构体指针
}

匿名嵌套(“继承”)

Go 允许字段在声明时只有类型,没有字段名,这称为匿名字段。当一个结构体匿名嵌套另一个结构体时,就实现了类似“继承”的效果。

type Animal struct {Name string
}func (a *Animal) Eat() {fmt.Printf("%s is eating...\n", a.Name)
}// Dog 匿名嵌套了 Animal
type Dog struct {Animal // 匿名字段Breed  string
}func (d *Dog) Bark() {fmt.Printf("%s is barking...\n", d.Name)
}func main() {d := &Dog{Animal: Animal{Name: "Buddy"}, // 需要这样初始化Breed:  "Golden Retriever",}// 字段提升:可以直接访问 Animal 的字段fmt.Println(d.Name)   // 输出: Buddy// 方法提升:可以直接调用 Animal 的方法d.Eat()               // 输出: Buddy is eating...d.Bark()              // 输出: Buddy is barking...
}

Dog 结构体“继承”了 AnimalName 字段和 Eat 方法。这种特性称为字段提升和方法提升

这在 Go 中是实现代码复用的主要方式,它本质上是组合 (HAS-A),而非继承 (IS-A),但提供了类似继承的便利。


结构体与 JSON 转换

在现代 Web 开发中,JSON 是最常用的数据交换格式。Go 的 encoding/json 包提供了结构体与 JSON 之间无缝转换的能力。

核心要求: 一个结构体字段若想被 JSON 包处理(序列化或反序列化),它必须是公有的(首字母大写)。

序列化 (Marshal):结构体 -> JSON

json.Marshal 函数将 Go 结构体转换为 JSON 格式的字节切片 ([]byte)。

import ("encoding/json""fmt"
)type Server struct {ServerName stringServerIP   stringPort       intprivateInfo string // 私有字段
}func main() {s1 := Server{ServerName: "WebServer",ServerIP:   "127.0.0.1",Port:       8080,privateInfo: "secret", // 此字段不会被序列化}// 序列化jsonByte, err := json.Marshal(s1)if err != nil {fmt.Println("json marshal error:", err)return}// jsonByte 是 []byte 类型,我们转为 string 打印fmt.Println(string(jsonByte))// 输出: {"ServerName":"WebServer","ServerIP":"127.0.0.1","Port":8080}
}

反序列化 (Unmarshal):JSON -> 结构体

json.Unmarshal 函数将 JSON 格式的字节切片解析到 Go 结构体中。

func main() {jsonStr := `{"ServerName":"CacheServer","ServerIP":"192.168.1.100","Port":6379}`var s2 Server// 反序列化// 注意:第二个参数必须是结构体的指针,否则函数无法修改 s2err := json.Unmarshal([]byte(jsonStr), &s2)if err != nil {fmt.Println("json unmarshal error:", err)return}fmt.Printf("%#v\n", s2)// 输出: main.Server{ServerName:"CacheServer", ServerIP:"192.168.1.100", Port:6379, privateInfo:""}
}

结构体标签 (Struct Tag)

我们经常遇到 Go 结构体字段与 JSON 字段不一致的情况。Go 提供了结构体标签来解决这个问题。

type Order struct {OrderID    string `json:"order_id"` // JSON 中的 key 变为 order_idAmount     float64 `json:"amount"`CustomerID string `json:"customer_id,omitempty"` // omitempty 表示如果该字段为零值,则序列化时忽略它password   string `json:"-"` // - 表示无论如何都忽略此字段
}func main() {o1 := Order{OrderID: "20241027",Amount:  99.9,}jsonByte, _ := json.Marshal(o1)fmt.Println(string(jsonByte))// 输出: {"order_id":"20241027","amount":99.9}// CustomerID 因 omitempty 被省略了
}

总结

Go 语言的结构体是其类型系统的基石。它虽然简单,但通过值/指针接收者、匿名嵌套(组合)以及强大的 encoding/json 包,构建出了一个高效、灵活且易于维护的数据模型。

掌握结构体,是从 Go 新手迈向资深开发者的关键一步。希望本文能为你打下坚实的基础。


2025.10.27 G33 前往杭州途中

http://www.dtcms.com/a/535987.html

相关文章:

  • 华为OD机试双机位A卷 - 最佳植树距离 (C++ Python JAVA JS GO)
  • Go学习资料整理
  • 旅游网站规划建设郑州网站建设网络公司
  • k8s滚动升级
  • 舆情网站入口wordpress文章添加seo标题代码
  • Android分区刷机原理深度解析:从Bootloader到Framework的完整启动流程
  • 高防 IP 如何保护企业网站?
  • 原创 网站 源码Discuz网站制作教程
  • windows 2003建设网站网站制作案例市场
  • mysql的安装和卸载过程
  • 软件设计师知识点总结:算法设计与分析
  • 互联网设计公司网站wordpress 404页面模板
  • python+ai智能根据doc教案文档生成ppt
  • PPT WPS ERROR +mn-ea
  • 技术解析 | QtScrcpy:一款基于Qt的跨平台Android投屏工具的实现原理与代码架构
  • F037 vue+neo4j 编程语言知识图谱可视化分析系统vue+flask+neo4j
  • qt设置运行框左上角图标
  • 大量PPT文件怎么快速转换成JPG格式的长图
  • 网站数据怎么做接口供小程序调用企业手机网站建设策划方案
  • LabVIEW机械零件尺寸检测
  • 网站建设公司整站源码专做网站公司
  • ProfiNet转EtherNet/IP工业智能网关实现欧姆龙PLC与倍福I/O模块通讯的实操案例
  • AR工业巡检:虚实融合的智能巡检技术详解
  • 【LUA教程】LUA脚本语言中文教程.PDF
  • 初识影刀--一款 AI 驱动的 RPA 自动化软件
  • SAP SD客户对账开票功能分享
  • 洛谷 P1177:【模板】排序 ← 基数排序实现
  • 株洲网站设计外包首选中国可信网站查询
  • 物联网智慧医疗:告别胶片时代的就医革命
  • 3步实现MQTT远程连接!EMQX+cpolar构建物联网消息高速公路