新类型 类型定义
Type Definition,基于已有类型
新类型 T1 是基于原生类型 int 定义的新类型,而新类型 T2 则是基于刚定义的类型 T1 定义的新类型
底层类型
如果一个新类型是基于某个原生类型定义的,那么该原生类型为新类型的底层类型(Underlying Type)
如果一个新类型不是基于原生类型定义的,那么就就递归查找
底层类型用来判断两个类型本质上是否相同(identical)
本质相同的两个类型,它们的变量可以通过显式转型进行相互赋值
本质不同的两个类型,变量之间都无法显式转型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type T1 int type T2 T1 type T3 string func main () { var n1 T1 var n2 T2 = 5 n1 = T1(n2) fmt.Printf("%T %v\n" , n1, n1) fmt.Printf("%T %v\n" , n2, n2) var s T3 = "Hello" n1 = T1(s) }
类型字面值
多用于自定义一个新的复合类型
1 2 type M map [int ]string type S []string
块式定义:var、const、type
1 2 3 4 5 type ( T1 int T2 T1 T3 string )
类型别名
Type Alias,常用于:渐进式重构 + 对包的二次封装
1 2 3 4 5 6 7 type T = string func main () { var s string = "hello" var t T = s fmt.Printf("%T\n" , t) }
定义 Struct 类型字面值
struct 包裹类型字面量(由若干字段聚合而成)
1 2 3 4 5 6 type T struct { Field1 T1 Field2 T2 ... FieldN TN }
1 2 3 4 5 6 type Book struct { Title string Pages int Indexes map [string ]int _ []string }
空结构体 1 2 3 4 5 6 type Empty struct {}func main () { var s Empty fmt.Printf("%v\n" , unsafe.Sizeof(s)) }
基于空结构体类型内存零开销的特性,经常使用空结构体类型元素作为一种事件,进行 goroutine 之间的通信
1 2 var c = make (chan Empty)c <- Empty{}
依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Person struct { Name string Phone string Address string } type Book struct { Title string Author Person } func main () { var book Book fmt.Println(book.Author.Phone) }
场景:定义包含结构体类型字段的结构体类型 嵌入字段(Embedded Field)或匿名字段:不提供字段名称,只使用类型 - Type Embedding
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Person struct { Name string Phone string Address string } type Book struct { Title string Person } func main () { var book Book fmt.Println(book.Person.Phone) fmt.Println(book.Phone) }
递归
结构体不支持递归定义
自身递归
结构体类型 T 的定义中不能包含类型为 T 的字段
1 2 3 4 type Node struct { left Node right Node }
多者递归
不同结构体之间的递归定义,也是不合法的
1 2 3 4 5 6 7 type A struct { b B } type B struct { a A }
合法
合法形式:指针、切片、map 值
1 2 3 4 5 type T struct { t *T st []T m map [string ]T }
声明 Struct 1 2 3 4 5 6 7 8 9 10 11 12 type Book struct { Title string } var b1 Bookvar b2 = Book{}var b3 Book = Book{}func main () { b4 := Book{} fmt.Println(b1, b2, b3, b4) }
初始化 Struct 零值初始化
使用结构体的零值作为其初始值,类型零值即一个类型的默认值
当结构体类型变量中的各个字段的值都是零值,那么该结构体类型的变量处于零值状态
1 2 3 4 5 6 7 8 9 10 11 type Book struct { Title string } var book Book func main () { fmt.Printf("%#v\n" , book) fmt.Println(book.Title == "" ) fmt.Println(unsafe.Sizeof(book)) }
践行零值可用的理念,可以简化代码和改善开发者体验
C - Mutex 非零值可用
1 2 3 4 5 6 7 8 9 #include <pthread.h> int main () { pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL ); pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex); }
Go - Mutex 零值可用,提升开发体验,无需对 Mutex 变量进行显式初始化
1 2 3 4 5 6 7 8 9 10 package mainimport "sync" func main () { var mutex sync.Mutex mutex.Lock() mutex.Unlock() }
1 2 3 4 var b bytes.Buffer b.Write([]byte ("Hello World!" )) fmt.Println(b.String())
复合字面量
按顺序依次给每个结构体字段赋值 - 不推荐
1 2 3 4 5 6 7 type Book struct { Title string Pages int Indexes map [string ]int } var book = Book{"Go 101" , 1 << 10 , make (map [string ]int )}
field:value - 推荐 未显式出现在复合字面值的结构体字段将采用它对应类型的零值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Book struct { Title string Pages int Indexes map [string ]int } var book = Book{ Title: "The Go Programming Language" , Pages: 380 , Indexes: map [string ]int { "Introduction" : 1 , "Chapter 1" : 12 , "Chapter 2" : 20 , "Chapter 3" : 34 , }, }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Book struct { Title string Pages int Indexes map [string ]int } var b1 = Book{ Title: "The Go Programming Language" , Pages: 380 , Indexes: map [string ]int { "Introduction" : 1 , "Chapter 1" : 12 , "Chapter 2" : 20 , "Chapter 3" : 34 , }, } var b2 = Book{} func main () { fmt.Printf("%#v\n" , b2) }
new
比较少用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Book struct { Title string Pages int Indexes map [string ]int } func main () { b1 := Book{} b2 := new (Book) fmt.Printf("%T, %d, %#v\n" , b1, unsafe.Sizeof(b1), b1) fmt.Printf("%T, %d, %d, %#v\n" , b2, unsafe.Sizeof(b2), unsafe.Sizeof(*b2), b2) }
构造函数
场景
结构体类型中包含未导出字段,且该未导出字段为零值不可用
结构体类型中的某些字段,需要复杂的初始化逻辑
time/sleep.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type runtimeTimer struct { pp uintptr when int64 period int64 f func (interface {}, uintptr ) arg interface {} seq uintptr nextwhen int64 status uint32 } type Timer struct { C <-chan Time r runtimeTimer } func NewTimer (d Duration) *Timer { c := make (chan Time, 1 ) t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f: sendTime, arg: c, }, } startTimer(&t.r) return t }
NewT 为结构体类型 T 的专用构造函数,参数列表通常与 T 中定义的导出字段相对应,返回 T 指针类型的变量
1 2 3 func NewT (field1, field2, field3, ...) *T { ... }
内存布局 理想情况
结构体类型将其字段以平铺的方式存放在一个连续的内存块中
结构体类型 T 在内存中的布局非常紧凑,Go 为其分配的内存都用来存储字段,没有被 Go 编译器插入额外字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type T struct { F1 int64 F2 int64 F3 int64 } func main () { var t T fmt.Printf("%v\n" , unsafe.Sizeof(t)) fmt.Printf("%v\n" , unsafe.Offsetof(t.F1)) fmt.Printf("%v\n" , unsafe.Offsetof(t.F2)) fmt.Printf("%v\n" , unsafe.Offsetof(t.F3)) }
真实情况
内存对齐 - 提高处理器存储数据的效率
对于基础数据类型,其变量的内存地址值必须是其类型本身大小的整数倍
int64 类型变量的内存地址,能被 8 整除
uint16 类型变量的内存地址,能被 2 整除
对于结构体而言,其变量的内存地址值,满足 N * min(最长字段长度, 系统对齐系数) 即可
对齐过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type T struct { b byte i int64 u uint16 } func main () { var ts [2 ]T var t0 = ts[0 ] fmt.Printf("%d\n" , unsafe.Sizeof(t0.b)) fmt.Printf("%d\n" , unsafe.Sizeof(t0.i)) fmt.Printf("%d\n" , unsafe.Sizeof(t0.u)) fmt.Printf("%d\n" , unsafe.Sizeof(t0)) fmt.Printf("%d\n" , unsafe.Sizeof(ts)) }
字段顺序会影响整个结构体的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type T struct { b byte i int64 u uint16 } type S struct { b byte u uint16 i int64 } func main () { fmt.Printf("%d\n" , unsafe.Sizeof(T{})) fmt.Printf("%d\n" , unsafe.Sizeof(S{})) }
内存填充一般由编译器自动完成,也可以做主动填充(通过 _ 标识符来进行主动填充)
runtime/mstats.go 1 2 3 4 5 6 7 type mstats struct { ... _ [1 - _NumSizeClasses%2 ]uint32 ... )