首页 / Go 语言入门教程 / 结构体字面量与嵌入

Go 语言入门教程

结构体字面量与嵌入

本教程共 80 篇 · 第 38 篇 · 更新于 2026-07-27 · 约 7 分钟阅读

GoGo 入门教程结构体字面量嵌入字段提升组合

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」关系
  • 多层嵌入可以层层提升,同名字段冲突要指定完整路径

下一章讲结构体方法,搞懂值接收者和指针接收者的区别。