title: Go web編程學(xué)習(xí)(一)
date: 2019-05-01 14:02:17
tags:
- study note
- Go web
categories:
- Study
- Go
關(guān)于Go語(yǔ)言Web編程的一些學(xué)習(xí)筆記鹊漠,這是第一篇裤纹,主要是粗略過(guò)一遍Go語(yǔ)言語(yǔ)法今艺,后面會(huì)寫Go web編程的一些東西庄撮。
前言
不知道什么時(shí)候開(kāi)始,整個(gè)人變得浮躁起來(lái)韭畸,沉不下心來(lái)學(xué)東西笛质。其實(shí)早就想靜下心來(lái)學(xué)點(diǎn)東西了炼邀,比如說(shuō)Go語(yǔ)言陆错、Java spring等灯抛,所以這次趁著五一小長(zhǎng)假,來(lái)學(xué)習(xí)一波音瓷。
環(huán)境
這次學(xué)習(xí)我主要是用一本書(shū)对嚼,名字叫Go Web編程,在Gitbook上绳慎,是一本開(kāi)源書(shū)籍纵竖。Go Web編程
編譯器我用的是: Visual Studio Code 2017
Go的版本是: go1.11.5 windows/amd64
話不多說(shuō),開(kāi)始學(xué)吧杏愤。
Go關(guān)鍵字
Go是一門類似C的編譯型語(yǔ)言靡砌,但是它的編譯速度非常快声邦。這門語(yǔ)言的關(guān)鍵字總共二十五個(gè)乏奥,把這二十五個(gè)關(guān)鍵字用會(huì)摆舟,Go語(yǔ)言差不多就入門了吧亥曹。
二十五個(gè)關(guān)鍵字如下:
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
Go包機(jī)制理解
先從hello world講起。先創(chuàng)建一個(gè)hello.go
package main
import "fmt"
func main() {
fmt.Printf("Hello, world or 你好恨诱,世界 or καλημ ?ρα κóσμ or こんにちはせかい\n")
}
這段程序輸出如下:
Hello, world or 你好媳瞪,世界 or καλημ ?ρα κóσμ or こんにちはせかい
現(xiàn)在來(lái)分析一下這段代碼
看到第一行package main
歸納一下就是package 遵從以下規(guī)則。
- package是最基本的分發(fā)單位和工程管理中依賴關(guān)系的體現(xiàn)
- 每個(gè)Go語(yǔ)言源代碼文件開(kāi)頭都必須要有一個(gè)package聲明照宝,表示源代碼文件所屬包
- 要生成Go語(yǔ)言可執(zhí)行程序蛇受,必須要有名為main的package包,且在該包下必須有且只有一個(gè)main函數(shù)
- 同一個(gè)路徑下只能存在一個(gè)package厕鹃,一個(gè)package可以由多個(gè)源代碼文件組成
這個(gè)package機(jī)制還是要搞懂一下的兢仰,不然程序運(yùn)行起來(lái)莫名出錯(cuò)乍丈。這里我也不知道該怎么表達(dá)清楚(水平有限233),只能放兩個(gè)例子幫助大家理解把将。
第一個(gè)例子: 生成使用自己的package
在GOPATH\src
目錄下轻专,新建test文件夾,test文件夾里新建test.go
package test
import "fmt"
func Test() {
fmt.Printf("this is package test test.go\n")
}
注意察蹲,這里第一行是package test
,說(shuō)明這是一個(gè)包文件请垛,它有一個(gè)函數(shù)命名是Test,首字母大寫了洽议,這里說(shuō)明一下宗收,在Go語(yǔ)言里,有一個(gè)簡(jiǎn)單的規(guī)則:如果一個(gè)名字是大寫字母開(kāi)頭的亚兄,那么該名字是導(dǎo)出的(漢字不區(qū)分大小寫混稽,因此漢字開(kāi)頭的名字是沒(méi)有導(dǎo)出的)。
這是一個(gè)簡(jiǎn)單的包文件审胚,命令行在test文件夾下輸入 go install
即可編譯生成test.a包文件荚坞。
然后就能在別的go程序里使用它了。如在
GOPATH\src\hello
文件夾下main.go文件中導(dǎo)入調(diào)用該包及其Test()函數(shù)菲盾。第二個(gè)例子: 同一目錄下的多文件package
在GOPATH\src\hello
文件夾下颓影,創(chuàng)建兩個(gè)文件,分別是main.go懒鉴、hello.go代碼分別如下
main.go
package main
import (
"fmt"
"test"
)
func test1() {
fmt.Printf("Hello, world or 你好诡挂,世界 or καλημ ?ρα κóσμ or こんにちはせかい\n")
}
func main() {
test.Test()
test1()
hello()
}
hello.go
package main
import "fmt"
func hello() {
fmt.Printf("hello, this is hello.go\n")
}
這里main.go程序下調(diào)用了hello.go里的函數(shù),這里的hello.go類似于C中的.h文件临谱,我們可以命令行在hello文件夾內(nèi)輸入go build .
,以此生成hello.exe璃俗,然后執(zhí)行∠つ可以看到城豁,生成的hello.exe執(zhí)行能調(diào)用hello.go里的函數(shù),這是同一路徑多文件package的情況抄课。
Go基礎(chǔ)
變量類型
這里講講定義變量唱星、常量、Go內(nèi)置類型以及Go程序設(shè)計(jì)中的一些技巧跟磨。
Go語(yǔ)言的數(shù)據(jù)類型有
整型:
uint8间聊、uint16、uint32抵拘、uint64哎榴、int8、int16、int32尚蝌、int64
浮點(diǎn)數(shù):
float32迎变、float64
復(fù)數(shù):
complex64、complex128
分別對(duì)應(yīng)float32飘言、float64兩種浮點(diǎn)數(shù)精度
內(nèi)置的complex函數(shù)用來(lái)構(gòu)建復(fù)數(shù)氏豌,內(nèi)置的real、imag函數(shù)分別返回復(fù)數(shù)的實(shí)部和虛部
var x complex128 = complex(1,2) //1+2i
var y complex128 = complex(3,4) //3+4i
fmt.Println(real(x*y)) //-5
fmt.Println(imag(x*y)) //10
布爾型:
只能是true热凹、false
字符串:
字符串就是一串固定長(zhǎng)度的字符連接起來(lái)的字符序列泵喘。Go 的字符串是由單個(gè)字節(jié)連接起來(lái)的。Go 語(yǔ)言的字符串的字節(jié)使用 UTF-8 編碼標(biāo)識(shí) Unicode 文本般妙。
用雙引號(hào)""
或反引號(hào)``定義纪铺,反引號(hào)可用于定義多行字符串類型是string。
具有l(wèi)en方法碟渺、切片鲜锚、+號(hào)連接等操作
使用var聲明方法:var s string = "miracle778"
字符串是不可修改的,如果非要修改的話苫拍,可以先轉(zhuǎn)為byte數(shù)組芜繁,然后再更改需要改的單位字符。如下圖
錯(cuò)誤類型:error
Go內(nèi)置有一個(gè)error類型绒极,專門用來(lái)處理錯(cuò)誤信息骏令,Go的package里面還專門有一個(gè)包errors來(lái)處理錯(cuò)誤
其他類型如iota枚舉類型,就不講了垄提,用的時(shí)候網(wǎng)上找找就好了榔袋。
變量定義方式
一般聲明,使用var <變量名> <變量類型>
進(jìn)行聲明
如:
var str string = "字符串" //字符串變量
var num uint32 = 77777 //uint32類型變量
簡(jiǎn)短聲明铡俐,使用<變量名>:=<表達(dá)式>
來(lái)聲明初始化變量凰兑。它會(huì)根據(jù)表達(dá)式自動(dòng)推導(dǎo)變量的類型。
因?yàn)楹?jiǎn)短靈活的特點(diǎn)审丘,簡(jiǎn)短變量聲明被廣泛用于大部分的局部變量聲明和初始化吏够。而var形式的聲明往往適用于需要顯示指明變量類型的地方,或者因?yàn)樽兞可院髸?huì)被重新賦值而初始值無(wú)關(guān)緊要的地方
變量定義方式可以一次定義多個(gè)變量滩报,like python
var a,b,c int = 1,2,3
a,b,c := 1,2,3
變量要注意的點(diǎn)
下劃線
_
是個(gè)特殊的變量名锅知,任何賦予它的值都會(huì)被丟棄
如:_,a = 7,8
這里將8賦給a,并同時(shí)丟棄7Go對(duì)于已聲明但未使用的變量會(huì)在編譯階段報(bào)錯(cuò)
Go語(yǔ)言的一些默認(rèn)規(guī)則
- 大寫字母開(kāi)頭的變量是可導(dǎo)出的露泊,也就是其它包可以讀取的喉镰,是公用變量旅择;小寫字母開(kāi)頭的就是不可導(dǎo)出的惭笑,是私有變量。
- 大寫字母開(kāi)頭的函數(shù)也是一樣,相當(dāng)于class中的帶public關(guān)鍵詞的公有函數(shù)沉噩;小寫字母開(kāi)頭的就是有private關(guān)鍵詞的私有函數(shù)捺宗。
Array 數(shù)組
數(shù)組定義方式
-
var聲明
var <name> [length]<type>
如:var arr [7]int
通過(guò)var聲明后,數(shù)組各項(xiàng)元素默認(rèn)為0或空
通過(guò)var聲明并初始化
var arr = [4]string{"a","b"}
-
簡(jiǎn)短聲明
a := [3]int{1, 2, 3} // 聲明了一個(gè)長(zhǎng)度為3的int數(shù)組 b := [10]int{1, 2, 3} // 聲明了一個(gè)長(zhǎng)度為10的int數(shù)組川蒙,其中前三個(gè)元素初始化為1、2、3旋恼,其它默認(rèn)為0 c := [...]int{4, 5, 6} // 可以省略長(zhǎng)度而采用`...`的方式泛鸟,Go會(huì)自動(dòng)根據(jù)元素個(gè)數(shù)來(lái)計(jì)算長(zhǎng)度
數(shù)組初始化的時(shí)候可以使用
索引:值
的形式賦值,如下圖
二維數(shù)組聲明
a:=[2][3]int{{1,2},{3,4,5}}
數(shù)組可以像python一樣切片
關(guān)于數(shù)組的一些容易混淆的點(diǎn)
由于長(zhǎng)度也是數(shù)組類型的一部分康聂,因此[3]int與[4]int是不同的類型贰健,數(shù)組也就不能改變長(zhǎng)度。數(shù)組之間的賦值是值的賦值恬汁,即當(dāng)把一個(gè)數(shù)組作為參數(shù)傳入函數(shù)的時(shí)候伶椿,傳入的其實(shí)是該數(shù)組的副本,而不是它的指針氓侧。
slice 動(dòng)態(tài)數(shù)組
slice并不是真正意義上的動(dòng)態(tài)數(shù)組脊另,而是一個(gè)引用類型。slice總是指向一個(gè)底層array约巷,slice的聲明也可以像array一樣偎痛,只是不需要長(zhǎng)度。
slice var聲明舉例 var a = []byte{'a','b','c'}
,簡(jiǎn)短聲明 a := []int
slice可以從一個(gè)數(shù)組或一個(gè)已經(jīng)存在的slice中再次聲明独郎。
// 聲明一個(gè)含有10個(gè)元素元素類型為byte的數(shù)組
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 聲明兩個(gè)含有byte的slice
var a, b []byte
// a指向數(shù)組的第3個(gè)元素開(kāi)始看彼,并到第五個(gè)元素結(jié)束,
a = ar[2:5]
//現(xiàn)在a含有的元素: ar[2]囚聚、ar[3]和ar[4]
// b是數(shù)組ar的另一個(gè)slice
b = ar[3:5]
// b的元素是:ar[3]和ar[4]
slice的一些切片操作靖榕,跟python類似
// 聲明一個(gè)數(shù)組
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 聲明兩個(gè)slice
var aSlice, bSlice []byte
// 演示一些簡(jiǎn)便操作
aSlice = array[:3] // 等價(jià)于aSlice = array[0:3] aSlice包含元素: a,b,c
aSlice = array[5:] // 等價(jià)于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
aSlice = array[:] // 等價(jià)于aSlice = array[0:10] 這樣aSlice包含了全部的元素
// 從slice中獲取slice
aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4顽铸,cap=7
bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
bSlice = aSlice[0:5] // 對(duì)slice的slice可以在cap范圍內(nèi)擴(kuò)展茁计,此時(shí)bSlice包含:d,e,f,g,h
bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g
slice是引用類型,所以當(dāng)引用改變其中元素的值時(shí)谓松,其它的所有引用都會(huì)改變?cè)撝敌茄梗缟厦娴腶Slice和bSlice,如果修改了aSlice中元素的值鬼譬,那么bSlice相對(duì)應(yīng)的值也會(huì)改變娜膘。
對(duì)于slice有幾個(gè)有用的內(nèi)置函數(shù):
- len 獲取slice的長(zhǎng)度
- cap 獲取slice的最大容量
- append 向slice里面追加一個(gè)或者多個(gè)元素,然后返回一個(gè)和slice一樣類型的slice
- copy 函數(shù)copy從源slice的src中復(fù)制元素到目標(biāo)dst优质,并且返回復(fù)制的元素的個(gè)數(shù)
對(duì)于上面4個(gè)內(nèi)置函數(shù)竣贪,len函數(shù)自然不用多說(shuō)军洼,返回slice長(zhǎng)度。長(zhǎng)度和最大容量之間關(guān)系如下圖示
下面演示下另外三個(gè)函數(shù)的用法
其中特別注意append函數(shù)
append函數(shù)會(huì)改變slice所引用的數(shù)組的內(nèi)容演怎,從而影響到引用同一數(shù)組的其它slice匕争。 但當(dāng)slice中沒(méi)有剩余空間(即(cap-len) == 0)時(shí),此時(shí)將動(dòng)態(tài)分配新的數(shù)組空間爷耀。返回的slice數(shù)組指針將指向這個(gè)空間甘桑,而原數(shù)組的內(nèi)容將保持不變;其它引用此數(shù)組的slice則不受影響歹叮。
上面這段話意思如下圖示跑杭,當(dāng)b slice使用append添加元素后,如果添加后的長(zhǎng)度小于cap最大長(zhǎng)度時(shí)咆耿,原數(shù)組中對(duì)應(yīng)位置元素也發(fā)生變化艘蹋。如下面a數(shù)組,append函數(shù)執(zhí)行后發(fā)生改變票灰。
上面那段話還提到當(dāng)使用append函數(shù)slice沒(méi)有剩余空間時(shí)女阀,此時(shí)動(dòng)態(tài)分配新的數(shù)組空間,返回的slice數(shù)組指針將指向這個(gè)空間屑迂,原數(shù)組內(nèi)容不變浸策,其他引用原數(shù)組的slice不受影響
copy函數(shù)主要是切片(slice)的拷貝,不支持?jǐn)?shù)組惹盼。將第二個(gè)slice里的元素拷貝到第一個(gè)slice里庸汗,拷貝的長(zhǎng)度為兩個(gè)slice中長(zhǎng)度較小的長(zhǎng)度值
此外還需介紹下append函數(shù)的三種用法。
slice := append([]int{1,2,3},4,5,6) fmt.Println(slice) //[1 2 3 4 5 6]
slice := append([]int{1,2,3},[]int{4,5,6}...) //末尾記住加三個(gè)點(diǎn) fmt.Println(slice) //[1 2 3 4 5 6]
- 特殊用法手报,將字符串當(dāng)作[]byte類型作為第二個(gè)參數(shù)傳入
bytes := append([]byte("hello"),"world"...)
append函數(shù)返回值必須要有變量接受蚯舱,不然會(huì)報(bào)錯(cuò)
map
map也就是Python中字典的概念,它的格式為map[keyType]valueType
聲明方式
-
用var聲明掩蛤,使用前需要用make初始化
-
簡(jiǎn)單聲明
初始化
var dict = map[int]string{0:"miracle"}
dict := map[int]string{0:"Miracle778"}
使用map過(guò)程中注意下面幾點(diǎn)
- map是無(wú)序的枉昏,每次打印出來(lái)的map都會(huì)不一樣,它不能通過(guò)index獲取揍鸟,而必須通過(guò)key獲取
- map的長(zhǎng)度是不固定的兄裂,也就是和slice一樣,也是一種引用類型
- 內(nèi)置的len函數(shù)同樣適用于map阳藻,返回map擁有的key的數(shù)量
- map的值可以很方便的修改晰奖,通過(guò)numbers["one"]=11可以很容易的把key為one的字典值改為11
- map和其他基本型別不同,它不是thread-safe腥泥,在多個(gè)go-routine存取時(shí)匾南,必須使用mutex lock機(jī)制
map的初始化可以通過(guò)key:val
的方式初始化值,同時(shí)map內(nèi)置有判斷是否存在key的方式
// 初始化一個(gè)字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有兩個(gè)返回值蛔外,第二個(gè)返回值蛆楞,如果不存在key溯乒,那么ok為false,如果存在ok為true
csharpRating, ok := rating["C#"]
if ok {
fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
fmt.Println("We have no rating associated with C# in the map")
}
delete(rating, "C") // 刪除key為C的元素
上面說(shuō)過(guò)了臊岸,map也是一種引用類型橙数,如果兩個(gè)map同時(shí)指向一個(gè)底層尊流,那么一個(gè)改變帅戒,另一個(gè)也相應(yīng)的改變:
m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // 現(xiàn)在m["hello"]的值已經(jīng)是Salut了
make、new操作
make用于內(nèi)建類型(map崖技、slice 和channel)的內(nèi)存分配逻住。new用于各種類型的內(nèi)存分配。
區(qū)別是new返回一個(gè)指針迎献,make返回引用
內(nèi)建函數(shù)new本質(zhì)上說(shuō)跟其它語(yǔ)言中的同名函數(shù)功能一樣:new(T)分配了零值填充的T類型的內(nèi)存空間瞎访,并且返回其地址,即一個(gè)*T類型的值吁恍。用Go的術(shù)語(yǔ)說(shuō)扒秸,它返回了一個(gè)指針,指向新分配的類型T的零值冀瓦。
內(nèi)建函數(shù)make(T, args)與new(T)有著不同的功能伴奥,make只能創(chuàng)建slice、map和channel翼闽,并且返回一個(gè)有初始值(非零)的T類型拾徙,而不是*T。本質(zhì)來(lái)講感局,導(dǎo)致這三個(gè)類型有所不同的原因是指向數(shù)據(jù)結(jié)構(gòu)的引用在使用前必須被初始化尼啡。例如,一個(gè)slice询微,是一個(gè)包含指向數(shù)據(jù)(內(nèi)部array)的指針崖瞭、長(zhǎng)度和容量的三項(xiàng)描述符;在這些項(xiàng)目被初始化之前撑毛,slice為nil读恃。對(duì)于slice、map和channel來(lái)說(shuō)代态,make初始化了內(nèi)部的數(shù)據(jù)結(jié)構(gòu)寺惫,填充適當(dāng)?shù)闹怠?/p>
make兩個(gè)用法
- map
dict := make(map[int]string)
dict[0],dict[1] = "name","passwd"
- slice
slice := make([]int,2,6)
//返回長(zhǎng)度為2 容量為6的slice
下圖說(shuō)明了make、new區(qū)別
零值
羅列部分類型的零值
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0 //rune的實(shí)際類型是 int32
byte 0x0 // byte的實(shí)際類型是 uint8
float32 0 //長(zhǎng)度為 4 byte
float64 0 //長(zhǎng)度為 8 byte
bool false
string ""
Go流程和函數(shù)
if語(yǔ)句
Go的if語(yǔ)句不需要括號(hào),Go的if還有一個(gè)強(qiáng)大的地方就是條件判斷語(yǔ)句里面允許聲明一個(gè)變量蹦疑,這個(gè)變量的作用域只能在該條件邏輯塊內(nèi)西雀,其他地方就不起作用了,如下面代碼
// 計(jì)算獲取值x,然后根據(jù)x返回的大小歉摧,判斷是否大于10艇肴。
if x := computedValue(); x > 10 {
fmt.Println("x is greater than 10")
} else { //else的位置要在 {同一行
fmt.Println("x is less than 10")
}
//這個(gè)地方如果這樣調(diào)用就編譯出錯(cuò)了腔呜,因?yàn)閤是條件里面的變量
fmt.Println(x)
多條件if
if integer == 3 {
fmt.Println("The integer is equal to 3")
} else if integer < 3 {
fmt.Println("The integer is less than 3")
} else {
fmt.Println("The integer is greater than 3")
}
goto語(yǔ)句
用法,注:標(biāo)簽名是大小寫敏感的
func myFunc() {
i := 0
Here: //這行的第一個(gè)詞再悼,以冒號(hào)結(jié)束作為標(biāo)簽
println(i)
i++
goto Here //跳轉(zhuǎn)到Here去
}
for語(yǔ)句
Go語(yǔ)言的for語(yǔ)句既可以用來(lái)循環(huán)讀取數(shù)據(jù)核畴,又可以當(dāng)作while來(lái)控制邏輯,還能迭代操作冲九。
- 循環(huán)讀數(shù)據(jù)
package main
import "fmt"
func main(){
sum := 0;
for index:=0; index < 10 ; index++ {
sum += index
}
fmt.Println("sum is equal to ", sum)
}
// 輸出:sum is equal to 45
- 當(dāng)while用
sum := 1
for sum < 1000 {
sum += sum
}
- 配合range 迭代
for k,v:=range map {
fmt.Println("map's key:",k)
fmt.Println("map's val:",v)
}
這里因?yàn)镚o 支持 “多值返回”, 而對(duì)于“聲明而未被調(diào)用”的變量, 編譯器會(huì)報(bào)錯(cuò), 在這種情況下, 可以使用_來(lái)丟棄不需要的返回值
for _, v := range map{
fmt.Println("map's val:", v)
}
switch語(yǔ)句
Go的switch非常靈活谤草,表達(dá)式不必是常量或整數(shù),執(zhí)行的過(guò)程從上至下莺奸,直到找到匹配項(xiàng)丑孩;而如果switch沒(méi)有表達(dá)式,它會(huì)匹配true
i := 10
switch i {
case 1:
fmt.Println("i is equal to 1")
case 2, 3, 4:
fmt.Println("i is equal to 2, 3 or 4")
case 10:
fmt.Println("i is equal to 10")
default:
fmt.Println("All I know is that i is an integer")
}
Go里面switch默認(rèn)相當(dāng)于每個(gè)case最后帶有break灭贷,匹配成功后不會(huì)自動(dòng)向下執(zhí)行其他case温学,而是跳出整個(gè)switch, 但是可以使用fallthrough強(qiáng)制執(zhí)行后面的case代碼。
integer := 6
switch integer {
case 4:
fmt.Println("The integer was <= 4")
fallthrough
case 5:
fmt.Println("The integer was <= 5")
fallthrough
case 6:
fmt.Println("The integer was <= 6")
fallthrough
case 7:
fmt.Println("The integer was <= 7")
fallthrough
case 8:
fmt.Println("The integer was <= 8")
fallthrough
default:
fmt.Println("default case")
}
//輸出
//The integer was <= 6
//The integer was <= 7
//The integer was <= 8
//default case
函數(shù)
聲明
聲明格式如下
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//這里是處理邏輯代碼
//返回多個(gè)值
return value1, value2
}
從上面的代碼可以看出
- 關(guān)鍵字func用來(lái)聲明一個(gè)函數(shù)funcName
- 函數(shù)可以有一個(gè)或者多個(gè)參數(shù)甚疟,每個(gè)參數(shù)后面帶有類型仗岖,通過(guò),分隔
- 函數(shù)可以返回多個(gè)值
- 上面返回值聲明了兩個(gè)變量output1和output2,如果你不想聲明也可以览妖,直接就兩個(gè)類型
- 如果只有一個(gè)返回值且不聲明返回值變量轧拄,那么你可以省略 包括返回值 的括號(hào)
- 如果沒(méi)有返回值,那么就直接省略最后的返回信息
- 如果有返回值黄痪, 那么必須在函數(shù)的外層添加return語(yǔ)句
下面一個(gè)簡(jiǎn)單代碼示例
package main
import "fmt"
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
a, b := 1, 7
fmt.Printf("Max(%d,%d):%d", a, b, max(a, b))
}
多個(gè)返回值
Go語(yǔ)言比C更先進(jìn)的特性紧帕,其中一點(diǎn)就是函數(shù)能夠返回多個(gè)值。
package main
import "fmt"
//返回 A+B 和 A*B
func SumAndProduct(A, B int) (int, int) {
return A+B, A*B
}
func main() {
x := 3
y := 4
xPLUSy, xTIMESy := SumAndProduct(x, y)
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}
上面的例子我們可以看到直接返回了兩個(gè)參數(shù)桅打,當(dāng)然我們也可以命名返回參數(shù)的變量是嗜,這個(gè)例子里面只是用了兩個(gè)類型,我們也可以改成如下這樣的定義挺尾,然后返回的時(shí)候不用帶上變量名鹅搪,因?yàn)橹苯釉诤瘮?shù)里面初始化了。但如果你的函數(shù)是導(dǎo)出的(首字母大寫)遭铺,官方建議:最好命名返回值丽柿,因?yàn)椴幻祷刂担m然使得代碼更加簡(jiǎn)潔了魂挂,但是會(huì)造成生成的文檔可讀性差甫题。
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A+B
Multiplied = A*B
return
}
變參
Go函數(shù)支持變參。接受變參的函數(shù)是有著不定數(shù)量的參數(shù)的涂召。為了做到這點(diǎn)坠非,首先需要定義函數(shù)使其接受變參:
func varArg(arg ...int){}
arg ...int
告訴Go這個(gè)函數(shù)接受不定數(shù)量的參數(shù)(可以把int換為別的類型)。注意果正,這些參數(shù)的類型全部是int炎码。在函數(shù)體中盟迟,變量arg是一個(gè)int的slice:
傳值與傳指針
傳值實(shí)際上是傳了這個(gè)值的一份copy,當(dāng)在被調(diào)用函數(shù)中修改參數(shù)值的時(shí)候潦闲,調(diào)用函數(shù)中相應(yīng)實(shí)參不會(huì)發(fā)生任何變化攒菠,因?yàn)閿?shù)值變化只作用在copy上。
而傳指針能更改參數(shù)的值歉闰,看下面代碼
package main
import "fmt"
//簡(jiǎn)單的一個(gè)函數(shù)辖众,實(shí)現(xiàn)了參數(shù)+1的操作
func add1(a *int) int { // 請(qǐng)注意,
*a = *a+1 // 修改了a的值
return *a // 返回新值
}
func main() {
x := 3
fmt.Println("x = ", x) // 應(yīng)該輸出 "x = 3"
x1 := add1(&x) // 調(diào)用 add1(&x) 傳x的地址
fmt.Println("x+1 = ", x1) // 應(yīng)該輸出 "x+1 = 4"
fmt.Println("x = ", x) // 應(yīng)該輸出 "x = 4"
}
變量在內(nèi)存中是存放于一定地址上的新娜,修改變量實(shí)際是修改變量地址處的內(nèi)存赵辕。只有add1函數(shù)知道x變量所在的地址既绩,才能修改x變量的值概龄。所以我們需要將x所在地址&x傳入函數(shù),并將函數(shù)的參數(shù)的類型由int改為*int饲握,即改為指針類型私杜,才能在函數(shù)中修改x變量的值。此時(shí)參數(shù)仍然是按copy傳遞的救欧,只是copy的是一個(gè)指針衰粹。
傳指針有什么好處
- 傳指針使得多個(gè)函數(shù)能操作同一個(gè)對(duì)象。
- 傳指針比較輕量級(jí) (8bytes),只是傳內(nèi)存地址笆怠,我們可以用指針傳遞體積大的結(jié)構(gòu)體铝耻。如果用參數(shù)值傳遞的話, 在每次copy上面就會(huì)花費(fèi)相對(duì)較多的系統(tǒng)開(kāi)銷(內(nèi)存和時(shí)間)。所以當(dāng)你要傳遞大的結(jié)構(gòu)體的時(shí)候蹬刷,用指針是一個(gè)明智的選擇瓢捉。
- Go語(yǔ)言中channel,slice办成,map這三種類型的實(shí)現(xiàn)機(jī)制類似指針泡态,所以可以直接傳遞,而不用取地址后傳遞指針迂卢。(注:若函數(shù)需改變slice的長(zhǎng)度某弦,則仍需要取地址傳遞指針)
defer
延遲(defer)語(yǔ)句,你可以在函數(shù)中添加多個(gè)defer語(yǔ)句而克。當(dāng)函數(shù)執(zhí)行到最后時(shí)靶壮,這些defer語(yǔ)句會(huì)按照逆序執(zhí)行,最后該函數(shù)返回员萍。特別是當(dāng)你在進(jìn)行一些打開(kāi)資源的操作時(shí)腾降,遇到錯(cuò)誤需要提前返回,在返回前你需要關(guān)閉相應(yīng)的資源充活,不然很容易造成資源泄露等問(wèn)題蜂莉。
下面是defer在打開(kāi)文件的應(yīng)用場(chǎng)景抄腔。
未使用defer
func ReadWrite() bool {
file.Open("file")
// 做一些工作
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return true
}
使用defer后萄焦,代碼變得簡(jiǎn)潔
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
如果有很多調(diào)用defer,那么defer是采用后進(jìn)先出模式,所以如下代碼會(huì)輸出4 3 2 1 0
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
函數(shù)作為值類型
Go中函數(shù)也是一種變量爱谁,我們可以通過(guò)type來(lái)定義它,它的類型就是所有擁有相同的參數(shù)憔购,相同的返回值的一種類型
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
如下面例子
package main
import "fmt"
type testInt func(int) bool // 聲明了一個(gè)函數(shù)類型
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// 聲明的函數(shù)類型在這個(gè)地方當(dāng)做了一個(gè)參數(shù)
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // 函數(shù)當(dāng)做值來(lái)傳遞了
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven) // 函數(shù)當(dāng)做值來(lái)傳遞了
fmt.Println("Even elements of slice are: ", even)
}
函數(shù)當(dāng)做值和類型在我們寫一些通用接口的時(shí)候非常有用焦匈,通過(guò)上面例子我們看到testInt這個(gè)類型是一個(gè)函數(shù)類型,然后兩個(gè)filter函數(shù)的參數(shù)和返回值與testInt類型是一樣的辕录,但是我們可以實(shí)現(xiàn)很多種的邏輯睦霎,這樣使得我們的程序變得非常的靈活。
Panic和Recover
Go沒(méi)有像Java那樣的異常機(jī)制走诞,它不能拋出異常副女,而是使用了panic和recover機(jī)制。一定要記住蚣旱,你應(yīng)當(dāng)把它作為最后的手段來(lái)使用碑幅,也就是說(shuō),你的代碼中應(yīng)當(dāng)沒(méi)有塞绿,或者很少有panic的東西沟涨。這是個(gè)強(qiáng)大的工具,請(qǐng)明智地使用它异吻。那么裹赴,我們應(yīng)該如何使用它呢?
Panic
是一個(gè)內(nèi)建函數(shù)诀浪,可以中斷原有的控制流程棋返,進(jìn)入一個(gè)令人恐慌的流程中。當(dāng)函數(shù)F調(diào)用panic笋妥,函數(shù)F的執(zhí)行被中斷懊昨,但是F中的延遲函數(shù)會(huì)正常執(zhí)行,然后F返回到調(diào)用它的地方春宣。在調(diào)用的地方酵颁,F(xiàn)的行為就像調(diào)用了panic。這一過(guò)程繼續(xù)向上月帝,直到發(fā)生panic的goroutine中所有調(diào)用的函數(shù)返回躏惋,此時(shí)程序退出∪赂ǎ恐慌可以直接調(diào)用panic產(chǎn)生簿姨。也可以由運(yùn)行時(shí)錯(cuò)誤產(chǎn)生,例如訪問(wèn)越界的數(shù)組。
Recover
是一個(gè)內(nèi)建的函數(shù)扁位,可以讓進(jìn)入令人恐慌的流程中的goroutine恢復(fù)過(guò)來(lái)准潭。recover僅在延遲函數(shù)中有效。在正常的執(zhí)行過(guò)程中域仇,調(diào)用recover會(huì)返回nil刑然,并且沒(méi)有其它任何效果。如果當(dāng)前的goroutine陷入恐慌暇务,調(diào)用recover可以捕獲到panic的輸入值泼掠,并且恢復(fù)正常的執(zhí)行。
Go中可以拋出一個(gè)panic的異常垦细,然后在defer中通過(guò)recover捕獲這個(gè)異常择镇,然后正常處理。
下面是一個(gè)例子
package main
import "fmt"
func main() {
defer func() { // 必須要先聲明defer括改,否則不能捕獲到panic異常
fmt.Println("c")
if err := recover(); err != nil {
fmt.Println(err) // 這里的err其實(shí)就是panic傳入的內(nèi)容腻豌,55
}
fmt.Println("d")
}() //匿名函數(shù)
f()
}
func f() {
fmt.Println("a")
panic(55)
fmt.Println("b")
fmt.Println("f")
}
recover必須定義在panic之前的defer語(yǔ)句中。在這種情況下叹谁,當(dāng)panic被觸發(fā)時(shí)饲梭,該goroutine不會(huì)簡(jiǎn)單的終止乘盖,而是會(huì)執(zhí)行在它之前定義的defer語(yǔ)句焰檩。
上面代碼輸出結(jié)果為
a
c
55
d
main、init函數(shù)
Go里面有兩個(gè)保留的函數(shù):init函數(shù)(能夠應(yīng)用于所有的package)和main函數(shù)(只能應(yīng)用于package main)订框。這兩個(gè)函數(shù)在定義時(shí)不能有任何的參數(shù)和返回值析苫。雖然一個(gè)package里面可以寫任意多個(gè)init函數(shù),但這無(wú)論是對(duì)于可讀性還是以后的可維護(hù)性來(lái)說(shuō)穿扳,我們都強(qiáng)烈建議用戶在一個(gè)package中每個(gè)文件只寫一個(gè)init函數(shù)衩侥。
Go程序會(huì)自動(dòng)調(diào)用init()和main(),所以你不需要在任何地方調(diào)用這兩個(gè)函數(shù)矛物。每個(gè)package中的init函數(shù)都是可選的茫死,但package main就必須包含一個(gè)main函數(shù)。
下面是代碼示例
mypkg包履羞,里面有兩個(gè)init函數(shù)
package mypkg
import (
"fmt"
)
var I int
func init() {
I = 0
fmt.Println("Call mypackage init1")
}
func init() {
I = 1
fmt.Println("Call mypackage init2")
}
main.go
package main
import (
"fmt"
mypkg "initFunc"
)
func main() {
fmt.Println("Hello go.... I = ", mypkg.I)
}
上面代碼中main函數(shù)不用介紹在所有語(yǔ)言中都一樣峦萎,它作為一個(gè)程序的入口,只能有一個(gè)忆首。init函數(shù)在每個(gè)package是可選的爱榔,可有可無(wú),甚至可以有多個(gè)(但是強(qiáng)烈建議一個(gè)package中一個(gè)init函數(shù))糙及,init函數(shù)在你導(dǎo)入該package時(shí)程序會(huì)自動(dòng)調(diào)用init函數(shù)详幽,所以init函數(shù)不用我們手動(dòng)調(diào)用,l另外它只會(huì)被調(diào)用一次,因?yàn)楫?dāng)一個(gè)package被多次引用時(shí),它只會(huì)被導(dǎo)入一次唇聘。
上面代碼執(zhí)行結(jié)果
Call mypackage init1
Call mypackage init2
Hello go.... I = 1
struct類型
struct聲明
Go語(yǔ)言中版姑,也和C或者其他語(yǔ)言一樣,我們可以聲明新的類型迟郎,作為其它類型的屬性或字段的容器漠酿。
例如,我們可以創(chuàng)建一個(gè)自定義類型person代表一個(gè)人的實(shí)體谎亩。這個(gè)實(shí)體擁有屬性:姓名和年齡炒嘲。這樣的類型我們稱之struct。如下代碼所示:
type person {
name string
age int
}
使用結(jié)構(gòu)體
type person struct {
name string
age int
}
var P person // P現(xiàn)在就是person類型的變量了
P.name = "Astaxie" // 賦值"Astaxie"給P的name屬性.
P.age = 25 // 賦值"25"給變量P的age屬性
fmt.Printf("The person's name is %s", P.name) // 訪問(wèn)P的name屬性.
另外的幾種聲明方式
- 按照順序提供初始化值
p := person{"miracle778",21}
- 按照
field:value
的方式初始化匈庭,這樣可以任意順序
p:= person{age:17,name:"miracle778"}
- 通過(guò)new分配一個(gè)指針,此處的p類型為 *person
p := new(person)
完整使用struct的例子
package main
import "fmt"
// 聲明一個(gè)新的類型
type person struct {
name string
age int
}
// 比較兩個(gè)人的年齡夫凸,返回年齡大的那個(gè)人,并且返回年齡差
// struct也是傳值的
func Older(p1, p2 person) (person, int) {
if p1.age>p2.age { // 比較p1和p2這兩個(gè)人的年齡
return p1, p1.age-p2.age
}
return p2, p2.age-p1.age
}
func main() {
var tom person
// 賦值初始化
tom.name, tom.age = "Tom", 18
// 兩個(gè)字段都寫清楚的初始化
bob := person{age:25, name:"Bob"}
// 按照struct定義順序初始化值
paul := person{"Paul", 43}
tb_Older, tb_diff := Older(tom, bob)
tp_Older, tp_diff := Older(tom, paul)
bp_Older, bp_diff := Older(bob, paul)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, bob.name, tb_Older.name, tb_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, paul.name, tp_Older.name, tp_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
bob.name, paul.name, bp_Older.name, bp_diff)
}
struct的匿名字段
上面介紹了如何定義一個(gè)struct阱持,定義的時(shí)候是字段名與其類型一一對(duì)應(yīng)夭拌,實(shí)際上Go支持只提供類型,而不寫字段名的方式衷咽,也就是匿名字段鸽扁,也稱為嵌入字段。
當(dāng)匿名字段是一個(gè)struct的時(shí)候镶骗,那么這個(gè)struct所擁有的全部字段都被隱式地引入了當(dāng)前定義的這個(gè)struct桶现。
如下面例子第11行,匿名字段為struct
package main
import "fmt"
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名字段鼎姊,那么默認(rèn)Student就包含了Human的所有字段
speciality string
}
func main() {
// 我們初始化一個(gè)學(xué)生
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
// 我們?cè)L問(wèn)相應(yīng)的字段
fmt.Println("His name is ", mark.name)
fmt.Println("His age is ", mark.age)
fmt.Println("His weight is ", mark.weight)
fmt.Println("His speciality is ", mark.speciality)
// 修改對(duì)應(yīng)的備注信息
mark.speciality = "AI"
fmt.Println("Mark changed his speciality")
fmt.Println("His speciality is ", mark.speciality)
// 修改他的年齡信息
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
// 修改他的體重信息
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}
上面代碼骡和,Student可以訪問(wèn)屬性age和name,實(shí)現(xiàn)了字段的繼承相寇。
此外student還能訪問(wèn)Human這個(gè)字段作為字段名慰于。如下面代碼
mark.Human = Human{"Marcus", 55, 220}
mark.Human.age -= 1
所有的內(nèi)置類型和自定義類型都是可以作為匿名字段的。請(qǐng)看下面的例子
package main
import "fmt"
type Skills []string //Skills 用slice類型唤衫,可以通過(guò)append函數(shù)修改增加
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名字段婆赠,struct
Skills // 匿名字段,自定義的類型string slice
int // 內(nèi)置類型作為匿名字段
speciality string
}
func main() {
// 初始化學(xué)生Jane
jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
// 現(xiàn)在我們來(lái)訪問(wèn)相應(yīng)的字段
fmt.Println("Her name is ", jane.name)
fmt.Println("Her age is ", jane.age)
fmt.Println("Her weight is ", jane.weight)
fmt.Println("Her speciality is ", jane.speciality)
// 我們來(lái)修改他的skill技能字段
jane.Skills = []string{"anatomy"}
fmt.Println("Her skills are ", jane.Skills)
fmt.Println("She acquired two new ones ")
jane.Skills = append(jane.Skills, "physics", "golang")
fmt.Println("Her skills now are ", jane.Skills)
// 修改匿名內(nèi)置類型字段
jane.int = 3
fmt.Println("Her preferred number is", jane.int)
}
當(dāng)繼承的字段名重復(fù)了的時(shí)候佳励,最外層的優(yōu)先訪問(wèn)休里,利用這個(gè)特性,可以實(shí)現(xiàn)重載功能植兰。
如下例
package main
import "fmt"
type Human struct {
name string
age int
phone string // Human類型擁有的字段
}
type Employee struct {
Human // 匿名字段Human
speciality string
phone string // 雇員的phone字段
}
func main() {
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
fmt.Println("Bob's work phone is:", Bob.phone)
// 如果我們要訪問(wèn)Human的phone字段
fmt.Println("Bob's personal phone is:", Bob.Human.phone)
}
面向?qū)ο?/h1>
上面結(jié)構(gòu)體有匿名字段繼承等操作份帐,如果把函數(shù)當(dāng)成struct的字段處理,就是面向?qū)ο蟆?br>
函數(shù)的另一種形態(tài)楣导,帶有接收者的函數(shù)废境,我們稱為method
method聲明
method
是附屬在一個(gè)給定的類型上的,他的語(yǔ)法和函數(shù)的聲明語(yǔ)法幾乎一樣,只是在func后面增加了一個(gè)receiver(也就是method所依從的主體)噩凹。
method聲明方法如下
func (r ReceiverType) funcName(parameters) (results)
下面看個(gè)例子
package main
import (
"fmt"
"math"
)
type Rectangle struct {
width, height float64
}
type Circle struct {
radius float64
}
func (r Rectangle) area() float64 { //接受者是Rectangle類型
return r.width*r.height
}
func (c Circle) area() float64 { //接受者是Circle類型
return c.radius * c.radius * math.Pi
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
c1 := Circle{10}
c2 := Circle{25}
fmt.Println("Area of r1 is: ", r1.area())
fmt.Println("Area of r2 is: ", r2.area())
fmt.Println("Area of c1 is: ", c1.area())
fmt.Println("Area of c2 is: ", c2.area())
}
使用method時(shí)注意幾點(diǎn)
- 雖然method的名字一模一樣巴元,但是如果接收者不一樣,那么method就不一樣
- method里面可以訪問(wèn)接收者的字段
- 調(diào)用method通過(guò).訪問(wèn)驮宴,就像struct里面訪問(wèn)字段一樣
在上例逮刨,method area() 分別屬于Rectangle和Circle, 于是他們的 Receiver 就變成了Rectangle 和 Circle, 或者說(shuō)堵泽,這個(gè)area()方法 是由 Rectangle/Circle 發(fā)出的修己。
值得說(shuō)明的一點(diǎn)是,圖示中method用虛線標(biāo)出迎罗,意思是此處方法的Receiver是以值傳遞睬愤,而非引用傳遞,是的纹安,Receiver還可以是指針, 兩者的差別在于, 指針作為Receiver會(huì)對(duì)實(shí)例對(duì)象的內(nèi)容發(fā)生操作,而普通類型作為Receiver僅僅是以副本作為操作對(duì)象,并不對(duì)原實(shí)例對(duì)象發(fā)生操作尤辱,下面對(duì)此會(huì)有詳細(xì)論述。
receiver為指針
為了說(shuō)明 指針作為receiver的用法厢岂,下面放一個(gè)復(fù)雜點(diǎn)例子
package main
import "fmt"
const(
WHITE = iota
BLACK
BLUE
RED
YELLOW
)
type Color byte
type Box struct {
width, height, depth float64
color Color
}
type BoxList []Box //a slice of boxes
func (b Box) Volume() float64 {
return b.width * b.height * b.depth
}
func (b *Box) SetColor(c Color) {
b.color = c
}
func (bl BoxList) BiggestColor() Color {
v := 0.00
k := Color(WHITE)
for _, b := range bl {
if bv := b.Volume(); bv > v {
v = bv
k = b.color
}
}
return k
}
func (bl BoxList) PaintItBlack() {
for i, _ := range bl {
bl[i].SetColor(BLACK)
}
}
func (c Color) String() string {
strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
return strings[c]
}
func main() {
boxes := BoxList {
Box{4, 4, 4, RED},
Box{10, 10, 1, YELLOW},
Box{1, 1, 20, BLACK},
Box{10, 10, 1, BLUE},
Box{10, 30, 1, WHITE},
Box{20, 20, 20, YELLOW},
}
fmt.Printf("We have %d boxes in our set\n", len(boxes))
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm3")
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
fmt.Println("The biggest one is", boxes.BiggestColor().String())
fmt.Println("Let's paint them all black")
boxes.PaintItBlack()
fmt.Println("The color of the second one is", boxes[1].color.String())
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
}
上面代碼的第25行光督,SetColor method的receiver是 Box,這里是因?yàn)橐淖傿ox的屬性塔粒,所以用*Box结借。那26行按道理應(yīng)該是*b.color = c
這樣寫,但是上面代碼卻寫成了b.color = c
,事實(shí)上窗怒,在Go語(yǔ)言中映跟,兩種寫法都行。當(dāng)你用指針去訪問(wèn)相應(yīng)的字段時(shí)(雖然指針沒(méi)有任何的字段)扬虚,Go知道你要通過(guò)指針去獲取這個(gè)值。
我們又看到43行的語(yǔ)句球恤,在PaintItBlack里面調(diào)用SetColor的時(shí)候是不是應(yīng)該寫成(&bl[i]).SetColor(BLACK)
辜昵,因?yàn)镾etColor的receiver是Box,而不是Box咽斧。但示例代碼寫成了bl[i].SetColor(BLACK)
堪置。事實(shí)上,這兩種寫法都行张惹。因?yàn)镚o知道receiver是指針舀锨,他自動(dòng)幫你轉(zhuǎn)了。
也就是說(shuō)
如果一個(gè)method的receiver是*T,你可以在一個(gè)T類型的實(shí)例變量V上面調(diào)用這個(gè)method宛逗,而不需要&V去調(diào)用這個(gè)method
類似的
如果一個(gè)method的receiver是T坎匿,你可以在一個(gè)T類型的變量P上面調(diào)用這個(gè)method,而不需要 P去調(diào)用這個(gè)method
所以,你不用擔(dān)心你是調(diào)用的指針的method還是不是指針的method替蔬,Go知道你要做的一切告私。
通俗講就是,不管method聲明的時(shí)候是func (var *struct) name() {}
還是func (var struct) name() {}
,調(diào)用的時(shí)候var.name()
可以通用承桥。(個(gè)人理解驻粟,可能有誤)
method繼承
method也是可以繼承的。如果匿名字段實(shí)現(xiàn)了一個(gè)method凶异,那么包含這個(gè)匿名字段的struct也能調(diào)用該method蜀撑。
如下例
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段
school string
}
type Employee struct {
Human //匿名字段
company string
}
//在human上面定義了一個(gè)method
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
method 重寫
和上面struct匿名字段沖突一樣的道理,我們可以在Employee上面定義一個(gè)method剩彬,重寫了匿名字段的方法屯掖。
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段
school string
}
type Employee struct {
Human //匿名字段
company string
}
//Human定義method
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Employee的method重寫Human的method
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
規(guī)則
通過(guò)這些內(nèi)容,我們可以設(shè)計(jì)出基本的面向?qū)ο蟮某绦蛄私笏ィ荊o里面的面向?qū)ο笫侨绱说暮?jiǎn)單贴铜,沒(méi)有任何的私有、公有關(guān)鍵字瀑晒,通過(guò)大小寫來(lái)實(shí)現(xiàn)(大寫開(kāi)頭的為公有绍坝,小寫開(kāi)頭的為私有),方法也同樣適用這個(gè)原則苔悦。
interface
什么是interface
簡(jiǎn)單的說(shuō)轩褐,interface是一組method的組合,我們通過(guò)interface來(lái)定義對(duì)象的一組行為玖详。
上面method重寫例子中Student和Employee都能SayHi把介,雖然他們的內(nèi)部實(shí)現(xiàn)不一樣,但是那不重要蟋座,重要的是他們都能say hi
讓我們來(lái)繼續(xù)做更多的擴(kuò)展拗踢,Student和Employee實(shí)現(xiàn)另一個(gè)方法Sing,然后Student實(shí)現(xiàn)方法BorrowMoney而Employee實(shí)現(xiàn)SpendSalary向臀。
這樣Student實(shí)現(xiàn)了三個(gè)方法:SayHi巢墅、Sing、BorrowMoney券膀;而Employee實(shí)現(xiàn)了SayHi君纫、Sing、SpendSalary芹彬。
上面這些方法的組合稱為interface(被對(duì)象Student和Employee實(shí)現(xiàn))蓄髓。例如Student和Employee都實(shí)現(xiàn)了interface:SayHi和Sing,也就是這兩個(gè)對(duì)象是該interface類型舒帮。而Employee沒(méi)有實(shí)現(xiàn)這個(gè)interface:SayHi会喝、Sing和BorrowMoney陡叠,因?yàn)镋mployee沒(méi)有實(shí)現(xiàn)BorrowMoney這個(gè)方法。
interface類型
interface類型定義了一組方法好乐,如果某個(gè)對(duì)象實(shí)現(xiàn)了某個(gè)接口的所有方法匾竿,則此對(duì)象就實(shí)現(xiàn)了此接口。詳細(xì)的語(yǔ)法參考下面這個(gè)例子
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段Human
school string
loan float32
}
type Employee struct {
Human //匿名字段Human
company string
money float32
}
//Human對(duì)象實(shí)現(xiàn)Sayhi方法
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Human對(duì)象實(shí)現(xiàn)Sing方法
func (h *Human) Sing(lyrics string) {
fmt.Println("La la, la la la, la la la la la...", lyrics)
}
//Human對(duì)象實(shí)現(xiàn)Guzzle方法
func (h *Human) Guzzle(beerStein string) {
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}
// Employee重載Human的Sayhi方法
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //此句可以分成多行
}
//Student實(shí)現(xiàn)BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
s.loan += amount // (again and again and...)
}
//Employee實(shí)現(xiàn)SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
e.money -= amount // More vodka please!!! Get me through the day!
}
// 定義interface
type Men interface {
SayHi()
Sing(lyrics string)
Guzzle(beerStein string)
}
type YoungChap interface {
SayHi()
Sing(song string)
BorrowMoney(amount float32)
}
type ElderlyGent interface {
SayHi()
Sing(song string)
SpendSalary(amount float32)
}
interface值
如果我們定義了一個(gè)interface的變量蔚万,那么這個(gè)變量里面可以存實(shí)現(xiàn)這個(gè)interface的任意類型的對(duì)象岭妖。
例如上面例子中,我們定義了一個(gè)Men interface類型的變量m反璃,那么m里面可以存Human昵慌、Student或者Employee值
因?yàn)閙能夠持有這三種類型的對(duì)象,所以我們可以定義一個(gè)包含Men類型元素的slice淮蜈,這個(gè)slice可以被賦予實(shí)現(xiàn)了Men接口的任意結(jié)構(gòu)的對(duì)象斋攀,這個(gè)和我們傳統(tǒng)意義上面的slice有所不同。
如下面例子
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段
school string
loan float32
}
type Employee struct {
Human //匿名字段
company string
money float32
}
//Human實(shí)現(xiàn)SayHi方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Human實(shí)現(xiàn)Sing方法
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
//Employee重載Human的SayHi方法
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone)
}
// Interface Men被Human,Student和Employee實(shí)現(xiàn)
// 因?yàn)檫@三個(gè)類型都實(shí)現(xiàn)了這兩個(gè)方法
type Men interface {
SayHi()
Sing(lyrics string)
}
func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
//定義Men類型的變量i
var i Men
//i能存儲(chǔ)Student
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
//i也能存儲(chǔ)Employee
i = tom
fmt.Println("This is tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
//定義了slice Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
//這三個(gè)都是不同類型的元素梧田,但是他們實(shí)現(xiàn)了interface同一個(gè)接口
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x{
value.SayHi()
}
}
空interface
空interface(interface{})不包含任何的method淳蔼,正因?yàn)槿绱耍械念愋投紝?shí)現(xiàn)了空interface裁眯○睦妫空interface對(duì)于描述起不到任何的作用(因?yàn)樗话魏蔚膍ethod),但是空interface在我們需要存儲(chǔ)任意類型的數(shù)值的時(shí)候相當(dāng)有用穿稳,因?yàn)樗梢源鎯?chǔ)任意類型的數(shù)值存皂。它有點(diǎn)類似于C語(yǔ)言的void*類型。
空interface使用示例
// 定義a為空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存儲(chǔ)任意類型的數(shù)值
a = i
a = s
一個(gè)函數(shù)把interface{}作為參數(shù)逢艘,那么他可以接受任意類型的值作為參數(shù)旦袋,如果一個(gè)函數(shù)返回interface{},那么也就可以返回任意類型的值遭贸。
interface函數(shù)參數(shù)
interface的變量可以持有任意實(shí)現(xiàn)該interface類型的對(duì)象酗宋,這給我們編寫函數(shù)(包括method)提供了一些額外的思考,我們是不是可以通過(guò)定義interface參數(shù)距境,讓函數(shù)接受各種類型的參數(shù)
例如fmt.Println函數(shù)可以接受任意類型的數(shù)據(jù)輸出搔课,那是因?yàn)槠湓次募?nèi)部實(shí)現(xiàn)有這樣一個(gè)定義
type Stringer interface {
String() string
}
也就是說(shuō)胰柑,任何實(shí)現(xiàn)了String方法的類型都能作為參數(shù)被fmt.Println調(diào)用
package main
import (
"fmt"
"strconv"
)
type Human struct {
name string
age int
phone string
}
// 通過(guò)這個(gè)方法 Human 實(shí)現(xiàn)了 fmt.Stringer
func (h Human) String() string {
return "?"+h.name+" - "+strconv.Itoa(h.age)+" years - ? " +h.phone+"?"
//strconv里兩個(gè)函數(shù) Itoa,int to string ; Atoi string to int
}
func main() {
Bob := Human{"Bob", 39, "000-7777-XXX"}
fmt.Println("This Human is : ", Bob)
}
//該程序輸出This Human is : ?Bob - 39 years - ? 000-7777-XXX?
現(xiàn)在我們?cè)倩仡櫼幌虑懊娴腂ox示例爬泥,你會(huì)發(fā)現(xiàn)Color結(jié)構(gòu)也定義了一個(gè)method:String。其實(shí)這也是實(shí)現(xiàn)了fmt.Stringer這個(gè)interface崩瓤,即如果需要某個(gè)類型能被fmt包以特殊的格式輸出袍啡,你就必須實(shí)現(xiàn)Stringer這個(gè)接口。如果沒(méi)有實(shí)現(xiàn)這個(gè)接口却桶,fmt將以默認(rèn)的方式輸出境输。
注:實(shí)現(xiàn)了error接口的對(duì)象(即實(shí)現(xiàn)了Error() string的對(duì)象)蔗牡,使用fmt輸出時(shí),會(huì)調(diào)用Error()方法嗅剖,因此不必再定義String()方法了辩越。
interface變量存儲(chǔ)的類型
我們知道interface的變量里面可以存儲(chǔ)任意類型的數(shù)值(該類型實(shí)現(xiàn)了interface)。那么我們?cè)趺捶聪蛑肋@個(gè)變量里面實(shí)際保存了的是哪個(gè)類型的對(duì)象呢信粮?目前常用的有兩種方法:
1黔攒、 Comma-ok斷言
Go語(yǔ)言里面有一個(gè)語(yǔ)法,可以直接判斷是否是該類型的變量: value, ok = element.(T)
强缘,這里value就是變量的值督惰,ok是一個(gè)bool類型,element是interface變量旅掂,T是斷言的類型赏胚。
如果element里面確實(shí)存儲(chǔ)了T類型的數(shù)值,那么ok返回true商虐,否則返回false觉阅。
如下例
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//定義了String方法,實(shí)現(xiàn)了fmt.Stringer
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index,value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n",index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n",index, value)
} else {
fmt.Printf("list[%d] is of a different type\n", index)
}
}
}
不過(guò)這里用到多個(gè)if else秘车,有點(diǎn)冗余典勇。這里就可以用到另一種方法,switch
2鲫尊、switch測(cè)試
把上面的例子用switch重寫一下
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//打印
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"Dennis", 70}
for index, element := range list{
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n",index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n",index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n",index, value)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
這里用到了 element.(type)
,需要強(qiáng)調(diào)的是:element.(type)
語(yǔ)法不能在switch外的任何邏輯里面使用痴柔,如果你要在switch外面判斷一個(gè)類型就使用comma-ok。
嵌入interface
如果一個(gè)interface1作為interface2的一個(gè)嵌入字段疫向,那么interface2隱式的包含了interface1里面的method咳蔚。類似于struct里面的匿名字段
源碼包c(diǎn)ontainer/heap里面有這樣的一個(gè)定義
type Interface interface {
sort.Interface //嵌入字段sort.Interface
Push(x interface{}) //a Push method to push elements into the heap
Pop() interface{} //a Pop elements that pops elements from the heap
}
我們看到sort.Interface其實(shí)就是嵌入字段,把sort包里的sort.Interface的所有method給隱式的包含進(jìn)來(lái)了搔驼。也就是下面三個(gè)方法:
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less returns whether the element with index i should sort
// before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
另一個(gè)例子就是io包下面的 io.ReadWriter 谈火,它包含了io包下面的Reader和Writer兩個(gè)interface:
// io.ReadWriter
type ReadWriter interface {
Reader
Writer
}
反射
反射是程序執(zhí)行時(shí)檢查其所擁有的結(jié)構(gòu)。尤其是類型的一種能力舌涨。所謂反射就是能檢查程序在運(yùn)行時(shí)的狀態(tài)糯耍。我們一般用到的包是reflect包。python中用hasattr方法實(shí)現(xiàn)囊嘉。
go語(yǔ)言中的反射通過(guò)refect包實(shí)現(xiàn)温技,reflect包實(shí)現(xiàn)了運(yùn)行時(shí)反射,允許程序操作任意類型的對(duì)象扭粱。
具體參考:https://www.cnblogs.com/wdliu/p/9222283.html