Go聖經-學習筆記之複合類型

上一篇 Go聖經-臨時插入ORM的小trickgolang

下一篇 Go聖經-學習筆記之複合類型(二)數組

數組

數組是一個固定長度類型的序列,由零個或者多個同構元素組成。Go語言不多直接使用數組,通常使用Slice多。數組的表現形式:安全

var q [3]int =[3]int{1,2,3}
var r [3]int =[3]int{1,2}
var s =[...]int{1,2,3}

數組類型是由類型和長度組成的。所以[3]int和[5]int是兩個不一樣的數據類型app

若是相同數組的元素類型是能夠比較的,則這個數組是能夠比較的。咱們能夠經過==進行數組比較。注意:這裏不是C++,a==b,不是指針比較,而是數組全部元素的比較函數

咱們如何比較兩個動態數組是否相等呢?有兩種方法:學習

  • 第一種,比較slice的len、cap和底層數組元素
  • 第二種,咱們能夠對slice序列化成byte流,而後利用sha256生成消息摘要, 摘要是32個字節的數組類型。而後直接比較便可。
func compareSlice(a []int, b []int) bool{
    if len(a) != len(b) {
        return false
    }
    for i:=0 ;i<len(a); i++{
        if a[i]!=b[i]{
            return false
        }
    }
    return true
}

這裏有必要提一下,爲啥判斷a和b相等, 不比較cap(a)與cap(b)的大小關係呢?是否是比較時,只比較數據呢?優化

注意:不用比較slice的底層數組指針所指向的內存地址, 數據存放確定不在一塊兒的。ui

package main

import (
	"crypto/sha256"
	"fmt"
)

func main() {
    c1 := sha256.Sum256([]byte("x"))
    c2 := sha256.Sum256([]byte("x"))
    fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1)
    // Output:
    // 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
    // 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
    // true
    // [32]uint8
}

當調用函數時,調用參數將會被賦值給函數內部的參數變量,因此函數參數接收是一個變量的副本,並非原始的變量。Go語言的數據傳遞所有是值類型。因此數組傳遞是低效的,而且對數組的修改都是發生在拷貝的數組上,對傳入的實參沒有任何影響。如果C++等其餘語言,則數組傳遞時,是傳遞的指針。同時附帶數組的長度信息。若是必定要傳數組,請傳數組指針,例如func(aptr *[32]byte)。.net

Slice動態數組

一個很形象的例子指針

months := []string{1: "January", /* ... */, 12: "December"}

月份動態數組

這裏有個我很懵逼的問題:

var months = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

// 夏天
summer:=months[6:9]

fmt.Println(summer[:20]) // slice超出了cap(summer),直接panic。  明白,由於summer容量是12-6=6

fmt.Println(summer[:6]) // [ 7,8,9,10,11,12 ] ,這裏有點懵逼,其實summer[:6]已經超出len(summer)了,我以爲應該是有越界控制的,可是沒有

var months = make([]int, 0, 12)
fmt.Println(months[:10]) // [ 0,0,0,0,0,0,0,0,0,0 ]
months[0] = 1 // panic: index out of range

主要懵逼的問題:

  • 當讀取slice動態數組時,只要讀取範圍不超過cap容量就ok了
  • 當寫入slice動態數組時,只要超出len長度了,就報"index out of range"的panic

個人問題是,爲何不作一致性呢?讀寫都控制在len範圍內呢?

由於上面這個問題,咱們要十分當心在slice動態數組讀取的時候,剋制len的範圍,否則就會出現下面這種狀況

// nonempty returns a slice holding only the non-empty strings.
// The underlying array is modified during the call.
func nonempty(strings []string) []string {
    i := 0
    for _, s := range strings {
        if s != "" {
            strings[i] = s
            i++
        }
    }
    return strings[:i]
}

func main() {
    data := []string{"one", "", "three"}
    data = nonempty(data)

    fmt.Println(data[:3]) // [one three three], 這個是不想看到的
    fmt.Println(data) // [one three]
}

利用slice動態數組,左旋轉slice的前N個元素

package main

import "fmt"

func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

func reverseN(s []int, n int) {
    reverse(s[:n]) // 反轉前N個數
    reverse(s[n:]) // 反轉後len(s)-n-1
    reverse(s)
}

func main() {
    var a = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
    var n = 5 // 指定旋轉前N個數
    reverseN(a, n)
    fmt.Println(a)  // [5 6 7 8 9 10 11 12 0 1 2 3 4]
}

slice的比較問題

和數組不一樣的是,slice動態數組不能直接比較,不過標準庫提供了bytes.Equal方法判斷兩個字節型slice是否相等。對於其餘咱們須要比較slice的元素個數和元素值。同時咱們又引出struct類型的比較。

type Person struct {
    Cap int
    Len int
    Bt  *byte
}

func main() {
    p1 := Person{
        Cap: 28,
        Len: 28,
        Bt:  new(byte),
    }
    p2 := Person{
        Cap: 28,
        Len: 28,
        Bt:  new(byte),
    }
    fmt.Println(p1 == p2)
}

上面這個DEMO是能夠編譯經過,且運行沒有問題的,返回false。

可是slice的底層結構和這個是相似的。可是它不能比較,slice是一個特殊類型。儘可能少的考慮它是一個struct類型。同時struct可以進行比較,取決於內部的各個元素是否可以比較。

那爲何不直接slice動態數組比較呢?只須要比較元素個數和每一個元素比較。咱們從《Go聖經-學習筆記入門bufio》, 的bytes.ReadSlice方法就能夠知道line多是間接引用的,它底層的數據可能會隨時發生變化。這樣比較的話是沒有意義的,因此安全的作法是直接禁止slice比較。

append函數

在使用slice動態數組的擴容時,咱們常常會用到append函數。它的原型:

func append(src []T, elem ...T) []T

由於cap容量有界的,因此當slice靜態增加到達cap容量後,就須要從新分配必定大小的內存空間,把老的slice動態數組複製到新的slice中,而後再靜態增加。整個流程就是這樣子的。

那麼,append函數就是這個做用,分兩種狀況討論:

  • 當 len(elem)+len(src)>cap(src)時,就須要動態擴容了,擴容後的cap容量=2*(len(elem)+len(src)), 增長後長度的兩倍。同時返回的slice是新的動態數組,與src無關。因此須要返回類型[]T。
  • 當 len(elem)+len(src) <= cap(src)時,知足靜態增加的須要。src和返回slice類型數據共用底層數組。

內存的動態增加,會產生暫時的內存浪費、以及屢次內存分配致使時效低下。若是咱們一開始分配合理的內存空間大小,既能夠節約內存空間,又能夠優化時間。

相關文章
相關標籤/搜索