go重要数据类型分析
1. 值类型和引用类型
值类型:包括基本数据类型,如 int、float、bool、string以及数组和结构体;值类型不管是否已经赋值,编译器都会为其赋值,此时值存储在栈上。
引用类型:包括指针、slice切片、map、chain 和 interface;引用类型必须申请内存才能使用,常用的创建内存函数有new和make.
1.1 new
func main() {
var i *int
i = new(int)//new的参数是一个类型,分配好内存后,返回一个指向该类型内存地址的指针
*i = 10
fmt.Println(*i)
}
1.2 make
make只用于chain、map和slice的内存创建,返回类型是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以没有必要返回他们的指针了。
func main() {
s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使⽤索引号。
fmt.Println(s1, len(s1), cap(s1))
s2 := make([]int, 6, 8) // 使⽤ make 创建,指定 len 和 cap 值。
fmt.Println(s2, len(s2), cap(s2))
s3 := make([]int, 6) // 省略 cap,相当于 cap = len。
fmt.Println(s3, len(s3), cap(s3))
}
2. rune
解释:几乎在所有方面等同于int32,它用来区分整数值和字符值
用法:想要获得真实的字符串长度,而不是其所占用的字节数
func main() {
var str = "hello 世界"
//Golang中默认编码是utf8;在utf8编码中,中文占3个字节;在unicode编码中,中文占两个字节
fmt.Println("len(str): ", len(str)) //输出:12
fmt.Println("len(rune[](str)): ", len([]rune(str))) //输出:8
fmt.Println("RuneCountInString:", utf8.RuneCountInString(str))//输出:8
}
3. 指针
- *类型: 普通指针类型,用于传递对象地址,不能进行指针运算。
- unsafe.Pointer: 通用指针类型,用于转换不同类型的指针,不能进行指针运算,不能读取内存存储的值(必须转换到某一类型的普通指针)。
- uintptr: 用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象。uintptr 类型的目标会被回收。
3.1 unsafe成员
//ArbitraryType表示任意类型
type ArbitraryType int
//Pointer表示任意指针,类似于c语言的void
type Pointer *ArbitraryType
//计算表达式占用的字节个数
func Sizeof(x ArbitraryType) uintptr
//返回结构体中元素所在内存的偏移量
func Offsetof(x ArbitraryType) uintptr
//返回变量指定属性的偏移量
func Alignof(x ArbitraryType) uintptr
3.2 unsafe.Pointer
unsafe.Pointer称为通用指针,官方文档对该类型有四个重要描述:
- 任何类型的指针都可以被转化为Pointer
- Pointer可以被转化为任何类型的指针
func main() {
var f float64 = 1.0
fmt.Println(reflect.TypeOf(unsafe.Pointer(&f))) //任何类型的指针都可以被转化为Pointer
fmt.Println(reflect.TypeOf((*uint64)(unsafe.Pointer(&f)))) //Pointer可以被转化为任何类型的指针
fmt.Printf("%#016x\n", *(*uint64)(unsafe.Pointer(&f))) //在变量前加*,是取值
}
3.3 uintptr
uintptr是golang的内置类型,是能存储指针的整型
- uintptr可以被转化为Pointer
- Pointer可以被转化为uintptr
func main() {
var x struct {
a bool
b int16
c []int
}
//将指针类型转化为uintptr整型,就可以进行指针的数值运算
pb := (*int16)(unsafe.Pointer( uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b) ))
//错误写法
//tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
//pb := (*int16)(unsafe.Pointer(tmp))
*pb = 12
fmt.Println(x.b)
}
总结:有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收器被称为移动GC。当一个变量被移动,所有的保存改变量旧地址的指针必须同时被更新为变量移动后的新地址。从垃圾收集器的视角来看,一个unsafe.Pointer是一个指向变量的指针,因此当变量被移动是对应的指针也必须被更新;但是uintptr类型的临时变量只是一个普通的数字,所以其值不应该被改变。上面错误的代码因为引入一个非指针的临时变量tmp,导致垃圾收集器无法正确识别这个是一个指向变量x的指针。当第二个语句执行时,变量x可能已经被转移,这时候临时变量tmp也就不再是现在的&x.b地址。第三个指向之前无效地址空间的赋值语句将彻底摧毁整个程序
4. Array
- 数组是值类型,赋值和传参会复制整个数组,⽽不是指针。
- 数组⻓度必须是常量,且是类型的组成部分。[2]int 和 [3]int 是不同类型。
- ⽀持 “==”、"!=" 操作符,因为内存总是被初始化过的。
- 指针数组
[n]*T
,数组指针*[n]T
。
data := [...]int{0, 1, 2, 3, 4, 5, 6}
5. Slice
- slice 并不是数组或数组指针。它通过内部指针和相关属性引⽤数组⽚段
- 已知数组起始位置从0开始,
slice=[1:3]
最大截取位置为2,不包含3
struct Slice
{ // must not move anything
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};
使用make创建Slice
# 创建一个长度为6,容量为9的Slice
data := make([]int,6,9)
使用append时,⼀旦超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满,所以建议一次性分配足够大的空间。
data := [...]int{0, 1, 2, 3, 4, 5, 6}
slice := data[:2:3]
slice = append(slice, 100, 200)
fmt.Print(slice)
fmt.Print(data)
输出:
[0 1 100 200][0 1 2 3 4 5 6]
copy的用法:
# 将第5,6位置的数据复制到0,1位置上
data := [...]int{0, 1, 2, 3, 4, 5, 6}
s := data[5:]
s1 := data[:5]
copy(s1, s)
fmt.Print(data)
fmt.Print(s1)
输出:
[5 6 2 3 4 5 6][5 6 2 3 4]
6. map
格式: map[keyType]valueType
//使用make申请一次数量内存,再赋值
func main() {
m := make(map[int]string,3)
m[1] = "A"
m[2] = "B"
m[3] = "C"
fmt.Println(m)
}
//直接初始化赋值
func main() {
m := map[int]string{1:"A",2:"B",3:"C"}
fmt.Println(m)
}
7. Struct
- 值类型,成员之间直接进行拷贝
- 使用
_
补位字段 - 匿名字段就是⼀个与成员类型同名 (不含包名) 的字段
- 面向对象三大特征:封装、继承、多态,只支持封装
func main() {
type Resouce struct {
_ uint8 //补位字体
id int
}
type User struct {
Resouce //匿名字段
name string
}
type Manager struct {
User//匿名字段
title string
}
var m Manager
m.id = 1
m.name = "jack"
m.title = "Administrator"
var p = &m;
fmt.Println(m)
fmt.Printf("%p, size: %d, cap: %d\n", unsafe.Pointer(p), unsafe.Sizeof(*p), unsafe.Alignof(p))
fmt.Printf("%p, %d\n",unsafe.Pointer(p), unsafe.Offsetof(p.id))
fmt.Printf("%p, %d\n",unsafe.Pointer(&p.name), unsafe.Offsetof(p.name))
fmt.Printf("%p, %d\n",unsafe.Pointer(&p.title), unsafe.Offsetof(p.title))
}
输出内容:
{{{0 1} jack} Administrator}
0xc000070330, size: 48, cap: 8
0xc000070330, 8
0xc000070340, 16
0xc000070350, 32