Go學(xué)習(xí)語法Golang

1.安裝

https://studygolang.com/dl

2.使用vscode編輯器安裝go插件

3.go語法

_是go的空白標(biāo)識(shí)符唇跨,忽視用的 結(jié)尾不需要";"編譯自動(dòng)加

package main //包名  mian包表示可獨(dú)立執(zhí)行的程序 包名可以不和目錄名一致  每個(gè)目錄一個(gè)包
import "fmt" //導(dǎo)入標(biāo)準(zhǔn)庫包 這個(gè)是目錄路徑  全局 ./相對(duì)目錄  /根目錄查找
/* 第二種導(dǎo)入方法 多行注釋
import fm "fmt" // 別名導(dǎo)入
import (  
    "fmt"
    "os"
 )
 */
 //init特殊的函數(shù),每個(gè)含有該函數(shù)的包都會(huì)首先執(zhí)行這個(gè)函數(shù)
func init(){
    
}
//主執(zhí)行函數(shù)
func main() {
    fmt.Println("hello, world")
}

3.1類型

類型可以是基本類型讯嫂,如:int寞肖、float恬试、bool笋婿、string嵌屎;結(jié)構(gòu)化的(復(fù)合的),如:struct恍涂、array宝惰、slice、map再沧、channel尼夺;只描述類型的行為的,如:interface产园。

結(jié)構(gòu)化的類型沒有真正的值汞斧,它使用 nil 作為默認(rèn)值。Go 語言中不存在類型繼承
一個(gè)函數(shù)可以擁有多返回值什燕,返回類型之間需要使用逗號(hào)分割粘勒,并使用小括號(hào) () 將它們括起來,如:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
return var1, var2

3.2常量

常量的定義格式:const identifier [type] = value
const Pi = 3.14159
在 Go 語言中屎即,你可以省略類型說明符 [type]庙睡,因?yàn)榫幾g器可以根據(jù)變量的值來推斷其類型。
常量還可以用作枚舉:

const (
    Unknown = 0
    Female = 1
    Male = 2
)

在這個(gè)例子中技俐,iota 可以被用作枚舉值:

const (
    a = iota
    b = iota
    c = iota
)

第一個(gè) iota 等于 0乘陪,每當(dāng) iota 在新的一行被使用時(shí),它的值都會(huì)自動(dòng)加 1雕擂;所以 a=0, b=1, c=2 可以簡寫為如下形式:

const (
    a = iota
    b
    c
)

在每遇到一個(gè)新的常量塊或單個(gè)常量聲明時(shí)啡邑, iota 都會(huì)重置為 0

3.3變量

聲明變量的一般形式是使用 var 關(guān)鍵字:var identifier type。

//這種因式分解關(guān)鍵字的寫法一般用于聲明全局變量井赌。
var (
    a int
    b bool
    str string
)

當(dāng)一個(gè)變量被聲明之后谤逼,系統(tǒng)自動(dòng)賦予它該類型的零值:int 為 0,float 為 0.0仇穗,bool 為 false流部,string 為空字符串,指針為 nil纹坐。記住枝冀,所有的內(nèi)存在 Go 中都是經(jīng)過初始化的。
變量的命名規(guī)則遵循駱駝命名法耘子,即首個(gè)單詞小寫果漾,每個(gè)新單詞的首字母大寫,例如:numShips 和 startDate谷誓。
但如果你的全局變量希望能夠被外部包所使用跨晴,則需要將首個(gè)單詞的首字母也大寫
當(dāng)你在函數(shù)體內(nèi)聲明局部變量時(shí),應(yīng)使用簡短聲明語法 :=片林,例如:a := 1

所有像 int、float、bool 和 string 這些基本類型都屬于值類型费封,使用這些類型的變量直接指向存在內(nèi)存中的值:像數(shù)組和結(jié)構(gòu)這些復(fù)合類型也是值類型
當(dāng)使用等號(hào) = 將一個(gè)變量的值賦值給另一個(gè)變量時(shí)焕妙,如:j = i,實(shí)際上是在內(nèi)存中將 i 的值進(jìn)行了拷貝:你可以通過 &i 來獲取變量 i 的內(nèi)存地址

在 Go 語言中弓摘,指針屬于引用類型焚鹊,其它的引用類型還包括 slices,maps和 channel韧献。被引用的變量會(huì)存儲(chǔ)在堆中末患,以便進(jìn)行垃圾回收,且比棧擁有更大的內(nèi)存空間锤窑。
簡短形式璧针,使用 := 賦值操作符
這是使用變量的首選形式,但是它只能被用在函數(shù)體內(nèi)渊啰,而不可以用于全局變量的聲明與賦值探橱。使用操作符 := 可以高效地創(chuàng)建一個(gè)新的變量,稱之為初始化聲明绘证。

3.4基本類型和運(yùn)算符

var b bool = true
Go 擁有以下復(fù)數(shù)類型:
complex64 (32 位實(shí)數(shù)和虛數(shù))
complex128 (64 位實(shí)數(shù)和虛數(shù))
復(fù)數(shù)使用 re+imI 來表示隧膏,其中 re 代表實(shí)數(shù)部分,im 代表虛數(shù)部分嚷那,I 代表根號(hào)負(fù) 1胞枕。

var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)
// 輸出: 5 + 10i

一些像游戲或者統(tǒng)計(jì)學(xué)類的應(yīng)用需要用到隨機(jī)數(shù)。rand 包實(shí)現(xiàn)了偽隨機(jī)數(shù)的生成魏宽。
類型別名
在 type TZ int 中腐泻,TZ 就是 int 類型的新名稱(用于表示程序中的時(shí)區(qū)),然后就可以使用 TZ 來操作 int 類型的數(shù)據(jù)湖员。
實(shí)際上贫悄,類型別名得到的新類型并非和原類型完全相同,新類型不會(huì)擁有原類型所附帶的方法
字符類型
var ch byte = 65 或 var ch byte = '\x41'

3.5字符串

字符串的內(nèi)容(純字節(jié))可以通過標(biāo)準(zhǔn)索引法來獲取娘摔,在中括號(hào) [] 內(nèi)寫入索引窄坦,索引從 0 開始計(jì)數(shù):

  • 字符串 str 的第 1 個(gè)字節(jié):str[0]
  • 第 i 個(gè)字節(jié):str[i - 1]
  • 最后 1 個(gè)字節(jié):str[len(str)-1]
    需要注意的是,這種轉(zhuǎn)換方案只對(duì)純 ASCII 碼的字符串有效凳寺。
    注意事項(xiàng) 獲取字符串中某個(gè)字節(jié)的地址的行為是非法的鸭津,例如:&str[i]。
    在循環(huán)中使用加號(hào) + 拼接字符串并不是最高效的做法肠缨,更好的辦法是使用函數(shù) strings.Join()
    strings 和 strconv 包
    HasPrefix 判斷字符串 s 是否以 prefix 開頭:
    strings.HasPrefix(s, prefix string) bool
    Contains 判斷字符串 s 是否包含 substr:
    strings.Contains(s, substr string) bool
    Index 返回字符串 str 在字符串 s 中的索引(str 的第一個(gè)字符的索引)逆趋,-1 表示字符串 s 不包含字符串 str:
    strings.Index(s, str string) int
    Replace 用于將字符串 str 中的前 n 個(gè)字符串 old 替換為字符串 new,并返回一個(gè)新的字符串晒奕,如果 n = -1 則替換所有字符串 old 為字符串 new:
    strings.Replace(str, old, new, n) string
    strings.ToLower(s) string //換為相應(yīng)的小寫字符
    strings.TrimSpace(s)// 來剔除字符串開頭和結(jié)尾的空白符號(hào)
    strings.Split(s, sep)用于自定義分割符號(hào)來對(duì)指定字符串進(jìn)行分割闻书,同樣返回 slice名斟。
    Join 用于將元素類型為 string 的 slice 使用分割符號(hào)來拼接組成一個(gè)字符串:
    strings.Join(sl []string, sep string) string

3.6時(shí)間和日期

time.Now()

3.7指針

Go 語言的取地址符是 &,放到一個(gè)變量前使用就會(huì)返回相應(yīng)變量的內(nèi)存地址魄眉。

var i1 = 5
fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)
var intP *int
intP = &i1

一個(gè)指針變量可以指向任何一個(gè)值的內(nèi)存地址 它指向那個(gè)值的內(nèi)存地址在 32 位機(jī)器上占用 4 個(gè)字節(jié)砰盐,在 64 位機(jī)器上占用 8 個(gè)字節(jié)
對(duì)于任何一個(gè)變量 var, 如下表達(dá)式都是正確的:var == *(&var)坑律。

4.控制結(jié)構(gòu)

  • if-else 結(jié)構(gòu)
  • switch 結(jié)構(gòu)
  • select 結(jié)構(gòu)哼凯,用于 channel 的選擇

4.1if-else 結(jié)構(gòu)

if condition1 {
    // do something    
} else if condition2 {
    // do something else    
}else {
    // catch-all or default
}
if initialization; condition {
    // do something
}
if value := process(data); value > max {
    ...
}

4.2 多返回值

value, err := pack1.Function1(param1)
anInt, _ = strconv.Atoi(origStr)

4.3 switch結(jié)構(gòu)

switch var1 {
    case val1:
        ...
    case val2,val3,val4:
    case 0: // 空分支柴淘,只有當(dāng) i == 0 時(shí)才會(huì)進(jìn)入分支
    case 0: fallthrough //執(zhí)行下一個(gè)分支的代碼
        ...
    default:
        ...
}

類似 if-else

switch {
    case i < 0:
        f1()
    case i == 0:
        f2()
    case i > 0:
        f3()
}

任何支持進(jìn)行相等判斷的類型都可以作為測試表達(dá)式的條件蛔外,包括 int姆怪、string、指針等宫屠。

//變量 a 和 b 被平行初始化列疗,然后作為判斷條件:
switch a, b := x[i], y[j]; {
    case a < b: t = -1
    case a == b: t = 0
    case a > b: t = 1
}

4.4 for 結(jié)構(gòu)

4.4.1基于計(jì)數(shù)器的迭代

for 初始化語句; 條件語句; 修飾語句 {}

//示例
 for i := 0; i < 5; i++ {
        fmt.Printf("This is the %d iteration\n", i)
    }

特別注意,永遠(yuǎn)不要在循環(huán)體內(nèi)修改計(jì)數(shù)器激况,這在任何語言中都是非常差的實(shí)踐作彤!
您還可以在循環(huán)中同時(shí)使用多個(gè)計(jì)數(shù)器:
for i, j := 0, N; i < j; i, j = i+1, j-1 {}

4.4.2基于條件判斷的迭代

for 結(jié)構(gòu)的第二種形式是沒有頭部的條件判斷迭代(類似其它語言中的 while 循環(huán)),基本形式為:for 條件語句 {}乌逐。

4.4.3無限循環(huán)

條件語句是可以被省略的竭讳,如 i:=0; ; i++ 或 for { } 或 for ;; { }(;; 會(huì)在使用 gofmt 時(shí)被移除):這些循環(huán)的本質(zhì)就是無限循環(huán)。最后一個(gè)形式也可以被改寫為 for true { }浙踢,但一般情況下都會(huì)直接寫 for { }绢慢。
想要直接退出循環(huán)體,可以使用 break 語句或 return 語句直接返回
break 只是退出當(dāng)前的循環(huán)體洛波,而 return 語句提前對(duì)函數(shù)進(jìn)行返回
無限循環(huán)的經(jīng)典應(yīng)用是服務(wù)器胰舆,用于不斷等待和接受新的請(qǐng)求。

for t, err = p.Token(); err == nil; t, err = p.Token() {
    ...
}
4.4.4for-range 結(jié)構(gòu)

這是 Go 特有的一種的迭代結(jié)構(gòu)
語法上很類似其它語言中 foreach 語句
一般形式為:for ix, val := range coll { }蹬挤。
一個(gè)字符串是 Unicode 編碼的字符(或稱之為 rune)集合缚窿,因此您也可以用它迭代字符串:

for pos, char := range str {
...
}

4.5Break 與 continue

break 語句退出當(dāng)前循環(huán)。
關(guān)鍵字 continue 忽略剩余的循環(huán)體而直接進(jìn)入下一次循環(huán)的過程焰扳,但不是無條件執(zhí)行下一次循環(huán)倦零,執(zhí)行之前依舊需要滿足循環(huán)的判斷條件。
另外吨悍,關(guān)鍵字 continue 只能被用于 for 循環(huán)中扫茅。

4.6標(biāo)簽與 goto

for、switch 或 select 語句都可以配合標(biāo)簽(label)形式的標(biāo)識(shí)符使用育瓜,即某一行第一個(gè)以冒號(hào)(:)結(jié)尾的單詞
(標(biāo)簽的名稱是大小寫敏感的葫隙,為了提升可讀性,一般建議使用全部大寫字母)

LABEL1:
    for i := 0; i <= 5; i++ {
        for j := 0; j <= 5; j++ {
            if j == 4 {
                continue LABEL1
            }
            fmt.Printf("i is: %d, and j is: %d\n", i, j)
        }
    }

特別注意 使用標(biāo)簽和 goto 語句是不被鼓勵(lì)的:它們會(huì)很快導(dǎo)致非常糟糕的程序設(shè)計(jì)躏仇,而且總有更加可讀的替代方案來實(shí)現(xiàn)相同的需求恋脚。
如果您必須使用 goto腺办,應(yīng)當(dāng)只使用正序的標(biāo)簽(標(biāo)簽位于 goto 語句之后),但注意標(biāo)簽和 goto 語句之間不能出現(xiàn)定義新變量的語句慧起,否則會(huì)導(dǎo)致編譯失敗菇晃。

5函數(shù)

Go 里面有三種類型的函數(shù):

  • 普通的帶有名字的函數(shù)
  • 匿名函數(shù)或者lambda函數(shù)
  • 方法(Methods)

假設(shè) f1 需要 3 個(gè)參數(shù) f1(a, b, c int),同時(shí) f2 返回 3 個(gè)參數(shù) f2(a, b int) (int, int, int)蚓挤,就可以這樣調(diào)用 f1:f1(f2(a, b))。

函數(shù)重載(function overloading)指的是可以編寫多個(gè)同名函數(shù)驻子,只要它們擁有不同的形參與/或者不同的返回值灿意,在 Go 里面函數(shù)重載是不被允許的。這將導(dǎo)致一個(gè)編譯錯(cuò)誤:
Go 語言不支持這項(xiàng)特性的主要原因是函數(shù)重載需要進(jìn)行多余的類型匹配影響性能崇呵;沒有重載意味著只是一個(gè)簡單的函數(shù)調(diào)度缤剧。所以你需要給不同的函數(shù)使用不同的名字,我們通常會(huì)根據(jù)函數(shù)的特征對(duì)函數(shù)進(jìn)行命名
如果需要申明一個(gè)在外部定義的函數(shù)域慷,你只需要給出函數(shù)名與函數(shù)簽名荒辕,不需要給出函數(shù)體:

func flushICache(begin, end uintptr) // implemented externally
函數(shù)也可以以申明的方式被使用,作為一個(gè)函數(shù)類型犹褒,就像:

type binOp func(int, int) int
在這里抵窒,不需要函數(shù)體 {}。

5.1函數(shù)參數(shù)與返回值

函數(shù)定義時(shí)叠骑,它的形參一般是有名字的李皇,不過我們也可以定義沒有形參名的函數(shù),只有相應(yīng)的形參類型宙枷,就像這樣:func f(int, int, float64)掉房。
沒有參數(shù)的函數(shù)通常被稱為 niladic 函數(shù)(niladic function),就像 main.main()慰丛。

按值傳遞(call by value) 按引用傳遞(call by reference)
Go 默認(rèn)使用按值傳遞來傳遞參數(shù)卓囚,也就是傳遞參數(shù)的副本。函數(shù)接收參數(shù)副本之后诅病,在使用變量的過程中可能對(duì)副本的值進(jìn)行更改哪亿,但不會(huì)影響到原來的變量
在函數(shù)調(diào)用時(shí),像切片(slice)睬隶、字典(map)锣夹、接口(interface)、通道(channel)這樣的引用類型都是默認(rèn)使用引用傳遞(即使沒有顯式的指出指針)苏潜。
如果一個(gè)函數(shù)需要返回四到五個(gè)值银萍,我們可以傳遞一個(gè)切片給函數(shù)(如果返回值具有相同類型)或者是傳遞一個(gè)結(jié)構(gòu)體(如果返回值具有不同的類型)。因?yàn)閭鬟f一個(gè)指針允許直接修改變量的值恤左,消耗也更少贴唇。
命名的返回值(named return variables)
命名返回值作為結(jié)果形參(result parameters)被初始化為相應(yīng)類型的零值搀绣,當(dāng)需要返回的時(shí)候,我們只需要一條簡單的不帶參數(shù)的return語句戳气。需要注意的是链患,即使只有一個(gè)命名返回值,也需要使用 () 括起來

func getX2AndX3(input int) (int, int) {
    return 2 * input, 3 * input
}

func getX2AndX3_2(input int) (x2 int, x3 int) {
    x2 = 2 * input
    x3 = 3 * input
    // return x2, x3
    return
}

盡量使用命名返回值:會(huì)使代碼更清晰瓶您、更簡短麻捻,同時(shí)更加容易讀懂
空白符(blank identifier)
空白符用來匹配一些不需要的值,然后丟棄掉呀袱,
i1, _, f1 = ThreeValues()

5.2傳遞變長參數(shù)

如果函數(shù)的最后一個(gè)參數(shù)是采用 ...type 的形式贸毕,那么這個(gè)函數(shù)就可以處理一個(gè)變長的參數(shù),這個(gè)長度可以為 0夜赵,這樣的函數(shù)稱為變參函數(shù)明棍。
func myFunc(a, b, arg ...int) {}
如果參數(shù)被存儲(chǔ)在一個(gè)數(shù)組 arr 中,則可以通過 arr... 的形式來傳遞參數(shù)調(diào)用變參函數(shù)寇僧。

package main

import "fmt"

func main() {
    x := min(1, 3, 2, 0)
    fmt.Printf("The minimum is: %d\n", x)
    arr := []int{7,9,3,5,1}
    x = min(arr...)
    fmt.Printf("The minimum in the array arr is: %d", x)
}

func min(a ...int) int {
    if len(a)==0 {
        return 0
    }
    min := a[0]
    for _, v := range a {
        if v < min {
            min = v
        }
    }
    return min
}

但是如果變長參數(shù)的類型并不是都相同的呢
1.使用結(jié)構(gòu)

type Options struct {
     par1 type1,
     par2 type2,
     ...
 }

2.使用空接口:
使用默認(rèn)的空接口 interface{}摊腋,這樣就可以接受任何類型的參數(shù)

5.3defer 和追蹤

關(guān)鍵字 defer 允許我們推遲到函數(shù)返回之前(或任意位置執(zhí)行 return 語句之后)一刻才執(zhí)行某個(gè)語句或函數(shù)(為什么要在返回之后才執(zhí)行這些語句?因?yàn)?return 語句同樣可以包含一些操作嘁傀,而不是單純地返回某個(gè)值)兴蒸。

關(guān)鍵字 defer 的用法類似于面向?qū)ο缶幊陶Z言 Java 和 C# 的 finally 語句塊,它一般用于釋放某些已分配的資源心包。
當(dāng)有多個(gè) defer 行為被注冊(cè)時(shí)类咧,它們會(huì)以逆序執(zhí)行(類似棧,即后進(jìn)先出)

func f() {
    for i := 0; i < 5; i++ {
        defer fmt.Printf("%d ", i) //4 3 2 1 0
    }
}

關(guān)鍵字 defer 允許我們進(jìn)行一些函數(shù)執(zhí)行完成后的收尾工作
1.關(guān)閉文件流 defer file.Close()
2.解鎖一個(gè)加鎖的資源
mu.Lock()
defer mu.Unlock()
3.打印最終報(bào)告defer printFooter()
4.關(guān)閉數(shù)據(jù)庫鏈接 defer disconnectFromDB()
使用 defer 語句實(shí)現(xiàn)代碼追蹤

package main

import "fmt"

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

使用 defer 語句來記錄函數(shù)的參數(shù)與返回值

package main

import (
    "io"
    "log"
)

func func1(s string) (n int, err error) {
    defer func() {
        log.Printf("func1(%q) = %d, %v", s, n, err)
    }()
    return 7, io.EOF
}

func main() {
    func1("Go")
}

5.4內(nèi)置函數(shù)

Go 語言擁有一些不需要進(jìn)行導(dǎo)入操作就可以使用的內(nèi)置函數(shù)

  • close 用于管道通信
  • len蟹腾、cap len 用于返回某個(gè)類型的長度或數(shù)量(字符串痕惋、數(shù)組、切片娃殖、map 和管道)值戳;cap 是容量的意思,用于返回某個(gè)類型的最大容量(只能用于切片和 map)
  • new炉爆、make new 和 make 均是用于分配內(nèi)存:new 用于值類型和用戶定義的類型堕虹,如自定義結(jié)構(gòu),make 用于內(nèi)置引用類型(切片芬首、map 和管道)赴捞。它們的用法就像是函數(shù),但是將類型作為參數(shù):new(type)郁稍、make(type)赦政。new(T) 分配類型 T 的零值并返回其地址,也就是指向類型 T 的指針。它也可以被用于基本類型:v := new(int)恢着。make(T) 返回類型 T 的初始化之后的值桐愉,因此它比 new 進(jìn)行更多的工作 new() 是一個(gè)函數(shù),不要忘記它的括號(hào)
  • copy掰派、append 用于復(fù)制和連接切片
  • panic从诲、recover 兩者均用于錯(cuò)誤處理機(jī)制
  • print、println 底層打印函數(shù)靡羡,在部署環(huán)境中建議使用 fmt 包
  • complex系洛、real imag 用于創(chuàng)建和操作復(fù)數(shù)

5.5遞歸函數(shù)

最經(jīng)典的例子便是計(jì)算斐波那契數(shù)列,即前兩個(gè)數(shù)為1略步,從第三個(gè)數(shù)開始每個(gè)數(shù)均為前兩個(gè)數(shù)之和碎罚。

package main

import "fmt"

func main() {
    result := 0
    for i := 0; i <= 10; i++ {
        result = fibonacci(i)
        fmt.Printf("fibonacci(%d) is: %d\n", i, result)
    }
}

func fibonacci(n int) (res int) {
    if n <= 1 {
        res = 1
    } else {
        res = fibonacci(n-1) + fibonacci(n-2)
    }
    return
}

5.6將函數(shù)作為參數(shù)

函數(shù)可以作為其它函數(shù)的參數(shù)進(jìn)行傳遞,然后在其它函數(shù)內(nèi)調(diào)用執(zhí)行纳像,一般稱之為回調(diào)。

package main

import (
    "fmt"
)

func main() {
    callback(1, Add)
}

func Add(a, b int) {
    fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}

func callback(y int, f func(int, int)) {
    f(y, 2) // this becomes Add(1, 2)
}

5.7 閉包

下面是一個(gè)計(jì)算從 1 到 1 百萬整數(shù)的總和的匿名函數(shù):

func() {
    sum := 0
    for i := 1; i <= 1e6; i++ {
        sum += i
    }
}()

defer 語句和匿名函數(shù)
匿名函數(shù)同樣被稱之為閉包
計(jì)算函數(shù)執(zhí)行時(shí)間

start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)

通過內(nèi)存緩存來提升性能
當(dāng)在進(jìn)行大量的計(jì)算時(shí)拯勉,提升性能最直接有效的一種方式就是避免重復(fù)計(jì)算竟趾。通過在內(nèi)存中緩存和重復(fù)利用相同計(jì)算的結(jié)果,稱之為內(nèi)存緩存宫峦。

5.8數(shù)組與切片

數(shù)組是具有相同 唯一類型 的一組已編號(hào)且長度固定的數(shù)據(jù)項(xiàng)序列(這是一種同構(gòu)的數(shù)據(jù)結(jié)構(gòu))
數(shù)組長度必須是一個(gè)常量表達(dá)式岔帽,并且必須是一個(gè)非負(fù)整數(shù)。數(shù)組長度也是數(shù)組類型的一部分导绷,所以[5]int和[10]int是屬于不同類型的
犀勒。數(shù)組的編譯時(shí)值初始化是按照數(shù)組順序完成的
元素的數(shù)目,也稱為長度或者數(shù)組大小必須是固定的并且在聲明該數(shù)組時(shí)就給出(編譯時(shí)需要知道數(shù)組長度以便分配內(nèi)存)妥曲;數(shù)組長度最大為 2Gb贾费。
聲明的格式是:
var identifier [len]type

2種方式遍歷
for i:=0; i < len(arr1); i++{
    arr1[i] = ...
}
for i,_:= range arr1 {
...
}

Go 語言中的數(shù)組是一種 值類型 也就是 =賦值就是拷貝

var arr1 = new([5]int)  //指針類型
    var arr2 [5]int  //值類型

這樣的結(jié)果就是當(dāng)把一個(gè)數(shù)組賦值給另一個(gè)時(shí),需要在做一次數(shù)組內(nèi)存的拷貝操作檐盟。

5.8.1數(shù)組常量

var arrAge = [5]int{18, 20, 15, 22, 16}
var arrLazy = [...]int{5, 6, 7, 8, 22} //從技術(shù)上說它們其實(shí)變化成了切片
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}//只有索引 3 和 4 被賦予實(shí)際的值褂萧,其他元素都被設(shè)置為空的字符串 在這里數(shù)組長度同樣可以寫成 ... 或者直接忽略。
幾何點(diǎn)(或者數(shù)學(xué)向量)是一個(gè)使用數(shù)組的經(jīng)典例子葵萎。為了簡化代碼通常使用一個(gè)別名:

type Vector3D [3]float32
var vec Vector3D

將數(shù)組傳遞給函數(shù)
把一個(gè)大數(shù)組傳遞給函數(shù)會(huì)消耗很多內(nèi)存导犹。有兩種方法可以避免這種現(xiàn)象:

  • 傳遞數(shù)組的指針
  • 使用數(shù)組的切片

5.8.2切片

切片(slice)是對(duì)數(shù)組一個(gè)連續(xù)片段的引用(該數(shù)組我們稱之為相關(guān)數(shù)組,通常是匿名的)羡忘,所以切片是一個(gè)引用類型(因此更類似于 C/C++ 中的數(shù)組類型谎痢,或者 Python 中的 list 類型)。這個(gè)片段可以是整個(gè)數(shù)組卷雕,或者是由起始和終止索引標(biāo)識(shí)的一些項(xiàng)的子集节猿。需要注意的是,終止索引標(biāo)識(shí)的項(xiàng)不包括在切片內(nèi).切片是一個(gè) 長度可變的數(shù)組爽蝴。
優(yōu)點(diǎn) 因?yàn)榍衅且勉迮运鼈儾恍枰褂妙~外的內(nèi)存并且比使用數(shù)組更有效率纫骑,所以在 Go 代碼中 切片比數(shù)組更常用。
聲明切片的格式是: var identifier []type(不需要說明長度)九孩。
一個(gè)切片在未初始化之前默認(rèn)為 nil先馆,長度為 0。
切片的初始化格式是:var slice1 []type = arr1[start:end]
如果某個(gè)人寫:var slice1 []type = arr1[:] 那么 slice1 就等于完整的 arr1 數(shù)組(所以這種表示方式是 arr1[0:len(arr1)] 的一種縮寫)躺彬。另外一種表述方式是:slice1 = &arr1煤墙。
arr1[2:] 和 arr1[2:len(arr1)] 相同,都包含了數(shù)組從第三個(gè)到最后的所有元素宪拥。
arr1[:3] 和 arr1[0:3] 相同仿野,包含了從第一個(gè)到第三個(gè)元素(不包括第三個(gè))。
一個(gè)由數(shù)字 1她君、2脚作、3 組成的切片可以這么生成:s := [3]int{1,2,3}[:] 甚至更簡單的 s := []int{1,2,3}。

s2 := s[:] 是用切片組成的切片缔刹,擁有相同的元素球涛,但是仍然指向相同的相關(guān)數(shù)組。一個(gè)切片 s 可以這樣擴(kuò)展到它的大小上限:s = s[:cap(s)]校镐,如果再擴(kuò)大的話就會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤
注意 絕對(duì)不要用指針指向 slice亿扁。切片本身已經(jīng)是一個(gè)引用類型,所以它本身就是一個(gè)指針!!
將切片傳遞給函數(shù)var arr = [5]int{0, 1, 2, 3, 4} sum(arr[:])
用 make() 創(chuàng)建一個(gè)切片
var slice1 []type = make([]type, len)
make 的使用方式是:func make([]T, len, cap)鸟廓,其中 cap 是可選參數(shù)从祝。
下面兩種方法可以生成相同的切片:

make([]int, 50, 100)
new([100]int)[0:50]

new() 和 make() 的區(qū)別

  • new(T) 為每個(gè)新的類型T分配一片內(nèi)存,初始化為 0 并且返回類型為*T的內(nèi)存地址:這種方法 返回一個(gè)指向類型為 T引谜,值為 0 的地址的指針牍陌,它適用于值類型如數(shù)組和結(jié)構(gòu)體(參見第 10 章);它相當(dāng)于 &T{}煌张。
  • make(T) 返回一個(gè)類型為 T 的初始值呐赡,它只適用于3種內(nèi)建的引用類型:切片、map 和 channel骏融。
    換言之链嘀,new 函數(shù)分配內(nèi)存,make 函數(shù)初始化
    bytes 包
    類型 []byte 的切片十分常見 bytes 包和字符串包十分類似
    Buffer 可以這樣定義:var buffer bytes.Buffer档玻。
    var r *bytes.Buffer = new(bytes.Buffer)
    func NewBuffer(buf []byte) *Buffer
    創(chuàng)建一個(gè) Buffer 對(duì)象并且用 buf 初始化好怀泊;NewBuffer 最好用在從 buf 讀取的時(shí)候使用。
    通過 buffer 串聯(lián)字符串
var buffer bytes.Buffer
for {
    if s, ok := getNextString(); ok { //method getNextString() not shown here
        buffer.WriteString(s)
    } else {
        break
    }
}
fmt.Print(buffer.String(), "\n")

這種實(shí)現(xiàn)方式比使用 += 要更節(jié)省內(nèi)存和 CPU误趴,尤其是要串聯(lián)的字符串?dāng)?shù)目特別多的時(shí)候霹琼。

5.8.3For-range 結(jié)構(gòu)

這種構(gòu)建方法可以應(yīng)用于數(shù)組和切片:

for ix, value := range slice1 {
    ...
}

5.8.4切片重組(reslice)

slice1 := make([]type, start_length, capacity)
改變切片長度的過程稱之為切片重組 reslicing,做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即長度)枣申。

5.8.5字符串售葡、數(shù)組和切片的應(yīng)用

  • 從字符串生成字節(jié)切片
    可以通過代碼 len([]int32(s)) 來獲得字符串中字符的數(shù)量,但使用 utf8.RuneCountInString(s) 效率會(huì)更高一點(diǎn)
  • 獲取字符串的某一部分
    使用 substr := str[start:end] 可以從字符串 str 獲取到從索引 start 開始到 end-1 位置的子字符串忠藤。同樣的挟伙,str[start:] 則表示獲取從 start 開始到 len(str)-1 位置的子字符串。而 str[:end] 表示獲取從 0 開始到 end-1 的子字符串模孩。
  • 字符串和切片的內(nèi)存結(jié)構(gòu)
    在內(nèi)存中尖阔,一個(gè)字符串實(shí)際上是一個(gè)雙字結(jié)構(gòu),即一個(gè)指向?qū)嶋H數(shù)據(jù)的指針和記錄字符串長度的整數(shù)
  • 修改字符串中的某個(gè)字符
    Go 語言中的字符串是不可變的
    將切片 b 的元素追加到切片 a 之后:a = append(a, b...)
    復(fù)制切片 a 的元素到新的切片 b 上:

b = make([]T, len(a))
copy(b, a)
刪除位于索引 i 的元素:a = append(a[:i], a[i+1:]...)

  • 切除切片 a 中從索引 i 至 j 位置的元素:a = append(a[:i], a[j:]...)
  • 為切片 a 擴(kuò)展 j 個(gè)元素長度:a = append(a, make([]T, j)...)
  • 在索引 i 的位置插入元素 x:a = append(a[:i], append([]T{x}, a[i:]...)...)
  • 在索引 i 的位置插入長度為 j 的新切片:a = append(a[:i], -
  • append(make([]T, j), a[i:]...)...)
  • 在索引 i 的位置插入切片 b 的所有元素:a = append(a[:i], append(b, -a[i:]...)...)
  • 取出位于切片 a 最末尾的元素 x:x, a = a[len(a)-1], a[:len(a)-1]
  • 將元素 x 追加到切片 a:a = append(a, x)

    6 Map

map 是一種特殊的數(shù)據(jù)結(jié)構(gòu):一種元素對(duì)(pair)的無序集合榨咐,pair 的一個(gè)元素是 key介却,對(duì)應(yīng)的另一個(gè)元素是 value,所以這個(gè)結(jié)構(gòu)也稱為關(guān)聯(lián)數(shù)組或字典块茁。map 這種數(shù)據(jù)結(jié)構(gòu)在其他編程語言中也稱為字典(Python)齿坷、hash 和 HashTable 等

6.1聲明、初始化和 make

map 是引用類型数焊,可以使用如下聲明:

var map1 map[keytype]valuetype
var map1 map[string]int

map 可以用 {key1: val1, key2: val2} 的描述方法來初始化胃夏,就像數(shù)組和結(jié)構(gòu)體一樣。
map 是 引用類型 的: 內(nèi)存用 make 方法來分配昌跌。
map 的初始化:var map1 = make(map[keytype]valuetype)。
或者簡寫為:map1 := make(map[keytype]valuetype)照雁。
不要使用 new蚕愤,永遠(yuǎn)用 make 來構(gòu)造 map
使用 func() int 作為值的 map:

package main
import "fmt"

func main() {
    mf := map[int]func() int{
        1: func() int { return 10 },
        2: func() int { return 20 },
        5: func() int { return 50 },
    }
    fmt.Println(mf)
}

輸出結(jié)果為:map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0]: 整形都被映射到函數(shù)地址。
用切片作為 map 的值

mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)

6.2 測試鍵值對(duì)是否存在及刪除元素

val1, isPresent = map1[key1]
isPresent 返回一個(gè) bool 值:如果 key1 存在于 map1饺蚊,val1 就是 key1 對(duì)應(yīng)的 value 值萍诱,并且 isPresent為true;如果 key1 不存在污呼,val1 就是一個(gè)空值裕坊,并且 isPresent 會(huì)返回 false。

if _, ok := map1[key1]; ok {
    // ...
}

從 map1 中刪除 key1: 直接 delete(map1, key1) 就可以燕酷。如果 key1 不存在籍凝,該操作不會(huì)產(chǎn)生錯(cuò)誤。

6.3 for-range 的配套用法

可以使用 for 循環(huán)構(gòu)造 map:

for key, value := range map1 {
    ...
}

如果只想獲取 key苗缩,你可以這么使用:

for key := range map1 {
    fmt.Printf("key is: %d\n", key)
}

6.4map 類型的切片

假設(shè)我們想獲取一個(gè) map 類型的切片饵蒂,我們必須使用兩次 make() 函數(shù),第一次分配切片酱讶,第二次分配 切片中每個(gè) map 元素

package main
import "fmt"

func main() {
    // Version A:
    items := make([]map[int]int, 5)
    for i:= range items {
        items[i] = make(map[int]int, 1)
        items[i][1] = 2
    }
    fmt.Printf("Version A: Value of items: %v\n", items)

    // Version B: NOT GOOD!
    items2 := make([]map[int]int, 5)
    for _, item := range items2 {
        item = make(map[int]int, 1) // item is only a copy of the slice element.
        item[1] = 2 // This 'item' will be lost on the next iteration.
    }
    fmt.Printf("Version B: Value of items: %v\n", items2)
}

map 默認(rèn)是無序的退盯,不管是按照 key 還是按照 value 默認(rèn)都不排序
將 map 的鍵值對(duì)調(diào)

7.結(jié)構(gòu)(struct)與方法(method)

結(jié)構(gòu)體是復(fù)合類型(composite types),當(dāng)需要定義一個(gè)類型,它由一系列屬性組成渊迁,每個(gè)屬性都有自己的類型和值的時(shí)候慰照,就應(yīng)該使用結(jié)構(gòu)體
結(jié)構(gòu)體也是值類型,因此可以通過 new 函數(shù)來創(chuàng)建
組成結(jié)構(gòu)體類型的那些數(shù)據(jù)稱為 字段(fields)琉朽。每個(gè)字段都有一個(gè)類型和一個(gè)名字毒租;在一個(gè)結(jié)構(gòu)體中,字段名字必須是唯一的漓骚。

7.1 結(jié)構(gòu)體定義

type identifier struct {
    field1 type1
    field2 type2
    ...
}

type T struct {a, b int} 也是合法的語法蝌衔,它更適用于簡單的結(jié)構(gòu)體。
使用 new
var t *T = new(T)
如果需要可以把這條語句放在不同的行
聲明 var t T 也會(huì)給 t 分配內(nèi)存蝌蹂,并零值化內(nèi)存噩斟,但是這個(gè)時(shí)候 t 是類型T
無論變量是一個(gè)結(jié)構(gòu)體類型還是一個(gè)結(jié)構(gòu)體類型指針,都使用同樣的 選擇器符(selector-notation) 來引用結(jié)構(gòu)體的字段:

type myStruct struct { i int }
var v myStruct    // v是結(jié)構(gòu)體類型變量
var p *myStruct   // p是指向一個(gè)結(jié)構(gòu)體類型變量的指針
v.i
p.i

初始化一個(gè)結(jié)構(gòu)體實(shí)例(一個(gè)結(jié)構(gòu)體字面量:struct-literal)的更簡短和慣用的方式如下:

 ms := &struct1{10, 15.5, "Chris"}
    // 此時(shí)ms的類型是 *struct1
var ms struct1
    ms = struct1{10, 15.5, "Chris"}

&struct1{a, b, c} 是一種簡寫孤个,底層仍然會(huì)調(diào)用 new ()剃允,
這里值的順序必須按照字段順序來寫。
表達(dá)式 new(Type) 和 &Type{} 是等價(jià)的

type Interval struct {
    start int
    end   int
}
 //初始化方式:
intr := Interval{0, 3}            (A)
intr := Interval{end:5, start:1}  (B)
intr := Interval{end:5}           (C)

如果想知道結(jié)構(gòu)體類型T的一個(gè)實(shí)例占用了多少內(nèi)存齐鲤,可以使用:size := unsafe.Sizeof(T{})

7.2 map 和 struct vs new() 和 make()

現(xiàn)在為止我們已經(jīng)見到了可以使用 make() 的三種類型中的其中兩個(gè)
slices / maps / channels
試圖 make() 一個(gè)結(jié)構(gòu)體變量斥废,會(huì)引發(fā)一個(gè)編譯錯(cuò)誤

7.3帶標(biāo)簽的結(jié)構(gòu)體

結(jié)構(gòu)體中的字段除了有名字和類型外,還可以有一個(gè)可選的標(biāo)簽(tag):它是一個(gè)附屬于字段的字符串给郊,可以是文檔或其他的重要標(biāo)記

package main

import (
    "fmt"
    "reflect"
)

type TagType struct { // tags
    field1 bool   "An important answer"
    field2 string "The name of the thing"
    field3 int    "How much there are"
}

func main() {
    tt := TagType{true, "Barak Obama", 1}
    for i := 0; i < 3; i++ {
        refTag(tt, i)
    }
}

func refTag(tt TagType, ix int) {
    ttType := reflect.TypeOf(tt)
    ixField := ttType.Field(ix)
    fmt.Printf("%v\n", ixField.Tag)
}

7.4 匿名字段和內(nèi)嵌結(jié)構(gòu)體

結(jié)構(gòu)體可以包含一個(gè)或多個(gè) 匿名(或內(nèi)嵌)字段牡肉,即這些字段沒有顯式的名字,只有字段的類型是必須的淆九,此時(shí)類型就是字段的名字统锤。
可以粗略地將這個(gè)和面向?qū)ο笳Z言中的繼承概念相比較,隨后將會(huì)看到它被用來模擬類似繼承的行為

package main

import "fmt"

type innerS struct {
    in1 int
    in2 int
}

type outerS struct {
    b    int
    c    float32
    int  // anonymous field
    innerS //anonymous field
}

func main() {
    outer := new(outerS)
    outer.b = 6
    outer.c = 7.5
    outer.int = 60
    outer.in1 = 5
    outer.in2 = 10

    fmt.Printf("outer.b is: %d\n", outer.b)
    fmt.Printf("outer.c is: %f\n", outer.c)
    fmt.Printf("outer.int is: %d\n", outer.int)
    fmt.Printf("outer.in1 is: %d\n", outer.in1)
    fmt.Printf("outer.in2 is: %d\n", outer.in2)

    // 使用結(jié)構(gòu)體字面量
    outer2 := outerS{6, 7.5, 60, innerS{5, 10}}
    fmt.Println("outer2 is:", outer2)
}

在一個(gè)結(jié)構(gòu)體中對(duì)于每一種數(shù)據(jù)類型只能有一個(gè)匿名字段炭庙。
內(nèi)嵌結(jié)構(gòu)體
同樣地結(jié)構(gòu)體也是一種數(shù)據(jù)類型饲窿,所以它也可以作為一個(gè)匿名字段來使用,如同上面例子中那樣焕蹄。
命名沖突
當(dāng)兩個(gè)字段擁有相同的名字(可能是繼承來的名字)時(shí)該怎么辦呢逾雄?

  • 外層名字會(huì)覆蓋內(nèi)層名字(但是兩者的內(nèi)存空間都保留),這提供了一種重載字段或方法的方式腻脏;
  • 如果相同的名字在同一級(jí)別出現(xiàn)了兩次鸦泳,如果這個(gè)名字被程序使用了,將會(huì)引發(fā)一個(gè)錯(cuò)誤(不使用沒關(guān)系)永品。沒有辦法來解決這種問題引起的二義性辽故,必須由程序員自己修正。

7.5方法

在 Go 語言中腐碱,結(jié)構(gòu)體就像是類的一種簡化形式誊垢,那么面向?qū)ο蟪绦騿T可能會(huì)問:類的方法在哪里呢掉弛?在 Go 中有一個(gè)概念,它和方法有著同樣的名字喂走,并且大體上意思相同:Go 方法是作用在接收者(receiver)上的一個(gè)函數(shù)殃饿,接收者是某種類型的變量。因此方法是一種特殊類型的函數(shù)芋肠。

一個(gè)類型加上它的方法等價(jià)于面向?qū)ο笾械囊粋€(gè)類乎芳。一個(gè)重要的區(qū)別是:在 Go 中,類型的代碼和綁定在它上面的方法的代碼可以不放置在一起帖池,它們可以存在在不同的源文件奈惑,唯一的要求是:它們必須是同一個(gè)包的。
類型 T(或 *T)上的所有方法的集合叫做類型 T(或 *T)的方法集睡汹。
不允許方法重載肴甸,即對(duì)于一個(gè)類型只能有一個(gè)給定名稱的方法
有同樣名字的方法可以在 2 個(gè)或多個(gè)不同的接收者類型上存在,比如在同一個(gè)包里這么做是允許的:

func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix

別名類型不能有它原始類型上已經(jīng)定義過的方法囚巴。
定義方法的一般格式如下:
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
如果 recv 一個(gè)指針原在,Go 會(huì)自動(dòng)解引用。如果方法不需要使用 recv 的值彤叉,可以用 _ 替換它庶柿,
recv 就像是面向?qū)ο笳Z言中的 this 或 self,
下面是非結(jié)構(gòu)體類型上方法的例子

package main

import "fmt"

type IntVector []int

func (v IntVector) Sum() (s int) {
    for _, x := range v {
        s += x
    }
    return
}

func main() {
    fmt.Println(IntVector{1, 2, 3}.Sum()) // 輸出是6
}

** 函數(shù)和方法的區(qū)別**
函數(shù)將變量作為參數(shù):Function1(recv)
方法在變量上被調(diào)用:recv.Method1()
receiver_type 叫做 (接收者)基本類型秽浇,這個(gè)類型必須在和方法同樣的包中被聲明浮庐。
方法沒有和數(shù)據(jù)定義(結(jié)構(gòu)體)混在一起:它們是正交的類型;表示(數(shù)據(jù))和行為(方法)是獨(dú)立的柬焕。
指針或值作為接收者
鑒于性能的原因兔辅,recv 最常見的是一個(gè)指向 receiver_type 的指針(因?yàn)槲覀儾幌胍粋€(gè)實(shí)例的拷貝,如果按值調(diào)用的話就會(huì)是這樣)击喂,特別是在 receiver 類型是結(jié)構(gòu)體時(shí),就更是如此了碰辅。

在值和指針上調(diào)用方法:
可以有連接到類型的方法懂昂,也可以有連接到類型指針的方法。
但是這沒關(guān)系:對(duì)于類型 T没宾,如果在 *T 上存在方法 Meth()凌彬,并且 t 是這個(gè)類型的變量,那么 t.Meth() 會(huì)被自動(dòng)轉(zhuǎn)換為 (&t).Meth()循衰。
指針方法和值方法都可以在指針或非指針上被調(diào)用

7.6方法和未導(dǎo)出字段

提供 getter 和 setter 方法铲敛。對(duì)于 setter 方法使用 Set 前綴,對(duì)于 getter 方法只使用成員名会钝。

package person

type Person struct {
    firstName string
    lastName  string
}

func (p *Person) FirstName() string {
    return p.firstName
}

func (p *Person) SetFirstName(newName string) {
    p.firstName = newName
}

可以覆寫方法(像字段一樣):和內(nèi)嵌類型方法具有同樣名字的外層類型的方法會(huì)覆寫內(nèi)嵌類型對(duì)應(yīng)的方法伐蒋。

package main

import (
    "fmt"
    "math"
)

type Point struct {
    x, y float64
}

func (p *Point) Abs() float64 {
    return math.Sqrt(p.x*p.x + p.y*p.y)
}

type NamedPoint struct {
    Point
    name string
}

func (n *NamedPoint) Abs() float64 {
    return n.Point.Abs() * 100.
}
func main() {
    n := &NamedPoint{Point{3, 4}, "Pythagoras"}

    fmt.Println(n.Abs()) // 打印5
}

因?yàn)橐粋€(gè)結(jié)構(gòu)體可以嵌入多個(gè)匿名類型工三,所以實(shí)際上我們可以有一個(gè)簡單版本的多重繼承
結(jié)構(gòu)體內(nèi)嵌和自己在同一個(gè)包中的結(jié)構(gòu)體時(shí),可以彼此訪問對(duì)方所有的字段和方法先鱼。

7.7多重繼承

通過在類型中嵌入所有必要的父類型俭正,可以很簡單的實(shí)現(xiàn)多重繼承。

package main

import (
    "fmt"
)

type Camera struct{}

func (c *Camera) TakeAPicture() string {
    return "Click"
}

type Phone struct{}

func (p *Phone) Call() string {
    return "Ring Ring"
}

type CameraPhone struct {
    Camera
    Phone
}

func main() {
    cp := new(CameraPhone)
    fmt.Println("Our new CameraPhone exhibits multiple behaviors...")
    fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())
    fmt.Println("It works like a Phone too: ", cp.Call())
}

7.8垃圾回收和 SetFinalizer

通過調(diào)用 runtime.GC() 函數(shù)可以顯式的觸發(fā) GC焙畔,但這只在某些罕見的場景下才有用掸读,比如當(dāng)內(nèi)存資源不足時(shí)調(diào)用 runtime.GC(),它會(huì)在此函數(shù)執(zhí)行的點(diǎn)上立即釋放一大片內(nèi)存宏多,
如果想知道當(dāng)前的內(nèi)存狀態(tài)儿惫,可以使用:

// fmt.Printf("%d\n", runtime.MemStats.Alloc/1024)
// 此處代碼在 Go 1.5.1下不再有效,更正為
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("%d Kb\n", m.Alloc / 1024)

8.0接口(Interfaces)與反射(reflection)

接口定義了一組方法(方法集)伸但,但是這些方法不包含(實(shí)現(xiàn))代碼:它們沒有被實(shí)現(xiàn)(它們是抽象的)肾请。接口里也不能包含變量。
通過如下格式定義接口:

type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
    ...
}

(按照約定砌烁,只包含一個(gè)方法的)接口的名字由方法名加 [e]r 后綴組成筐喳,例如 Printer、Reader函喉、Writer避归、Logger、Converter 等等管呵。還有一些不常用的方式(當(dāng)后綴 er 不合適時(shí))梳毙,比如 Recoverable,此時(shí)接口名以 able 結(jié)尾捐下,或者以 I 開頭(像 .NET 或 Java 中那樣)账锹。

8.1類型斷言

一個(gè)接口類型的變量 varI 中可以包含任何類型的值,必須有一種方式來檢測它的 動(dòng)態(tài) 類型坷襟,即運(yùn)行時(shí)在變量中存儲(chǔ)的值的實(shí)際類型
v := varI.(T) // unchecked type assertion
varI 必須是一個(gè)接口變量奸柬,否則編譯器會(huì)報(bào)錯(cuò)
更安全的方式是使用以下形式來進(jìn)行類型斷言:

if v, ok := varI.(T); ok {  // checked type assertion
    Process(v)
    return
}

8.2類型判斷:type-switch

switch t := areaIntf.(type) {
case *Square:
    fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
    fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
    fmt.Printf("nil value: nothing to check?\n")
default:
    fmt.Printf("Unexpected type %T\n", t)
}

可以用 type-switch 進(jìn)行運(yùn)行時(shí)類型分析,但是在 type-switch 不允許有 fallthrough 婴程。

switch areaIntf.(type) {
case *Square:
    // TODO
case *Circle:
    // TODO
...
default:
    // TODO
}

8.3測試一個(gè)值是否實(shí)現(xiàn)了某個(gè)接口

type Stringer interface {
    String() string
}

if sv, ok := v.(Stringer); ok {
    fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}

使用接口使代碼更具有普適性廓奕。

8.4使用方法集與接口

作用于變量上的方法實(shí)際上是不區(qū)分變量到底是指針還是值的
指針調(diào)用值類型方法時(shí)候會(huì) 指針會(huì)被自動(dòng)解引用
在接口上調(diào)用方法時(shí),必須有和方法定義時(shí)相同的接收者類型或者是可以從具體類型 P 直接可以辨識(shí)的:

  • 指針方法可以通過指針調(diào)用
  • 值方法可以通過值調(diào)用
  • 接收者是值的方法可以通過指針調(diào)用档叔,因?yàn)橹羔槙?huì)首先被解引用
  • 接收者是指針的方法不可以通過值調(diào)用桌粉,因?yàn)榇鎯?chǔ)在接口中的值沒有地址

Go 語言規(guī)范定義了接口方法集的調(diào)用規(guī)則:

  • 類型 T 的可調(diào)用方法集包含接受者為 T 或 T 的所有方法集
  • 類型 T 的可調(diào)用方法集包含接受者為 T 的所有方法
  • 類型 T 的可調(diào)用方法集不包含接受者為 *T 的方法

8.5空接口

接口或者最小接口 不包含任何方法,它對(duì)實(shí)現(xiàn)不做任何要求:
type Any interface {}
任何其他類型都實(shí)現(xiàn)了空接口(它不僅僅像 Java/C# 中 Object 引用類型)衙四,any 或 Any 是空接口一個(gè)很好的別名或縮寫铃肯。
每個(gè) interface {} 變量在內(nèi)存中占據(jù)兩個(gè)字長:一個(gè)用來存儲(chǔ)它包含的類型,另一個(gè)用來存儲(chǔ)它包含的數(shù)據(jù)或者指向數(shù)據(jù)的指針传蹈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末押逼,一起剝皮案震驚了整個(gè)濱河市步藕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宴胧,老刑警劉巖漱抓,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恕齐,居然都是意外死亡乞娄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門显歧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仪或,“玉大人,你說我怎么就攤上這事士骤》渡荆” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵拷肌,是天一觀的道長到旦。 經(jīng)常有香客問我,道長巨缘,這世上最難降的妖魔是什么添忘? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮若锁,結(jié)果婚禮上搁骑,老公的妹妹穿的比我還像新娘。我一直安慰自己又固,他們只是感情好仲器,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仰冠,像睡著了一般乏冀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洋只,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天辆沦,我揣著相機(jī)與錄音,去河邊找鬼木张。 笑死,一個(gè)胖子當(dāng)著我的面吹牛端三,可吹牛的內(nèi)容都是我干的舷礼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼郊闯,長吁一口氣:“原來是場噩夢啊……” “哼妻献!你這毒婦竟也來了蛛株?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤育拨,失蹤者是張志新(化名)和其女友劉穎谨履,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熬丧,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笋粟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了析蝴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片害捕。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闷畸,靈堂內(nèi)的尸體忽然破棺而出尝盼,到底是詐尸還是另有隱情,我是刑警寧澤佑菩,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布盾沫,位于F島的核電站,受9級(jí)特大地震影響殿漠,放射性物質(zhì)發(fā)生泄漏赴精。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一凸舵、第九天 我趴在偏房一處隱蔽的房頂上張望祖娘。 院中可真熱鬧,春花似錦啊奄、人聲如沸渐苏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琼富。三九已至,卻和暖如春庄新,著一層夾襖步出監(jiān)牢的瞬間鞠眉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來泰國打工择诈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留械蹋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓羞芍,卻偏偏與公主長得像哗戈,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荷科,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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

  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,212評(píng)論 0 4
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)唯咬,也就是一...
    悟名先生閱讀 4,116評(píng)論 0 13
  • 出處---Go編程語言 歡迎來到 Go 編程語言指南纱注。本指南涵蓋了該語言的大部分重要特性 Go 語言的交互式簡介,...
    Tuberose閱讀 18,399評(píng)論 1 46
  • 憋了一個(gè)冬天 干燥的天空 開始將之前吸走的水汽 挑了個(gè)風(fēng)大的日子 傾吐出不懈的恩澤 這樣濕潤的柳枝 是這春雨的杰作...
    叫我梅芳就好閱讀 244評(píng)論 0 0
  • 這一天,所有的老鼠為它而瘋狂…… 下水道是城市的良心蜀涨。這是長老跟我說的瞎嬉,但我并不能懂他這話是什么意思,長老也不期待...
    空瑾閱讀 305評(píng)論 0 0