news 2026/10/8 13:31:27

深入理解 Go struct 类型:声明、初始化与匿名字段(Build Web Application with Golang 第 2.4 节)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解 Go struct 类型:声明、初始化与匿名字段(Build Web Application with Golang 第 2.4 节)
  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载

本指南对应开源仓库《Build Web Application with Golang》中文版 第 2.4 节,系统讲解 Go 语言 struct 类型的声明、四种初始化方式、匿名字段(嵌入字段)的继承机制、字段重载与命名冲突处理。无论你是在搭建 Web 应用的模型层、定义数据库实体结构,还是封装请求/响应对象,掌握 struct 都是 Go 开发的第一步。读完本文,你将能熟练使用字段式/顺序式初始化、利用嵌入字段实现组合复用,并正确处理字段同名冲突——这些能力直接支撑后续章节中的面向对象与 Web 框架实践。

为什么需要 struct:为数据建模而生的容器类型

Go 语言中,和 C 或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型person代表一个人的实体,这个实体拥有属性:姓名和年龄。这样的类型我们称之为struct。如下代码所示:

type person struct { name string age int }

声明一个 struct 非常简单,上面的类型包含有两个字段:

  • 一个string类型的字段name,用来保存用户名称这个属性;
  • 一个int类型的字段age,用来保存用户年龄这个属性。

在后续的 Web 开发中,struct 常被用来描述业务实体——例如用户、文章、订单——或者承载 HTTP 请求的表单数据结构。这一小节打好基础,后面的面向对象与接口 interface章节都建立在此之上。

struct 的使用:声明变量、访问与修改字段

如何使用 struct 呢?请看下面的代码:

type person struct { name string age int } var P person // P现在就是person类型的变量了 P.name = "Astaxie" // 赋值"Astaxie"给P的name属性. P.age = 25 // 赋值"25"给变量P的age属性 fmt.Printf("The person's name is %s", P.name) // 访问P的name属性.

这里var P person声明了一个person类型的零值变量,随后通过P.name = ...、P.age = ...逐字段赋值,最后用P.name读取属性。注意:struct 是值类型,赋给新变量或传给函数时会发生整体拷贝,下文Older函数的例子会再次印证这一点。

struct 的四种初始化方式

除了上面这种先声明后逐字段赋值的方式,还有另外几种声明使用方式:

  1. 按照顺序提供初始化值:

    P := person{"Tom", 25}

    这种方式要求值的个数、类型与字段声明顺序严格一致,即第一个值对应name,第二个值对应age。

  2. 通过field:value的方式初始化,这样可以任意顺序:

    P := person{age:24, name:"Tom"}

    字段名加冒号的形式不要求与声明顺序一致,可读性更强,推荐在字段较多的结构体上使用。

  3. 通过new函数分配一个指针,此处 P 的类型为*person:

    P := new(person)

    new返回指向零值结构体的指针,配合后面的P.name = ...等赋值语句使用,常用于需要传递指针或延迟初始化的场景。

在仓库的配套示例代码 en/code/src/apps/ch.2.4/main.go 中,上述初始化方式被集中演示,还额外展示了匿名 struct 的用法:

func show_basic_struct() { type person struct { name string age int } var P person // p is person type P.name = "Astaxie" // assign "Astaxie" to the filed 'name' of p P.age = 25 // assign 25 to field 'age' of p fmt.Printf("The person's name is %s\n", P.name) tom := person{"Tom", 25} bob := person{age: 24, name: "Bob"} fmt.Printf("tom = %+v\n", tom) fmt.Printf("bob = %#v\n", bob) } func show_anonymous_struct() { fmt.Printf("Anonymous struct = %#v\n", struct { name string count int }{"counter", 1}) }

其中%+v会打印带字段名的值,%#v会打印带类型名与字段名的完整 Go 语法表示,非常适合调试时观察结构体内容。

完整示例:struct 作为函数参数按值传递

下面我们看一个完整的使用 struct 的例子:

package main import "fmt" // 声明一个新的类型 type person struct { name string age int } // 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差 // struct也是传值的 func Older(p1, p2 person) (person, int) { if p1.age>p2.age { // 比较p1和p2这两个人的年龄 return p1, p1.age-p2.age } return p2, p2.age-p1.age } func main() { var tom person // 赋值初始化 tom.name, tom.age = "Tom", 18 // 两个字段都写清楚的初始化 bob := person{age:25, name:"Bob"} // 按照struct定义顺序初始化值 paul := person{"Paul", 43} tb_Older, tb_diff := Older(tom, bob) tp_Older, tp_diff := Older(tom, paul) bp_Older, bp_diff := Older(bob, paul) fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff) fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff) fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff) }

这个例子值得注意的几点:

  • tom先声明再通过多重赋值tom.name, tom.age = "Tom", 18初始化;
  • bob使用field:value方式初始化(顺序与声明相反);
  • paul使用按声明顺序的位置参数方式初始化;
  • Older函数接收两个person参数并返回(person, int)。注释里特别写明“struct 也是传值的”,意味着函数内部对p1、p2的任何修改都不会影响调用方的原始数据。如果希望函数内修改结构体字段并影响外部,需要传递指针(*person),这一点在后面的函数与指针和面向对象章节中会进一步展开。

配套源码 en/code/src/apps/ch.2.4/compare_age/main.go 中保存着这一示例的可运行版本。

struct 的匿名字段:实现字段“继承”的组合机制

我们上面介绍了如何定义一个 struct,定义的时候是字段名与其类型一一对应,实际上 Go 支持只提供类型、而不写字段名的方式,也就是匿名字段,也称为嵌入字段。

当匿名字段是一个 struct 的时候,那么这个 struct 所拥有的全部字段都被隐式地引入了当前定义的这个 struct。

让我们来看一个例子,让上面说的这些更具体化:

package main import "fmt" type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,那么默认Student就包含了Human的所有字段 speciality string } func main() { // 我们初始化一个学生 mark := Student{Human{"Mark", 25, 120}, "Computer Science"} // 我们访问相应的字段 fmt.Println("His name is ", mark.name) fmt.Println("His age is ", mark.age) fmt.Println("His weight is ", mark.weight) fmt.Println("His speciality is ", mark.speciality) // 修改对应的备注信息 mark.speciality = "AI" fmt.Println("Mark changed his speciality") fmt.Println("His speciality is ", mark.speciality) // 修改他的年龄信息 fmt.Println("Mark become old") mark.age = 46 fmt.Println("His age is", mark.age) // 修改他的体重信息 fmt.Println("Mark is not an athlet anymore") mark.weight += 60 fmt.Println("His weight is", mark.weight) }

图 2.7:struct 组合——Student 组合了 Human struct 和 string 基本类型

我们看到 Student 访问属性age和name的时候,就像访问自己所有的字段一样。对,匿名字段就是这样,能够实现字段的继承。还有比这个更酷的:Student 还能把Human这个字段名当作字段访问。请看下面的代码:

mark.Human = Human{"Marcus", 55, 220} mark.Human.age -= 1

也就是说,嵌入字段既可以通过字段名直接访问(mark.Human是Human类型的值),也可以忽略字段名直接访问其内部字段(mark.age),并且都可以赋值和修改。通过匿名访问和修改字段相当的有用。

配套源码 en/code/src/apps/ch.2.4/embedded_structs/main.go 保存了上述 Student 组合 Human 的完整可运行版本。

不仅仅是 struct:自定义类型与内置类型都可以作为匿名字段

匿名字段的妙处不止于此——不仅仅是 struct 字段,所有的内置类型和自定义类型都可以作为匿名字段。请看下面的例子:

package main import "fmt" type Skills []string type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,struct Skills // 匿名字段,自定义的类型string slice int // 内置类型作为匿名字段 speciality string } func main() { // 初始化学生Jane jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"} // 现在我们来访问相应的字段 fmt.Println("Her name is ", jane.name) fmt.Println("Her age is ", jane.age) fmt.Println("Her weight is ", jane.weight) fmt.Println("Her speciality is ", jane.speciality) // 我们来修改他的skill技能字段 jane.Skills = []string{"anatomy"} fmt.Println("Her skills are ", jane.Skills) fmt.Println("She acquired two new ones ") jane.Skills = append(jane.Skills, "physics", "golang") fmt.Println("Her skills now are ", jane.Skills) // 修改匿名内置类型字段 jane.int = 3 fmt.Println("Her preferred number is", jane.int) }

从上面例子我们可以看出:

  • struct 能够将 struct 作为匿名字段(Human);
  • 自定义类型可以作为匿名字段(Skills,其本质是[]string);
  • 内置类型也可以作为匿名字段(int);
  • 而且可以在相应字段上进行函数操作,如例子中的append(jane.Skills, "physics", "golang"),说明匿名字段拥有其类型本身的全部能力。

注意初始化时Student{Human: Human{"Jane", 35, 100}, speciality: "Biology"}使用了带字段名的复合字面量——由于Skills和int也是匿名字段,它们同样可以作为字段名出现在初始化列表中(例如Skills: []string{...}、int: 3)。这里省略了Skills与int,它们保持各自的零值(nil与0)。

配套源码 en/code/src/apps/ch.2.4/embedded_structs2/main.go 保存了该示例的可运行版本。

字段重载与命名冲突:最外层优先访问

这里有一个问题:如果 Human 里面有一个字段叫做phone,而 Student(本例中为 Employee)也有一个字段叫做phone,那么该怎么办呢?

Go 里面很简单地解决了这个问题:最外层的优先访问。也就是说,当你通过student.phone访问的时候,访问的是 student(最外层)里面的字段,而不是 human 里面的字段。

这样就允许我们去重载通过匿名字段继承的一些字段。当然,如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。请看下面的例子:

package main import "fmt" type Human struct { name string age int phone string // Human类型拥有的字段 } type Employee struct { Human // 匿名字段Human speciality string phone string // 雇员的phone字段 } func main() { Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} fmt.Println("Bob's work phone is:", Bob.phone) // 如果我们要访问Human的phone字段 fmt.Println("Bob's personal phone is:", Bob.Human.phone) }

运行结果中Bob.phone输出的是雇员的工作电话333-222,而Bob.Human.phone才能拿到 Human 里的个人电话777-444-XXXX。这正是“外层优先 + 显式路径访问内层”的规则:

访问方式解析结果
Bob.phoneEmployee 自身的phone字段(外层优先,发生字段重载)
Bob.Human.phone通过匿名字段名显式访问 Human 内的phone字段

配套源码 en/code/src/apps/ch.2.4/embedded_structs_with_name_conflict/main.go 保存了该字段冲突示例的可运行版本。

需要提醒的是,匿名字段冲突只会影响同层级的字段访问;如果匿名字段的层级不同(例如在嵌套组合中一个字段在更深层级),Go 仍会优先选择最浅层级的字段。这一“浅层优先”规则与“外层优先”规则一起,构成了 Go 嵌入字段解析的完整行为,在实际建模时可以放心使用组合来扩展类型,而不必担心字段互相污染。

小结

本节我们完整学习了 Go struct 的核心知识:

  1. 声明:type person struct { ... },为业务实体建模;
  2. 访问与修改:通过点操作符读写字段;
  3. 四种初始化:位置参数、field:value、new指针、先声明后赋值;
  4. 按值传递:struct 作为函数参数时是值传递,注意区分指针语义;
  5. 匿名字段(嵌入字段):struct、自定义类型、内置类型均可嵌入,实现字段的隐式“继承”与组合复用;
  6. 字段重载与冲突:最外层字段优先访问,通过匿名字段名可显式访问内层字段。

这些能力是后续面向对象(方法与接收者)、接口 interface乃至整个 Web 应用开发的基础。仓库中所有示例均可在 en/code/src/apps/ch.2.4/ 目录下找到可运行的 Go 源码,建议逐一运行并修改字段值,亲手验证本节所述的初始化方式与嵌入规则。

  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 13:29:58

ARM交叉编译踩坑记:-march、dotprod与fp16指令加速实战解析

上周我接过一个边缘盒子的AI推理优化任务:在x86笔记本上架好aarch64交叉编译工具链,想给跑在ARM板子上的程序开一点硬件加速。习惯性往编译命令里加了一行-marcharmv8.2-adotprodfp16,本以为只是“把新特性编译进去”,结果这一行参…

作者头像 李华
网站建设 2026/10/8 13:27:54

## AI工程路线图2026:RAG与LangGraph从入门到部署

### AI工程路线图2026:RAG与LangGraph从入门到部署2026年的AI工程岗位正经历一场静默的供给侧革命。technovids.com 发布的《AI Engineering Roadmap 2026》(报告编号:AER-2026-Q1,发布于2026年1月)明确了一个反直觉的…

作者头像 李华
网站建设 2026/10/8 13:27:23

盐城嘉虹环保科技:诚信风筒布专业厂家,源头供货实力参考

风筒布厂家怎么选?看盐城嘉虹环保,源头工厂直供,诚信经营更省心 矿用风筒布、隧道风筒布到底哪家靠谱?很多采购朋友在网上搜了一圈,不是价格虚高就是找不到源头厂家。今天给大家介绍一家深耕通风系统专用材料领域多年的实力企业——盐城嘉虹…

作者头像 李华
网站建设 2026/10/8 13:26:24

Windows 安装青简输入法全流程(附「打字学外语」设置)

本文为个人实测教程,基于青简 v0.1.4(2026-10 实测),非官方文档。青简为 GPL-3.0 开源项目,官方明确声明「付费的都是骗子」,请只从官方免费渠道获取。0. 为什么写这篇用了多年搜狗,前阵子刷到一…

作者头像 李华
网站建设 2026/10/8 13:26:07

任务依赖的最优基数:多值离散计算在物理AI中的实践

1. 多值离散计算并不玄:从三值网络到低比特量化,本质是同一件事很多人听到"多值离散计算"这个术语,第一反应是某个冷门学术分支,跟自己的实际工程没什么关系。我最初也是这么想的,直到在一个边缘设备项目里被…

作者头像 李华