刚接触的 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 的类型,都未被自动初始化。因为这三个类型本质上也是引用类型,需要引用一块未知大小的内存空间,故不直接进行初始化。