Go語言學習11-數據的使用

4. Go語言數據的使用

前面的博文,咱們介紹了Go語言的各類數據類型,包括基本數據類型、數組類型、切片類型、字典類型、函數類型、接口類型、結構體類型和指針類型;從本篇開始咱們一塊兒來了解Go語言數據的使用。數組

4.1 賦值語句

若是值 x 能夠被賦給類型爲 T 的變量,那麼它們至少須要知足如下條件中的一個賦值規則:app

  1. 若是值 x 的類型是 T , 那麼 x 能夠被賦給 T 類型的變量。框架

  2. 若是值 x 的類型是 V,那麼 VT 應該具備相同的潛在類型,而且它們之中至少有一個是未命名的類型。未命名的類型是指未被署名的數據類型。例如,字面量:分佈式

    struct {
        a int
        b string
    }{0,"string"}

    所表明的值的類型是ide

    struct {
        a int
        b string
    }

    而這個類型就是一個未命名的類型。它的潛在類型與結構體類型函數

    type Anonym struct {
        a int
        b string
    }

    相同。所以,上面的字面量能夠被賦給類型爲 Anonym 的變量。ui

  3. 類型 T 是一個接口類型,且值 x 的類型實現了 T。所以,x 就能夠被賦給類型爲 T 的變量。指針

  4. 若是值 x 是一個雙向通道類型的值,而 T 也是一個通道類型。那麼 x 的類型 VT 應該具備相同的元素類型,而且它們之中至少有一個是未命名的類型。(在以後講述通道類型的時候詳細說明)code

  5. 若是值 x 預約義標識符 nil,那麼它能夠被賦給切片類型、字典類型、函數類型、接口類型、指針類型和通道類型的變量。只要變量不是值類型,它就能夠被賦予空值 nil索引

  6. 若是值 x 是一個由某個數據類型的值表明的無類型的常量(能夠理解爲字面量),那麼他就能夠被賦給該數據類型的變量。例如,字符串字面量「ABC」能夠被賦給 string 類型的變量,以及整數字面量 123 能夠被賦給 int 類型的變量。

  7. 全部值均可以被賦給 空標識符"_"。空標識符有時也被稱爲佔位標識符。它只起到佔位的做用,不會與任何值創建綁定關係。

賦值語句通常由左右分立的兩個表達式列表和處於中間的一個賦值操做符組成。例如:

var ints = []int{1, 2, 3}

表達式列表中的多個表達式之間須要有逗號做爲分隔符。在大多數狀況下,左右兩邊的表達式的數量必須是相同的。當左邊表達式的數量大於1時,就造成了多個賦值操做同時進行的狀況,這種狀況經常被稱爲平行賦值

在賦值操做符左邊的那個表達式列表中的每一個表達式的結果值都必須是可尋址的。若是不須要對複製操做符右邊的某個表達式的結果值進行綁定,能夠在賦值操做符左邊的相應位置上應用 空標識符"_"。例如:

ints[1], _ = (ints[1] + 1), (ints[2] + 1)

對於普通賦值語句(以=爲賦值操做符的賦值語句)來講,在賦值操做符兩邊的表達式的數量能夠不相等。當在賦值操做符的右邊只有一個表達式且該表達式是一個多值表達式(與單值表達式相對應)的時候,在賦值操做符的左邊的表達式能夠有多個。這時右邊惟一的表達式有4種狀況:

  1. 此表達式是一個調用會返回多個結果的函數或者方法的表達式。這時,在賦值操做符的左邊的表達式的數量應該等於該函數或方法的結果的數量。

  2. 此表達式是一個應用於字典值之上的索引表達式。這時,在賦值操做符的左邊能夠有一個或兩個表達式。例如:

    v, ok := map["k1"]
  3. 此表達式是一個類型斷言表達式。這時,在賦值操做符的左邊能夠有一個或兩個表達式。例如:

    v, ok := x.(string)
  4. 此表達式是一個由接收操做符和通道類型值組成的表達式。這時,在賦值操做符的左邊能夠有一個或兩個表達式。例如:

    v, ok := <-ch

賦值語句的執行分爲兩個階段:

  • 第一個階段,在賦值操做符左邊的索引表達式和取址表達式的操做數以及在賦值操做符右邊的表達式,都會按照一般的順序被求值。

  • 第二個階段,賦值會以從左到右的順序進行。

在Go語言中可使用平行賦值來交換兩個變量的值:

a, b = b, a

因爲平行賦值永遠是從左向右進行的,因此即便靠右的賦值引起了運行時恐慌,它左邊的賦值也依然生效。

在賦值語句中,每一個右邊的表達式的結果值必須是能夠被賦給與其相對應的左邊的表達式的類型的,即便這些值由無類型的常量表明。

4.2 常量與變量

常量一旦被聲明它的值就不能被改變,而對於變量卻沒有這樣的限制。

4.2.1 常量

在Go語言中,常量總會在編譯期間被建立,即便它們做爲局部變量被定義在了函數內部。常量只能由字面量常量或常量表達式來賦值。常量表達式是可以且會在編譯期間被求值的。而其餘的表達式只能在程序運行期間被求值,因此它們並不能被賦給常量。

Go語言的常量:布爾常量rune常量(也成爲字符常量)、整數字面量浮點數字面量複數字面量字符串字面量表示。由相應的字面量表示的基本數據類型的值均可以被稱爲常量值。由字面量表示的常量值也簡稱爲字面常量。例如,布爾字面量true是一個字面常量。

常量能夠是有類型的也能夠是沒有類型的。有字面量表示的常量,如truefalse「A」iota 以及由僅以無類型的常量做爲其操做數的常量表達式的結果值都屬於無類型的常量。

從語言規範上來講,數組型常量能夠有任意的精度,但編譯器卻只會使用一個有限精度的內部表示方法來實現它們。對於每個實現,都必須知足一下條件:

  1. 整數字面量至少要用256個比特位來表示。

  2. 浮點數字面量的小數部分至少要用256個比特位來表示,而其指數部分至少要用32個比特位來表示。對於複數常量的實部和虛部中的相應部分也是如此。

  3. 若不能精確地表示一個整數常量,則要給出一個錯誤。

  4. 若因爲溢出而不能表示一個浮點數常量或複數常量,則要給出一個錯誤。

  5. 若因爲精度限制而不能表示一個浮點數常量或複數常量,則這個值會被四捨五入爲一個可表示的最相近的常量。
4.2.1.1 常量表達式

常量表達式就是僅以常量做爲操做數的表達式。無類型的布爾常量、數值常量和字符串常量均可以做爲常量表達式的操做數。若是一個二元操做的操做數是兩個不一樣種類的無類型的數組型常量,那麼對於非布爾操做(不包含比較操做符的操做)來講其操做結果的種類遵循着這樣的優先級順序(從高到低):複數浮點數rune整數。例如:

2 + 3.0  // 結果是一個無類型的浮點數常量5.0
15 / 4.0 // 結果是一個無類型的浮點數常量3.75
'w' + 1  // 結果是一個無類型的rune常量'x'

操做數爲無類型常量的移位操做的結果總會是一個無類型的整數常量。例如:

1 << 3.0 // 結果是一個無類型的整數常量8
1.0 << 3 // 結果是一個無類型的整數常量8

比較操做結果總會是一個無類型的布爾常量。例如:

"A" > "C" // 結果是一個無類型的布爾常量false

常量表達式總會被正確地求值。中間值和做爲表達式結果的常量值自身都會有足夠的精度。這個精度能夠比Go語句中預約義的那些類型所支持的精度更高。例如:

1 << 100 // 結果是一個無類型的整數常量1267650600228229401496703205376

這個常量值實際上已經超出了Go語言中任何一個整數類型(即便是 uint64 類型)所能表示的範圍了。

對於有類型的常量來講,它的值必須永遠可以被精確地表示爲其類型的值。

4.2.1.2 常量的聲明

常量聲明會將字面量或常量表達式與標識符綁定在一塊兒。與變量不一樣的是,對常量的賦值必須與其聲明同時進行。而且,只能對常量賦值一次。

一條常量聲明語句總會以關鍵字 const 開始。例如:

const untypedConstant = 10.0      // 無類型的常量的聲明
const typedContstant int64 = 1024 // 有類型的常量的聲明

在常量聲明語句中還能夠包含平行賦值。以下:

const tc1, tc2, tc3 int64 = 1024, -10, 88

在包含平行賦值的常量聲明語句中,若是類型被給定,那麼全部的常量的類型都應該與這個被給定的類型一致。相應的,賦值操做符右邊的字面常量或常量表達式的結果值也均可以被賦給這個類型。

若是在包含平行賦值的常量聲明語句中爲給定類型,那麼賦值操做符右邊的多個字面量或常量表達式的結果值的種類都會是彼此獨立的,即它們的種類均可以是任意的。例如:

// 標識符 utc1, utc2, utc3分別表示了一個浮點數常量,一個布爾常量和一個字符串常量。
const utc1, utc2, utc3 = 6.3, false, "C"

將上面的常量聲明語句進行拆分,以下:

const (
    utc1 = 6.3
    utc2 = false
    utc3 = "C"
)

在這個圓括號中的每一行均可以被稱爲一個常量聲明。在圓括號中的常量聲明內,一樣能夠進行平行賦值:

const (
    utc1, utc2 = 6.3, false
    utc3       = "C"
)

在帶圓括號的常量聲明語句中,有時候並不須要顯式地對全部的常量進行賦值。被省略了賦值的常量實際上仍是有值的,只不過這個值是被隱含地賦予了。它們的值及其類型都會與上面的,最近的且被顯式賦值的那個常量相同。所以對第一個被聲明的常量的賦值是永遠不可以被省略的。例如:

const (
    utc1, utc2 = 6.3, false
    utc3       = "C"
    utc4
    utc5
) // 常量utc4和utc5的值及其類型都會與在它們上面被聲明的常量utc3相同。

儘管能夠被省略,可是仍是有兩個與此有關的約束:

  1. 若是有一個未被顯示賦值的常量,那麼與它同一行的常量(若是有的話)的賦值也都必須被省略。

  2. 在未包含顯示賦值的那一行常量中的常量標識符的數量必須與在它上面的、最近的且包含顯式賦值的那一行常量聲明中的常量標識符的數量相等。例如:

    const (
        utc1, utc2, utc3 = 6.3, false, "C"
        utc4, utc5
    ) // 不合法,不符合約束2,會形成一個編譯錯誤。

    能夠這樣解決:

    const (
        utc1 = 6.3
        utc2, utc3 = false, "C"
        utc4, utc5
    )

在Go語言中不但能夠爲隱含地多個常量賦予同一個值,並且還能夠更加方便地對多個常量分別賦予一系列連續的值。例如:

const {
    Sunday = itoa
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

在常量聲明語句中,iota 表明了連續的、無類型的整數常量。它第一次出如今一個以 const 開始的常量聲明語句中的時候總會表示整數常量 0

在同一條常量聲明語句中,iota 在第二個包含它的常量聲明中會表示爲整數常量 1,在第三個包含它的常量聲明中會表示爲 2,以此類推。隨着在同一條常量聲明語句中包含 iota 的常量聲明的數量的增長,iota 所表示的整數值也會遞增。

const x = iota // 常量x的值是整數常量0
const y = iota // 常量y的值是整數常量0

利用 iota 進行更加靈活的常量隱式賦值。例如:

const {
    u = 1 << iota
    v
    w
} // 常量u、v和w的值分別是1, 2, 4。

在同一條常量聲明語句中,iota 表明的整數常量的值是否遞增取決因而否又有一個常量聲明包含了它,而不是它是否又在常量聲明中出現了一次。例如:

const {
    e, f = iota, 1 << iota
    g, h
    i, j
} // e,f的值爲0,1   g,h的值爲1,2   i,j的值爲2,4

Go語言中能夠利用 空標識符"_" 來跳過 iota 表示的遞增序列中的某個或某些值。例如:

const {
    e, f = iota, 1 << iota
    _, _
    g, h
    i, j
} // e,f的值爲0,1   g,h的值爲2,4   i,j的值爲3,8

總結:對常量進行聲明的時候必須同時對它進行賦值。對一個常量的賦值只能進行一次,且只有字面常量和常量表達式能夠做爲它的值。能夠利用 隱式賦值平行賦值itoa 對常量進行很是靈活和複雜的賦值操做。

4.2.2 變量

變量常量 的最主要的區別是它在被聲明以後能夠被賦值任意次。對於變量來講,它的值是能夠在程序運行期間才被計算出來的。

4.2.2.1 變量聲明

一個變量聲明能夠將一個標誌符與一個變量值綁定在一塊兒。前提條件是這個變量值與該變量的類型之間必需要知足賦值規則。

變量聲明語句老是會以關鍵字 var 開始:

var v int64 = 0

咱們也能夠省略變量的類型:

var v = 0

若是變量的類型未被顯式地指定,那麼它將會由變量值推導得出。若是在省略類型的同時,賦值操做符右邊的表達式的求值結果是一個字面常量,那麼該變量的類型將會根據這個字面常量的種類被推導出來。

字面常量與變量類型的對應關係

字面常量的種類 變量的類型
布爾常量 bool
字符常量 rune
浮點數常量 float64
複數常量 complex128
字符串常量 string

以上的對應狀況下,Go語言的運行時程序會根據字面常量的種類將其轉換爲對應的數據類型,而後在賦給相應的變量。

在Go語言中一樣能夠對多個變量進行平行賦值

var v1, v2 = 0, -1

把多個變量的聲明拆分紅多行:

var (
    v1 = 0
    v2 = -1
)

注意隱式賦值 在變量聲明中是不可用的。

在Go語言中,能夠不對一個新聲明的變量的值進行初始化。若是初始化的顯示賦值被省略,那麼變量的值將會是與該變量的類型相對應的零值。這時,變量的類型不能夠被省略。若是是平行賦值的話,要麼省略其中全部變量的初始賦值,要麼就必須對全部變量進行初始賦值。例如:

var v3, v4, v5 float64

或者必須這樣:

var v3, v4, v5 float64 = 3, 4, 5
4.2.2.2 局部變量

與常量相同,變量聲明能夠做爲源碼文件中的頂級元素,也能夠成爲函數體內容的一部分。前者能夠稱爲全局變量,後者能夠被稱爲某個函數的局部變量局部變量有時也被稱爲本地變量

在函數體內部,局部變量會遮蔽與它同名的全局變量。例如:

packge main

import "fmt"

var v6 bool //沒有初始化,默認爲false

func main() {
    var v6 bool = true
    fmt.Printf("v6: %v\n", v6) // 打印字符串 v6: true
}

在函數內部聲明變量的時候能夠採用一種簡單方式,前面已經涉及過,以下:

v6 := true // 短變量聲明,根據它的值推出變量的類型

短變量聲明也不須要以 var 開始,這種特殊標記 := 只會被用於對變量的聲明和初始化的語句中,因此並不會產生歧義。短變量聲明與普通變量聲明同樣,也支持平行賦值。例如:

v7, v8 := "Go", 1.2

重聲明僅會出如今短變量聲明中,能夠理解爲對當前的已存在變量的又一次賦值。例如:

v8, v9 := 2.0, false

短變量聲明的約束條件:

  • 短變量聲明僅可以在函數體內部聲明變量的時候使用。
  • 在短變量聲明中的:=的左邊的標識符至少要有一個表明在當前上下文環境中的新變量。

注意空標識符"_" 表明的並非新的變量,即便它在當前上下文環境中並無出現過。

短變量聲明能夠出如今 ifforswitch 等語句的初始化器中,並被用來聲明僅存在於這些語句塊中的本地臨時變量。這些知識點在以後的博文講述的Go語言流程控制方法中介紹。

若是咱們在當前上下文環境中聲明瞭某個局部變量但沒有使用它,那麼就會形成一個編譯錯誤。Go語言認爲這種狀況是對計算機資源的浪費,甚至預示着更加嚴重的問題的出現。

注意:對變量的賦值並不算是對它的使用。

4.3 可比性與有序性

4.3.1 類型的恆等

別名類型和它的源類型是兩個徹底不一樣的類型。一個命名類型和一個匿名類型老是不恆等的。若是兩個匿名類型的類型字面量是相同的,就能夠說它們是恆等的。

各個數據類型的恆等判斷方法的規則:

  • 對於兩個數組類型,若是它們的長度一致且元素的數據類型是恆等的,那麼它們就是恆等的。

    [4]string
    [4]string//這兩個數組類型就是恆等的
    [4]string
    [3]string//這兩個數組類型就是不恆等的。

    數組類型的長度是類型聲明的一部分,也是類型的一部分

  • 對於兩個切片類型,若是它們的元素的數據類型恆等,那麼它們就是恆等的。

    []string
    []string//這兩個切片類型是恆等的。

    切片類型的長度並不會存在於類型聲明中,而且兩個同一切片類型的值的長度也不必定會相同。

  • 對於兩個結構體類型來講,若是它們之中的字段聲明的數量是相同的,而且在對應位置上的字段具備相同的字段名稱(若是有的話)和恆等的數據類型,那麼這兩個結構體數據類型就是恆等的。

    var a1 struct {
        f1 sort.Interface
        f2 int64
    }
    
    var a2 struct {
        f1 sort.Interface
        f2 int64
    }

    從字面看,變量 a1 和變量 a2 的類型顯然是恆等的。若是其中一個結構體類型聲明中有匿名函數,那麼在另外一個結構體類型的聲明中的對應位置上的字段聲明也必須不包含名稱,不然,它們就是不相等的。若是結構體類型的某個字段聲明是有標籤的,那麼這個標籤也應該做爲恆等判斷的一個依據。

  • 對兩個指針類型,若是它們的基本類型(也就是它們指向的那個類型)恆等,那麼它們就是恆等的。

  • 對於兩個函數類型,若是它們包含了相同數量的參數聲明和結果聲明,而且在對應位置上的參數或結果的類型都是恆等的,那麼它們就是恆等的。

    func(name string, dept string, isManager bool) (id int, done bool)
    func(appName string, targetOs string, authRequired bool) (id int, doen bool)

    函數類型的恆等判斷並不會以參數和結果的名稱爲依據,而只關心它的數量順序類型。若是其中一個函數類型是可變參函數,那麼另外一個函數類型也應該是這樣,不然它們就是不恆等的。

  • 對於兩個接口類型,若是它們擁有相同的方法集合,那麼它們就是恆等的。兩個接口類型所包含的方法聲明的數量必須相同,而且對於一個接口類型中包含的每個方法聲明都可以在另外一個接口類型中找到與它徹底相等的方法聲明。兩個接口類型中的方法聲明的順序是可有可無的。以下兩個是恆等的:

    type Ia interface {
        Name() string
        Age() int
    }
    
    type Ib interface {
        Age() int
        Name() string
    }
  • 對於兩個字典類型,若是它們具備恆等的鍵類型和元素類型,那麼它們就是恆等的。

  • 對於兩個通道類型,若是它們具備恆等的元素類型,而且方向相同,那麼它們就是恆等的。(以後的博文中會詳細的介紹)

注意: 若是兩個數據類型在不一樣的代碼包中,即便它們知足了上述相關規則也是不相等的。

4.3.2 數據的可比性與有序性

上面的類型的恆等闡述了Go語言的數據類型之間的可比性,下面關注的是數據類型的值之間的可比性和有序性。可比性 是能夠判斷相等與否,有序性 是能夠比較大小的含義。

各個數據類型的值的相關特性:

  • 布爾類型值具備可比性。布爾值只有 truefalse 兩種可能。兩個布爾值能夠判斷是否相等,卻沒法比較兩個布爾值的大小。

  • 整數類型值具備可比性,也具備有序性。

  • 浮點數類型值具備可比性,也具備有序性。這被定義在IEEE-754標準中(一個針對二進制浮點數的算術標準)。

  • 複數類型值具備可比性。判斷兩個複數類型值是否相等的結果是經過分別對它們的實部和虛部上的值進行比較而得出的。

  • 字符串類型值具備可比性,也具備有序性。兩個字符串類型值判斷相等或比較大小的方法就是對它們中的每一個對應位置上的字節進行判斷或比較。這就至關於對多對整數類型值依次進行判斷或比較,直到能夠得出結果爲止。

  • 指針類型具備可比性。若是兩個指針類型值指向了同一個變量,或者它們都爲空值 nil,那麼就能夠斷定它們是相等的。例如:

    numArray := [3]int{1, 23, 456}
    p1 := &numArray
    p2 := &numArray
    fmt.Printf("%v\n", p1 == p2) // 打印true
  • 通道類型值具備可比性。若是兩個通道類型值的元素類型和緩衝區大小都一致,那麼就能夠被斷定爲相等。若是兩個通道類型的變量的值都是 nil ,那麼它們也是相等的。

  • 接口類型值具備可比性。若是兩個接口類型值擁有相等的動態類型和相同的動態值,那麼就能夠斷定它們是相等的。若是有一個接口類型的變量,那麼在這個變量中就只能存儲實現了該接口類型的類型的值。把存儲在該變量中的那個值的類型叫做該變量的動態類型,而把這個值叫做該變量的動態值。若是兩個接口類型的變量的值都是空值,那麼它們也是相等的。例如:

    type Ic interface {
        Code() string
    }
    
    type Sc struct {
        code string
    }
    
    func (self Sc) Code() string {
        return self.code
    }

    結構體類型 Sc 是接口類型 Ic 的一個實現類型。若是有兩個 Ic 類型的變量:

    var ic1 Ic = Sc{code: "A"}
    var ic2 Ic = Sc{code: "A"}
    fmt.Printf("%v\n", ic1 == ic2) // 打印true
  • 非接口類型 X 的值 x 能夠與接口類型 T 的值 t 判斷相等,當且僅當接口類型 T 具備可比性且類型 X 是接口類型 T 的實現類型。新增一個變量聲明:

    var sc1 Sc = Sc{code: "A"}
    fmt.Printf("%v\n", ic1 == sc1) // 打印true
    fmt.Printf("%v\n", ic2 == sc1) // 打印true
  • 若是一個結構體類型中的全部字段都具備可比性,那麼這個結構體類型的值就具備可比性。若是兩個結構體值中的對應的字段值是相等的,那麼這兩個結構體類型值就是相等的。例如:

    type Sd struct {
        ints []int
    }
    sd1 := Sd{ints: []int{0, 1}}
    sd2 := Sd{ints: []int{0, 1}}
    fmt.Printf("%v\n", sd1 == sd2)//被編譯的時候就會形成一個編譯錯誤

    結構體類型 Sc 中包含了一個切片類型的字段,而切片類型的值是不具備可比性的。

  • 數組類型值具備可比性,當前僅當其元素類型的值具備可比性。若是兩個數組類型值在對應位置上的值都是相等的,那麼這兩個數組類型值就是相等的。例如:

    slices1 := [3][]int{[]int{0, 1}}
    slices2 := [3][]int{[]int{0, 1}}
    fmt.Printf("%v\n", slices1 == slices2)//被編譯的時候就會形成一個編譯錯誤

    變量 slice1slice2 都表明了元素類型爲 [ ]int 的數組類型的值。這兩個值的類型的元素類型都是不具備可比性的,從而這兩個數組類型的值也不具備可比性。

在判斷兩個具備相同接口類型的值是否相等的時候,若是它們的動態類型不具備可比性就會引起一個運行恐慌。好比,兩個接口類型的變量的動態類型是 切片類型字典類型別名類型。一樣適用於以下狀況,以接口類型爲元素類型的數組類型的值,以及以接口類型爲其中某個字段的類型的結構體類型的值。

type Se []int

func (self Se) Code() string {
    return ""
}
// 這個實現了接口類型Ic的類型的值就能夠被賦給Ic類型的變量
var ic3 Ic = Se{1, 2}
var ic4 Ic = Se{1, 2}
fmt.Printf("%v\n", ic3 == ic4) // 這裏會引起一個運行時恐慌,切片類型Se不具備可比性

func (self Sd) Code() string {
    return ""
}

var ic5 Ic = Sd{ints: []int{0, 1}}
var ic6 Ic = Sd{ints: []int{0, 1}}
fmt.Printf("%v\n", ic5 == ic6) // 這裏會引起一個運行時恐慌,結構體類型Sd不具備可比性

注意切片類型字典類型函數類型 的值是不具備可比性的。但這些值能夠與空值 nil 進行判等的。

下篇繼續講解Go語言數據的使用,主要包括 類型轉換內建函數 的介紹。

最後附上知名的Go語言開源框架:

NSQ: 一個實時的分佈式消息平臺。它擁有很高的可伸縮性,並可以天天處理數以十億計的消息。它的官方網址是:http://nsq.io

相關文章
相關標籤/搜索