Go學習筆記四(指針與內存分配)

Go指針

Go 有指針,但是沒有指針運算。你不能用指針變量遍歷字符串的各個字節。
在 Go 中調用函數的時候,得記得變量是 值傳遞 的

通過類型作爲前綴來定義一個指針’ * ’:var p * int。現在 p 是一個指向整數值的指針。所有新定義的變量都被賦值爲其類型的零值,而指針也一樣。一個新定義的或者沒有任何指向的指針,有值 nil。在其他語言中,這經常被叫做空(NULL)指針,在 Go 中就是 nil 。讓指針指向某些內容,可以使用取址操作符 ( & ),像這樣:

package main 

import "fmt"

func main() {
    var p *int 
    fmt.Printf("%v\n",p) //← 打印 nil

    var i int //← 定義一個整形變量 i
    p = &i    //← 使得 p 指向 i, 獲取 i 的地址
    fmt.Printf("%v\n",p) //打印內存地址

    *p = 8
    fmt.Printf("%v\n",*p) //打印8
    fmt.Printf("%v\n",i) //打印8

}

前面已經說了,沒有指針運算,所以如果這樣寫: *p++ ,它表示 (*p)++ :首先獲取指針指向的值,然後對這個值加一。這裏注意與C語言的區別

內存分配

Go 同樣也垃圾收集,也就是說無須擔心內存分配和回收。
Go 有兩個內存分配原語, new 和 make 。它們應用於不同的類型,做不同的工作,可能有些迷惑人,但是規則很簡單。

用 new 分配內存

內建函數 new 本質上說跟其他語言中的同名函數功能一樣: new(T) 分配了零值填充的 T 類型的內存空間,並且返回其地址,一個 *T 類型的值。用 Go 的術語說,它返回了一個指針,指向新分配的類型 T 的零值。記住這點非常重要。
這意味着使用者可以用 new 創建一個數據結構的實例並且可以直接工作。 如
bytes.Buffer 的文檔所述 “Buffer 的零值是一個準備好了的空緩衝。” 類似的,sync.Mutex 也沒有明確的構造函數或 Init 方法。取而代之, sync.Mutex 的零值被定義爲非鎖定的互斥量。

type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
//SyncedBuffer 的值在分配內存或定義之後立刻就可以使用。在這個片段中, p 和 v 都可以在沒有任何更進一步處理的情況下工作。
p := new (SyncedBuffer)// ← Type *SyncedBuffer ,已經可以使用
var v SyncedBuffer //← Type SyncedBuffer ,同上

用 make 分配內存

內建函數 make(T, args) 與 new(T) 有着不同的功能。它 只能 創建
slice,map 和 channel,並且返回一個有初始值(非零)的 T 類型,而不是 *T 。本質來講,導致這三個類型有所不同的原因是指向數據結構的引用在使用前必須被初始化。例如,一個 slice,是一個包含指向數據(內部 array)的指針,長度和容量的三項描述符;在這些項目被初始化之前,slice 爲 nil 。對於 slice,map 和 channel, make 初始化了內部的數據結構,填充適當的值。
例如,make ([] int , 10, 100) 分配了 100 個整數的數組,然後用長度 10 和容量 100
創建了 slice 結構指向數組的前 10 個元素。區別是,new ([] int ) 返回指向新分配的內存的指針,而零值填充的 slice 結構是指向 nil 的 slice 值

務必記得 make 僅適用於 map,slice 和 channel,並且返回的不是指針。應當用 new 獲得特定的指針。

package main 

import "fmt"

func main() {
    var p *[] int = new ([] int ) //← 分配 slice 結構內存;很少使用
    var v [] int = make ([] int , 100) //← v 指向一個新分配的有 100 個整數的數組
    //==========================
    var p *[] int = new ([] int ) //← 不必要的複雜例子
    *p = make ([] int , 100, 100)
    //==========================
    v := make ([] int , 100) //← 更常見    

}

定義自己的類型

自然,Go 允許定義新的類型,通過關鍵字 type 實現:

type foo int

創建了一個新的類型 foo 作用跟 int 一樣。創建更加複雜的類型需要用到 struct 關鍵字。這有個在一個數據結構中記錄某人的姓名( string )和年齡( int ),並且使其成爲一個新的類型的例子:

package main

import "fmt"

type NameAge struct{
    name string //私有(字段首字母小寫爲私有,大寫爲公有屬性)
    age int //私有
}

func main() {
    a := new(NameAge)
    a.name = "Peter"
    a.age = 55
    fmt.Printf("%v\n",a)
    fmt.Printf("%v\n",a.age)
    fmt.Printf("%v\n",a.name)
}

可以對新定義的類型創建函數以便操作,可以通過兩種途徑:
1. 創建一個函數接受這個類型的參數。

func doSomething(n1 *NameAge, n2 int ) { /* */ }

2 . 創建一個工作在這個類型上的函數

func (n1 *NameAge) doSomething(n2 int ) { /* */ }
//這是 方法調用 ,可以類似這樣使用:
var n *NameAge
n.doSomething(2)

類型轉換

有時需要將一個類型轉換爲另一個類型
並不是所有的轉換都是允許的。

字符串轉換

package main

import "fmt"

func main() {
    //從 string 到字節或者 rune 的 slice。
    mystring := "hello this is string"
    //轉換到 byte slice,每個 byte 保存字符串對應字節的整數值。注意 Go 的字符串是 UTF-8 編碼的,一些字符可能是 1、2、3 或者 4 個字節結尾
    byteslice := [] byte (mystring)
    // 轉換到 rune slice,每個 rune 保存 Unicode 編碼的指針。字符串中的每個字符對應一個整數
    runeslice := [] rune (mystring)
    // 從字節或者整形的 slice 到 string。
    b := [] byte{ 'h','e','l','l','o' } // 複合聲明
    s := string (b)
    i := [] rune{ 115,116,114,105,110,113 }
    r := string (i)

    fmt.Printf("mystring = %v\n",mystring)
    fmt.Printf("byteslice = %v\n",byteslice)
    fmt.Printf("runeslice = %v\n",runeslice)
    fmt.Printf("b = %v\n",b)
    fmt.Printf("s = %v\n",s)
    fmt.Printf("i = %v\n",i)
    fmt.Printf("r = %v\n",r)
}

數值轉換

對於數值,定義了下面的轉換:
• 將整數轉換到指定的(bit)長度:uint8 ( int ) ;
• 從浮點數到整數:int ( float32 ) 。這會截斷浮點數的小數部分;
• 其他的類似:float32 ( int ) 。

用戶定義類型的轉換

package main

import "fmt"

func main() {
    type foo struct { int }// ← 匿名字段
    type bar foo //← bar 是 foo 的別名
    var b bar = bar { 1 } //← 聲明 b 爲 bar 類型
    //var f foo = b //← 賦值 b 到 f cannot use b (type bar) as type foo in assignment
    // 這可以通過類型轉換來修復:
    var f foo = foo(b) //類型轉換
    fmt.Printf("%v\n",f)
}

練習

// Q17. (1) 指針運算
// 1. 在正文的第 54 頁有這樣的文字:
// … 這裏沒有指針運算,因此如果這樣寫: *p++ ,它被解釋爲 (*p)++ :
// 首先解析引用然後增加值。
// 當像這樣增加一個值的時候,什麼類型可以工作?
// 2. 爲什麼它不能工作在所有類型上?


1. 這僅能工作於指向數字( int, uint 等等)的指針值。

2. ++ 僅僅定義在數字類型上,同時由於在 Go 中沒有運算符重載,所以會在其他
類型上失敗(編譯錯誤)。
// Q18. (2) 使用 interface 的 map 函數
// 1. 使用練習 Q11 的答案,利用 interface 使其更加通用。讓它至少能同時工作於
// int 和 string。
// Q11. (1) map 函數 map() 函數是一個接受一個函數和一個列表作爲參數的函數。函
// 數應用於列表中的每個元素,而一個新的包含有計算結果的列表被返回。因此:
// map(f(),(a 1 ,a 2 ,...,a n−1 ,a n )) = (f(a 1 ),f(a 2 ),...,f(a n−1 ),f(a n ))
// 1. 編寫 Go 中的簡單的 map() 函數。它能工作於操作整數的函數就可以了。
// 2. 擴展代碼使其工作於字符串列表。
package main
import "fmt"
//* define the empty interface as a type
type e interface {}
func mult2(f e) e {
    switch f.( type ) {
        case int :
            return f.( int ) * 2
        case string :
            return f.( string ) + f.( string ) + f.( string )+ f.( string )

    }
    return f
}
func Map(n []e, f func (e) e) []e {
    m := make ([]e, len (n))
    for k, v := range n {
        m[k] = f(v)
    }
    return m
}
func main() {
    m := []e { 1, 2, 3, 4 }
    s := []e { "a", "b", "c", "d" }
    mf := Map(m, mult2)
    sf := Map(s, mult2)
    fmt.Printf("%v\n", mf)
    fmt.Printf("%v\n", sf)
}

// Q19. (1) 指針
// 1. 假設定義了下面的結構:
type Person struct {
    name string
    age int
}
// 下面兩行之間的區別是什麼?
var p1 Person
p2 := new (Person)
// 1. 
// 第一行:var p1 Person 分配了 Person 值 給 p1 。 p1 的類型是 Person 。
// 第二行: p2 := new (Person) 分配了內存並且將 指針 賦值給 p2 。 p2 的類型是
// *Person 。
//------------分割線--------------------
// 2. 下面兩個內存分配的區別是什麼?
func Set(t *T) {
    x = t
}
// 和
func Set(t T) {
    x= &t
}
// 2. 
// 在第二個函數中, x 指向一個新的(堆上分配的)變量 t ,其包含了實際參數值
// 的副本。
// 在第一個函數中, x 指向了 t 指向的內容,也就是實際上的參數指向的內容。
// 因此在第二個函數,我們有了 “額外” 的變量存儲了相關值的副本。
// Q20. (1) Linked List
// 1. Make use of the package container/list to create a (doubly) linked list. Push the
// values 1, 2 and 4 to the list and then print it.
// 2. Create your own linked list implementation. And perform the same actions as in
// question

//打印雙向鏈表
package main
import (
    "fmt"
    "container/list"
)
func main() {
    l := list.New()
    l.PushBack(1)
    l.PushBack(2)
    l.PushBack(4)
    for e := l.Front() ; e != nil ; e = e.Next() {
        fmt.Printf("%v\n", e.Value)
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章