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

##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 = %d\n", a, *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語言的內存管理系統會幫咱們打理一切。bash

###7.2.3 指針作函數參數數據結構

func swap01(a, b int) {
    a, b = b, a
    fmt.Printf("swap01 a = %d, b = %d\n", a, 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 = %d\n", a, b)
}
複製代碼

##7.3 數組 ###7.3.1 概述 數組是指一系列同一類型數據的集合。數組中包含的每一個數據被稱爲數組元素(element),一個數組包含的元素個數被稱爲數組的長度。app

數組⻓度必須是常量,且是類型的組成部分。 [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[", i, "]=", v)     }性能

內置函數 len(長度) 和 cap(容量) 都返回數組⻓度 (元素數量):ui

    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, 11}, {20, 21}, {30, 31}, {40, 41}} //第二維不能寫"..."
    f := [4][2]int{1: {20, 21}, 3: {40, 41}}
    g := [4][2]int{1: {0: 20}, 3: {1: 41}}
    fmt.Println(d, e, f, g)
複製代碼

相同類型的數組之間可使用 == 或 != 進行比較,但不可使用 < 或 >,也能夠相互賦值:

    a := [3]int{1, 2, 3}
    b := [3]int{1, 2, 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, 2, 3, 4, 5} // 定義並初始化一個數組
    modify(array)                  // 傳遞給一個函數,並試圖在函數體內修改這個數組內容
    //In main(), array values: [1 2 3 4 5]
    fmt.Println("In main(), array values:", array)
}
複製代碼

數組指針作函數參數:

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, 2, 3, 4, 5} // 定義並初始化一個數組
    modify(&array)                 // 數組指針
    //In main(), array values: [10 2 3 4 5]
    fmt.Println("In main(), array values:", 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, 0)

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

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

###7.4.3 切片的操做 ####7.4.3.1 切片截取

圖片.png
圖片.png

####7.4.3.2 切片和底層數組關係     s := []int{0, 1, 2, 3, 4, 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, 2, 3)    //追加2個元素
    s1 = append(s1, 4, 5, 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, 2, 3}
    s3 = append(s3, 4, 5)
    fmt.Println(s3)//[1 2 3 4 5]
複製代碼

append函數會智能地底層數組的容量增加,一旦超過原底層數組容量,一般以2倍容量從新分配底層數組,並複製原來的數據:

func main() {
    s := make([]int, 0, 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, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    s1 := data[8:]  //{8, 9}
    s2 := data[:5] //{0, 1, 2, 3, 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, ", i, 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, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    test(slice)

    fmt.Println("main : ")
    for i, v := range slice {
        fmt.Printf("slice[%d]=%d, ", i, 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 初始化

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

    //二、自動推導類型 :=
    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", k, 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 := range m1 {
        fmt.Printf("%d ----> %s\n", k, v)
        //1 ----> mike
        //2 ----> yoyo
        //3 ----> lily
    }
    delete(m1, 2) //刪除key值爲3的map

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

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

    DeleteMap(m, 2) //刪除key值爲3的map

    for k, v := range m {
        fmt.Printf("len(m)=%d, %d ----> %s\n", len(m), k, v)
        //len(m)=2, 1 ----> mike
        //len(m)=2, 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() {
    //一、順序初始化,必須每一個成員都初始化
    var s1 Student = Student{1, "mike", 'm', 18, "sz"}
    s2 := Student{2, "yoyo", 'f', 20, "sz"}
    //s3 := Student{2, "tom", 'm', 20} //err, too few values in struct initializer

    //二、指定初始化某個成員,沒有初始化的成員爲零值
    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", 'm', 16, "bj"}
    s6 := &Student{4, "rocco", 'm', 3, "sh"}
}
複製代碼

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

    //===============結構體變量爲普通變量
    //一、打印成員
    var s1 Student = Student{1, "mike", 'm', 18, "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)

    //二、成員變量賦值
    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 指針變量

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

    //四、普通變量和指針變量類型打印
    var s4 Student = Student{4, "yyy", 'm', 18, "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, "mike", 'm', 18, "sz"}
    s2 := Student{1, "mike", 'm', 18, "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, "mike", 'm', 18, "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, "mike", 'm', 18, "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)
}

複製代碼
相關文章
相關標籤/搜索