首頁 > 軟體

GO中 分組宣告與array, slice, map函數

2022-03-31 13:02:56

前言:

在 Go 語言中,同時宣告多個常數、變數,或者匯入多個包時,可採用分組的方式進行宣告。

例如下面的程式碼:

import "fmt"
import "os"

const i = 100
const pi = 3.1415
const prefix = "Go_"

var i int
var pi float32
var prefix string1

可以改成下面的方式:

import(
    "fmt"
    "os"
)

const(
    i = 100
    pi = 3.1415
    prefix = "Go_"
)

var(
    i int
    pi float32
    prefix string
)

iota 列舉

Go 裡面有一個關鍵字 iota,這個關鍵字用來宣告 enum 的時候採用,它預設開始值是 0,每呼叫一次加 1:

const(
    x = iota // x == 0
    y = iota // y == 1
    z = iota // z == 2
    w // 常數宣告省略值時,預設和之前一個值的字面相同。
      //這裡隱式地說 w =
      //iota,因此 w == 3。其實上面 y 和 z 可同樣不用"= iota"
)

注:const v = iota // 每遇到一個 const 關鍵字,iota 就會重置,此時 v == 0。

Go 程式設計的一些規則

Go 之所以會那麼簡潔,是因為它有一些預設的行為:

  • 大寫字母開頭的變數是可匯出的,也就是其它包可以讀取的,是公用變數;小寫字母開頭的就是不可匯出的,是私有變數
  • 大寫字母開頭的函數也是一樣,相當於 class 中的帶 public 關鍵詞的公有函數;小寫字母開頭的就是有 private 關鍵詞的私有函數。

陣列

array 就是陣列,它的定義方式如下:

var arr [n]type

在[n]type 中,n 表示陣列的長度,type 表示儲存元素的型別。對陣列的操作和其它語言類似,都是通過[]來進行讀取或賦值:

var arr [10]int // 宣告了一個 int 型別的陣列
arr[0] = 42 // 陣列下標是從 0 開始的
arr[1] = 13 // 賦值操作
fmt.Printf("The first element is %dn", arr[0]) // 獲取資料,返回 42
fmt.Printf("The last element is %dn", arr[9]) //返回未賦值的最後一個元素,預設返回 0

由於長度也是陣列型別的一部分,因此[3]int 與[4]int 是不同的型別,陣列也就不能改變長度。

陣列之間的賦值是值的賦值,即當把一個陣列作為引數傳入函數的時候,傳入的其實是該陣列的副本,而不是它的指標。

如果要使用指標,那麼就需要用到後面介紹的 slice 型別了。

陣列可以使用另一種:=來宣告。

a := [3]int{1, 2, 3} // 宣告了一個長度為 3 的 int 陣列
b := [10]int{1, 2, 3} // 宣告了一個長度為 10 的 int 陣列,其中前三個元素初始化為 1、2、3,其它預設
為 0
c := [...]int{4, 5, 6} // 可以省略長度而採用`...`的方式,Go 會自動根據元素個數來計算長度

也許你會說,我想陣列裡面的值還是陣列,能實現嗎?

當然咯,Go 支援巢狀陣列,即多維 陣列。

比如下面的程式碼就宣告了一個二維陣列:

// 宣告了一個二維陣列,該陣列以兩個陣列作為元素,其中每個陣列中又有 4 個 int 型別的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}

// 如果內部的元素和外部的一樣,那麼上面的宣告可以簡化,直接忽略內部的
型別
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

陣列的分配如下所示:

切片

在很多應用場景中,陣列並不能滿足我們的需求。在初始定義陣列時,我們並不知道需要多大的陣列,因此我們就需要“動態陣列”。在 Go 裡面這種資料結構叫 slice , 翻譯過來就是切片的意思,大白話就是切成一片一片的:

  • slice 並不是真正意義上的動態陣列,而是一個參照型別。
  • slice 總是指向一個底層array。
  • slice 的宣告也可以像 array 一樣,只是不需要長度。
// 和宣告 array 一樣,只是少了長度
var fslice []int

接下來我們可以宣告一個 slice,並初始化資料,如下所示:

slice := []byte {'a', 'b', 'c', 'd'}
  • slice 可以從一個陣列或一個已經存在的 slice 中再次宣告。
  • slice 通過 array[i:j]來獲取,其中 i 是陣列的開始位置,j 是結束位置,但不包含 array[j],即[i,j),前包括後不包括, 它的長度是 j-i。
// 宣告一個含有 10 個元素元素型別為 byte 的陣列
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 宣告兩個含有 byte 的 slice
var a, b []byte
// a 指向陣列的第 3 個元素開始,併到第五個元素結束,
a = ar[2:5]
//現在 a 含有的元素: ar[2]、ar[3]和 ar[4]

// b 是陣列 ar 的另一個 slice
b = ar[3:5]
// b 的元素是:ar[3]和 ar[4]

注意 slice 和陣列在宣告時的區別:

  • 宣告陣列時,方括號內寫明瞭陣列的長度或使用...。
  • 自動計算長度,而宣告 slice 時,方括號內沒有任何字元。

它們的資料結構如下所示:

slice 有一些簡便的操作:

  • slice 的預設開始位置是 0,ar[:n]等價於 ar[0:n]。
  • slice 的第二個序列預設是陣列的長度,ar[n:]等價於ar[n:len(ar)]。
  • 如果從一個陣列裡面直接獲取 slice,可以這樣 ar[:],因為預設第一個序列是 0,第 二個是陣列的長度,即等價於 ar[0:len(ar)]。

下面這個例子展示了更多關於 slice 的操作:

// 宣告一個陣列
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

// 宣告兩個 slice
var aSlice, bSlice []byte

// 演示一些簡便操作
aSlice = array[:3] // 等價於 aSlice = array[0:3] aSlice 包含元素: a,b,c
aSlice = array[5:] // 等價於 aSlice = array[5:10] aSlice 包含元素: f,g,h,i,j

aSlice = array[:] // 等價於 aSlice = array[0:10] 這樣 aSlice 包含了全部的元素

// 從 slice 中獲取 slice
aSlice = array[3:7] // aSlice 包含元素: d,e,f,g,len=4,cap=7

bSlice = aSlice[1:3] // bSlice 包含 aSlice[1], aSlice[2] 也就是含有: e,f

bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是
含有: d,e,f

bSlice = aSlice[0:5] // 對 slice 的 slice 可以在 cap 範圍內擴充套件,此時
bSlice 包含:d,e,f,g,h

bSlice = aSlice[:] // bSlice 包含所有 aSlice 的元素: d,e,f,g

slice 是參照型別,所以當參照改變其中元素的值時,其它的所有參照都會改變該值,例如上面的aSlice 和bSlice,如果修改了aSlice中元素的值,那麼 bSlice相對應的值也會改變。 從概念上面來說 slice像一個結構體,這個結構體包含了三個元素:

  • 一個指標,指向陣列中slice指定的開始位置。
  • 長度,即 slice 的長度。
  • 最大長度,也就是 slice 開始位置到陣列的最後位置的長度。
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

Slice_a := Array_a[2:5]

上面程式碼的真正儲存結構如下圖所示:

對於 slice 有幾個有用的內建函數:

  • len 獲取 slice 的長度。
  • cap 獲取 slice 的最大容量。
  • append 向 slice 裡面追加一個或者多個元素,然後返回一個和 slice 一樣型別的slice。
  • copy 函數 copy 從源 slice 的 src 中複製元素到目標 dst,並且返回複製的元素的個數。

注:append 函數會改變 slice 所參照的陣列的內容,從而影響到參照同一陣列的其它 slice。

但當 slice 中沒有剩餘空間(即(cap-len) == 0)時,此時將動態分配新的陣列空間。

返回的slice 陣列指標將指向這個空間,而原陣列的內容將保持不變;

其它參照此陣列的 slice 則不受影響。

map

map 也就是 Python 中字典的概念,它的格式為 map[keyType]valueType我們看下面的程式碼,map 的讀取和設定也類似 slice 一樣,通過 key 來操作,只是 slice 的index只能是int型別,而 map 多了很多型別,可以是 int,可以是 string 及所有完全定
義了==與!=操作的型別。

// 宣告一個 key 是字串,值為 int 的字典,這種方式的宣告需要在使用之前使用 make 初始化
var numbers map[string] int
// 另一種 map 的宣告方式
numbers := make(map[string]int)
numbers["one"] = 1 //賦值
numbers["ten"] = 10 //賦值
numbers["three"] = 3
fmt.Println("第三個數位是: ", numbers["three"]) // 讀取資料
// 列印出來如:第三個數位是: 3

這個 map 就像我們平常看到的表格一樣,左邊列是 key,右邊列是值使用 map 過程中需要注意的幾點:

  • map 是無序的,每次列印出來的 map 都會不一樣,它不能通過 index 獲取,而必須通過 key 獲取。
  • map 的長度是不固定的,也就是和 slice 一樣,也是一種參照型別。
  • 內建的 len 函數同樣適用於 map,返回 map 擁有的 key 的數量。
  • map 的值可以很方便的修改,通過 numbers["one"]=11 可以很容易的把 key 為 one 的字典值改為 11。

map 的初始化可以通過 key:val 的方式初始化值,同時 map 內建有判斷是否存在 key 的方式,通過 delete 刪除 map 的元素:

// 初始化一個字典
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map 有兩個返回值,第二個返回值,如果不存在 key,那麼 ok 為 false,如果存在 ok 為 true
csharpRating, ok := rating["C#"]
if ok {
    fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
    fmt.Println("We have no rating associated with C# in the map")
}
delete(rating, "C") // 刪除 key 為 C 的元素

上面說過了,map 也是一種參照型別,如果兩個 map 同時指向一個底層,那麼一個改變, 另一個也相應的改變:

m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // 現在 m["hello"]的值已經是 Salut 了

make、new 操作

  • make用於內建型別(map、slice 和 channel)的記憶體分配。
  • new 用於各種型別的記憶體分配。
  • 內建函數 new 本質上說跟其它語言中的同名函數功能一樣:new(T)分配了零值填充的 T 型別的記憶體空間,並且返回其地址,即一個*T型別的值。用 Go 的術語說,它返回了一個指標,指向新分配的型別 T的零值。
  • 有一點非常重要:new 返回指標。
  • 內建函數make(T, args)與 new(T)有著不同的功能,make 只能建立 slice、map 和 channel,並且返回一個有初始值(非零)的 T 型別,而不是*T。
  • 本質來講,導致這三個型別有所不同的 原因是指向資料結構的參照在使用前必須被初始化。例如,一個 slice,是一個包含指向數 據(內部 array)的指標、長度和容量的三項描述符;在這些專案被初始化之前,slice為nil。
  • 對於 slice、map 和 channel 來說,make初始化了內部的資料結構,填充適當的值。
  • make 返回初始化後的(非零)值。下面這個圖詳細的解釋了 new 和 make 之間的區別。

關於“零值”,所指並非是空值,而是一種“變數未填充前”的預設值,通常為0。 此處羅列部分型別 的“零值”。

int     0
int8     0
int32     0
int64     0
uint     0x0
rune     0 //rune 的實際型別是 int32
byte     0x0 // byte 的實際型別是 uint8
float32 0 //長度為 4 byte
float64 0 //長度為 8 byte
bool     false
string     ""

到此這篇關於GO中 分組宣告與array, slice, map函數的文章就介紹到這了,更多相關GO分組宣告與array, slice, map內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com