1.安裝
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ù)的指針传蹈。