1. 程式人生 > 程式設計 >go語言20小時從入門到精通(七、複合型別)

go語言20小時從入門到精通(七、複合型別)

##7.1 分類

圖片.png
##7.2 指標 指標是一個代表著某個記憶體地址的值。這個記憶體地址往往是在記憶體中儲存的另一個變數的值的起始位置。Go語言對指標的支援介於Java語言和C/C++語言之間,它既沒有想Java語言那樣取消了程式碼對指標的直接操作的能力,也避免了C/C++語言中由於對指標的濫用而造成的安全和可靠性問題。

###7.2.1 基本操作 Go語言雖然保留了指標,但與其它程式語言不同的是: 預設值 nil,沒有 NULL 常量 操作符 "&" 取變數地址, "*" 通過指標訪問目標物件 不支援指標運算,不支援 "->" 運運算元,直接⽤ "." 訪問目標成員

func main
() {     var a int = 10 //宣告一個變數,同時初始化     fmt.Printf("&a = %p\n",&a) //操作符 "&" 取變數地址     var p *int = nil //宣告一個變數p,型別為 *int,指標型別     p = &a     fmt.Printf("p = %p\n",p)     fmt.Printf("a = %d,*p = %d\n",a,*p)     *p = 111 //*p操作指標所指向的記憶體,即為a     fmt.Printf("a = %d,*p) } 複製程式碼

###7.2.2 new函式 表示式new(T)將建立一個T型別的匿名變數,所做的是為T型別的新值分配並清零一塊記憶體空間,然後將這塊記憶體空間的地址作為結果返回,而這個結果就是指向這個新的T型別值的指標值,返回的指標型別為*T。

func main() {
    var p1 *int
    p1 = new(int)              //p1為*int 型別,指向匿名的int變數
    fmt.Println("*p1 = ",*p1) //*p1 =  0

    p2 := new(int) //p2為*int 型別,指向匿名的int變數
    *p2 = 111
    fmt.Println("*p2 = "
,*p2) //*p1 = 111 } 複製程式碼

我們只需使用new()函式,無需擔心其記憶體的生命週期或怎樣將其刪除,因為Go語言的記憶體管理系統會幫我們打理一切。

###7.2.3 指標做函式引數

func swap01(a,b int) {
    a,b = b,a
    fmt.Printf("swap01 a = %d,b = %d\n",b)
}

func swap02(x,y *int) {
    *x,*y = *y,*x
}

func main() {
    a := 10
    b := 20

    //swap01(a,b) //值傳遞
    swap02(&a,&b) //變數地址傳遞
    fmt.Printf("a = %d,b)
}
複製程式碼

##7.3 陣列 ###7.3.1 概述 陣列是指一系列同一型別資料的集合。陣列中包含的每個資料被稱為陣列元素(element),一個陣列包含的元素個數被稱為陣列的長度。

陣列⻓度必須是常量,且是型別的組成部分。 [2]int 和 [3]int 是不同型別。     var n int = 10     var a [n]int //err,non-constant array bound n     var b [10]int //ok

###7.3.2 運算元組 陣列的每個元素可以通過索引下標來訪問,索引下標的範圍是從0開始到陣列長度減1的位置。

    var a [10]int
    for i := 0; i < 10; i++ {
        a[i] = i + 1
        fmt.Printf("a[%d] = %d\n",i,a[i])
    }
複製程式碼

//range具有兩個返回值,第一個返回值是元素的陣列下標,第二個返回值是元素的值     for i,v := range a {         fmt.Println("a[","]=",v)     }

內建函式 len(長度) 和 cap(容量) 都返回陣列⻓度 (元素數量):

    a := [10]int{}
    fmt.Println(len(a),cap(a))//10 10
複製程式碼

初始化:

    a := [3]int{1,2}           // 未初始化元素值為 0
    b := [...]int{1,2,3}      // 通過初始化值確定陣列長度
    c := [5]int{2: 100,4: 200} // 通過索引號初始化元素,未初始化元素值為 0
    fmt.Println(a,b,c)        //[1 2 0] [1 2 3] [0 0 100 0 200]

    //支援多維陣列
    d := [4][2]int{{10,11},{20,21},{30,31},{40,41}}
    e := [...][2]int{{10,41}} //第二維不能寫"..."
    f := [4][2]int{1: {20,3: {40,41}}
    g := [4][2]int{1: {0: 20},3: {1: 41}}
    fmt.Println(d,e,f,g)
複製程式碼

相同型別的陣列之間可以使用 == 或 != 進行比較,但不可以使用 < 或 >,也可以相互賦值:

    a := [3]int{1,3}
    b := [3]int{1,3}
    c := [3]int{1,2}
    fmt.Println(a == b,b == c) //true false

    var d [3]int
    d = a
    fmt.Println(d) //[1 2 3]
複製程式碼

###7.3.3 在函式間傳遞陣列 根據記憶體和效能來看,在函式間傳遞陣列是一個開銷很大的操作。在函式之間傳遞變數時,總是以值的方式傳遞的。如果這個變數是一個陣列,意味著整個陣列,不管有多長,都會完整複製,並傳遞給函式。

func modify(array [5]int) {
    array[0] = 10 // 試圖修改陣列的第一個元素
    //In modify(),array values: [10 2 3 4 5]
    fmt.Println("In modify(),array values:",array)
}

func main() {
    array := [5]int{1,3,4,5} // 定義並初始化一個陣列
    modify(array)                  // 傳遞給一個函式,並試圖在函式體內修改這個陣列內容
    //In main(),array values: [1 2 3 4 5]
    fmt.Println("In main(),array)
}
複製程式碼

陣列指標做函式引數:

func modify(array *[5]int) {
    (*array)[0] = 10
    //In modify(),*array)
}

func main() {
    array := [5]int{1,5} // 定義並初始化一個陣列
    modify(&array)                 // 陣列指標
    //In main(),array values: [10 2 3 4 5]
    fmt.Println("In main(),array)
}
複製程式碼

##7.4 slice ###7.4.1 概述 陣列的長度在定義之後無法再次修改;陣列是值型別,每次傳遞都將產生一份副本。顯然這種資料結構無法完全滿足開發者的真實需求。Go語言提供了陣列切片(slice)來彌補陣列的不足。

切片並不是陣列或陣列指標,它通過內部指標和相關屬性引⽤陣列⽚段,以實現變⻓⽅案。

slice並不是真正意義上的動態陣列,而是一個引用型別。slice總是指向一個底層array,slice的宣告也可以像array一樣,只是不需要長度。

圖片.png

###7.4.2 切片的建立和初始化 slice和陣列的區別:宣告陣列時,方括號內寫明瞭陣列的長度或使用...自動計算長度,而宣告slice時,方括號內沒有任何字元。

var s1 []int //宣告切片和宣告array一樣,只是少了長度,此為空(nil)切片

    s2 := []int{}

    //make([]T,length,capacity) //capacity省略,則和length的值相同
    var s3 []int = make([]int,0)
    s4 := make([]int,0)

    s5 := []int{1,3} //建立切片並初始化
複製程式碼

注意:make只能建立slice、map和channel,並且返回一個有初始值(非零)。

###7.4.3 切片的操作 ####7.4.3.1 切片擷取

圖片.png
圖片.png

####7.4.3.2 切片和底層陣列關係     s := []int{0,1,5,6,7,8,9}

s1 := s[2:5] //[2 3 4]     s1[2] = 100 //修改切片某個元素改變底層陣列     fmt.Println(s1,s) //[2 3 100] [0 1 2 3 100 5 6 7 8 9]

s2 := s1[2:6] // 新切片依舊指向原底層陣列 [100 5 6 7]     s2[3] = 200     fmt.Println(s2) //[100 5 6 200]

fmt.Println(s) //[0 1 2 3 100 5 6 200 8 9]

####7.4.3.3 內建函式

  1. append append函式向 slice 尾部新增資料,返回新的 slice 物件:
    var s1 []int //建立nil切換
    //s1 := make([]int,0)
    s1 = append(s1,1)       //追加1個元素
    s1 = append(s1,3)    //追加2個元素
    s1 = append(s1,6) //追加3個元素
    fmt.Println(s1)          //[1 2 3 4 5 6]

    s2 := make([]int,5)
    s2 = append(s2,6)
    fmt.Println(s2) //[0 0 0 0 0 6]

    s3 := []int{1,3}
    s3 = append(s3,5)
    fmt.Println(s3)//[1 2 3 4 5]
複製程式碼

append函式會智慧地底層陣列的容量增長,一旦超過原底層陣列容量,通常以2倍容量重新分配底層陣列,並複製原來的資料:

func main() {
    s := make([]int,1)
    c := cap(s)
    for i := 0; i < 50; i++ {
        s = append(s,i)
        if n := cap(s); n > c {
            fmt.Printf("cap: %d -> %d\n",c,n)
            c = n
        }
    }
    /*
        cap: 1 -> 2
        cap: 2 -> 4
        cap: 4 -> 8
        cap: 8 -> 16
        cap: 16 -> 32
        cap: 32 -> 64
    */
}
複製程式碼
  1. copy 函式 copy 在兩個 slice 間複製資料,複製⻓度以 len 小的為準,兩個 slice 可指向同⼀底層陣列。
    data := [...]int{0,9}
    s1 := data[8:]  //{8,9}
    s2 := data[:5] //{0,4}
    copy(s2,s1)    // dst:s2,src:s1

    fmt.Println(s2)   //[8 9 2 3 4]
    fmt.Println(data) //[8 9 2 3 4 5 6 7 8 9]
複製程式碼

###7.4.4 切片做函式引數

func test(s []int) { //切片做函式引數
    s[0] = -1
    fmt.Println("test : ")
    for i,v := range s {
        fmt.Printf("s[%d]=%d,",v)
        //s[0]=-1,s[1]=1,s[2]=2,s[3]=3,s[4]=4,s[5]=5,s[6]=6,s[7]=7,s[8]=8,s[9]=9,    }
    fmt.Println("\n")
}

func main() {
    slice := []int{0,9}
    test(slice)

    fmt.Println("main : ")
    for i,v := range slice {
        fmt.Printf("slice[%d]=%d,v)
        //slice[0]=-1,slice[1]=1,slice[2]=2,slice[3]=3,slice[4]=4,slice[5]=5,slice[6]=6,slice[7]=7,slice[8]=8,slice[9]=9,    }
    fmt.Println("\n")
}
複製程式碼

##7.5 map ###7.5.1 概述 Go語言中的map(對映、字典)是一種內建的資料結構,它是一個無序的key—value對的集合,比如以身份證號作為唯一鍵來標識一個人的資訊。

圖片.png

map格式為:     map[keyType]valueType

在一個map裡所有的鍵都是唯一的,而且必須是支援==和!=操作符的型別,切片、函式以及包含切片的結構型別這些型別由於具有引用語義,不能作為對映的鍵,使用這些型別會造成編譯錯誤:     dict := map[ []string ]int{} //err,invalid map key type []string

map值可以是任意型別,沒有限制。map裡所有鍵的資料型別必須是相同的,值也必須如何,但鍵和值的資料型別可以不相同。

注意:map是無序的,我們無法決定它的返回順序,所以,每次列印結果的順利有可能不同。

###7.5.2 建立和初始化 ####7.5.2.1 map的建立

    var m1 map[int]string  //只是宣告一個map,沒有初始化,此為空(nil)map
    fmt.Println(m1 == nil) //true
    //m1[1] = "mike" //err,panic: assignment to entry in nil map

    //m2,m3的建立方法是等價的
    m2 := map[int]string{}
    m3 := make(map[int]string)
    fmt.Println(m2,m3) //map[] map[]

    m4 := make(map[int]string,10) //第2個引數指定容量
    fmt.Println(m4)                //map[]
複製程式碼

####7.5.2.2 初始化

    //1、定義同時初始化
    var m1 map[int]string = map[int]string{1: "mike",2: "yoyo"}
    fmt.Println(m1) //map[1:mike 2:yoyo]

    //2、自動推導型別 :=
    m2 := map[int]string{1: "mike",2: "yoyo"}
    fmt.Println(m2)
複製程式碼

###7.5.3 常用操作 ####7.5.3.1 賦值

    m1 := map[int]string{1: "mike",2: "yoyo"}
    m1[1] = "xxx"   //修改
    m1[3] = "lily"  //追加, go底層會自動為map分配空間
    fmt.Println(m1) //map[1:xxx 2:yoyo 3:lily]

    m2 := make(map[int]string,10) //建立map
    m2[0] = "aaa"
    m2[1] = "bbb"
    fmt.Println(m2)           //map[0:aaa 1:bbb]
    fmt.Println(m2[0],m2[1]) //aaa bbb
複製程式碼

####7.5.3.2 遍歷

    m1 := map[int]string{1: "mike",2: "yoyo"}
    //迭代遍歷1,第一個返回值是key,第二個返回值是value
    for k,v := range m1 {
        fmt.Printf("%d ----> %s\n",k,v)
        //1 ----> mike
        //2 ----> yoyo
    }

    //迭代遍歷2,第一個返回值是key,第二個返回值是value(可省略)
    for k := range m1 {
        fmt.Printf("%d ----> %s\n",m1[k])
        //1 ----> mike
        //2 ----> yoyo
    }

    //判斷某個key所對應的value是否存在,第一個返回值是value(如果存在的話)
    value,ok := m1[1]
    fmt.Println("value = ",value,",ok = ",ok) //value =  mike,ok =  true

    value2,ok2 := m1[3]
    fmt.Println("value2 = ",value2,ok2 = ",ok2) //value2 =,ok2 =  false
複製程式碼

####7.5.3.3 刪除

    m1 := map[int]string{1: "mike",2: "yoyo",3: "lily"}
    //迭代遍歷1,第一個返回值是key,第二個返回值是value
    for k,v)
        //1 ----> mike
        //2 ----> yoyo
        //3 ----> lily
    }
    delete(m1,2) //刪除key值為3的map

    for k,v)
        //1 ----> mike
        //3 ----> lily
    }
複製程式碼

###7.5.4 map做函式引數 在函式間傳遞對映並不會製造出該對映的一個副本,不是值傳遞,而是引用傳遞:

func DeleteMap(m map[int]string,key int) {
    delete(m,key) //刪除key值為3的map

    for k,v := range m {
        fmt.Printf("len(m)=%d,%d ----> %s\n",len(m),v)
        //len(m)=2,1 ----> mike
        //len(m)=2,3 ----> lily
    }
}

func main() {
    m := map[int]string{1: "mike",3: "lily"}

    DeleteMap(m,3 ----> lily
    }
}
複製程式碼

##7.6 結構體 ###7.6.1 結構體型別 有時我們需要將不同型別的資料組合成一個有機的整體,如:一個學生有學號/姓名/性別/年齡/地址等屬性。顯然單獨定義以上變數比較繁瑣,資料不便於管理。

圖片.png

結構體是一種聚合的資料型別,它是由一系列具有相同型別或不同型別的資料構成的資料集合。每個資料稱為結構體的成員。

###7.6.2 結構體初始化 ####7.6.2.1 普通變數

type Student struct {
    id   int
    name string
    sex  byte
    age  int
    addr string
}

func main() {
    //1、順序初始化,必須每個成員都初始化
    var s1 Student = Student{1,"mike",'m',18,"sz"}
    s2 := Student{2,"yoyo",'f',20,"sz"}
    //s3 := Student{2,"tom",20} //err,too few values in struct initializer

    //2、指定初始化某個成員,沒有初始化的成員為零值
    s4 := Student{id: 2,name: "lily"}
}
複製程式碼

####7.6.2.2 指標變數

type Student struct {
    id   int
    name string
    sex  byte
    age  int
    addr string
}

func main() {
    var s5 *Student = &Student{3,"xiaoming",16,"bj"}
    s6 := &Student{4,"rocco","sh"}
}
複製程式碼

###7.6.3 結構體成員的使用 ####7.6.3.1 普通變數

    //===============結構體變數為普通變數
    //1、列印成員
    var s1 Student = Student{1,"sz"}
    //結果:id = 1,name = mike,sex = m,age = 18,addr = sz
    fmt.Printf("id = %d,name = %s,sex = %c,age = %d,addr = %s\n",s1.id,s1.name,s1.sex,s1.age,s1.addr)

    //2、成員變數賦值
    var s2 Student
    s2.id = 2
    s2.name = "yoyo"
    s2.sex = 'f'
    s2.age = 16
    s2.addr = "guangzhou"
    fmt.Println(s2) //{2 yoyo 102 16 guangzhou}
複製程式碼

####7.6.3.2 指標變數

    //===============結構體變數為指標變數
    //3、先分配空間,再賦值
    s3 := new(Student)
    s3.id = 3
    s3.name = "xxx"
    fmt.Println(s3) //&{3 xxx 0 0 }

    //4、普通變數和指標變數型別列印
    var s4 Student = Student{4,"yyy","sz"}
    fmt.Printf("s4 = %v,&s4 = %v\n",s4,&s4) //s4 = {4 yyy 109 18 sz},&s4 = &{4 yyy 109 18 sz}

    var p *Student = &s4
    //p.成員 和(*p).成員 操作是等價的
    p.id = 5
    (*p).name = "zzz"
    fmt.Println(p,*p,s4) //&{5 zzz 109 18 sz} {5 zzz 109 18 sz} {5 zzz 109 18 sz}
複製程式碼

###7.6.4 結構體比較 如果結構體的全部成員都是可以比較的,那麼結構體也是可以比較的,那樣的話兩個結構體將可以使用 == 或 != 運運算元進行比較,但不支援 > 或 < 。

func main() {
    s1 := Student{1,"sz"}
    s2 := Student{1,"sz"}

    fmt.Println("s1 == s2",s1 == s2) //s1 == s2 true
    fmt.Println("s1 != s2",s1 != s2) //s1 != s2 false
}
複製程式碼

###7.6.5 結構體作為函式引數 ####7.6.5.1 值傳遞

func printStudentValue(tmp Student) {
    tmp.id = 250
    //printStudentValue tmp =  {250 mike 109 18 sz}
    fmt.Println("printStudentValue tmp = ",tmp)
}

func main() {
var s Student = Student{1,"sz"}

    printStudentValue(s)        //值傳遞,形參的修改不會影響到實參
    fmt.Println("main s = ",s) //main s =  {1 mike 109 18 sz}
}
複製程式碼

####7.6.5.2 引用傳遞

func printStudentPointer(p *Student) {
    p.id = 250
    //printStudentPointer p =  &{250 mike 109 18 sz}
    fmt.Println("printStudentPointer p = ",p)
}

func main() {
    var s Student = Student{1,"sz"}

    printStudentPointer(&s)     //引用(地址)傳遞,形參的修改會影響到實參
    fmt.Println("main s = ",s) //main s =  {250 mike 109 18 sz}
}
複製程式碼

###7.6.6 可見性 Go語言對關鍵字的增加非常吝嗇,其中沒有private、 protected、 public這樣的關鍵字。

要使某個符號對其他包(package)可見(即可以訪問),需要將該符號定義為以大寫字母開頭。

目錄結構:

test.go示例程式碼如下:

//test.go
package test

//student01只能在本檔案件引用,因為首字母小寫
type student01 struct {
    Id   int
    Name string
}

//Student02可以在任意檔案引用,因為首字母大寫
type Student02 struct {
    Id   int
    name string
}
複製程式碼

main.go示例程式碼如下:

// main.go
package main

import (
    "fmt"
    "test" //匯入test包
)

func main() {
    //s1 := test.student01{1,"mike"} //err,cannot refer to unexported name test.student01

    //err,implicit assignment of unexported field 'name' in test.Student02 literal
    //s2 := test.Student02{2,"yoyo"}
    //fmt.Println(s2)

    var s3 test.Student02 //宣告變數
    s3.Id = 1             //ok
    //s3.name = "mike"  //err,s3.name undefined (cannot refer to unexported field or method name)
    fmt.Println(s3)
}

複製程式碼