【Go内存分配】

程序的运行都需要内存,比如变量的创建、函数的调用、数据的计算等。所以在需要内存的时候就需要申请内存,进行内存分配。在C/C++这类语言中,内存是由开发者自己管理的,需要主动申请和释放,而在Go语言中则是由该语言自己管理的,开发者不用关心太多,只需要声明变量,Go语言就会根据变量的类型自动分配相应的内存。

​ Go语言程序所管理的虚拟内存空间被分为两个部分:堆内存和栈内存。栈内存主要有Go语言来管理,开发者无法干涉太多,堆内存才是我们开发者的舞台,因为程序的数据大部分分配在堆内存上,一个程序的大部分内存占用也是在堆内存上。

提示: 我们常说的Go语言的内存垃圾回收是针对堆内存的垃圾回收。

​ 变量的声明、初始化就涉及内存的分配,比如声明变量会用到var关键字,如果要对变量初始化,就会用到 = 赋值运算符、除此之外还可以使用内置函数make和new,这两个函数功能非常相似,但是可能还会有些迷惑,接下来让我们基于内存分配,引出内置函数make和new,讲讲它们的不同,以及使用场景。

变量

​ 一个数据类型,在声明初始化后都会被赋值给一个变量,变量存储了程序运行所需的数据。

变量的声明

// 我们来复习一下变量的声明
var s string 
// 这个例子中,我们只是声明了一个变量s,类型为string,并没有对它进行初始化,所以它的值是string的零值,也就是 ""(空字符串)

var sp *string
// 我们声明了一个指针类型的变量,也没有初始化,所以它的值是*string的零值,即nil

变量的赋值

​ 变量可以通过=赋值运算符,修改变量的值。如果在声明一个变量的时候就给这个变量赋值,这种操作称为变量的初始化。如果要对一个变量初始化,可以有三种办法

  1. 声明时直接初始化,var s string = “奔跑的蜗牛”
  2. 声明后在初始化,s = “奔跑的蜗牛” // (假设已经声明了变量s)
  3. 使用简短声明,如 s:= “奔跑的蜗牛”

提示:变量的初始化也是一种赋值,只不过发生在变量声明的时候,时机最靠前。即获得这个变量时,就已经被赋值了

那么,指针类型的变量能直接赋值吗

func main() {
    var sp *string
    *sp = "奔跑的蜗牛"
    fmt.Println(*sp)
}

// 上述代码运行会报错,错误信息如下:
painc: runtime error: invalid memory address or nil pointer dereference


// 因为指针类型的变量如果没有分配内存,就默认值是nil,它没有指向的内存,所以无法师用,强行使用会得到 nil指针错误。  对于值类型来说,即使只是声明一个变量,并没有对其初始化,该变量也会有分配好的内存。

func main() {
    var s string
    fmt.Println(&s)
}
// 我们可以获取到变量s的内存地址,这是Go语言帮我们做的,可以直接使用。

var wg sync.WaitGroup 声明的变量wg,我们并没有对其初始化就可以使用了,因为sync.WaitGroup 是一个 struct 结构体,是一个值类型,Go语言自动分配了内存,所以可以直接使用,不会报nil异常。

小结如果要对一个变量赋值,这个变量必须有对应的分配好的内存,这样才可以对这块内存操作,完成赋值操作。对于指针变量,如果没有分配内存,取值操作也一样会报nil异常,因为没有可以操作的内存。 所以,一个变量必须要经过声明、内存分配才能赋值,才可以在声明的时候进行初始化。指针类型在声明的时候,Go语言没有自动分配好内存,所以不能对其进行赋值操作,这一点和值类型不一样。

提示: map和chan也一样,它们本质上也是指针类型。

new函数

​ 我们知道了声明的指针变量是没有分配内存的,那么如何给它分配一块呢? 那就是通过内置new函数

func main() {
    var sp *string
    sp = new(string) // 关键点,通过内置new函数生成了一个*string,并赋值给了变量sp。
    *sp = "奔跑的蜗牛"
    fmt.Println(*sp)
}

// 内置new的作用是什么呢? 我们来分析一下源码
func new(Type) *Type

new函数的作用就是根据传入的类型申请一块内存,然后返回指向这块内存的指针,指针指向的数据就是该类型的零值。比如传入的类型是string,那么返回的就是string指针,这个string指针指向的数据就是空字符串。

spl = new(string)
fmt.Println(*spl) // 打印空字符串,也就是string的零值。

​ 通过new函数分配内存并返回指向该内存的指针后,就可以通过该指针对这块内存进行赋值、取值等操作。

变量初始化

​ 当声明了一些类型的变量时,这些变量的零值并不能满足我们需要,这时需要在变量声明同时进行赋值(修改变量的值),这个过程称为变量初始化。

​ 不止基础类型可以通过字面量的方式进行初始化,复合类型也可以,比如结构体。

type person struct{
    name string
    age int
}

func main() {
    // 字面量初始化
    p := person{name:"zhangsan", age:18}
}

指针变量初始化

​ 我们知道new函数可以申请内存并返回一个指向该内存的指针,但是这块内存中数据的默认值是该类型的零值,在一些情况下并不满足需要。如果我们想要获得一个*person类型的指针,并且初始化,但是new函数只有一个类型参数,并没有初始化值的参数,怎么办呢? 可以自定义一个函数,对指针变量进行初始化。

func NewPerson() *person {
    p := new(person)
    p.name = "zhangsan"
    p.age = 18
    return p
}

p := NewPerson()
fmt.Println("name:", p.name, "age:", p.age)

// 这就是工厂函数,NewPerson 函数就是工厂函数,除了使用new函数创建了指针外,还进行了赋值,即初始化。通过NewPerson函数做了一层包装,把内存分配(new 函数)和初始化(赋值)都完成了。

make函数

​ 接下来讲讲make函数。我们知道,使用make函数创建map的时候,其实调用的是makehmap函数。

func makemap(t *maptype, hint int ,h *hmap) *hmap {}

// makemap函数返回的是*hmap类型,而hmap是一个结构体,源码如下

type hmap struct {
    count 		int 
    flags 		uint8
    B 			uint8
    noverflow	uint16
    hash0		uint16
    buckets     unsafe.Pointer
    oldbuckets  unsafe.Pointer
    nevacuate   uintptr
    extra  		*mapextra
}


m := make(map[string]int, 10)
// make函数和我们的自定义NewPerson函数很像。其实make函数就是map类型的工厂函数,它可以根据传递它的K-V键值对类型,创建不同类型的map,同时可以初始化map的大小。

​ 可以看到,我们平常使用的map关键字非常复杂,包含map的大小count、存储桶buckets等。要想这样使用hmap,不是简单的通过new函数返回一个*hmap就可以,还需要对其初始化,这就是make函数要做的事情。

提示: make函数不只是map类型的工厂函数,还是chan、slice的工厂函数。它同时可以用于slice、chan和map三种类型的初始化。

总结:new和make函数的区别如下:

1. new函数只用于分配内存,并且把内存清零,也就是返回一个指向对应类型零值的指针。new函数一般用于需要显示的返回指针的情况,不是太常用。**new返回的是对象的指针,对指针所在对象的更改,会影响指针指向的原始对象的值**。
2. make函数只用于slice、chan和map这三种内置类型的创建和初始化,因为这三种类型的结构比较复杂,比如slice要提前初始化号内部元素的类型、slice的长度和容量等,这样才能更好地使用它们。**make返回的是对象**
 *   对值类型对象的修改,不会影响原始对象的值
 *   对引用类型的修改,会影响原始对象的值

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值