HomeBlogProjects

Go 中 struct 初始化

sorcererxw

刚接触的 Golang 总是被 Golang 当中的 struct 困扰,无论是初始化方式、多种数据类型和结构体组合方式,都和以往接触的其他语言有不小的区别,本文将进行梳理。

疑问

Go 当中有多种方式来初始化一个 struct:

  • 初始化为值:foo:=Foo{},也可以通过默认方式来创建 var foo Foo
  • 初始化为指针:foo:=New(Foo),当然也可以先初始化为值再取地址 foo:=&Foo{}
那么这些方式都有什么区别呢?为什么简洁至上的 Go 要引入这么多不同的初始化方式?

Go 当中存在多种类型,包括以下:

  • 字面量值/指针:数值、字符串、布尔、数组
  • 结构体值/指针
  • slice、map、channel(需要make初始化的值)
  • 还有就是 Go struct 特有的组合模式,其中就包含了内嵌值与内嵌指针的区别

定义如下的 Foobar 结构体:

type Foo struct {
}

func (Foo) callVFoo() {
	fmt.Println("vfoo")
}

func (*Foo) callPFoo() {
	fmt.Println("pfoo")
}

type Bar struct {
}

func (Bar) callVBar() {
	fmt.Println("vbar")
}

func (*Bar) callPBar() {
	fmt.Println("pbar")
}
type Foobar struct {
	Foo
	*Bar

	vFoo Foo
	pFoo *Foo

	vNum int
	pNum *int

	vArr [5]int
	pArr *[5]int

  s  []int
	m  map[int]int
	c  chan int
}
这些成员变量都会以何种信息被初始化?

实验

对于不同的 fb 分别运行以下代码,并观察结果:

1. fb := Foobar{}
2. fb := new(Foobar)
3. var fb Foobar
4. var fb *Foobar

func test(fb) {
	fmt.Printf("%+v\n", fb)
  fmt.Printf("slice is nil: %t\n", fb.s == nil)
	fmt.Printf("map is nil: %t\n", fb.m == nil)
	fmt.Printf("chan is nil: %t\n", fb.c == nil)

	fb.callPFoo()
	fb.callVFoo()
	fb.callPBar()
	fb.callVBar()
}
运行结果1:
{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果2:
&{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果3:
{Foo:{} Bar:<nil> vFoo:{} pFoo:<nil> vNum:0 pNum:<nil> vArr:[0 0 0 0 0] pArr:<nil> s:[] m:map[] c:<nil>}
slice is nil: true
map is nil: true
chan is nil: true
pfoo
vfoo
pbar
panic: runtime error: invalid memory address or nil pointer dereference
运行结果4:
<nil>
panic: runtime error: invalid memory address or nil pointer dereference

结论

  • 对于前三种初始化方式,虽然可能获得了 struct value 或者 struct pointer,但是其最终指向的值内存都是相等的。唯独直接声明了 *struct 将无法初始化而获得 nil。
  • 对于内嵌结构体,如果内嵌的为值,则可初始化;如果内嵌为指针,则无法初始化。

    原因:内嵌值的情况下,在初始化外部结构体的时候会申请整块连续内存,顺便就可以初始化内部结构体。而内嵌指针的情况,则不会。

    另外,虽然内嵌结构体为 nil,但是依然可以调用 receiver 为指针的 func,但是无法调用 receiver 为 value 的 func。

  • slice、map、channel 等需要 make 的类型,都未被自动初始化。因为这三个类型本质上也是引用类型,需要引用一块未知大小的内存空间,故不直接进行初始化。