go结构体的初始化及内存布局
初始化方式
方式一:通过 var 声明结构体
在 Go 语言中当一个变量被声明的时候,系统会自动初始化它的默认值,比如 int 被初始化为 0,指针为 nil。
var 声明同样也会为结构体类型的数据分配内存,所以我们才能像上一段代码中那样,在声明了 var s T
之后就能直接给他的字段进行赋值
方式二:使用 new
使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)。
type struct1 struct {
i1 int
f1 float32
str string
}
func main() {
ms := new(struct1)
ms.i1 = 10
ms.f1 = 15.5
ms.str= "Chris"
fmt.Printf("The int is: %d\n", ms.i1)
fmt.Printf("The float is: %f\n", ms.f1)
fmt.Printf("The string is: %s\n", ms.str)
fmt.Println(ms)
}
与面向对象语言相同,使用点操作符可以给字段赋值:structname.fieldname = value
。
同样的,使用点操作符可以获取结构体字段的值:structname.fieldname
。
方式三:使用字面量
type Person struct {
name string
age int
address string
}
func main() {
var p1 Person
p1 = Person{"lisi", 30, "shanghai"} //方式A
p2 := Person{address:"beijing", age:25, name:"wangwu"} //方式B
p3 := Person{address:"NewYork"} //方式C
}
在方式A中,值必须以字段在结构体定义时的顺序给出。
方式B是在值前面加上了字段名和冒号,这种方式下值的顺序不必一致,并且某些字段还可以被忽略掉,就像方式C那样。
方式 四
除了上面这三种方式外,还有一种初始化结构体实体更简短和常用的方式,如下:
ms := &Person{"name", 20, "bj"}
ms2 := &Person{name:"zhangsan"}
&Person{a, b, c}
是一种简写,底层仍会调用 new()
,这里值的顺序必须按照字段顺序来写,同样它也可以使用在值前面加上字段名和冒号的写法(见上文的方式B,C)。
- 表达式
new(Type)
和&Type{}
是等价的。
几种初始化方式之间的区别
到目前为止,我们已经了解了三种初始化结构体的方式:
//第一种,在Go语言中,可以直接以 var 的方式声明结构体即可完成实例化(var 声明同样也会为结构体类型的数据分配内存 )
var t T
t.a = 1
t.b = 2
//第二种,使用 new() 实例化
t := new(T)
//第三种,使用字面量初始化
t := T{a, b} // 分配内存,并返回该类型
t := &T{} //简写方式,等效于 new(T),就是对上面那种写法取地址,返回指针
- 使用
var t T
会给 t 分配内存,并零值化内存,但是这个时候的 t 的类型是 T; - 使用 new 关键字时
t := new(T)
,变量 t 则是一个指向 T 的指针。
从内存布局上来看,我们就能看出这三种初始化方式的区别:
使用var 声明:直接为该变量分配内存。

使用 new 初始化:为类型分配内存后,返回该类型的指针。

使用取结构体的地址实例化:相当于上面两种方式的简写。

下面来看一个具体的例子
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
var p1 Person // 分配内存,并初始化为 0 值
p1.name = "zhangsan"
p1.age = 18
fmt.Printf("This is %s, %d years old\n", p1.name, p1.age)
p2 := new(Person) //分配内存,返回指针
p2.name = "lisi"
p2.age = 20
(*p2).age = 23 //这种写法也是合法的
fmt.Printf("This is %s, %d years old\n", p2.name, p2.age)
p3 := Person{"wangwu", 25} //分配内存,并初始化为字面值
fmt.Printf("This is %s, %d years old\n", p3.name, p3.age)
}
输出:
This is zhangsan, 18 years old
This is lisi, 23 years old
This is wangwu, 25 years old
上面例子的第二种情况,虽然 p2 是指针类型,但我们仍然可以像 p2.age = 23
这样赋值,不需要像 C++ 中那样使用 ->
操作符,Go 会自动进行转换。
注意也可以先通过 *
操作符来获取指针所指向的内容,再进行赋值:(*p2).age = 23
。
疑点解答
- 为什么new 出来的指针变量,也可以像 var 声明的变量一样来使用结构体字段?
虽然 p2 是指针类型,但我们仍然可以像 p2.age = 23
这样赋值,不需要像 C++ 中那样使用 ->
操作符,Go 会自动进行转换。
结构体的内存布局
Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的。
即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。
不像 Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,这点和 Go 语言中的指针很像。
下面的例子清晰地说明了这些情况:
type Rect1 struct {Min, Max Point }
type Rect2 struct {Min, Max *Point }
