)
文档教程【免费下载链接】build-web-application-with-golangA 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 persons name is %s, P.name) // 访问P的name属性.这里var P person声明了一个person类型的零值变量随后通过P.name ...、P.age ...逐字段赋值最后用P.name读取属性。注意struct 是值类型赋给新变量或传给函数时会发生整体拷贝下文Older函数的例子会再次印证这一点。struct 的四种初始化方式除了上面这种先声明后逐字段赋值的方式还有另外几种声明使用方式按照顺序提供初始化值P : person{Tom, 25}这种方式要求值的个数、类型与字段声明顺序严格一致即第一个值对应name第二个值对应age。通过field:value的方式初始化这样可以任意顺序P : person{age:24, name:Tom}字段名加冒号的形式不要求与声明顺序一致可读性更强推荐在字段较多的结构体上使用。通过new函数分配一个指针此处 P 的类型为*personP : 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 persons 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.agep2.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.7struct 组合——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(Bobs work phone is:, Bob.phone) // 如果我们要访问Human的phone字段 fmt.Println(Bobs 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 的核心知识声明type person struct { ... }为业务实体建模访问与修改通过点操作符读写字段四种初始化位置参数、field:value、new指针、先声明后赋值按值传递struct 作为函数参数时是值传递注意区分指针语义匿名字段嵌入字段struct、自定义类型、内置类型均可嵌入实现字段的隐式“继承”与组合复用字段重载与冲突最外层字段优先访问通过匿名字段名可显式访问内层字段。这些能力是后续面向对象方法与接收者、接口 interface乃至整个 Web 应用开发的基础。仓库中所有示例均可在 en/code/src/apps/ch.2.4/ 目录下找到可运行的 Go 源码建议逐一运行并修改字段值亲手验证本节所述的初始化方式与嵌入规则。赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐Go struct 详解定义、初始化与嵌入字段 —— Build Web Application with Golang 第 2.4 节实战指南Go struct 详解定义、初始化与嵌入字段 —— Build Web Application with Golang 第 2.4 节实战指南 本篇技术指南文档教程《Build Web Application with Golang》第 2.4 节精讲Go 语言 struct 定义、初始化与字段嵌入机制《Build Web Application with Golang》第 2.4 节精讲Go 语言 struct 定义、初始化与字段嵌入机制 本文基于开源仓库文档教程Go 语言 struct 完全指南定义、初始化、嵌入字段与名称遮蔽build-web-application-with-golang 第 2.4 节Go 语言 struct 完全指南定义、初始化、嵌入字段与名称遮蔽build web application with golang 第 2.4 节 本篇文档教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考