golang 中结构体匿名嵌套时的初始化问题

2,037 阅读3分钟

当创建了两个结构体,其中一个结构体通过匿名嵌套在另一个结构体中,如下面的定义

type Person struct {
	Name string
	Age  int
}

type Student struct {
	Person
	Grade string
}

Person 为匿名结构体嵌套在了Student 内,那么此时如果想要初始化一个Student对象时该如何初始化呢?

依次给出每个字段的值

这种最好理解,但是却不能在初始化的时候直接给出

s1 := Student{
    Grade: "一年级"// 以下两行不能编译
    Name: "yyx",
    Age: 18
}

直接在初始化Student对象是赋匿名结构体的值是不行的,提示 unknown field Name in struct literal 但是我们可以先初始化Student 结构体自己的属性值,如student的Grade属性,之后再对Student的Name和Age赋值

s1 := Student{
    Grade: "一年级",
}
s1.Name = "yyx"
s1.Age = 18
fmt.Println(s1)

这里打印的结果为 {{yyx 18} 一年级}, 也就是说student对象还是将Person的Name和Age看做一个整体。 但是student却可以直接访问Name和Age

s1 := Student{
    Grade: "一年级",
}
s1.Name = "yyx"
s1.Age = 18
fmt.Println(s1.Name) // yyx
fmt.Println(s1.Person.Name) //yyx

通过s1.Name或者s1.Person.Name都可以获取到 yyx

具名赋值

这种赋值就是在初始化的时候写上对应的结构体名称

func main() {
    s1 := Student{
        Grade: "一年级",
        Person: Person{
            Name: "yyx",
            Age:  18,
        },
    }
    fmt.Println(s1.Name) // "yyx"
    fmt.Println(s1.Person.Name) // "yyx"
}

先初始化匿名结构体变量

这种方式其实和上面的方式是一样的,只是将匿名的结构体先定义了一个变量

func main() {
    // 先初始化一个Person 对象
    p := Person{
        Name: "yangyanxing",
        Age:  18,
    }
    s := Student{
        Grade:  "一年级",
        Person: p,  // 再将p赋值给匿名结构体
    }
    fmt.Printf("%#v\n", s)
    fmt.Println(s.Name)
    fmt.Println(s.Age)
    fmt.Println(s.Grade)
}

这里有个问题,嵌入的结构体不在同个包怎么办?

├── go.mod
├── main.go
└── utils
    └── utils.go

上面main.go中的Student 结果体嵌入了utils包下的Person结构体

// utils.go
package utils

type Person struct {
	Name string
	Age  int
}


//main.go
type Student struct {
	utils.Person
	Grade string
}

这时候采用第一种方法先初始化变量再对属性进行赋值还是可以的

func main() {
	s1 := Student{
		Grade: "一年级",
	}
	s1.Name = "yyx1111"
	fmt.Println(s1.Name)
	fmt.Println(s1.Person.Name)
}

但是后两种方法就不行了,就会报错了 invalid field name utils.Person in struct literal

这里只需要把utils 去掉,直接使用Person来赋值就可以了

func main() {
	p := utils.Person{
		Name: "yangyanxing",
		Age:  18,
	}
	s := Student{
		Grade:  "一年级",
		Person: p,
	}

	fmt.Printf("%#v\n", s)
	fmt.Println(s.Name)
	fmt.Println(s.Age)
	fmt.Println(s.Grade)
}

但是又会引出一个新的问题,如果一个结构体引入了两个不同包下的同名的结构体又该怎么处理呢?

├── common
│   └── common.go
└── utils
    └── utils.go
├── go.mod
├── main.go

这里我又新建了一个common目录,里面有一个common.go,里面也有一个Person 结构体,和utils.go 中的结构体不一样, 多了一个性别

package common

type Person struct {
	Name string
	Age  int
	Sex  string
}

此时如果在main.go 中定义的Student如下

type Student struct {
	utils.Person
	common.Person 
	Grade string
}

首先,这种在语法上就会报错了,Person redeclared ,编译器也编译不过

其次,如果你实在是想两个都引入,那么只能将其中一个变为具名属性

type Student struct {
	utils.Person
	Cp common.Person // 将commmon.Person 变为具名的Cp
	Grade string
}

这样,就可区分是utils下的Person还是common下的Person了

结构体本身和匿名结构体如果有相同的属性名怎么办?

type Person struct {
	Name string
	Age  int
}

type Student struct {
	Person
	Name  string
	Grade string
}

func main() {
	p := Person{
		Name: "yangyanxing",
		Age:  18,
	}
	s := Student{
		Grade:  "一年级",
		Person: p,
	}

	fmt.Println(s.Name) // 这里为空 ""
	fmt.Println(s.Person.Name) // yangyanxing
	fmt.Println(s.Age) //18
	fmt.Println(s.Grade)
}

如果结构体本身和嵌套的结构体具体相同的属性名,则在赋值的时候需要区分,对象.属性 是从本身结构体中寻找,找不到则为该类型的零值。