【Go內存分配】

程序的運行都需要內存,比如變量的創(chuàng)建、函數(shù)的調用、數(shù)據(jù)的計算等。所以在需要內存的時候就需要申請內存,進行內存分配。在C/C++這類語言中,內存是由開發(fā)者自己管理的,需要主動申請和釋放,而在Go語言中則是由該語言自己管理的,開發(fā)者不用關心太多,只需要聲明變量,Go語言就會根據(jù)變量的類型自動分配相應的內存。

Go語言程序所管理的虛擬內存空間被分為兩個部分:**堆內存和棧內存**。棧內存主要有Go語言來管理,開發(fā)者無法干涉太多,堆內存才是我們開發(fā)者的舞臺,因為程序的數(shù)據(jù)大部分分配在堆內存上,一個程序的大部分內存占用也是在堆內存上。

提示: 我們常說的Go語言的內存垃圾回收是針對堆內存的垃圾回收。

變量的聲明、初始化就涉及內存的分配,比如聲明變量會用到var關鍵字,如果要對變量初始化,就會用到 = 賦值運算符、除此之外還可以使用內置函數(shù)make和new,這兩個函數(shù)功能非常相似,但是可能還會有些迷惑,接下來讓我們基于內存分配,引出內置函數(shù)make和new,講講它們的不同,以及使用場景。

變量

一個數(shù)據(jù)類型,在聲明初始化后都會被賦值給一個變量,變量存儲了程序運行所需的數(shù)據(jù)。

變量的聲明

// 我們來復習一下變量的聲明
var s string 
// 這個例子中,我們只是聲明了一個變量s,類型為string,并沒有對它進行初始化,所以它的值是string的零值,也就是 ""(空字符串)

var sp *string
// 我們聲明了一個指針類型的變量,也沒有初始化,所以它的值是*string的零值,即nil

變量的賦值

變量可以通過=賦值運算符,修改變量的值。如果在聲明一個變量的時候就給這個變量賦值,這種操作稱為變量的初始化。如果要對一個變量初始化,可以有三種辦法
  1. 聲明時直接初始化,var s string = "奔跑的蝸牛"
  2. 聲明后在初始化,s = "奔跑的蝸牛" // (假設已經聲明了變量s)
  3. 使用簡短聲明,如 s:= "奔跑的蝸牛"

提示:變量的初始化也是一種賦值,只不過發(fā)生在變量聲明的時候,時機最靠前。即獲得這個變量時,就已經被賦值了

那么,指針類型的變量能直接賦值嗎?

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函數(shù)

我們知道了聲明的指針變量是沒有分配內存的,那么如何給它分配一塊呢? 那就是通過**內置new函數(shù)**。
func main() {
    var sp *string
    sp = new(string) // 關鍵點,通過內置new函數(shù)生成了一個*string,并賦值給了變量sp。
    *sp = "奔跑的蝸牛"
    fmt.Println(*sp)
}

// 內置new的作用是什么呢? 我們來分析一下源碼
func new(Type) *Type
**new函數(shù)**的作用就是根據(jù)傳入的類型申請一塊內存,然后返回指向這塊內存的指針,指針指向的數(shù)據(jù)就是該類型的零值。比如傳入的類型是string,那么返回的就是string指針,這個string指針指向的數(shù)據(jù)就是空字符串。
spl = new(string)
fmt.Println(*spl) // 打印空字符串,也就是string的零值。
通過new函數(shù)分配內存并返回指向該內存的指針后,就可以通過該指針對這塊內存進行賦值、取值等操作。

變量初始化

當聲明了一些類型的變量時,這些變量的零值并不能滿足我們需要,這時需要在變量聲明同時進行賦值(修改變量的值),這個過程稱為變量初始化。

不止基礎類型可以通過字面量的方式進行初始化,復合類型也可以,比如結構體。
type person struct{
    name string
    age int
}

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

指針變量初始化

我們知道new函數(shù)可以申請內存并返回一個指向該內存的指針,但是這塊內存中數(shù)據(jù)的默認值是該類型的零值,在一些情況下并不滿足需要。如果我們想要獲得一個*person類型的指針,并且初始化,但是new函數(shù)只有一個類型參數(shù),并沒有初始化值的參數(shù),怎么辦呢? 可以自定義一個函數(shù),對指針變量進行初始化。
func NewPerson() *person {
    p := new(person)
    p.name = "zhangsan"
    p.age = 18
    return p
}

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

// 這就是工廠函數(shù),NewPerson 函數(shù)就是工廠函數(shù),除了使用new函數(shù)創(chuàng)建了指針外,還進行了賦值,即初始化。通過NewPerson函數(shù)做了一層包裝,把內存分配(new 函數(shù))和初始化(賦值)都完成了。

make函數(shù)

接下來講講make函數(shù)。我們知道,使用make函數(shù)創(chuàng)建map的時候,其實調用的是makehmap函數(shù)。
func makemap(t *maptype, hint int ,h *hmap) *hmap {}

// makemap函數(shù)返回的是*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函數(shù)和我們的自定義NewPerson函數(shù)很像。其實make函數(shù)就是map類型的工廠函數(shù),它可以根據(jù)傳遞它的K-V鍵值對類型,創(chuàng)建不同類型的map,同時可以初始化map的大小。
可以看到,我們平常使用的map關鍵字非常復雜,包含map的大小count、存儲桶buckets等。要想這樣使用hmap,不是簡單的通過new函數(shù)返回一個*hmap就可以,還需要對其初始化,這就是make函數(shù)要做的事情。

提示: make函數(shù)不只是map類型的工廠函數(shù),還是chan、slice的工廠函數(shù)。它同時可以用于slice、chan和map三種類型的初始化。

總結:new和make函數(shù)的區(qū)別如下:

1. new函數(shù)只用于分配內存,并且把內存清零,也就是返回一個指向對應類型零值的指針。new函數(shù)一般用于需要顯示的返回指針的情況,不是太常用。**new返回的是對象的指針,對指針所在對象的更改,會影響指針指向的原始對象的值**。
2. make函數(shù)只用于slice、chan和map這三種內置類型的創(chuàng)建和初始化,因為這三種類型的結構比較復雜,比如slice要提前初始化號內部元素的類型、slice的長度和容量等,這樣才能更好地使用它們。**make返回的是對象**
 *   對值類型對象的修改,不會影響原始對象的值
 *   對引用類型的修改,會影響原始對象的值

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容