结构体字面量与嵌入
本教程共 80 篇 · 第 38 篇 · 更新于 2026-07-27 · 约 7 分钟阅读
38. 结构体字面量与嵌入
本节目标:掌握结构体的两种初始化写法,搞懂匿名嵌入和字段提升,学会用组合替代继承。
字面量初始化的两种写法
创建结构体实例时,有两种写法。
命名式(推荐)
用字段名赋值,顺序不重要:
type Person struct {
Name string
Age int
City string
}
p := Person{
Name: "Alice",
Age: 30,
City: "北京",
}
可以省略部分字段,省略的会自动用零值:
p := Person{Name: "Bob"} // Age=0, City=""
Tip命名式是最推荐的写法。加字段、改顺序都不影响已有代码。
位置式
按字段顺序赋值,不写字段名:
p := Person{"Alice", 30, "北京"}
Warning位置式必须给所有字段赋值,不能省略。而且一旦结构体字段顺序变了,所有用位置式初始化的代码都得改。不推荐使用。
混合使用
Go 不允许两种写法混用:
// 编译错误!不能混用
p := Person{Name: "Alice", 30}
取地址初始化
很多时候你需要的是结构体指针而不是值,用 & 取地址:
p := &Person{
Name: "Alice",
Age: 30,
}
// p 的类型是 *Person
Go 有个语法糖:&Person{...} 直接返回结构体指针,效果类似先 new(Person) 再逐个赋值,但写法更简洁,而且能同时设初始值,所以更常用。
匿名嵌入
上一章提到过匿名字段。这里展开讲讲。
匿名嵌入就是把一个类型直接放进另一个结构体里,不写字段名:
type Address struct {
City string
ZipCode string
}
type Person struct {
Name string
Address // 匿名嵌入 Address
}
字段提升
嵌入后,被嵌入类型的字段会被「提升」到外层结构体,可以直接访问:
package main
import "fmt"
type Address struct {
City string
ZipCode string
}
type Person struct {
Name string
Address
}
func main() {
p := Person{
Name: "Alice",
Address: Address{
City: "北京",
ZipCode: "100000",
},
}
// 直接访问提升的字段
fmt.Println(p.City) // 北京 等价于 p.Address.City
fmt.Println(p.ZipCode) // 100000
// 也可以通过完整路径访问
fmt.Println(p.Address.City) // 北京
}
字段提升就是编译器帮你自动加了一层路径。p.City 实际上就是 p.Address.City。
嵌入不是继承
Go 没有继承,只有组合。嵌入虽然看起来像继承,但本质不同:
type Animal struct {
Name string
}
type Dog struct {
Animal // 嵌入
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
func main() {
d := Dog{Animal{Name: "Rex"}}
// 方法也会提升
fmt.Println(d.Speak()) // Rex makes a sound
// 但 Dog 的类型不是 Animal
var a Animal = d // 编译错误!Dog 不是 Animal
}
Dog 嵌入了 Animal,能直接用 Animal 的字段和方法,但 Dog 不是 Animal 的子类型。Go 的类型系统是组合,不是继承。
Note如果你学过 Java 或 Python,可能会把嵌入当成继承。记住:Go 里没有「is-a」关系,只有「has-a」关系。嵌入是为了复用代码,不是为了类型层次。
多层嵌入
嵌入可以多层嵌套,字段会层层提升:
package main
import "fmt"
type Address struct {
City string
}
type Contact struct {
Phone string
Address // Contact 嵌入 Address
}
type Person struct {
Name string
Contact // Person 嵌入 Contact
}
func main() {
p := Person{
Name: "Alice",
Contact: Contact{
Phone: "123456",
Address: Address{
City: "北京",
},
},
}
// City 从 Address 提升到 Contact,再提升到 Person
fmt.Println(p.City) // 北京
fmt.Println(p.Phone) // 123456
}
嵌入冲突
如果两个嵌入类型有同名字段,直接用简写会报歧义错误:
type A struct {
Name string
}
type B struct {
Name string
}
type C struct {
A
B
}
c := C{A: A{Name: "Alice"}, B: B{Name: "Bob"}}
// fmt.Println(c.Name) // 编译错误:歧义,不知道是 A.Name 还是 B.Name
fmt.Println(c.A.Name) // Alice 必须指定完整路径
fmt.Println(c.B.Name) // Bob
Warning嵌入多个类型时要注意字段名冲突。外层定义的同名字段会覆盖内层的,但同层之间冲突就必须用完整路径。
小结
结构体初始化和嵌入的要点:
- 用命名式初始化(
Field: value),不用位置式 - 匿名嵌入实现代码复用,字段和方法会提升
- 嵌入是组合不是继承,没有「is-a」关系
- 多层嵌入可以层层提升,同名字段冲突要指定完整路径
下一章讲结构体方法,搞懂值接收者和指针接收者的区别。