<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Golang基礎(chǔ)筆記七之指針,值類型和引用類型

      本文首發(fā)于公眾號:Hunter后端

      原文鏈接:Golang基礎(chǔ)筆記七之指針,值類型和引用類型

      本篇筆記介紹 Golang 里的指針,值類型與引用類型相關(guān)的概念,以下是本篇筆記目錄:

      1. 指針
      2. 值類型與引用類型
      3. 內(nèi)存逃逸
      4. 減少內(nèi)存逃逸的幾種方案

      1、指針

      在計(jì)算機(jī)內(nèi)存中,每個變量都存儲在特定的內(nèi)存地址上,而指針是一種特殊的變量,它存儲的是一個變量的內(nèi)存地址。

      我們可以通過指針訪問變量的內(nèi)存地址,也可以通過指針訪問或修改這個變量的內(nèi)存地址存儲的值。

      1. 指針的聲明與初始化

      使用 & 符號來獲取變量的內(nèi)存地址,使用 * 獲取指針指向的內(nèi)存地址的值:

      var a int = 10
      var a_ptr *int = &a
      fmt.Println("a 的內(nèi)存地址是: ", &a)
      fmt.Println("a_ptr 的值是: ", a_ptr)
      fmt.Println("根據(jù)指針獲取的值是: ", *a_ptr)
      

      2. 指針操作

      使用 * 獲取變量指向的內(nèi)存地址的值后,可以直接使用,也可以對其進(jìn)行修改,在上面操作后,我們接著操作:

      *a_ptr = 20
      fmt.Println("修改后 a 的值是: ", a)
      

      可以看到,通過指針修改后,a 的值已經(jīng)變成了 20。

      3. 指針作為函數(shù)傳參

      如果我們將指針作為函數(shù)的參數(shù)傳入,并且在函數(shù)內(nèi)部對其進(jìn)行了修改,那么會直接修改指針?biāo)赶虻淖兞康闹担旅媸且粋€示例:

      func ModityValue(ptr *int) {
          *ptr = 20
      }
      
      func main() {
          var a int = 10
          fmt.Println("修改前, a 的值是:", a)  // 修改前, a 的值是: 10
          ModityValue(&a)
          fmt.Println("修改后, a 的值是:", a)  // 修改后, a 的值是: 20
      }
      

      2、值類型與引用類型

      1. 值類型與引用類型包括的數(shù)據(jù)類型

      值類型包括整型、浮點(diǎn)型、布爾型、字符串、數(shù)組、結(jié)構(gòu)體等,值類型的變量直接存儲值,內(nèi)存通常分配在棧上。

      引用類型包括切片、映射、通道等,引用類型的變量存儲的是一個引用(內(nèi)存地址),內(nèi)存通常分配在堆上。

      2. 棧和堆

      值類型的變量通常分配在棧上,引用類型的變量通常分配在堆上,注意,這里是通常,還會有特殊情況后面再介紹。

      先來介紹一下棧和堆。

      1) 棧

      先介紹一下棧相關(guān)的信息:

      1. 棧內(nèi)存由編譯器自動管理,在函數(shù)調(diào)用時分配,函數(shù)返回后立即釋放,效率極高
      2. 棧上變量的生命周期嚴(yán)格限定在函數(shù)執(zhí)行期間。函數(shù)調(diào)用開始,變量被創(chuàng)建并分配內(nèi)存;函數(shù)調(diào)用結(jié)束,變量占用的內(nèi)存會被立即回收
      2) 堆
      1. 堆用于存儲程序運(yùn)行期間動態(tài)分配的內(nèi)存,其分配和釋放不是由函數(shù)調(diào)用的生命周期決定,而是由程序員或垃圾回收機(jī)制來管理。
      2. 堆上的變量生命周期不依賴于函數(shù)調(diào)用的結(jié)束,變量可以在函數(shù)調(diào)用結(jié)束后仍然存在,直到?jīng)]有任何引用指向它,然后由垃圾回收機(jī)制進(jìn)行回收。

      3. 值類型與引用類型的內(nèi)存分配

      值類型變量通常具有明確的生命周期,通常與其所在的函數(shù)調(diào)用相關(guān),函數(shù)調(diào)用結(jié)束后,這些變量占用的內(nèi)存可以立即被回收,使用棧來存儲值類型可以充分利用棧的高效內(nèi)存管理機(jī)制。

      而引用類型的變量需要動態(tài)分配內(nèi)存,并且其生命周期可能超出函數(shù)調(diào)用的范圍,比如切片可以動態(tài)調(diào)整大小,映射也可以增減鍵值對,這些操作需要在運(yùn)行時進(jìn)行內(nèi)存的分配和釋放,使用堆來存儲引用類型可以更好地支持這些動態(tài)特性。

      前面介紹值類型通常會被分配到棧上,但是也有可能被分配到堆上,這種情況就是內(nèi)存逃逸。

      內(nèi)存逃逸的內(nèi)容在下一個小節(jié)中再介紹。

      4. 值類型和引用類型的復(fù)制

      值類型的復(fù)制會復(fù)制整個數(shù)據(jù),是深拷貝的操作,副本的修改不會影響到原始數(shù)據(jù),比如下面的操作:

      type Person struct {
          Name string
          Age  int
      }
      
      func main() {
          p := Person{Name: "Hunter", Age: 18}
          p2 := p
          p2.Name = "Tom"
          fmt.Printf("p1 name is:%s, p2 name is:%s \n", p.Name, p2.Name)
          // p1 name is:Hunter, p2 name is:Tom 
      }
      

      而引用類型的復(fù)制則復(fù)制的是其引用,屬于淺拷貝的操作,多個變量會共享底層數(shù)據(jù),修改其中一個副本會影響原始數(shù)據(jù),比如下面的操作:

          s := []int{1, 2, 3}
          s2 := s
          s2[1] = 8
      
          fmt.Println("s:", s)  // s: [1 8 3]
          fmt.Println("s2:", s2)  // s2: [1 8 3]
      

      5. 值類型和引用類型的函數(shù)傳參

      值類型和引用類型的函數(shù)傳參和復(fù)制一樣,值類型傳遞的是變量的副本,在函數(shù)內(nèi)部修改不會影響原始變量,而引用類型傳遞的是原始數(shù)據(jù)的引用,函數(shù)內(nèi)部修改會影響外部變量。

      下面是值類型的函數(shù)傳參的示例:

      func ChangePerson(p Person) {
          p.Name = "Tom"
          fmt.Println("inner func p.Name is:", p.Name)
          // inner func p.Name is: Tom
      }
      
      func main() {
          p := Person{Name: "Hunter", Age: 18}
          ChangePerson(p)
          fmt.Println("outer func p.Name is:", p.Name)
          // outer func p.Name is: Hunter
      }
      

      以下是引用類型傳參的示例:

      func ChangeSlice(s []int) {
          s[2] = 9
          fmt.Println("inner func slice is:", s)
          // inner func slice is: [1 2 9]
      }
      func main() {
          s := []int{1, 2, 3}
          ChangeSlice(s)
          fmt.Println("outer func slice is:", s)
          // outer func slice is: [1 2 9]
      }
      

      對于函數(shù)傳參,還有兩點(diǎn)需要注意,一個是值類型函數(shù)傳參的性能問題,一個是引用類型涉及擴(kuò)容的問題。

      1) 值類型函數(shù)傳參的性能問題

      對于值類型變量,比如一個結(jié)構(gòu)體,擁有非常多的字段,當(dāng)其作為函數(shù)傳參,傳遞的會是變量的副本,也就是會將其值復(fù)制出來傳遞,那么當(dāng)這個變量非常大的時候可能就會涉及性能問題。

      為了解決這個問題,有個方法就是傳遞其變量的指針,但是需要注意傳遞指針在函數(shù)內(nèi)部對其修改后,會影響到原始變量的值。

      2) 引用類型函數(shù)傳參擴(kuò)容問題

      當(dāng)引用類型作為函數(shù)傳參,如果在函數(shù)內(nèi)部修改涉及到擴(kuò)容,那么其地址就會更改,那么函數(shù)內(nèi)部的修改就不會反映到其原值上了,比如下面這個是切片在函數(shù)內(nèi)部修改的示例:

      func ChangeSlice(s []int) {
          s = append(s, []int{4, 5, 6}...)
          fmt.Println("inner func slice is:", s)
          // inner func slice is: [1 2 3 4 5 6]
      }
      
      func main() {
          s := []int{1, 2, 3}
          ChangeSlice(s)
          fmt.Println("outer func slice is:", s)
          // outer func slice is: [1 2 3]
      }
      

      3、內(nèi)存逃逸

      Golang 里編譯器決定內(nèi)存分配位置是在棧上還是在堆上,這個就是逃逸分析,這個過程發(fā)生在編譯階段。

      1. 逃逸分析的方法

      我們可以使用下面的命令來查看逃逸分析的結(jié)果:

       go build -gcflags="-m" main.go
      

      2. 內(nèi)存逃逸的場景

      內(nèi)存逃逸可能會存在于以下這些情況,比如函數(shù)返回一個值類型變量的指針,或者閉包引用局部變量等。

      1) 函數(shù)返回局部變量的指針

      如果一個函數(shù)返回值是變量的指針,那么該局部變量會逃逸到堆上:

      func CreateInt() *int {
          x := 1
          return &x
      }
      func main() {
          _ = CreateInt()
      }
      

      使用逃逸分析的命令:

       go build -gcflags="-m" main.go
      

      可以看到輸出如下:

      # command-line-arguments
      ./main.go:14:2: moved to heap: x
      

      說明 x 這個變量會逃逸到堆上。

      2) 閉包引用局部變量

      如果閉包引用了函數(shù)的局部變量,這些局部變量會逃逸到堆上,因?yàn)殚]包可能在函數(shù)調(diào)用結(jié)束后繼續(xù)存在并訪問這些變量:

      func counter() func() int {
          count := 0
          return func() int {
              count++
              return count
          }
      }
      func main() {
          _ = counter()
      }
      

      對此使用逃逸分析的命令,輸出結(jié)果如下:

      # command-line-arguments
      ./main.go:14:2: moved to heap: count
      ./main.go:15:9: func literal escapes to heap
      
      3) 向接口類型變量賦值

      當(dāng)我們將值賦給接口類型的變量,因?yàn)榻涌陬愋托枰龠\(yùn)行時才能確定具體的類型,所以這個值也會逃逸到堆上,最常見的一個例子就是 fmt.Println():

      func main() {
          s := "hello world"
          fmt.Println(s)
      }
      

      其逃逸分析結(jié)果如下:

      # command-line-arguments
      ./main.go:25:13: ... argument does not escape
      ./main.go:25:14: s escapes to heap
      

      除此之外,還有一些原因也可能造成內(nèi)存逃逸,比如大對象超出了棧容量限制,被強(qiáng)制分配到堆、發(fā)送變量到 channel 等。

      3. 逃逸分析的意義

      內(nèi)存逃逸就是原本分配在棧上的變量被分配到了堆上,而分配到堆上的變量在函數(shù)調(diào)用結(jié)束后仍然存在,直到?jīng)]有任何引用指向它,然后由垃圾回收機(jī)制進(jìn)行回收。

      所以通過逃逸分析,我們可以減輕GC(垃圾回收)的壓力。

      4、減少內(nèi)存逃逸的幾種方案

      1. 減少堆分配,避免函數(shù)不必要的指針返回,優(yōu)先通過返回值傳遞小對象
      2. 避免閉包引用局部變量
      3. 減少使用向接口類型賦值,如 fmt.Println() 這種
      4. 避免大對象超出棧容量限制
      posted @ 2025-06-30 20:50  XHunter  閱讀(383)  評論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 内射中出无码护士在线| 四虎女优在线视频免费看| 久久国产乱子伦免费精品无码 | av大片在线无码免费| 欧美xxxxhd高清| 国偷自产av一区二区三区| 国产精品一区二区三区专区| 99久久久国产精品免费无卡顿 | 亚洲精品成人一二三专区| 另类专区一区二区三区| 国产国产久热这里只有精品| 一本色道久久加勒比综合| 久久精品亚洲精品国产区| 双乳奶水饱满少妇呻吟免费看| 中文午夜乱理片无码| 亚洲精品中文字幕二区| 50路熟女| 国精品91人妻无码一区二区三区| 久久99精品国产麻豆婷婷| 国产精品三级国产精品高| 国产精品免费中文字幕| 国产一区二区不卡自拍| 国内免费视频成人精品| 在线欧美中文字幕农村电影| 成人嫩草研究院久久久精品| 国产精品国产三级在线专区| 美女禁区a级全片免费观看| 国产成人一区二区三区视频免费| 大帝AV在线一区二区三区| 久久妇女高潮喷水多| 国产亚洲情侣一区二区无| 亚洲欧美自偷自拍视频图片| 国产毛片精品av一区二区| 国产亚洲精品第一综合另类无码无遮挡又大又爽又黄的视频 | 激情啪啪啪一区二区三区| 日本一级午夜福利免费区| 国产成人a∨激情视频厨房| 国产精品一区二区久久岳| 人妻系列中文字幕精品 | 精品国产高清中文字幕| 东京热一区二区三区在线|