Go语言入门分享
簡介:?Go語言出自Ken Thompson、Rob Pike和Robert Griesemer之手,起源于2007年,并在2009年正式對外發(fā)布。Go的主要目標(biāo)是“兼具Python等動(dòng)態(tài)語言的開發(fā)速度和C/C++等編譯型語言的性能與安全性”,旨在不損失應(yīng)用程序性能的情況下降低代碼的復(fù)雜性,具有“部署簡單、并發(fā)性好、語言設(shè)計(jì)良好、執(zhí)行性能好”等優(yōu)勢。
作者 | 賦行
來源 | 阿里技術(shù)公眾號(hào)
前言
曾經(jīng)我是一名以Java語言為主的開發(fā)者,做過JavaWeb相關(guān)的開發(fā),后來轉(zhuǎn)Android,還是離不開Java,直到轉(zhuǎn)去做大前端了,其實(shí)也就是一直在用JS寫業(yè)務(wù)。如今由于個(gè)人發(fā)展原因,來到阿里云,由于項(xiàng)目需要就擼起了Go語言;多年編程經(jīng)驗(yàn)告訴我,語言只是工具罷了,重要的還是其思想與邏輯,所以只需學(xué)學(xué)語法就好了,于是我便三天入門Go,期間主要用Java和JS來類比,語法變化之大,差點(diǎn)讓我從入門到放棄了!其實(shí),還真不是學(xué)習(xí)語法就好了呢,其中包含了很多Go的設(shè)計(jì)理念。正所謂好記性不如敲爛鍵盤,學(xué)過的東西,還是要沉淀沉淀,也可以分享出來一起探討,更有助于成長,于是我就簡單記錄了一下我的Go語言入門學(xué)習(xí)筆記。
一 簡介
Go語言出自Ken Thompson、Rob Pike和Robert Griesemer之手,起源于2007年,并在2009年正式對外發(fā)布,其實(shí)都是Google的,設(shè)計(jì)Go語言的初衷都是為了滿足Google的需求。Go的主要目標(biāo)是“兼具Python等動(dòng)態(tài)語言的開發(fā)速度和C/C++等編譯型語言的性能與安全性”,旨在不損失應(yīng)用程序性能的情況下降低代碼的復(fù)雜性,具有“部署簡單、并發(fā)性好、語言設(shè)計(jì)良好、執(zhí)行性能好”等優(yōu)勢。最主要還是為了并發(fā)而生,并發(fā)是基于goroutine的,goroutine類似于線程,但并非線程,可以將goroutine理解為一種虛擬線程。Go語言運(yùn)行時(shí)會(huì)參與調(diào)度goroutine,并將goroutine合理地分配到每個(gè)CPU中,最大限度地使用CPU性能。
二 環(huán)境
我們玩Java的時(shí)候需要下載JDK,類似于此,用Go開發(fā)也需要下載Go,里面提供各種develop-kit、library以及編譯器。在官網(wǎng)下載mac版本pkg后直接安裝,最后用 go version 命令驗(yàn)證版本:
然后就是設(shè)置這兩個(gè)環(huán)境變量,mac系統(tǒng)是在 .bash_profile 文件里面:
export GOROOT=/usr/local/go export GOPATH=$HOME/go- GOROOT:表示的是Go語言編譯、工具、標(biāo)準(zhǔn)庫等的安裝路徑,其實(shí)就相當(dāng)于配置JAVA_HOME那樣。
- GOPATH:這個(gè)和Java有點(diǎn)不一樣,Java里并不需要設(shè)置這個(gè)變量,這個(gè)表示Go的工作目錄,是全局的,當(dāng)執(zhí)行Go命令的時(shí)候會(huì)依賴這個(gè)目錄,相當(dāng)于一個(gè)全局的workspace。一般還會(huì)把$GOPATH/bin設(shè)置到PATH目錄,這樣編譯過的代碼就可以直接執(zhí)行了。
1 純文本開發(fā)
編寫代碼,可以保存在任意地方,例如新建一個(gè)helloworld目錄,創(chuàng)建hello.go文件:
package main import "fmt" func main() { fmt.Println("hello, world") }然后執(zhí)行 go build hello.go 就可以編譯出hello文件,在./hello就可以執(zhí)行了;或者直接 go run hello.go 合二為一去執(zhí)行。執(zhí)行這個(gè)命令并不需要設(shè)置環(huán)境變量就可以了。看起來和c差不多,但是和Java不一樣,運(yùn)行的時(shí)候不需要虛擬機(jī)。早期的GO工程也是使用Makefile來編譯,后來有了強(qiáng)大的命令 go build、go run,可以直接識(shí)別目錄還是文件。
2 GoLand
自動(dòng)import,超爽的體驗(yàn)!不用按command + /了!
運(yùn)行項(xiàng)目需要設(shè)置build config,和Android、Java的都差不多,例如創(chuàng)建一個(gè)hello-goland項(xiàng)目:
導(dǎo)入go module項(xiàng)目的時(shí)候需要勾選這項(xiàng),否則無法像maven/gradle那樣sync下載依賴:
3 VSCODE
直接搜索Go插件,第一個(gè)最多安裝量的就是了,我還沒用過所以不太清楚如何。
三 工程結(jié)構(gòu)
在設(shè)置GOPATH環(huán)境變量的時(shí)候,這個(gè)目錄里面又分了三個(gè)子目錄bin、pkg、src,分別用于存放可執(zhí)行文件、包文件和源碼文件。當(dāng)我們執(zhí)行Go命令的時(shí)候,如果我們指定的不是當(dāng)前目錄的文件或者絕對路徑的目錄的話,就會(huì)去GOPATH目錄的去找。這樣在GOPATH目錄創(chuàng)建了xxx的目錄后,就可以在任意地方執(zhí)行 go build xx 命令來構(gòu)建或者運(yùn)行了。
pkg目錄應(yīng)該是在執(zhí)行 go install 后生成的包文件,包括.a這樣的文件,相當(dāng)于一個(gè)歸檔。
├── bin │ ├── air │ ├── govendor │ ├── swag │ └── wire ├── pkg │ ├── darwin_amd64 │ ├── mod │ └── sumdb └── src├── calc├── gin-blog├── github.com├── golang.org├── google.golang.org├── gopkg.in└── simplemath這樣對于我們具體項(xiàng)目來說并不好,沒有Workspace的概念來隔離每個(gè)項(xiàng)目了,所以我覺得這個(gè)GOPATH目錄放的應(yīng)該是公用的項(xiàng)目,例如開源依賴的。我們在開發(fā)過程中,也會(huì)下載很多的依賴,這些依賴都下載到這個(gè)目錄,和我們的項(xiàng)目文件混在一起了。
另外,通過IDE可以設(shè)置project的GOPATH,相當(dāng)于在執(zhí)行的時(shí)候給GOPATH增加了一個(gè)目錄變量,也就是說,我們創(chuàng)建一個(gè)項(xiàng)目,然后里面也有bin、src、pkg這三個(gè)目錄,和GOPATH一樣的,本質(zhì)上,IDE在運(yùn)行的時(shí)候其實(shí)就是設(shè)置了一下GOPATH:
GOPATH=/Users/fuxing/develop/testgo/calc-outside:/Users/fuxing/develop/go #gosetupGo語言在尋找變量、函數(shù)、類屬性及方法的時(shí)候,會(huì)先查看GOPATH這個(gè)系統(tǒng)環(huán)境變量,然后根據(jù)該變量配置的路徑列表依次去對應(yīng)路徑下的src目錄下根據(jù)包名查找對應(yīng)的目錄,如果對應(yīng)目錄存在,則再到該目錄下查找對應(yīng)的變量、函數(shù)、類屬性和方法。
其實(shí)官方提供了Go Modules的方法更好解決。
1 Go Modules
從Go 1.11版本開始,官方提供了Go Modules管理項(xiàng)目和依賴,從1.13版本開始,更是默認(rèn)開啟了對Go Modules的支持,使用Go Modules的好處是顯而易見的 —— 不需要再依賴GOPATH,你可以在任何位置創(chuàng)建Go項(xiàng)目,并且在國內(nèi),可以通過 GOPROXY 配置鏡像源加速依賴包的下載。也就是說,創(chuàng)建一個(gè)項(xiàng)目就是一個(gè)mod,基本上目前Go開源項(xiàng)目都是這樣做的。其實(shí)就是類似于Maven和Gradle。
// 創(chuàng)建mod項(xiàng)目,也是可以用IDE來new一個(gè)mod項(xiàng)目的: go mod init calc-mod// 一般開源在github上面的項(xiàng)目名字是這樣的;和maven、gradle不一樣的是,開發(fā)完成根本不需要發(fā)布到倉庫!只要提交代碼后打tag就可以了 go mod init github.com/fuxing-repo/fuxing-module-name// 創(chuàng)建一個(gè)模塊:執(zhí)行這個(gè)命令主要是多了一個(gè)go.mod文件,里面就一行內(nèi)容: module calc-mod// import以后,執(zhí)行下載依賴命令,不需要編輯go.mod文件。依賴會(huì)下載到GOPATH/pkg/mod目錄 go list用GoLand來打開不同的項(xiàng)目,顯示依賴的外部庫是不一樣的,如果是用GOPATH創(chuàng)建的項(xiàng)目,需要用命令下載依賴包到GOPATH:
go get -u github.com/fuxing-repo/fuxing-module-name四 語法
1 包:Package 和 Import
Java里面的包名一般是很長的,和文件夾名稱對應(yīng),作用就是命名空間,引入的時(shí)候需要寫長長的一串,也可以用通配符:
Go里面一般的包名是當(dāng)前的文件夾名稱,同一個(gè)項(xiàng)目里面,可以存在同樣的包名,如果同時(shí)都需要引用同樣包名的時(shí)候,就可以用alias區(qū)分,類似于JS那樣。一般import的是一個(gè)包,不像Java那樣import具體的類。同一個(gè)包內(nèi),不同文件,但是里面的東西是可以使用的,不需要import。這有點(diǎn)類似于C的include吧。如果多行的話,用括號(hào)換行包起來。
Go語言中,無論是變量、函數(shù)還是類屬性及方法,它們的可見性都是與包相關(guān)聯(lián)的,而不是類似Java那樣,類屬性和方法的可見性封裝在對應(yīng)的類中,然后通過 private、protected 和 public 這些關(guān)鍵字來描述其可見性,Go語言沒有這些關(guān)鍵字,和變量和函數(shù)一樣,對應(yīng)Go語言的自定義類來說,屬性和方法的可見性根據(jù)其首字母大小寫來決定,如果屬性名或方法名首字母大寫,則可以在其他包中直接訪問這些屬性和方法,否則只能在包內(nèi)訪問,所以Go語言中的可見性都是包一級(jí)的,而不是類一級(jí)的。
在Java里面,只有靜態(tài),或者對象就可以使用點(diǎn)運(yùn)算符,而且是極其常用的操作,而在Go里面,還可以用一個(gè)包名來點(diǎn),這就是結(jié)合了import來使用,可以點(diǎn)出一個(gè)函數(shù)調(diào)用,也可以點(diǎn)出一個(gè)結(jié)構(gòu)體,一個(gè)接口。另外區(qū)別于C,不管是指針地址,還是對象引用,都是用點(diǎn)運(yùn)算符,不需要考慮用點(diǎn)還是箭頭了!
入口的package必須是main,否則可以編譯成功,但是跑不起來:
Compiled binary cannot be executed.原因就是找不到入口函數(shù),跟C和Java一樣吧,也需要main函數(shù)。
2 變量
- 用 var 關(guān)鍵字修飾(類似于JS),有多個(gè)變量的時(shí)候用括號(hào) () 包起來,默認(rèn)是有初始化值的,和Java一樣。
- 如果初始化的時(shí)候就賦值了那可以不需要 var 來修飾,和Java不同的是變量類型在變量后面而不是前面,不過需要 := 符號(hào)。
- 最大的變化就是類型在變量后面!
- 語句可以省略分號(hào) ;
多重賦值
i, j = j, i可以實(shí)現(xiàn)變量交換,有點(diǎn)像JS的對象析構(gòu),但是其實(shí)不一樣。有了這個(gè)能力,函數(shù)是可以返回多個(gè)值了!
匿名變量
用 _ 來表示,作用就是可以避免創(chuàng)建定義一些無意義的變量,還有就是不會(huì)分配內(nèi)存。
指針變量
和C語言一樣的,回想一下交換值的例子即可,到底傳值和傳址作為參數(shù)的區(qū)別是啥。
Go語言之所以引入指針類型,主要基于兩點(diǎn)考慮,一個(gè)是為程序員提供操作變量對應(yīng)內(nèi)存數(shù)據(jù)結(jié)構(gòu)的能力;另一個(gè)是為了提高程序的性能(指針可以直接指向某個(gè)變量值的內(nèi)存地址,可以極大節(jié)省內(nèi)存空間,操作效率也更高),這在系統(tǒng)編程、操作系統(tǒng)或者網(wǎng)絡(luò)應(yīng)用中是不容忽視的因素。
指針在Go語言中有兩個(gè)使用場景:類型指針和數(shù)組切片。
作為類型指針時(shí),允許對這個(gè)指針類型的數(shù)據(jù)進(jìn)行修改指向其它內(nèi)存地址,傳遞數(shù)據(jù)時(shí)如果使用指針則無須拷貝數(shù)據(jù)從而節(jié)省內(nèi)存空間,此外和C語言中的指針不同,Go語言中的類型指針不能進(jìn)行偏移和運(yùn)算,因此更為安全。
變量類型
Go語言內(nèi)置對以下這些基本數(shù)據(jù)類型的支持:
- 布爾類型:bool
- 整型:int8、byte、int16、int、uint、uintptr 等
- 浮點(diǎn)類型:float32、float64
- 復(fù)數(shù)類型:complex64、complex128
- 字符串:string
- 字符類型:rune,本質(zhì)上是uint32
- 錯(cuò)誤類型:error
此外,Go語言也支持以下這些復(fù)合類型:
- 指針(pointer)
- 數(shù)組(array)
- 切片(slice)
- 字典(map)
- 通道(chan)
- 結(jié)構(gòu)體(struct)
- 接口(interface)
還有const常量,iota這個(gè)預(yù)定義常量用來定義枚舉。可以被認(rèn)為是一個(gè)可被編譯器修改的常量,在每一個(gè)const關(guān)鍵字出現(xiàn)時(shí)被重置為0,然后在下一個(gè)const出現(xiàn)之前,每出現(xiàn)一次iota,其所代表的數(shù)字會(huì)自動(dòng)增1。
const (Sunday = iota Monday Tuesday Wednesday Thursday Friday Saturday numberOfDays )類型強(qiáng)轉(zhuǎn)
v1 := 99.99 v2 := int(v1) // v2 = 99v1 := []byte{'h', 'e', 'l', 'l', 'o'} v2 := string(v1) // v2 = hello//字符相關(guān)的轉(zhuǎn)化一般用strconv包 v1 := "100" v2, err := strconv.Atoi(v1) // 將字符串轉(zhuǎn)化為整型,v2 = 100v3 := 100 v4 := strconv.Itoa(v3) // 將整型轉(zhuǎn)化為字符串, v4 = "100"//結(jié)構(gòu)體類型轉(zhuǎn)換 //類型斷言 //x.(T) 其實(shí)就是判斷 T 是否實(shí)現(xiàn)了 x 接口,如果實(shí)現(xiàn)了,就把 x 接口類型具體化為 T 類型; claims, ok := tokenClaims.Claims.(*jwt.StandardClaims)數(shù)組與切片
//定義數(shù)組 var a [8]byte // 長度為8的數(shù)組,每個(gè)元素為一個(gè)字節(jié) var b [3][3]int // 二維數(shù)組(9宮格) var c [3][3][3]float64 // 三維數(shù)組(立體的9宮格) var d = [3]int{1, 2, 3} // 聲明時(shí)初始化 var e = new([3]string) // 通過 new 初始化 var f = make([]string, 3) // 通過 make初始化//初始化 a := [5]int{1,2,3,4,5} b := [...]int{1, 2, 3}//切片 b := []int{} //數(shù)組切片slice就是一個(gè)可變長數(shù)組 c := a[1:3] // 有點(diǎn)類似于subString,或者js.slice d := make([]int, 5) //make相當(dāng)于,new、alloc,用來分配內(nèi)存//數(shù)組的長度 length := len(a)//添加一個(gè)元素 b = append(b, 4)字典
其實(shí)就是Java里的map,使用上語法有很多不同。
var testMap map[string]int testMap = map[string]int{"one": 1,"two": 2,"three": 3, } //還可以這樣初始化: var testMap = make(map[string]int) //map[string]int{} testMap["one"] = 1 testMap["two"] = 2 testMap["three"] = 3make和new
// The make built-in function allocates and initializes an object of type // slice, map, or chan (only). Like new, the first argument is a type, not a // value. Unlike new, make's return type is the same as the type of its // argument, not a pointer to it. The specification of the result depends on // the type: // Slice: The size specifies the length. The capacity of the slice is // equal to its length. A second integer argument may be provided to // specify a different capacity; it must be no smaller than the // length. For example, make([]int, 0, 10) allocates an underlying array // of size 10 and returns a slice of length 0 and capacity 10 that is // backed by this underlying array. // Map: An empty map is allocated with enough space to hold the // specified number of elements. The size may be omitted, in which case // a small starting size is allocated. // Channel: The channel's buffer is initialized with the specified // buffer capacity. If zero, or the size is omitted, the channel is // unbuffered. func make(t Type, size ...IntegerType) Type// The new built-in function allocates memory. The first argument is a type, // not a value, and the value returned is a pointer to a newly // allocated zero value of that type. func new(Type) *Type區(qū)別就是返回值和參數(shù)不同,一個(gè)是值,一個(gè)是指針,slice、chan、map只能用make,本身就是指針。其他make、new都行。
神奇的nil
Java里面用null比較舒服,直接就判空了,除了在string類型的時(shí)候,還要判斷字符為 "",但是Go里面的string要判斷為空就簡單一點(diǎn),不能判斷nil,只能判斷 ""。然而Go里面的nil卻和null不一樣,其實(shí)是和JS里面 ==、=== 很像。
nil也是有類型的。
func Foo() error {var err *os.PathError = nil// …return err //實(shí)際返回的是[nil, *os.PathError]//return nil //正確的方式是直接return nil 實(shí)際返回的是[nil, nil] }func main() {err := Foo()fmt.Println(err) // <nil>fmt.Println(err == nil) // falsefmt.Println(err == (*os.PathError)(nil)) //true }根對象:Object
在Java里面,如果不用多態(tài),沒有接口,父類,超類的話,就用Object作為根對象,在Go里面,如果函數(shù)參數(shù)不知道用什么類型,通常會(huì)用 interface{},這是個(gè)空接口,表示任意類型,因?yàn)椴皇侨躅愋驼Z言,沒有any類型,也不是強(qiáng)面向?qū)ο笳Z言,沒有Object,所以就有這個(gè)空接口的出現(xiàn)。
3 語句
比較大的一個(gè)特點(diǎn)就是能不用括號(hào)的地方都不用了。
控制流程
if語句的判斷條件都沒有了括號(hào)包起來,還可以前置寫變量初始化語句,類似于for循環(huán),左花括號(hào) { 必須與 if 或者 else 處于同一行。
switch語句變得更強(qiáng)大了,有這些變化:
- switch關(guān)鍵字后面可以不跟變量,這樣case后面就必須跟條件表達(dá)式,其實(shí)本質(zhì)上就是美化了if-else-if。
- 如果switch后面跟變量,case也變得強(qiáng)大了,可以出現(xiàn)多個(gè)結(jié)果選項(xiàng),通過逗號(hào)分隔。
- swtich后面還可以跟一個(gè)函數(shù)。
- 不需要用break來明確退出一個(gè)case,如果要穿透執(zhí)行一層,可以用 fallthrough 關(guān)鍵字。
循環(huán)流程
去掉了 while、repeat 這些關(guān)鍵字了,只保留了 for 這個(gè)關(guān)鍵字,其實(shí)用起來差不多。break , continue 這些關(guān)鍵字還是有的。
//通用的用法 for i := 1; i <= 5; i++ {fmt.Println(i) }//類似于while的用法 a := 1 for a <= 5 {fmt.Println(a)a ++ }//死循環(huán) for {// do something } for ;; {// do something }//類似java for-each的用法 listArray := [...]string{"xiaobi", "xiaoda", "xiaoji"} for index, item := range listArray {fmt.Printf("hello, %d, %s\n", index, item) } //java for (String item : someList) {System.out.println(item); }跳轉(zhuǎn)流程
Go很神奇的保留了一直被放棄的goto語句,記得是Basic、Pascal那些語言才會(huì)有,不知道為啥。
i := 1 flag: for i <= 10 {if i%2 == 1 {i++goto flag}fmt.Println(i)i++ }defer流程有點(diǎn)像Java里面的finally,保證了一定能執(zhí)行,我感覺底層也是goto的實(shí)現(xiàn)吧。在后面跟一個(gè)函數(shù)的調(diào)用,就能實(shí)現(xiàn)將這個(gè)xxx函數(shù)的調(diào)用延遲到當(dāng)前函數(shù)執(zhí)行完后再執(zhí)行。
這是壓棧的變量快照實(shí)現(xiàn)。
func printName(name string) {fmt.Println(name) }func main() {name := "go"defer printName(name) // output: goname = "python"defer printName(name) // output: pythonname = "java"printName(name) // output: java }//output: java python go//defer后于return執(zhí)行 var name string = "go" func myfunc() string {defer func() {name = "python"}()fmt.Printf("myfunc 函數(shù)里的name:%s\n", name)return name } func main() {myname := myfunc()fmt.Printf("main 函數(shù)里的name: %s\n", name)fmt.Println("main 函數(shù)里的myname: ", myname) }//output: myfunc 函數(shù)里的name:go main 函數(shù)里的name: python main 函數(shù)里的myname: go4 函數(shù)
- 關(guān)鍵字是 func,Java則完全沒有 function 關(guān)鍵字,而是用 public、void 等等這樣的關(guān)鍵字,JS也可以用箭頭函數(shù)來去掉 function 關(guān)鍵字了。
- 函數(shù)的花括號(hào)強(qiáng)制要求在首行的末尾。
- 可以返回多個(gè)值!返回值的類型定義在參數(shù)后面了,而不是一開始定義函數(shù)就需要寫上,跟定義變量一樣,參數(shù)的類型定義也是一樣在后面的,如果相同則保留最右邊的類型,其他省略。
- 可以顯式聲明了返回值就可以了,必須每個(gè)返回值都顯式,就可以省略 return 變量。
匿名函數(shù)和閉包
在Java里面的實(shí)現(xiàn)一般是內(nèi)部類、匿名對象,不能通過方法傳遞函數(shù)作為參數(shù),只能傳一個(gè)對象,實(shí)現(xiàn)接口。
Go則和JS一樣方便,可以傳遞函數(shù),定義匿名函數(shù)。
//傳遞匿名函數(shù) func main() {i := 10add := func (a, b int) {fmt.Printf("Variable i from main func: %d\n", i)fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)}callback(1, add); } func callback(x int, f func(int, int)) {f(x, 2) }//return 匿名函數(shù) func main() {f := addfunc(1)fmt.Println(f(2)) } func addfunc(a int) func(b int) int {return func(b int) int {return a + b} }不定參數(shù)
和Java類似,不同的是在調(diào)用是也需要用 ... 來標(biāo)識(shí)。
//定義 func SkipHandler(c *gin.Context, skippers ...SkipperFunc) bool {for _, skipper := range skippers {if skipper(c) {return true}}return false }//調(diào)用 middlewares.SkipHandler(c, skippers...)五 面向?qū)ο?/h3>
在C語言里面經(jīng)常會(huì)有用到別名的用法,可以用 type 類起一個(gè)別名,很常用,特別是在看源碼的時(shí)候經(jīng)常出現(xiàn):
type Integer int1 類
沒有 class 的定義,Go里面的類是用結(jié)構(gòu)體來定義的。
type Student struct {id uintname stringmale boolscore float64 }//沒有構(gòu)造函數(shù),但是可以用函數(shù)來創(chuàng)建實(shí)例對象,并且可以指定字段初始化,類似于Java里面的靜態(tài)工廠方法 func NewStudent(id uint, name string, male bool, score float64) *Student {return &Student{id, name, male, score} }func NewStudent2(id uint, name string, male bool, score float64) Student {return Student{id, name, male, score} }2 成員方法
定義類的成員函數(shù)方法比較隱式,方向是反的,不是聲明這個(gè)類有哪些成員方法,而是聲明這個(gè)函數(shù)是屬于哪個(gè)類的。聲明語法就是在 func 關(guān)鍵字之后,函數(shù)名之前,注意不要把Java的返回值定義給混淆了!
//這種聲明方式和C++一樣的,這個(gè)就是不是普通函數(shù)了,而是成員函數(shù)。 //注意到的是,兩個(gè)方法一個(gè)聲明的是地址,一個(gè)聲明的是結(jié)構(gòu)體,兩個(gè)都能直接通過點(diǎn)操作。 func (s Student) GetName() string {return s.name } func (s *Student) SetName(name string) {s.name = name }//使用 func main() {//a是指針類型a := NewStudent(1, "aa", false, 45)a.SetName("aaa")fmt.Printf("a name:%s\n", a.GetName())b := NewStudent2(2, "bb", false, 55)b.SetName("bbb")fmt.Printf("b name:%s\n", b.GetName()) }//如果SetName方法和GetName方法歸屬于Student,而不是*Student的話,那么修改名字就會(huì)不成功 //本質(zhì)上,聲明成員函數(shù),就是在非函數(shù)參數(shù)的地方來傳遞對象、指針、或者說是引用,也就是變相傳遞this指針 //所以才會(huì)出現(xiàn)修改名字不成功的case3 繼承
沒有 extend 關(guān)鍵字,也就沒有了繼承,只能通過組合的方式來實(shí)現(xiàn)。組合就解決了多繼承問題,而且多繼承的順序不同,內(nèi)存結(jié)構(gòu)也不同。
type Animal struct {name string } func (a Animal) FavorFood() string {return "FavorFood..." } func (a Animal) Call() string {return "Voice..." } type Dog struct {Animal } func (d Dog) Call() string {return "汪汪汪" }//第二種方式,在初始化就需要指定地址,其他都沒變化 type Dog2 struct { *Animal } func test() {d1 := Dog{}d1.name = "mydog"d2 := Dog2{}d2.name = "mydog2"//結(jié)構(gòu)體是值類型,如果傳入值變量的話,實(shí)際上傳入的是結(jié)構(gòu)體值的副本,對內(nèi)存耗費(fèi)更大,//所以傳入指針性能更好a := Animal{"ddog"}d3 := Dog{a}d4 := Dog2{&a} }這種語法并不是像Java里面的組合,使用成員變量,而是直接引用Animal并沒有定義變量名稱(當(dāng)然也是可以的,不過沒必要了),然后就可以訪問Animal中的所有屬性和方法(如果兩個(gè)類不在同一個(gè)包中,只能訪問父類中首字母大寫的公共屬性和方法),還可以實(shí)現(xiàn)方法重寫。
4 接口
Java的接口是侵入式的,指的是實(shí)現(xiàn)類必須明確聲明自己實(shí)現(xiàn)了某個(gè)接口。帶來的問題就是,如果接口改了,實(shí)現(xiàn)類都必須改,所以以前總是會(huì)有一個(gè)抽象類在中間。
//定義接口: type Phone interface {call() } //實(shí)現(xiàn)接口: type IPhone struct {name string } func (phone IPhone) call() {fmt.Println("Iphone calling.") }Go的接口是非侵入式的,因?yàn)轭惻c接口的實(shí)現(xiàn)關(guān)系不是通過顯式聲明,而是系統(tǒng)根據(jù)兩者的方法集合進(jìn)行判斷。一個(gè)類必須實(shí)現(xiàn)接口所有的方法才算是實(shí)現(xiàn)了這個(gè)接口。接口之間的繼承和類的繼承一樣,通過組合實(shí)現(xiàn),多態(tài)的實(shí)現(xiàn)邏輯是一樣的,如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以賦值給接口A。
六 并發(fā)編程
目前并發(fā)編程方面還沒學(xué)習(xí)多少,就簡單從網(wǎng)上摘了這一個(gè)經(jīng)典的生產(chǎn)者消費(fèi)者模型例子來初步感受一下,后續(xù)深入學(xué)習(xí)過后再進(jìn)行分享。
// 數(shù)據(jù)生產(chǎn)者 func producer(header string, channel chan<- string) {// 無限循環(huán), 不停地生產(chǎn)數(shù)據(jù)for {// 將隨機(jī)數(shù)和字符串格式化為字符串發(fā)送給通道channel <- fmt.Sprintf("%s: %v", header, rand.Int31())// 等待1秒time.Sleep(time.Second)} } // 數(shù)據(jù)消費(fèi)者 func customer(channel <-chan string) {// 不停地獲取數(shù)據(jù)for {// 從通道中取出數(shù)據(jù), 此處會(huì)阻塞直到信道中返回?cái)?shù)據(jù)message := <-channel// 打印數(shù)據(jù)fmt.Println(message)} } func main() {// 創(chuàng)建一個(gè)字符串類型的通道channel := make(chan string)// 創(chuàng)建producer()函數(shù)的并發(fā)goroutinego producer("cat", channel)go producer("dog", channel)// 數(shù)據(jù)消費(fèi)函數(shù)customer(channel) }//output: dog: 1298498081 cat: 2019727887 cat: 1427131847 dog: 939984059 dog: 1474941318 cat: 911902081 cat: 140954425 dog: 336122540七 總結(jié)
這只是一個(gè)簡單入門,其實(shí)Go還有很多很多東西我沒有去涉及的,例如context、try-catch、并發(fā)相關(guān)(如鎖等)、Web開發(fā)相關(guān)的、數(shù)據(jù)庫相關(guān)的。以此貼開始,后續(xù)繼續(xù)學(xué)習(xí)Go語言分享。
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
- 上一篇: GitHub Action + ACK:
- 下一篇: 读完《云原生架构白皮书》,我们来谈谈开放