进程空间栈
栈和堆上内存分配与回收
变量和对象
1、变量声明和初始化
- 第一种:var i int,此时只有零值0
- 第二种:var i = 0,此时会自行判定为int,并赋初值为0
- 第三种: i := 0,跟第二种类似
- 第三种:var i int = 0,一般不这么写**
2、关于零值
- 数值类型(包括complex64和complex128)零值为0,[complex64由一个float32表示实数和一个float32表示虚数,complex28用两个float64表示)
- bool零值为false
- string零值为””
- nil可以作为interface、function、pointer、map、slice、channel的空值,如果不特别指定,go是无法识别类型,编译报错:cannot use nil as type xxx
3、go的多重赋值
go会先计算”=”左边的表达式,再计算右边的表达式,比如下面例子:
i, j := 0, 0
i, j = 1, i
结果是:
i=1, j=0
4、go常量
go常量会在预编译阶段直接展开,作为指令数据使用,所以无法寻址;而变量则是在运行期间分配内存,可以正常寻址,能得到其地址或取其指针。例如:
const i = 1
var i = 123
fmt.Println(&i, &j)
会报错:cannot take the address of i
因为i是常量
5、go值类型和引用类型
6、go对象
- map对象:
底层由两个hashmap构成,map因为会动态扩容,导致其value内存地址可能会变,所以map的value不能寻址,不能通过&获取地址,也就不能直接进行单独赋值,除非用临时变量或者value指针表示。
type Person struct {
age int
}
var m = map[int]Person{
1: Person{age: 20},
}
m[1].age = 30
可以用一下方式替换:
// 1.指针方式
var m = map[int]*Person{
1: &Person{age: 20},
}
m[1].age = 30
// 2.临时变量覆盖
var m = map[int]Person{
1: Person{age: 20},
}
person := Person{age: 30}
m[1] = person
另外因为go的运行时决定了,在gc时,不能让外部持有变量的内部变量的指针,所以map的键值不能寻址。
7、不可寻址的类型
不可寻址意思是不能用&取其地址,即不能改变其值,满足以下三个条件之一就不能寻址。
- 不可变的:常量和基本类型的字面量会存储在固定区域
- 临时结果:对字面量施加的表达式的求值结果都看作是临时结果
获得某个元素的索引表达式 获得某个切片(片段)的切片表达式 访问某个字段的选择表达式
调用某个函数或方法的调用表达式
转换值的类型的类型转换表达式
判断值的类型的类型断言表达式
向通道发送元素或从通道哪里接收元素值的接收表达式
- 不安全的
map的键值的地址可能会变,对其取地址,会破坏map的一致性
new和make
1、new
内建函数:func new(Type) *Type
理解为只分配内存
new只有一个参数,返回的是指向变量的类型指针
2、make
内建函数:func make(Type, size …IntegerType) Type
只适用于初始化slice map或channel
所以make至少有2个参数,按指定大小初始化变量类型,返回的是变量的引用类型【该引用类型的指向的值就是变量的实际value的地址,也可以把这个地址叫指针】
如:make([]int, 10),初始化一个len=10,cap=10的slice【注意cap>=len,否则编译报错】
make(slice)返回的是切片对象,是一个实体
make(map)返回的是map指针
3、比较
从上面定义和比较看出
new可以给一切支持的变量类型分配内存,如果new的是复杂类型(map slice chan),此时也只生成了指向变量的指针(或叫地址1),而变量指向实际值的指针(或叫地址2)还是nil
而make只针对涉及到多个元素个数的这种复杂变量类型(map slice chan),按指定的长度初始化变量,因为返回的是引用,所以该引用是直接指向的变量内存块,该内存块中指向实际值的指针(或叫地址2)不是nil,而是指向了已初始化的数据,虽然可能是空值
如下图: