閱讀839 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Go語言與數據庫開發:01-04

講完基礎數據類型之後,我們接著學習複合數據類型。

複合數據類型,它是以不同的方式組合基本類型可以構造出來的複合數據類型。


幾個基本的複合數據類型:

數組,是由同構的元素組成——每個數組元素都是完全相同的類型;
結構體,則是由異構的元素組成的;

數組和結構體都是有固定內存大小的數據結構;

slice和map則是動態的數據結構,它們將根據需要動態增長;


數組:

數組是一個由固定長度的特定類型元素組成的序列,一個數組可以由零個或多個元素組成。

因為數組的長度是固定的,因此在Go語言中很少直接使用數組。和數組對應的類型是
Slice(切片),它是可以增長和收縮動態序列,slice功能也更靈活,但是要理解slice工作原
理的話需要先理解數組。

數組的每個元素可以通過索引下標來訪問,索引下標的範圍是從0開始到數組長度減1的位
置。內置的len函數將返回數組中元素的個數。

var a[3] int // array of 3 integers
fmt.Println(a[0]) // print the first element
fmt.Println(a[len(a)-1]) // print the last element, a[2]

// Print the indices and elements.
for i, v := range a {
fmt.Printf("%d %d\n", i, v)
}
// Print the elements only.
for _, v := range a {
fmt.Printf("%d\n", v)
}

默認情況下,數組的每個元素都被初始化為元素類型對應的零值,對於數字類型來說就是0。
var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"

在數組字麵值中,如果在數組的長度位置出現的是“...”省略號,則表示數組的長度是根據初始
化值的個數來計算。因此,上麵q數組的定義可以簡化為
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"

數組的長度必須是常量表達式,因為數組的長度需要在編譯階段確定。

如果一個數組的元素類型是可以相互比較的,那麼數組類型也是可以相互比較的,這時候我
們可以直接通過==比較運算符來比較兩個數組,隻有當兩個數組的所有元素都是相等的時候
數組才是相等的。不相等比較運算符!=遵循同樣的規則。

當調用一個函數的時候,函數的每個調用參數將會被賦值給函數內部的參數變量,所以函數
參數變量接收的是一個複製的副本,並不是原始調用的變量。

因為函數參數傳遞的機製導致傳遞大的數組類型將是低效的,並且對數組參數的任何的修改
都是發生在複製的數組上,並不能直接修改調用時原始的數組變量。在這個方麵,Go語言對
待數組的方式和其它很多編程語言不同,其它編程語言可能會隱式地將數組作為引用或指針
對象傳入被調用的函數。

當然,我們可以顯式地傳入一個數組指針,那樣的話函數通過指針對數組的任何修改都可以
直接反饋到調用者。

雖然通過指針來傳遞數組參數是高效的,而且也允許在函數內部修改數組的值,但是數組依
然是僵化的類型,因為數組的類型包含了僵化的長度信息。

數組依然很少用作函數參數;相反,我們一般使用slice來替代數組。


slice

Slice(切片)代表變長的序列,序列中每個元素都有相同的類型。一個slice類型一般寫作
[]T,其中T代表slice中元素的類型;slice的語法和數組很像,隻是沒有固定長度而已。

數組和slice之間有著緊密的聯係。一個slice是一個輕量級的數據結構,提供了訪問數組子序
列(或者全部)元素的功能,而且slice的底層確實引用一個數組對象。一個slice由三個部分
構成:指針、長度和容量。指針指向第一個slice元素對應的底層數組元素的地址,要注意的
是slice的第一個元素並不一定就是數組的第一個元素。長度對應slice中元素的數目;長度不
能超過容量,容量一般是從slice的開始位置到底層數據的結尾位置。內置的len和cap函數分
別返回slice的長度和容量。

多個slice之間可以共享底層的數據,並且引用的數組部分區間可能重疊。

如果切片操作超出cap(s)的上限將導致一個panic異常,但是超出len(s)則是意味著擴展了
slice,因為新slice的長度會變大。

x[m:n]切片操作對於字符串則生成一個新字符串,如果x是[]byte的話則生成一個新的[]byte。

因為slice值包含指向第一個slice元素的指針,因此向函數傳遞slice將允許在函數內部修改底
層數組的元素。換句話說,複製一個slice隻是對底層的數組創建了一個新的slice別名。

一個零值的slice等於nil。一個nil值的slice並沒有底層數組。一個nil值的slice的長度和容量都
是0,但是也有非nil值的slice的長度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。

我們可以用[]int(nil)類型轉換表達式來生成一個對應類型slice的nil值。

內置的make函數創建一個指定元素類型、長度和容量的slice。容量部分可以省略,在這種情
況下,容量將等於長度。

make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

在底層,make創建了一個匿名的數組變量,然後返回一個slice;隻有通過返回的slice才能引
用底層匿名的數組變量。在第一種語句中,slice是整個數組的view。在第二個語句中,slice
隻引用了底層數組的前len個元素,但是容量將包含整個的數組。額外的元素是留給未來的增
長用的。

內置的append函數用於向slice追加元素:
var runes []rune
for _, r := range "Hello, 世界" {
runes = append(runes, r)
}
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"

為了提高內存使用效率,新分配的數組一般略大於保存x和y所需要的最低大小。通過在每次
擴展數組時直接將長度翻倍從而避免了多次內存分配,也確保了添加單個元素操的平均時間
是一個常數時間。


map

哈希表是一種巧妙並且實用的數據結構。它是一個無序的key/value對的集合,其中所有的key
都是不同的,然後通過給定的key可以在常數時間複雜度內檢索、更新或刪除對應的value。

在Go語言中,一個map就是一個哈希表的引用,map類型可以寫為map[K]V,其中K和V分別
對應key和value。

map中所有的key都有相同的類型,所有的value也有著相同的類型,但是
key和value之間可以是不同的數據類型。

內置的make函數可以創建一個map:
ages := make(map[string]int) // mapping from strings to ints

我們也可以用map字麵值的語法創建map,同時還可以指定一些最初的key/value:
ages := map[string]int{
"alice": 31,
"charlie": 34,
}

另一種創建空的map的表達式是 map[string]int{}

Map中的元素通過key對應的下標語法訪問:
ages["alice"] = 32
fmt.Println(ages["alice"]) // "32"

使用內置的delete函數可以刪除元素:
delete(ages, "alice") // remove element ages["alice"]

但是map中的元素並不是一個變量,因此我們不能對map的元素進行取址操作
Map的迭代順序是不確定的,並且不同的哈希函數實現可能導致不同的遍曆順序。在實踐
中,遍曆的順序是隨機的,每一次遍曆的順序都不相同。

Map的迭代順序是不確定的,並且不同的哈希函數實現可能導致不同的遍曆順序。在實踐
中,遍曆的順序是隨機的,每一次遍曆的順序都不相同。這是故意的,每次都使用隨機的遍
曆順序可以強製要求程序不會依賴具體的哈希函數實現。如果要按順序遍曆key/value對,我
們必須顯式地對key進行排序,可以使用sort包的Strings函數對字符串slice進行排序。

map上的大部分操作,包括查找、刪除、len和range循環都可以安全工作在nil值的map上,它
們的行為和一個空的map類似。但是向一個nil值的map存入元素將導致一個panic異常。

在向map存數據前必須先創建map。

通過key作為索引下標來訪問map將產生一個value。如果key在map中是存在的,那麼將得到
與key對應的value;如果key不存在,那麼將得到value對應類型的零值。


結構體

結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每個值稱為結
構體的成員。

type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
var dilbert Employee

dilbert結構體變量的成員可以通過點操作符訪問。

或者是對成員取地址,然後通過指針訪問:
position := &dilbert.Position
*position = "Senior " + *position // promoted, for outsourcing to Elbonia

點操作符也可以和指向結構體的指針一起工作:
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"
相當於下麵語句
(*employeeOfTheMonth).Position += " (proactive team player)"

如果結構體成員名字是以大寫字母開頭的,那麼該成員就是導出的;這是Go語言導出規則決
定的。一個結構體可能同時包含導出和未導出的成員。

結構體類型往往是冗長的,因為它的每個成員可能都會占一行。

一個命名為S的結構體類型將不能再包含S類型的成員:因為一個聚合的值不能包含它自身。
(該限製同樣適應於數組。)但是S類型的結構體可以包含 *S 指針類型的成員,這可以讓我
們創建遞歸的數據結構,比如鏈表和樹結構等。

結構體類型的零值是每個成員都對是零值如果考慮效率的話,
較大的結構體通常會用指針的方式傳入和返回。

如果要在函數內部修改結構體成員的話,用指針傳入是必須的;因為在Go語言中,所有的函
數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始變量。

因為結構體通常通過指針處理,可以用下麵的寫法來創建並初始化一個結構體變量,並返回
結構體的地址:
pp := &Point{1, 2}

它是下麵的語句是等價的
pp := new(Point)
*pp = Point{1, 2}

匿名成員:

Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字;這類成員就
叫匿名成員。匿名成員的數據類型必須是命名的類型或指向一個命名的類型的指針。

下麵的代碼中,Circle和Wheel各自都有一個匿名成員。我們可以說Point類型被嵌入到了Circle結構
體,同時Circle類型被嵌入到了Wheel結構體。
type Circle struct {
Point
Radius int
}

type Wheel struct {
Circle
Spokes int
}

最後更新:2017-08-15 15:02:45

  上一篇:go  創建UDF的簡單介紹
  下一篇:go  阿裏雲新一代關係型數據庫 PolarDB 剖析