Go語言基礎(chǔ)4 - 數(shù)據(jù)(基本數(shù)據(jù)結(jié)構(gòu))

概述

我們將用幾節(jié)來學(xué)習(xí)Go語言基礎(chǔ)炭晒,本文結(jié)構(gòu)如下:

數(shù)據(jù)
    new 分配
    構(gòu)造函數(shù)與復(fù)合字面
    make 分配
    數(shù)組
    切片
    二維切片
    映射
    打印
    追加
 初始化
    常量
    變量
    init 函數(shù)

數(shù)據(jù)

本節(jié)包含了 Go 為變量分配內(nèi)存的方式执庐,和常用的數(shù)組贰镣,map兩種數(shù)據(jù)結(jié)構(gòu)。

Go提供了兩種分配方式孽惰,即內(nèi)建函數(shù) new 和 make晚岭。

關(guān)鍵點:

  • make 只適用于映射、切片和信道且不返回指針灰瞻。
  • 若要獲得明確的指針腥例, 請使用 new 分配內(nèi)存辅甥。

new 分配

new 函數(shù)格式為: new(T)
特點:它返回一個指針酝润, 該指針指向新分配的,類型為 T 的零值

內(nèi)建函數(shù) new 是個用來分配內(nèi)存的內(nèi)建函數(shù)璃弄, 但與其它語言中的同名函數(shù)不同要销,它不會初始化內(nèi)存,只會將內(nèi)存置零夏块。

Go 的 new比于java的情形是疏咐,java可以通過 new 執(zhí)行構(gòu)造來初始化一個對象,而Go不能初始化(賦初值)脐供,它只能置為”零值“

也就是說浑塞,new(T) 會為類型為 T 的新項分配已置零的內(nèi)存空間, 并返回它的地址政己,也就是一個類型為 *T 的值酌壕。用Go的術(shù)語來說,它返回一個指針, 該指針指向新分配的卵牍,類型為 T 的零值果港。

這樣的設(shè)計,使得無需像Java那樣面對不同對象的豐富多彩的構(gòu)造函數(shù)和參數(shù)糊昙。

既然 new 返回的內(nèi)存已置零辛掠,就不必進一步初始化了,使用者只需用 new 創(chuàng)建一個新的對象就能正常工作释牺。

例如:

  • bytes.Buffer 的文檔中提到“零值的 Buffer 就是已準(zhǔn)備就緒的緩沖區(qū)萝衩。"
  • sync.Mutex 并沒有顯式的構(gòu)造函數(shù)或 Init 方法, 而是零值的 sync.Mutex 就已經(jīng)被定義為已解鎖的互斥鎖了没咙。

p := new(SyncedBuffer) // type *SyncedBuffer
var v SyncedBuffer // type SyncedBuffer

如上的兩種方式欠气,都會分配好內(nèi)存空間,而類型是不同的镜撩。

構(gòu)造函數(shù)與復(fù)合字面

有些場景下预柒,仍然需要一個初始化構(gòu)造函數(shù),就像 os 包中的這段代碼所示:

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    return f
}

上面的代碼過于冗長袁梗。我們可通過復(fù)合字面來簡化它:

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := File{fd, name, nil, 0}
    return &f
}

注意 File{fd, name, nil, 0} 這樣的寫法就是 復(fù)合字面的寫法宜鸯。該表達式在每次求值時都會創(chuàng)建新的實例。

復(fù)合字面的字段必須按順序全部列出遮怜。但如果以 字段:值對的形式明確地標(biāo)出元素淋袖,初始化字段時就可以按任何順序出現(xiàn),未給出的字段值將賦予零值锯梁。 因此即碗,我們可以用如下形式:

return &File{fd: fd, name: name}

make 分配

內(nèi)建函數(shù) make 的格式為: make(T, args)
特點:它只用于創(chuàng)建切片、映射和信道陌凳,并返回類型為 T(而非 *T)的一個已初始化 (而非置零)的值剥懒。

切片、映射和信道 本質(zhì)上為引用數(shù)據(jù)類型合敦,在使用前必須初始化初橘。 例如,切片是一個具有三項內(nèi)容的描述符充岛,包含一個指向(數(shù)組內(nèi)部)數(shù)據(jù)的指針保檐、長度以及容量, 在這三項被初始化之前崔梗,該切片為 nil夜只。

對于切片、映射和信道蒜魄,make 用于初始化其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)并準(zhǔn)備好將要使用的值扔亥。

例如:
make([]int, 10, 100) 分配一個具有100個 int 的數(shù)組空間爪膊,接著創(chuàng)建一個長度為10, 容量為100并指向該數(shù)組中前10個元素的切片結(jié)構(gòu)
new([]int) 會返回一個指向新分配的砸王,已置零的切片結(jié)構(gòu)推盛, 即一個指向 nil 切片值的指針。

下面的例子闡明了 new 和 make 之間的區(qū)別:

var p *[]int = new([]int)       // 分配切片結(jié)構(gòu)谦铃;*p == nil耘成;基本沒用
var v  []int = make([]int, 100) // 切片 v 現(xiàn)在引用了一個具有 100 個 int 元素的新數(shù)組

// 沒必要的復(fù)雜:
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// 習(xí)慣用法:
v := make([]int, 100)

再次說明關(guān)鍵點:

  • make 只適用于映射、切片和信道且不返回指針驹闰。
  • 若要獲得明確的指針瘪菌, 請使用 new 分配內(nèi)存。

數(shù)組

在規(guī)劃內(nèi)存布局時嘹朗,數(shù)組是非常有用的师妙,有時還能避免過多的內(nèi)存分配, 在Go中屹培,數(shù)組主要用作切片的構(gòu)件默穴,在構(gòu)建切片時使用。

數(shù)組在Go和C中的主要區(qū)別褪秀。在Go中:

  • 數(shù)組是值蓄诽。將一個數(shù)組賦予另一個數(shù)組會復(fù)制其所有元素。
  • 若將某個數(shù)組傳入某個函數(shù)媒吗,它將接收到該數(shù)組的一份副本而非指針仑氛。
  • 數(shù)組的大小是其類型的一部分。類型 [10]int 和 [20]int 是不同的闸英。

數(shù)組為值的屬性很有用锯岖,但代價高昂;若你想要C那樣的行為和效率甫何,你可以傳遞一個指向該數(shù)組的指針出吹。

在 Go 中,更習(xí)慣的的用法是使用 切片沛豌。

切片

切片通過對數(shù)組進行封裝趋箩,為有序列的數(shù)據(jù)提供了更通用、強大而方便的方式加派。

除了矩陣變換這類需要明確維度的情況外,Go中的大部分數(shù)組編程都是通過切片來完成的跳芳。

切片保存了對底層數(shù)組的引用芍锦,若你將某個切片賦予另一個切片,它們會引用同一個數(shù)組飞盆。 若某個函數(shù)將一個切片作為參數(shù)傳入娄琉,則它對該切片元素的修改對調(diào)用者而言同樣可見次乓, 這可以理解為傳遞了底層數(shù)組的指針。

修改長度:只要切片不超出底層數(shù)組的限制孽水,它的長度就是可變的票腰,只需產(chǎn)生新的切片再次指向自身變量即可。

切片的長度:

len(切片)

切片的容量可通過內(nèi)建函數(shù) cap 獲得女气,它將給出該切片可取得的最大長度杏慰。函數(shù)為:

  cap(切片)

若數(shù)據(jù)超出其容量,則會重新分配該切片炼鞠。返回值即為所得的切片缘滥。

向切片追加?xùn)|西的很常用,因此有專門的內(nèi)建函數(shù) append谒主。

一般情況下朝扼,如果我們要寫一個 append 方法的話,最終返回值必須返回切片霎肯。示例:

      func Append(slice, data[]byte) []byte {
        l := len(slice)
        if l + len(data) > cap(slice) {  // 重新分配
            // 為了后面的增長擎颖,需分配兩份。
            newSlice := make([]byte, (l+len(data))*2)
            // copy 函數(shù)是預(yù)聲明的观游,且可用于任何切片類型肠仪。
            copy(newSlice, slice)
            slice = newSlice
        }
        slice = slice[0:l+len(data)]
        for i, c := range data {
            slice[l+i] = c
        }
        return slice
    }

如上,輸入?yún)?shù)是切片和插入的元素值备典,返回值是切片异旧,注意切片的長度會發(fā)生變化。
因為盡管 Append 可修改 切片 的元素提佣,但切片自身(其運行時數(shù)據(jù)結(jié)構(gòu)包含指針吮蛹、長度和容量)是通過值傳遞的。

二維切片

要創(chuàng)建等價的二維數(shù)組或切片拌屏,就必須定義一個數(shù)組的數(shù)組潮针, 或切片的切片,示例:

type Transform [3][3]float64  // 一個 3x3 的數(shù)組倚喂,其實是包含多個數(shù)組的一個數(shù)組每篷。
type LinesOfText [][]byte     // 包含多個字節(jié)切片的一個切片。

每行都有其自己的長度:
由于切片長度是可變的端圈,因此其內(nèi)部可能擁有多個不同長度的切片焦读。

映射 (map)

映射 是Go中 數(shù)據(jù)結(jié)構(gòu)中的 map結(jié)構(gòu)實現(xiàn),即 key: value的形式存儲舱权。

映射的值可以是各種類型矗晃。
映射的鍵可以是整數(shù)、浮點數(shù)宴倍、復(fù)數(shù)张症、字符串仓技、指針、接口等俗他。

映射的鍵(或者叫索引)可以是任何相等性操作符支持的類型脖捻, 如整數(shù)、浮點數(shù)兆衅、復(fù)數(shù)地沮、字符串、指針涯保、接口(只要其動態(tài)類型支持相等性判斷)诉濒、結(jié)構(gòu)以及數(shù)組。 切片不能用作映射鍵夕春,因為它們的相等性還未定義未荒。與切片一樣,映射也是引用類型及志。

如果將映射作為參數(shù)傳入函數(shù)中片排,并更改了該映射的內(nèi)容,則此修改對調(diào)用者同樣可見速侈。

映射可使用一般的復(fù)合字面語法進行構(gòu)建率寡,其鍵-值對使用逗號分隔,有點像JSON:

var timeZone = map[string]int{
    "UTC":  0*60*60,
    "EST": -5*60*60,
    "CST": -6*60*60,
    "MST": -7*60*60,
    "PST": -8*60*60,
}

獲取值:

offset := timeZone["EST"]

注意:若試圖通過映射中不存在的鍵來取值倚搬,就會返回與該映射中項的類型對應(yīng)的零值冶共。例如,若某個映射包含整數(shù)每界,當(dāng)查找一個不存在的鍵時會返回 0捅僵。

判斷某個值是否存在:

seconds, ok = timeZone[tz]

上面是慣用的 "逗號 ok” 法:

  • 若 tz 存在, seconds 就會被賦予適當(dāng)?shù)闹嫡2悖?ok 會被置為 true庙楚; - 若不存在,seconds 則會被置為零趴樱,而 ok 會被置為 false馒闷。

若僅需判斷映射中是否存在某項而不關(guān)心實際的值,可使用空白標(biāo)識符 _ 來代替該值的一般變量叁征。

_, present := timeZone[tz]

要刪除映射中的某項纳账,可使用內(nèi)建函數(shù) delete。即便對應(yīng)的鍵不在該映射中航揉,此操作也是安全的塞祈。

delete(timeZone, "PDT")  

打印

Go的格式化打印風(fēng)格和C的 printf 類似,但卻更加豐富而通用帅涂。 這些函數(shù)位于 fmt 包中议薪,且函數(shù)名首字母均為大寫:如 fmt.Printf、fmt.Fprintf媳友,fmt.Sprintf 等斯议。

看例子:

  // 以f 結(jié)尾的這幾個,傳入格式化字符串作為參數(shù), 不換行
fmt.Printf("hello, %v \n","zhang3")
fmt.Fprintf(os.Stdout,"hello, %v \n","zhang3")
str := fmt.Sprintf("hello, %v \n","zhang3")

//下面這幾個醇锚,會換行
fmt.Println(str)
// 注意下面這個哼御,會自動在元素間插入空格
fmt.Fprintln(os.Stdout,"f1","f2","f3")

Sprintf 用于構(gòu)造字符串: 字符串函數(shù)(Sprintf 等)會返回一個字符串,而不是寫入到數(shù)據(jù)流中焊唬。

Fprint 用于寫入到各種流中:fmt.Fprint 一類的格式化打印函數(shù)可接受任何實現(xiàn)了 io.Writer 接口的對象作為第一個實參恋昼;比如 os.Stdout 與 os.Stderr 。

下面對 Printf 支持的格式化的字符做一些說明:
-- 格式: %d
像 %d 不接受表示符號或大小的標(biāo)記赶促, 會根據(jù)實際的類型來決定這些屬性液肌。

var x uint64 = 1<<64 - 1 // x 是無符號整數(shù), 下面的 int64(x) 轉(zhuǎn)換為有符合整數(shù)
fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))

將打印

18446744073709551615 ffffffffffffffff; -1 -1

-- 格式: %v
%v 可理解為 實際的 value鸥滨。
它還能打印任意值嗦哆,甚至包括數(shù)組、結(jié)構(gòu)體和映射婿滓。

fmt.Printf("%v\n", timeZone)  // 或只用 fmt.Println(timeZone)

這會輸出

map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200]

%+v 和 %#v
當(dāng)打印結(jié)構(gòu)體時老速,格式 %+v 會帶上每個字段的字段名,而格式 %#v 會帶上類型凸主。

  type T struct {
    a int
    b float64
    c string
  }
  t := &T{ 7, -2.35, "abc\tdef" }
  fmt.Printf("%v\n", t)
  fmt.Printf("%+v\n", t)
  fmt.Printf("%#v\n", t)

將打印

&{7 -2.35 abc   def} // 請注意其中的&符號
&{a:7 b:-2.35 c:abc     def} // 有了字段名
&main.T{a:7, b:-2.35, c:"abc\tdef"} //有了類型

-- 格式:%q
當(dāng)遇到 string 或 []byte 值時橘券, 可使用 %q 產(chǎn)生帶引號的字符串;而格式 %#q 會盡可能使用反引號卿吐。

--格式:%x
%x 還可用于字符串旁舰、字節(jié)數(shù)組以及整數(shù),并生成一個很長的十六進制字符串但两, 而帶空格的格式(% x)還會在字節(jié)之間插入空格鬓梅。

--格式: %T
它會打印某個值的類型.

fmt.Printf("%T\n", timeZone)

會打印

map[string] int

-- 為結(jié)構(gòu)圖自定義輸出
類似 java 中的 toString(),對結(jié)構(gòu)圖自定義類型的默認格式谨湘,只需為該類型定義一個具有 String() string 簽名的方法绽快。對于我們簡單的類型 T,可進行如下操作紧阔。

func (t *T) String() string {
        return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}

fmt.Printf("%v\n", t)

會打印出如下格式:

7/-2.35/"abc\tdef"

-- 任意數(shù)量的
Printf 的簽名為其最后的實參使用了 ...interface{} 類型坊罢,這樣格式的后面就能出現(xiàn)任意數(shù)量,任意類型的形參了擅耽。

func Printf(format string, v ...interface{}) (n int, err error) {

在 Printf 函數(shù)的實現(xiàn)中活孩,v 看起來更像是 []interface{} 類型的變量,但如果將它傳遞到另一個變參函數(shù)中乖仇,它就像是常規(guī)實參列表了憾儒。實際上询兴,它直接將其實參傳遞給 fmt.Sprintln 進行實際的格式化。

// Println 通過 fmt.Println 的方式將日志打印到標(biāo)準(zhǔn)記錄器起趾。
func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))  // Output 接受形參 (int, string)
}

注意上面的 ...interface{} 和 v... 的寫法诗舰。

追加 ( append 函數(shù) 說明 )

append 函數(shù)的簽名就像這樣:

func append(slice []T, 元素 ...T) []T

其中的 T 為任意給定類型的占位符。實際上训裆,你無法編寫一個類型 T 由調(diào)用者決定的函數(shù)眶根。這也就是為何 append 為內(nèi)建函數(shù)的原因:它需要編譯器的支持。
append 會在切片末尾追加元素并返回結(jié)果边琉。我們必須返回結(jié)果属百, 原因是,底層數(shù)組可能會被改變(注意數(shù)組的長度是類型的一部分)变姨。

以下簡單的例子

x := []int{1,2,3}
x = append(x, 4, 5, 6)
fmt.Println(x)

將打印

[1 2 3 4 5 6]

將一個切片追加到另一個切片很簡單:在調(diào)用的地方使用 ...

x := []int{1,2,3}
y := []int{4,5,6}
x = append(x, y...)
fmt.Println(x)

如果沒有 ...族扰,它就會由于類型錯誤而無法編譯,因為 y 不是 int 類型的钳恕。三個點符號 “ ... ” 的作用有點像“ 展開 ” 的作用别伏,即將 y這個切片的元素放到了這里。

初始化

GO 的huaGo的初始化很強大忧额,在初始化過程中厘肮,不僅可以構(gòu)建復(fù)雜的結(jié)構(gòu),還能正確處理不同包對象間的初始化順序睦番。

常量

常量在編譯時被創(chuàng)建类茂,即便函數(shù)中定義的局部變量也一樣。
常量只能是數(shù)字托嚣、字符(符文)巩检、字符串或布爾值。

由于編譯時的限制示启, 定義它們的表達式必須是可被編譯器求值的常量表達式兢哭。例如 1<<3 就是一個常量表達式。

枚舉常量
枚舉常量使用枚舉器 iota 創(chuàng)建夫嗓。由于 iota 可為表達式的一部分迟螺,而表達式可以被隱式地重復(fù),這樣也就更容易構(gòu)建復(fù)雜的值的集合了舍咖。

  type ByteSize float64

  const (
      // 通過賦予空白標(biāo)識符來忽略第一個值
      _           = iota // ignore first value by assigning to blank identifier
      KB ByteSize = 1 << (10 * iota)
      MB
      GB
      TB
      PB
      EB
      ZB
      YB
  )

變量

變量的初始化與常量類似矩父,但其初始值也可以是在運行時才被計算的一般表達式。

var (
    home   = os.Getenv("HOME")
    user   = os.Getenv("USER")
    gopath = os.Getenv("GOPATH")
)

init 函數(shù)

每個源文件都可以通過定義自己的無參數(shù) init 函數(shù)來設(shè)置一些必要的狀態(tài)排霉。格式為:

func init() { 
    ...
 }

而 init 方法執(zhí)行結(jié)束窍株,就意味著初始化結(jié)束了:只有該包中的所有變量聲明都通過它們的初始化器求值后 init 才會被調(diào)用, 而那些 init 只有在所有已導(dǎo)入的包都被初始化后才會被求值。

init 函數(shù)還常被用在程序真正開始執(zhí)行前球订,檢驗或校正程序的狀態(tài)后裸。示例:

  func init() {
    if user == "" {
        log.Fatal("$USER not set")
    }
    if home == "" {
        home = "/home/" + user
    }
    if gopath == "" {
        gopath = home + "/go"
    }
    // gopath 可通過命令行中的 --gopath 標(biāo)記覆蓋掉。
    flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
  }

END

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辙售,一起剝皮案震驚了整個濱河市轻抱,隨后出現(xiàn)的幾起案子飞涂,更是在濱河造成了極大的恐慌旦部,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件较店,死亡現(xiàn)場離奇詭異士八,居然都是意外死亡,警方通過查閱死者的電腦和手機梁呈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門婚度,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人官卡,你說我怎么就攤上這事蝗茁。” “怎么了寻咒?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵哮翘,是天一觀的道長。 經(jīng)常有香客問我毛秘,道長饭寺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任叫挟,我火速辦了婚禮艰匙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抹恳。我一直安慰自己员凝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布奋献。 她就那樣靜靜地躺著健霹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秽荞。 梳的紋絲不亂的頭發(fā)上骤公,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音扬跋,去河邊找鬼阶捆。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的洒试。 我是一名探鬼主播倍奢,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼垒棋!你這毒婦竟也來了卒煞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤叼架,失蹤者是張志新(化名)和其女友劉穎畔裕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乖订,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡扮饶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乍构。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甜无。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哥遮,靈堂內(nèi)的尸體忽然破棺而出岂丘,到底是詐尸還是另有隱情,我是刑警寧澤眠饮,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布奥帘,位于F島的核電站,受9級特大地震影響君仆,放射性物質(zhì)發(fā)生泄漏翩概。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一返咱、第九天 我趴在偏房一處隱蔽的房頂上張望钥庇。 院中可真熱鬧,春花似錦咖摹、人聲如沸评姨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吐句。三九已至,卻和暖如春店读,著一層夾襖步出監(jiān)牢的瞬間嗦枢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工屯断, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留文虏,地道東北人侣诺。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像氧秘,于是被迫代替她去往敵國和親年鸳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容