Go語言中的複合型別詳細介紹

NO IMAGE

golang複合型別包括:結構體、陣列、切片、Maps。

1、陣列

陣列

golang中的陣列與C語言中的陣列差異很大,倒更類似Pascal中的陣列。 (Slice,下個話題,有些像C語言中的陣列)

複製程式碼 程式碼如下:
var ar [3]int

宣告ar為一個擁有三個整型數的陣列,所有元素初始化為0。

大小是型別的一個組成部分。

內建的函式len可以用於獲取陣列大小:

複製程式碼 程式碼如下:
len(ar) = 3

陣列是值型別

golang中的陣列是值,而非C語言中的隱式指標。你可以獲得陣列的地址,並生成一個指向陣列的指標(例如,將其高效地傳遞給函式):
複製程式碼 程式碼如下:
func f(a [3]int) { fmt.Println(a) }  
func fp(a *[3]int) { fmt.Println(a) }  
 
func main() {  
    var ar [3] int 
    f(ar) // 傳遞一個ar的拷貝  
    fp(&ar) // 傳遞一個指向ar的指標  

輸出結果:

複製程式碼 程式碼如下:
[0 0 0]

&[0 0 0]

陣列字面值

所有的符合型別都有相同的值建立語法。以陣列為例,其語法如下:

3個整數的陣列:

複製程式碼 程式碼如下:
[3]int{1, 2, 3}

10個整數的陣列,前三個元素不是0:

複製程式碼 程式碼如下:
[10]int{ 1, 2, 3}

不想數?使用…代表長度:

複製程式碼 程式碼如下:
[…]int{1, 2, 3}

不想初始化所有值?使用key:value對:

複製程式碼 程式碼如下:
[10]int{2:1, 3:1, 5:1, 7:1}

指向陣列字面值的指標

你可以獲取陣列字面值的地址,這樣可以得到一個指向新建陣列例項的指標:

複製程式碼 程式碼如下:
func fp(a *[3]int) { fmt.Println(a) }  
func main() {  
    for i := 0; i < 3; i {  
        fp(&[3]int{i, i*i, i*i*i})  
    }  

輸出結果:

複製程式碼 程式碼如下:
&[0 0 0]
&[1 1 1]
&[2 4 8]

2、切片(Slice)

切片

切片是對陣列中某一段的引用。

切片比普通陣列應用得更多也更廣泛。

切片使用的代價很低。

一個切片型別很像一個沒有大小的陣列型別:

複製程式碼 程式碼如下:
var a []int

內建的len(a)可以返回切片中元素的個數。

通過對陣列或切片進行”切片”,我們可以建立一個新切片:

複製程式碼 程式碼如下:
a = ar[7:9]

a(上面例子中的a)的有效下標值是0和1;len(a) == 2。

切片速記

當對陣列進行切片時,第一個下標值預設是0:

ar[:n]等價於a[0:n]。

第二個下標值預設為len(array/slice):

ar[n:]等價於ar[n:len(ar)]。

因此由陣列建立切片時:

ar[:]等價於ar[0:len(ar)]。

切片引用陣列

概念上:

複製程式碼 程式碼如下:
type Slice struct {
base *elemType // 指向0th元素的指標
len int // 切片中元素的數量
cap int // 切片可以容納元素的數量
}

陣列:

複製程式碼 程式碼如下:
ar: 7 1 5 4 3 8 7 2 11 5 3

切片:

複製程式碼 程式碼如下:
a = ar[7:9] :base = &ar[7](指向ar中的2) len = 2 cap = 4

建立切片

切片字面值看起來像沒有指定大小的陣列字面值:

複製程式碼 程式碼如下:
var slice = []int{1,2,3,4,5}

上面程式碼建立了一個長度為5的陣列並建立一個切片用於引用這個陣列。

我們可以使用內建的make函式分配一個切片(底層實際是個陣列):

複製程式碼 程式碼如下:
var s100 = make([]int, 100) // slice: 100 ints

為何用make而不是用new?因為我們需要建立切片,而不僅僅是為了分配記憶體。注意make([]int, 10)返回[]int,而new([]int)返回*[]int。

使用make建立切片、map以及channel。

切片容量

切片是對底層陣列的一個引用。因此存在一些在陣列裡但卻沒在切片引用的範圍內的元素。

內建的函式cap(capacity)用於報告切片可能增長到多長。

複製程式碼 程式碼如下:
var ar = [10]int{0,1,2,3,4,5,6,7,8,9}
var a = ar[5:7] // 引用子陣列{5,6}

len(a) = 2,cap(a) = 5,現在我們可以重新切片:

複製程式碼 程式碼如下:
a = a[0:4] // 引用子陣列 {5,6,7,8}

len(a)現在是4,而cap(a)依舊是5。

調整切片大小

切片可被當作可增長的陣列用。使用make分配一個切片,並指定其長度和容量。當要增長時,我們可以做重新切片:

複製程式碼 程式碼如下:
var sl = make([]int, 0, 100) // 長度 0, 容量 100  
func appendToSlice(i int, sl []int) []int {  
    if len(sl) == cap(sl) { error(…) }  
    n := len(sl)  
    sl = sl[0:n 1] // 長度增加1  
    sl[n] = i  
    return sl  
}

因此,sl的長度總是元素的個數,但其容量可根據需要增加。

這種手法代價很小,並且是Go語言中的慣用法。

切片使用的代價很小

你可以根據需要自由地分配和調整切片大小。它們的傳遞僅需要很小的代價;不必分配。

記住它們是引用,因此下層的儲存可以被修改。

例如,I/O使用切片,而不是計數:

複製程式碼 程式碼如下:
func Read(fd int, b []byte) int 
var buffer [100]byte  
    for i := 0; i < 100; i {  
    // 每次向Buffer中填充一個位元組  
    Read(fd, buffer[i:i 1]) // no allocation here  

拆分一個Buffer:

複製程式碼 程式碼如下:
header, data := buf[:n], buf[n:]

字串也可以被切片,而且效率相似。

3、Maps

maps

Map是另外一種引用型別。它們是這樣宣告的:
複製程式碼 程式碼如下:
var m map[string]float64

這裡宣告瞭一個map,索引key的型別為string,值型別為float64。這類似於C 中的型別*map<string, float64>。

對於給定map m,len(m)返回key的數量。

map的建立

和建立一個切片一樣,一個map變數是一個空引用;在可以使用它之前,應先要向裡面放入一些內容。

三種方式:

1) 字面值:逗號分隔的key:value對列表
複製程式碼 程式碼如下:
m = map[string]float64{“1”:1, “pi”:3.1415}

2) 建立
複製程式碼 程式碼如下:
m = make(map[string]float64) // make not new

3) 賦值
複製程式碼 程式碼如下:
var m1 map[string]float64
m1 = m // m1和m現在引用相同的map

map索引

(接下來的幾個例子全都使用:
複製程式碼 程式碼如下:
m = map[string]float64{“1”:1, “pi”:3.1415})

訪問一個元素;如果該元素不存在,則得到對應map value型別的零值:
複製程式碼 程式碼如下:
one := m[“1”]
zero := m[“not present”] // zero被置為0.0.

設定一個元素的值(兩次設定將更新為最新值)
複製程式碼 程式碼如下:
m[“2”] = 2
m[“2”] = 3 // 思維混亂

測試存在性

要測試一個map中是否存在某個key,我們可以使用一個多項賦值的”comma, om”形式:
複製程式碼 程式碼如下:
m = map[string]float64{“1”:1, “pi”:3.1415}

var value float64
var present bool

value, present = m[x]

或者按慣例:

複製程式碼 程式碼如下:
value, ok := m[x] // “comma ok” 形式

如果map中存在x這個key,布林變數會被設定為true;value會被賦值為map中key對應的值。相反,布林變數會被設定為false,value被設定為相應值型別的零值。

刪除

使用多元賦值可以刪除map中的一個值:

複製程式碼 程式碼如下:
m = map[string]float64{“1”:1.0, “pi”:3.1415}

var keep bool
var value float64
var x string = f()

m[x] = v, keep

如果keep的值為true,則將v賦值到map中;如果keep為false,則刪除map中的key x。因此刪除一個key:

複製程式碼 程式碼如下:
m[x] = 0, false // 從map中刪除x

譯註:Go 1中上述的刪除方式已被取消,取而代之的是delete(m, x)。

for和range

對於陣列、切片和map(以及我們在第三部分將要看到的更多型別),for迴圈提供了一種特殊的語法用於迭代訪問其中的元素。

複製程式碼 程式碼如下:
m := map[string]float64{“1”:1.0, “pi”:3.1415}

for key, value := range m {
fmt.Printf(“key %s, value %g\n”, key, value)
}

只用一個變數,我們可以獲得key:

複製程式碼 程式碼如下:
for key = range m {
fmt.Printf(“key %s\n”, key)
}

變數可以用:=賦值或宣告。

對於陣列和切片來說,通過這種方式我們可以獲得元素的下標以及元素值。

將range用於字串

將for range用於字串時,實際迭代的元素是Unicode碼點(code point),而不是位元組(對位元組,可使用[]byte或使用標準的for語句)。我們假設字串包

含使用UTF-8編碼的字元。

下面迴圈:

複製程式碼 程式碼如下:
s := “[\u00ff\u754c]”
for i, c := range s {
fmt.Printf(“%d:%q “, i, c) // %q for ‘quoted’
}

輸出:0:'[‘ 1:’ÿ’ 3:’界’ 6:’]’

如果遇到了錯誤的UTF-8碼點,這個字元將被設定為U FFFD,下標向後移動一個位元組。

4、Structs

structs

對於Go中的struct,你應該感覺十分熟悉:簡單的資料欄位宣告。

複製程式碼 程式碼如下:
var p struct {
x, y float64
}

更常用的是:

複製程式碼 程式碼如下:

type Point struct {
x, y float64
}
var p Point

struct允許程式設計師定義記憶體佈局。

struct是值型別

struct是值型別,new(StructType)返回一個指向零值的指標(分配的記憶體都被置0)。

複製程式碼 程式碼如下:
type Point struct {
x, y float64
}
var p Point
p.x = 7
p.y = 23.4
var pp *Point = new(Point)
*pp = p
pp.x = Pi // (*pp).x的語法糖

對於結構體指標,沒有->符號可用。Go提供了間接的方式。

建立結構體

結構體是值型別,因此你可只通過宣告就可以建立一個全0的結構體變數。

你也可以使用new建立一個結構體。

複製程式碼 程式碼如下:
var p Point // 零值
pp := new(Point) // 慣用法

結構體字面值語法也不出所料:

複製程式碼 程式碼如下:
p = Point{7.2, 8.4}
p = Point{y:8.4, x:7.2}
pp = &Point{7.2, 8.4} // 慣用法
pp = &Point{} //也是慣用法,== new(Point)

和陣列一樣,得到了結構體字面值的地址,就得到了新建結構體的地址。

這些例子都是構造器。

匯出型別和欄位

只有當結構體的欄位(和方法,即將講解)名字的首字母大寫時,它才能被包外可見。

私有型別和欄位:
複製程式碼 程式碼如下:
type point struct { x, y float64 }

匯出型別和欄位:
複製程式碼 程式碼如下:
type Point struct { X, Y float64 }

匯出型別和私有型別混合欄位:
複製程式碼 程式碼如下:
type Point struct {
X, Y float64 // exported
name string // not exported
}

你甚至可以建立一個帶有匯出欄位的私有型別。(練習:何時能派上用場呢?)

匿名欄位

在一個結構體內,你可以宣告不帶名字的欄位,比如另外一個結構體型別。這些欄位被稱為匿名欄位。它們看起來就像裡層的結構體簡單插入或“嵌入”到

外層結構體似的。

這個簡單的機制為從其他型別繼承已有的實現提供了一種方法。

下面是一個例子。

一個匿名結構體欄位:

複製程式碼 程式碼如下:
type A struct {
ax, ay int
}

type B struct {
A
bx, by float64
}

B看起來像有四個欄位ax、ay、bx和by。B可看成{ax, ay int; bx, by float64}。

然後B的字面值必須提供細節:

複製程式碼 程式碼如下:
b := B{A{1, 2}, 3.0, 4.0}
fmt.Println(b.ax, b.ay, b.bx, b.by)

輸出1 2 3 4

匿名欄位以型別作為名字

匿名欄位不僅僅是簡單插入這些欄位這麼簡單,其含義更為豐富:B還擁有欄位A。匿名欄位看起來就像名字為其型別名的欄位。

複製程式碼 程式碼如下:
b := B{A{ 1, 2}, 3.0, 4.0}
fmt.Println(b.A)

輸出:{1 2}。如果A來自於另外一個包,這個欄位依舊被稱為A。

複製程式碼 程式碼如下:
import “pkg”
type C struct { pkg.A }

c := C {pkg.A{1, 2}}
fmt.Println(c.A) // 不是 c.pkg.A

任意型別的匿名欄位

任何具名型別或指向具名型別的指標都可以用作匿名欄位。它們可以出現在結構體中的任意位置。

複製程式碼 程式碼如下:
type C struct {
x float64
int
string
}
c := C{3.5, 7, “hello”}
fmt.Println(c.x, c.int, c.string)

輸出:3.5 7 hello

衝突和遮蔽

如果有兩個欄位具有相同的名字(可能是一個繼承型別的名字),程式碼將遵循下面規則:

1) 外層的名字遮蔽內層的名字。這提供了一個重寫欄位/方法的方式。
2) 如果在同一層次上出現了相同的名字,如果名字被使用,那麼將是一個錯誤。(如果沒有使用,不會出現錯誤)

二義性是沒有規則能解決的,必須被修正。

衝突的例子

複製程式碼 程式碼如下:
type A struct { a int }
type B struct { a, b int }
type C struct { A; B }
var c C

使用c.a將會出現錯誤。它到底是c.A.a還是c.B.a呢?

複製程式碼 程式碼如下:
type D struct { B; b float64 }
var d D

使用d.b沒有問題:它是float64型別變數,不是d.B.b。要獲得內層的b,可用d.B.b。

您可能感興趣的文章:

Go語言轉換所有字串為大寫或者小寫的方法GO語言型別轉換和型別斷言例項分析Go語言中轉換JSON資料簡單例子Go語言基本的語法和內建資料型別初探GO語言基本型別分析GO語言基本資料型別總結Go語言struct型別詳解golang實現unicode轉換為字串string的方法