基本类型的字节数
可以用Unsafe.Sizeof查看任何变量的字节大小
- int大小跟随系统字长
- 指针的大小也是系统字长
空结构体
空结构体大小为0 但有地址, 但所有的空结构体(独立出现, 没有被其它struct引用)都指向同一地址(zerobase, 0x8a82f8) 当被内嵌在其它struct中, 地址不是zerobase 空结构体主要是为了节约内存, 比如使用map实现hashset
// value 不占任何空间
hashSet := map[string]struct{}{}
hashSet["a"] = struct{}{}
比如用channel传输信号, 不想携带任何信息
a := make(chan struct{}, 1)
字符串
字符串本质上是个结构体
type stringStruct struct {
str unsafe.Pointer
len int
}
对string取Sizeof的时候, 取的是指针大小. Data指针指向底层Byte数组 len表示byte数组的长度, 编码不同, 字符的个数也不同(UTF8下, 一个字符占三个字节)
字符编码问题
所有字符都是用Unicode字符集,使用UTF-8编码
- Unicode字符集 一种统一的字符集, 包含了绝多数文字的绝大多数字符, 14w个字符, 至少需要3字节(2^24)才能表示 英文字母排在前128个
- UTF-8编码 Unicode的一种变长格式 128个US-ASCII字符只需一个字节编码 西方常用字符需要两个字节 其他字符3个字节, 极少4个字节
字符串遍历
自动判断多个字节是不是一个字符的不同部分(runtime下的utf8.go实现, rune就是UTF-8编码) 字符串被range遍历的时候, 被解码成rune类型的字符
s := "中国科学院"
// 这样不正确
for i := 0; i < len(s); i++ {
fmt.Printf("%c", s[i])
}
// 正确方式
for _, c := range s {
fmt.Printf("%c", c)
}
字符串切片
- 转为rune数组
- 切片
- 转为string
s = string([]rune(s)[:3]) // 取前三个汉字
切片
是一个结构体 切片的本质是对数组的引用
type slice struct {
array unsafe.Pointer
len int // 长度
cap int // 总容量
}
切片创建
- 根据数组创建
arr[0:3]
slice[0:3]
- 字面量创建: 编译时插入创建数组的代码
slice := []int{1, 2, 3}
- make: 运行时创建数组
slice := make([]int, 10)
切片扩容
- 不扩容时, 只调整len(编译器负责)
- 扩容时, 编译时转为调用runtime.growslice()
< 1024的时候, 二倍增长, 将原数据复制过来.> 1024的时候, 每次增加25% 切片扩容时, 并发不安全, 注意切片并发需要加锁
总结
- 字符串与切片都是对底层数组的引用
- 字符串有UTF-8变长编码的特点
- 切片的容量和长度不同
- 切片追加时, 可能需要重新创建底层数组
接口
接口显式好还是隐式好?
Go隐式接口特点
- 只要实现了接口的全部方法, 就是自动实现接口
- 可以在不修改代码的情况下抽象出新的接口
接口的底层表示
底层使用runtime.iface表示
type iface struct{
tab *itab // 接口类型, 接口装载的类型, 实现了哪些方法
data unsafe.Pointer // 指向结构体(数据)
}
类型断言
- 类型断言是一个使用在接口值上的操作
- 可以将接口值转换为其它类型值(实现或者兼容接口)
- 还可以配合switch 进行类型判断
var c Car = Truck{}
t := c.(Truck)
接受者为结构体的时候, 会自动添加一个接受者为指针的方法; 但如果用结构体指针实现了方法, 只会存在指针的.
空接口
是eface 可以承载任何类型. 底层不是普通接口
用途
- 作为任意类型的函数入参
- 函数调用的时候, 会新生成一个空接口, 再传参
总结
- Go的隐式接口更方便系统的扩展和重构
- 接口提和指针都可以实现接口
- 空接口值可以称在任何类型的数据