go面試題學(xué)習(xí)筆記
go高級(jí)面試題
go語言設(shè)計(jì)與實(shí)現(xiàn)
go語言的深度理解-知乎
[Golang數(shù)據(jù)類型](http://www.itdecent.cn/p/ea17fa167585)
- 關(guān)于學(xué)習(xí)
學(xué)習(xí)的方法可以說是一千個(gè)人有一千個(gè)哈姆雷特。我比較討厭教科書式的學(xué)習(xí),把書從第一頁(yè)翻到最后一頁(yè),這樣可能是表面上讀完了。但是記住了多少呢?其實(shí)呢,我們可能往往只需要記住一個(gè)點(diǎn)就行了,一個(gè)點(diǎn)可以牽扯出很多東西,以點(diǎn)及線,以線及面 - 安裝略
- 創(chuàng)建項(xiàng)目
mkdir /Users/zhangguofu/app/goApp
cd /Users/zhangguofu/app/goApp
- 使用go mod 作為包管理
go mod init gone
- 遇到報(bào)錯(cuò) $GOPATH/go.mod exists but should not,
- 產(chǎn)生原因:開啟模塊支持后,并不能與GOPATH共存,所以把項(xiàng)目從$GOPATH中移出即可,在goland 中配置即可
- 創(chuàng)建一個(gè)web服務(wù),代碼如下
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}


- 可見 go mod 解決了包依賴的問題,會(huì)自動(dòng)下載所需要的包
怎么解決goland 報(bào)錯(cuò)問題呢?
- 我們已經(jīng)運(yùn)行了項(xiàng)目,但是goland中的包并不能點(diǎn)擊進(jìn)入,怎么辦呢?可以執(zhí)行以下命令
go mod tidy
怎么解決包引用的問題?
-
首先看我的目錄
image.png -
那么我這么命名我的module
image.png - 我引用src/math下面的的math.go里面的方法,包名和文件名往往一樣。怎么使用呢,看一下我的math.go文件,就是一個(gè)加法返回,很簡(jiǎn)單
package math
func Add(a int ,b int)int {
return a+b;
}
- 再看我main文件怎么調(diào)用吧
package main
import (
"fmt"
"goapp/src/math"http://在module目錄下尋找文件
)
func main() {
sum:=math.Add(2,3)
fmt.Println(sum)
}
https://zhuanlan.zhihu.com/p/109828249
下面開始學(xué)習(xí)go 面試題,并總結(jié)經(jīng)驗(yàn)
首先聊一聊第一個(gè)話題 defer、panic、recover實(shí)踐
先來了解一下代表什么意思
defer
defer 語句將一個(gè)函數(shù)放入一個(gè)棧中,defer 會(huì)在當(dāng)前函數(shù)返回前執(zhí)行傳入的函數(shù),經(jīng)常用于關(guān)閉文件描述符,數(shù)據(jù)庫(kù)連接,redis連接等,用于清理資源,避免資源浪費(fèi)。比如下面這個(gè)栗子
package main
import (
"fmt"
"goapp/src/math"
)
func main() {
sum:=math.Add(2,3)
fmt.Println(sum)
defer func() {fmt.Println("i am defer1")}()
res := test_defer();
fmt.Println(res)
}
func test_defer() float64 {
defer func() {fmt.Println("i am defer2")}()
defer func() {fmt.Println("i am defer3")}()
res :=math2.Mod(5,3)
return res;
}
執(zhí)行結(jié)果是什么呢?
- 執(zhí)行 一個(gè)加法,打印返回值 5;
2.defer1入棧
3.執(zhí)行函數(shù)test_defer,defer2入棧,defer3入棧,執(zhí)行函數(shù)邏輯,在return 之前 呢,會(huì)執(zhí)行 棧里面的defer,棧嘛,先進(jìn)后出,和隊(duì)列相反,所以依次執(zhí)行defer3,defer2,然后返回結(jié)果
4.main函數(shù)收到test_defer的返回值,開始打印結(jié)果
5.main函數(shù)在結(jié)束之前呢,會(huì)執(zhí)行一下本函數(shù)內(nèi)的defer,所以開始執(zhí)行defer1
那結(jié)果是不是這樣執(zhí)行的呢?我們來看一下結(jié)果,毫無相差

那么此處可能有小伙伴要問一下,defer為什么要設(shè)計(jì)成棧?
- 通俗來講一個(gè)場(chǎng)景,defer 是做清場(chǎng)工作的,對(duì)吧,那么這樣一個(gè)場(chǎng)景,一個(gè)小偷去倉(cāng)庫(kù)偷東西,干完活了,要擦除腳印對(duì)吧,那他不可能從進(jìn)門的位置開始擦腳印吧,他只能退著擦,先擦最后一步的腳印,而且,很多時(shí)候最后一步是基于前面的基礎(chǔ)上的,比如,還是這個(gè)小偷,他想偷柜子里面的珠寶,那他是不是得先打開門啊,那小偷做清理工作的時(shí)候,不可能先關(guān)閉門,在關(guān)閉柜子吧。
- defer是用來釋放資源的,比如一個(gè)操作,先給文件上鎖,然后修改文件。那defer執(zhí)行的時(shí)候應(yīng)該是先關(guān)閉文件,再釋放鎖。如果釋放鎖,再關(guān)閉文件,那不是亂套了嗎?從因果關(guān)系上說,后分配的資源可能會(huì)依賴前面的資源,但是前面的資源肯定不會(huì)依賴后面打開的資源。所以倒過來執(zhí)行關(guān)閉 不會(huì)產(chǎn)生問題。
那有的人說,我就是讓defer先進(jìn)先出,不行嗎?允許是允許,但是不提倡。哈哈,是不是感受到了羅翔老師的氣場(chǎng),請(qǐng)看下面的代碼,如果defer嵌套,那么defer會(huì)從外往里執(zhí)行,剝洋蔥似的,一層一層剝。
package main
func main() {
defer func() {
println("i am defer1")
defer func() {
println("i am defer2")
defer func() {
println("i am defer3")
}()
}()
}()
panic("i am panic")
}

panic
panic往往和recover是成對(duì)出現(xiàn)的,與defer也有緊密的聯(lián)系,panic能夠改變程序的控制流,調(diào)用panic后會(huì)立刻停止執(zhí)行當(dāng)前函數(shù)的剩余代碼,并在當(dāng)前Goroutine(協(xié)程)中遞歸執(zhí)行調(diào)用方的defer
我們看下面一段代碼
package main
import "time"
func main() {
defer func() {
println("i am main defer1")
}()
go func() {
defer func() {
println("i am goroutine defer2")
}()
defer func() {
println("i am goroutine defer3")
}()
panic("i am panic")
defer func() {
println("i am goroutine defer4")
}()
}()
time.Sleep(1 * time.Second)
}
- 從前面的分析我們得知以下結(jié)果
- defer1 入棧
2.執(zhí)行g(shù)oroutine
3.defer2 入棧
4.defer3入棧
5.panic打斷程序執(zhí)行,依次執(zhí)行defer3,defer2,panic,而panic 后面的程序不會(huì)再運(yùn)行,并且main里面的defer也不會(huì)執(zhí)行

-
我為什么要加time.Sleep 如果不加呢?
image.png
從截圖里面看到,如果沒有time.Sleep,協(xié)程好像沒有被執(zhí)行一樣,為什么會(huì)這樣呢?因?yàn)槲覀冎?,協(xié)程不是搶占式的,如果刪除time.Sleep,主goroutine不會(huì)放棄對(duì)輔助goroutine的控制,但是goroutine 必須放棄控制才能運(yùn)行另一個(gè)goroutine,而time.Sleep就是放棄控制的一種方法。簡(jiǎn)單來說,你這個(gè)程序 從頭到尾都是被main 主協(xié)程占用著,子協(xié)程不會(huì)主動(dòng)搶占cpu,那么必須得是主協(xié)程主動(dòng)讓出cpu,讓子協(xié)程有機(jī)會(huì)被cpu輪詢到,子協(xié)程才會(huì)被執(zhí)行
詳細(xì)講一講什么是協(xié)程
- 協(xié)程是go語言最重要的特色之一,那么我們?cè)趺蠢斫鈪f(xié)程呢?協(xié)程,簡(jiǎn)單說就是輕量級(jí)的線程,一個(gè)協(xié)程的大小是2k 左右,這也解釋了為什么go能單機(jī)百萬。
- go語言里的協(xié)程創(chuàng)建很簡(jiǎn)單,在go關(guān)鍵詞后面加一個(gè)函數(shù)調(diào)用就可以了。代碼舉栗
package main
import "time"
func main() {
println("i am main goroutine")
go func() {
println("i am goroutine_in_1")
go func() {
println("i am goroutine_in_2")
go func() {
println("i am goroutine_in_3")
}()
}()
}()
time.Sleep(1*time.Second);
println("main goroutine is over")
}

main 函數(shù)是怎么運(yùn)行的呢?其實(shí)main函數(shù)也是運(yùn)行在goroutine里面,不過是主協(xié)程,上面的栗子我們是嵌套了幾個(gè)協(xié)程,但是他們中間并沒有什么層級(jí)關(guān)系,協(xié)程只有兩種,子協(xié)程和主協(xié)程。上面的代碼中,我們讓主協(xié)程休息了一秒,等待子協(xié)程返回結(jié)果。如果不讓主協(xié)程休息一秒,即讓出cpu,讓子協(xié)程是沒有機(jī)會(huì)執(zhí)行的,因?yàn)橹鲄f(xié)程運(yùn)行結(jié)束后,不管子協(xié)程是任何狀態(tài),都會(huì)全部消亡。
但是在實(shí)際使用中,我們要保護(hù)好每一個(gè)子協(xié)程,確保他們安全運(yùn)行,一個(gè)子協(xié)程的異常會(huì)傳播到主協(xié)程,直接導(dǎo)致主協(xié)程掛掉,程序崩潰。比如下面這個(gè)栗子
package main
import "time"
func main() {
println("i am main goroutine")
go func() {
println("i am goroutine_in_1")
go func() {
println("i am goroutine_in_2")
go func() {
println("i am goroutine_in_3")
panic("i am panic")
}()
}()
}()
time.Sleep(1*time.Second);
println("main goroutine is over")
}

最后一句,main goroutine is over沒有打印,程序沒有執(zhí)行到。 前面我們說到了。不管你是什么樣的程序,遇到panic 我就終止程序往下執(zhí)行,哪怕是子協(xié)程呢!好了,協(xié)程先說到這里。我們繼續(xù)往下看recover
recover
recover 可以中止panic造成的程序崩潰,它是一個(gè)只能在defer中發(fā)揮作用的函數(shù)。在其他作用域中不會(huì)發(fā)揮作用。為什么這么說呢?我們看下面這個(gè)栗子
package main
import "fmt"
func main() {
defer func() {
println("i am main")
}()
if err := recover();err != nil {
fmt.Println(err)
}
panic("i am panic")
}
看一下執(zhí)行結(jié)果

我們看到,遇到panic,執(zhí)行了defer,然后執(zhí)行了panic ,并沒有執(zhí)行if條件判斷,為什么?recover是捕捉錯(cuò)誤的,運(yùn)行到if 還沒有錯(cuò)誤,捕捉什么?運(yùn)行到panic 的時(shí)候 if 已經(jīng)執(zhí)行過了,怎么捕捉?那么可能有人想,我把if放到panic后面不就行了嗎?行嗎?答案是否定的,panic 我們前面已經(jīng)說過了,甭管你是誰,看見我就得停止。那就回到我們剛才說的,panic 出現(xiàn),程序停止往下執(zhí)行,但是程序會(huì)循環(huán)執(zhí)行defer啊,那如果我在defer里面捕捉錯(cuò)誤,是不是就可以解決這個(gè)問題了呢??梢奼o的設(shè)計(jì)者是用心良苦!到這里有沒有人會(huì)問一個(gè)問題defer可以嵌套,那么panic能否嵌套呢?當(dāng)然可以,defer可以容納一切,panic放到defer里面一樣可以嵌套
package main
func main() {
defer func() {
defer func() {
defer func() {
panic("i am panic3")
}()
panic("i am panic2")
}()
panic("i am panic1")
}()
panic("i am panic")
}

為什么會(huì)先執(zhí)行 最后一行panic ,才執(zhí)行defer呢,這和前面說的遇到panic先執(zhí)行defer有點(diǎn)出入是吧,但是你這樣看 defer優(yōu)先于panic優(yōu)先于defer+panic。
那么現(xiàn)在,我們來寫一個(gè)例子,看defer 如何捕捉panic并恢復(fù)程序正常執(zhí)行
package main
import "fmt"
func main() {
havePanic();
println("i will go on ")
}
func havePanic() {
defer func() {
if err:=recover();err !=nil {
fmt.Println("i get a panic")
fmt.Println(err)
}
}()
panic("i am panic");
}
解讀一下上面的程序,執(zhí)行havePanic ,havePanic的第一個(gè)defer入棧,往下執(zhí)行碰到panic,首先會(huì)執(zhí)行defer,defer里面打印了err信息,并可以做一些其他的處理,比如記錄日志,重試什么的。然后main繼續(xù)執(zhí)行下面的print,看一下執(zhí)行結(jié)果

下面再補(bǔ)充一點(diǎn)協(xié)程的知識(shí)
go不是號(hào)稱百萬協(xié)程嗎?那么我們真給它來個(gè)百萬協(xié)程看一下我的電腦到底能不能hold住
來!寫一段代碼
package main
import (
"fmt"
"time"
)
func main() {
i :=1;
for {
go func() {
for {
time.Sleep(time.Second)
}
}()
if i > 1000000 {
fmt.Printf("我已經(jīng)啟動(dòng)了%d個(gè)協(xié)程\n",i)
}else{
fmt.Printf("當(dāng)前是第%d個(gè)協(xié)程\n",i)
}
i++
}
}
截圖看一下我當(dāng)前的機(jī)器狀態(tài)

百萬協(xié)程掛起之后的截圖

因?yàn)檩敵龈簧纤俣绕鋵?shí)最后跑了1842504個(gè)協(xié)程

說一下跑后感:風(fēng)扇呼呼的轉(zhuǎn)了大概三分鐘的樣子
,我算了一下一個(gè)協(xié)程大概是2.45kb的樣子

協(xié)程和線程的區(qū)別
一個(gè)進(jìn)程內(nèi)部可以運(yùn)行多個(gè)線程,而每個(gè)線程又可以運(yùn)行很多協(xié)程。線程要負(fù)責(zé)對(duì)協(xié)程進(jìn)行調(diào)度,保證每個(gè)協(xié)程都有機(jī)會(huì)得到執(zhí)行。當(dāng)一個(gè)協(xié)程睡眠時(shí),它要將線程的運(yùn)行權(quán)讓給其它的協(xié)程來運(yùn)行,而不能持續(xù)霸占這個(gè)線程。同一個(gè)線程內(nèi)部最多只會(huì)有一個(gè)協(xié)程正在運(yùn)行。
線程的調(diào)度是由操作系統(tǒng)負(fù)責(zé)的,調(diào)度算法運(yùn)行在內(nèi)核態(tài),而協(xié)程的調(diào)用是由 Go 語言的運(yùn)行時(shí)負(fù)責(zé)的,調(diào)度算法運(yùn)行在用戶態(tài)。
協(xié)程可以簡(jiǎn)化為三個(gè)狀態(tài),運(yùn)行態(tài)、就緒態(tài)和休眠態(tài)。同一個(gè)線程中最多只會(huì)存在一個(gè)處于運(yùn)行態(tài)的協(xié)程,就緒態(tài)的協(xié)程是指那些具備了運(yùn)行能力但是還沒有得到運(yùn)行機(jī)會(huì)的協(xié)程,它們隨時(shí)會(huì)被調(diào)度到運(yùn)行態(tài),休眠態(tài)的協(xié)程還不具備運(yùn)行能力,它們是在等待某些條件的發(fā)生,比如 IO 操作的完成、睡眠時(shí)間的結(jié)束等。
操作系統(tǒng)對(duì)線程的調(diào)度是搶占式的,也就是說單個(gè)線程的死循環(huán)不會(huì)影響其它線程的執(zhí)行,每個(gè)線程的連續(xù)運(yùn)行受到時(shí)間片的限制。
Go 語言運(yùn)行時(shí)對(duì)協(xié)程的調(diào)度并不是搶占式的。如果單個(gè)協(xié)程通過死循環(huán)霸占了線程的執(zhí)行權(quán),那這個(gè)線程就沒有機(jī)會(huì)去運(yùn)行其它協(xié)程了,你可以說這個(gè)線程假死了。不過一個(gè)進(jìn)程內(nèi)部往往有多個(gè)線程,假死了一個(gè)線程沒事,全部假死了才會(huì)導(dǎo)致整個(gè)進(jìn)程卡死。
每個(gè)線程都會(huì)包含多個(gè)就緒態(tài)的協(xié)程形成了一個(gè)就緒隊(duì)列,如果這個(gè)線程因?yàn)槟硞€(gè)別協(xié)程死循環(huán)導(dǎo)致假死,那這個(gè)隊(duì)列上所有的就緒態(tài)協(xié)程是不是就沒有機(jī)會(huì)得到運(yùn)行了呢?Go 語言運(yùn)行時(shí)調(diào)度器采用了 work-stealing 算法,當(dāng)某個(gè)線程空閑時(shí),也就是該線程上所有的協(xié)程都在休眠(或者一個(gè)協(xié)程都沒有),它就會(huì)去其它線程的就緒隊(duì)列上去偷一些協(xié)程來運(yùn)行。也就是說這些線程會(huì)主動(dòng)找活干,在正常情況下,運(yùn)行時(shí)會(huì)盡量平均分配工作任務(wù)。
我的線程數(shù)到底有多少?
默認(rèn)情況下,Go 運(yùn)行時(shí)會(huì)將線程數(shù)會(huì)被設(shè)置為機(jī)器 CPU 邏輯核心數(shù)。同時(shí)它內(nèi)置的 runtime 包提供了 GOMAXPROCS(n int) 函數(shù)允許我們動(dòng)態(tài)調(diào)整線程數(shù),注意這個(gè)函數(shù)名字是全大寫。該函數(shù)會(huì)返回修改前的線程數(shù),如果參數(shù) n <=0 ,就不會(huì)產(chǎn)生修改效果,等價(jià)于讀操作。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print(runtime.GOMAXPROCS(0))//獲取默認(rèn)線程數(shù) 8
println("\n")
runtime.GOMAXPROCS(10)//設(shè)置線程數(shù)為10
fmt.Print(runtime.GOMAXPROCS(0))//獲取新線程數(shù) 10
}

今天學(xué)習(xí)一下slice的使用方法
我們先來看一道面試題來引出今天的問題
package main
import "fmt"
func main() {
s1 :=[]int{1,2,3,4}
m := make(map[int]*int)
for key,val :=range s1{
m[key]=&val;
}
for key,val := range m{
fmt.Println(key,"->",*val)
}
}
打印結(jié)果

先來看一下go的語言結(jié)構(gòu)
go的語言基礎(chǔ)部分由這幾個(gè)部分構(gòu)成,其實(shí)其他語言也都差不多,我以php對(duì)比
- 包聲明
就像命名空間 - 引入包
就是命名空間下面的use - 函數(shù)
函數(shù)的命名 分為首字母是否大寫,大寫的話可以在包外調(diào)用,小寫的話就自己玩自己了。這個(gè)很好解釋,小名,比如 狗剩兒,小花兒,在自己的圈子里叫叫就得了,對(duì)外還得使用大名,在一個(gè)go文件中,包名+函數(shù)名 構(gòu)成唯一性,那如果不唯一 怎么辦?編譯直接就報(bào)錯(cuò)了,報(bào)錯(cuò)內(nèi)容function redeclared in this block,php里面是命名空間+函數(shù)名 來解決函數(shù)名沖突的問題,對(duì)吧。那個(gè) 包外是否能否訪問 有點(diǎn)像 方法屬性,小寫的是私有屬性,pravite,大寫的就是public - 變量
首先我們先聲明這一點(diǎn),Go語言通過首字母的大小寫來控制訪問權(quán)限。 無論是方法,變量,常量或是自定義的變量類型,如果首字母大寫,則可以被外部包訪問,反之則不可以,包括結(jié)構(gòu)體也是一樣。
我們知道,go是靜態(tài)語言,因此go里面的變量肯定是要明確類型的,那么有幾種定義變量的方式呢
1.var name type ,var 是聲明變量的關(guān)鍵字,name 是變量名,type 是變量的類型,比如
var age int
age =12
println(age)
但是這里有一個(gè)小問題,為什么是 var name type ,而不是 var type name?
第一種解釋是這樣:語義,讀起來通順,比如英文講,vary age as int 以整形的類型定義age;
而第二種解釋 是從編譯的角度來講的,我們先來看go的聲明語法結(jié)構(gòu),完整的聲明應(yīng)該是如下這種
var age int = 12 <==> (var) + (age) + [int] + [ = 12]
發(fā)現(xiàn)了什么沒有?了解正則的朋友應(yīng)該明白()里面都是必填的,[]是選填的。所以go其實(shí)就是 把必填的都放到前面,把可以缺省的放到后面,這樣是符合編譯原理的,確保編譯器parse這結(jié)構(gòu)的時(shí)候是正確的,比如,go里面可以這樣
package main
func main() {
var age,score ,score1 int
age =12
score=100
score1=101
println(age,score)
}
如果你不想var 太多,也可以這樣
var (
a int
b int
c bool
d float64
)
在函數(shù)的內(nèi)部呢,可以更簡(jiǎn)單的使用 :=來完成賦值操作
func main() {
a:=12
b,c:="abc",123
println(a,b,c)
}
但是這樣命名要注意以下幾個(gè)問題
1.只能用在函數(shù)的內(nèi)部
2.必須要顯式的初始化,什么是顯式?我們前面說過,go是靜態(tài)語言,任何變量都要聲明類型,如果你不聲明,那么至少應(yīng)該讓編譯器通過推斷的方式知道你是什么類型,比如a:=12,我可以推斷出來a 是整形的,
3.如果你想直接的提供數(shù)據(jù)類型,就像 var一樣,帶出數(shù)據(jù)類型,行不行呢?不行,這就是第三個(gè)限制,不能提供數(shù)據(jù)類型,也就是編譯器都可以推斷出來了,你就別畫蛇添足了,另外一方面,在編譯時(shí)推斷變量類型的語言,還有c++;
- 語句表達(dá)式
- 注釋
go 變量的聲明方法
在前面,go 聲明 變量,比如 字符串,數(shù)字,bool等簡(jiǎn)單類型的已經(jīng)說過了,那么還有一種復(fù)合類型,我們?cè)谶@里講一講
指針類型
指針其實(shí)就是指向一個(gè)對(duì)象的地址值,對(duì)指針的任何操作都會(huì)映射到指針?biāo)傅膶?duì)象上面,而這個(gè)對(duì)象可以是任何一種數(shù)據(jù)類型,包括指針自己。我們看下面這個(gè)栗子
package main
func main() {
var b * int
a:=20
b=&a
c :=&a
println(b,c)
}
指針類型既可以var來實(shí)現(xiàn),也可以 :=來賦值,但是我們來看,var b * int,為什么會(huì)有一個(gè)呢?代表的是b的類型 指針,而int 代表的是b所指向的對(duì)象是int,還是那句話,靜態(tài)語言任何變量都有類型,包括指針。
但是我們?cè)谶@段代碼里面,包括前面的代碼,平凡看到*和&的出現(xiàn),那么他們兩個(gè)到底什么關(guān)系呢?那我們來探討一下
*和&的關(guān)系
專業(yè)的說,&是取地址符號(hào),獲取某個(gè)對(duì)象的地址,比如上面我們用 &a獲取到了a的地址并賦值給了指針b,
- 首先表示是一種指針類型,對(duì)吧,我們定義b 的時(shí)候,是這樣
var b * int,這樣定義了b是指針類型,但是我們想一下,我們既然拿到了指向?qū)ο蟮牡刂罚褪?amp;a,那么我們?cè)趺粗?a的值呢?這就是*的第二個(gè)作用,不僅用來作為指針存儲(chǔ)指向?qū)ο蟮牡刂?,我還可以根據(jù)地址獲取到對(duì)應(yīng)的值。怎么獲取值呢?我們看下面代碼
package main
func main() {
var b * int
a:=20
b=&a
c :=&a
println(b,*c)
}
看一下輸出

數(shù)組類型的聲明方法
我們前面看見過這個(gè)
var age int = 12 <==> (var) + (age) + [int] + [ = 12]
數(shù)組是具有相同唯一類型的一組已編號(hào)且長(zhǎng)度固定的數(shù)據(jù)項(xiàng)序列,這種類型可以是任意的原始類型例如整型、字符串或者自定義類型。如果數(shù)組長(zhǎng)度不確定,可以使用 ... 代替數(shù)組的長(zhǎng)度,編譯器會(huì)根據(jù)元素個(gè)數(shù)自行推斷數(shù)組的長(zhǎng)度:
但是對(duì)于復(fù)合類型的數(shù)據(jù),我們應(yīng)該怎么聲明呢,我們先看一下數(shù)組
var <數(shù)組名稱> = [<數(shù)組長(zhǎng)度>]<數(shù)組元素類型>{元素1,元素2,...},可以看到,中括號(hào)是可以缺省的,元素也是可以缺省的,應(yīng)該默認(rèn)填充零值,所謂零值,看不同的元素類型,比如int的零值是0,字符串的零值就是空字符串,比如下面這個(gè)
package main
func main() {
var arr=[5]int{1,2,3,4}
for key,val:=range arr{
println(key,val)
}
}
或者你也可以這樣
package main
func main() {
var arr=[]int{2,3,4,5,6}
for key,val:=range arr{
println(key,val)
}
}
聲明時(shí)如果不設(shè)定大小,賦值后語言本身會(huì)計(jì)算數(shù)組大小,如果在里面想指定某個(gè)元素的索引呢,用冒號(hào)就可以了 比如以下
package main
func main() {
var arr=[...]int{8:8,3:3,4,5,6}
for key,val:=range arr{
println(key,val)
}
}
//打印效果如下
/private/var/folders/z5/b9w5zj9s4n322m0pzt95hwf40000gn/T/___go_build_main_go
0 0
1 0
2 0
3 3
4 4
5 5
6 6
7 0
8 8
slice類型的聲明方法
slice 切片是對(duì)數(shù)組的抽象,由于數(shù)組的長(zhǎng)度不能改變,這就導(dǎo)致數(shù)組的靈活性不高,所以go提供了一種動(dòng)態(tài)數(shù)組,這就是切片,雖然是動(dòng)態(tài)數(shù)組,但是本質(zhì)還是有區(qū)別的
- 數(shù)組是值類型,值類型是相對(duì)于引用類型來講的,比如以下代碼
func main() {
var arr=[3]int{1,2,3}
var b=arr
println(&arr,"------------\n")
println(&b,"------------\n")
println("------------\n")
b[1]=66
for key,val:=range arr{
println(key,val)
}
for key,val:=range b{
println(key,val)
}
}
//輸出
/private/var/folders/z5/b9w5zj9s4n322m0pzt95hwf40000gn/T/___go_build_main_go
0xc00003a748 ------------
0xc00003a730 ------------
0 1
1 2
2 3
------------
0 1
1 66
2 3
Process finished with exit code 0
你把一個(gè)數(shù)組的值賦值給另一個(gè)數(shù)組,其實(shí)是另開了一塊內(nèi)存
- slice 是一個(gè)引用類型,一個(gè)動(dòng)態(tài)的執(zhí)行數(shù)組切片的指針,而且是一個(gè)被封裝過的指針
- slice 是一個(gè)不定長(zhǎng)的,總是指向底層的數(shù)組
我們來看一下slice是否是引用值
func main() {
var arr=[]int{1,2,3}
var b=arr
println(&arr,"------------\n")
println(&b,"------------\n")
println("------------\n")
b[1]=66
for key,val:=range arr{
println(key,val)
}
for key,val:=range b{
println(key,val)
}
}
//輸出
0xc00003a760 ------------//這是slice arr的地址
0xc00003a748 ------------//這是slice b的地址
------------
0 1
1 66
2 3
0 1
1 66
2 3
- slice的另一種聲明方法
func main() {
b:=make([]int,5,10)//元素個(gè)數(shù)為5,即size,預(yù)留10個(gè)元素的空間
println(cap(b),"\n")
println(len(b),"\n")
for key,val:=range b{
println(key,val)
}
}
//輸出
10
5
0 0
1 0
2 0
3 0
4 0
有沒有感覺到 b :=[]int 特別像我們之前寫的數(shù)組arr :=[5]int{},沒有強(qiáng)調(diào)中介的長(zhǎng)度和后面的元素
map類型的聲明方法
我們?cè)賮砜?一下什么是map,map 是一種無序的鍵值對(duì)集合,通過key可以快速檢索到value,我們可以像數(shù)組一樣迭代它,但是我們并不能決定map的返回順序,這是因?yàn)?Map 是使用 hash 表來實(shí)現(xiàn)的。與 slice 類似也是一個(gè)引用類型。map 本身其實(shí)是個(gè)指針,指向內(nèi)存中的某個(gè)空間。聲明方式與數(shù)組類似,聲明方式:
- var 變量名 map[key類型]值類型 或直接使用 make 函數(shù)初始化:
- make(map[key類型]值類型, 初始空間大小)
- 其中key值可以是任何可以用==判斷的值類型,對(duì)應(yīng)的值類型沒有要求。
- 聲明之后需要初始化使用,聲明后賦值報(bào)錯(cuò),make 就是 返回空值
func main() {
//第一種形式
//var m1 =map[string]string{}
//m1 :=map[string]string{}
m1 :=make(map[string]string);
m1["name"]="xiaoqiang"
m1["age"]="20"
fmt.Println(m1)
}
- 接下來我們看一下單引號(hào)和雙引號(hào)的區(qū)別
在Go中,雙引號(hào)是用來表示字符串string,其實(shí)質(zhì)是一個(gè)byte類型的數(shù)組,單引號(hào)表示rune類型。rune代表一個(gè) UTF-8 字符,當(dāng)需要處理中文、日文或者其他復(fù)合字符時(shí),則需要用到 rune 類型。rune 類型等價(jià)于 int32 類型。
func main() {
s1:="abcde"
s2:='8'
println(s1[1:3])
println(s2)
}
//輸出
bc
56
Process finished with exit code 0

- make 和new的區(qū)別
make 和new 都在堆內(nèi)存分配空間,但是他們的行為和適用的類型有所不同;
new只接受一個(gè)參數(shù),這個(gè)參數(shù)是一個(gè)類型,分配好內(nèi)存后,返回一個(gè)指向該類型內(nèi)存地址的指針。同時(shí)請(qǐng)注意它同時(shí)把分配的內(nèi)存置為零,也就是類型的零值。它返回的永遠(yuǎn)是類型的指針,指向分配類型的內(nèi)存地址。以下代碼是等價(jià)的
package main
import "fmt"
func main() {
p1 := new(int)
fmt.Println(p1)
fmt.Println(*p1)
var p2 * int
i:=0
p2=&i
fmt.Println(p2)
fmt.Println(*p2)
}
new 返回的指針,指針指向的就是該類型的0值
make也是用于內(nèi)存分配的,但是和new不同,它只用于chan、map以及切片的內(nèi)存創(chuàng)建,而且可以接收多個(gè)參數(shù),比如b:=make([]int,5,10)//元素個(gè)數(shù)為5,即size,預(yù)留10個(gè)元素的空間,有沒有想到有點(diǎn)像php里面構(gòu)造函數(shù),make(T, args) 返回的是初始化之后的 T 類型的值,這個(gè)新值并不是 T 類型的零值,也不是指針 *T,是經(jīng)過初始化之后的 T 的引用。比如我們上面舉栗,b是一個(gè)slice,length和cap都已經(jīng)被初始化完成,以零值填充。
package main
import "fmt"
func main() {
s1:= make([]int,5,10)
println(len(s1),"\n")
println(cap(s1),"\n")
fmt.Printf("%#v",s1)
}
//輸出
/private/var/folders/z5/b9w5zj9s4n322m0pzt95hwf40000gn/T/___go_build_main_go
5
10
[]int{0, 0, 0, 0, 0}
Process finished with exit code 0
channel
channel 為什么出現(xiàn)?其實(shí)channel 就是為了配合go的goroutine使用的,為協(xié)程直接的通訊提供通道,前幾天看一本書提到,goroutine本來是互相獨(dú)立的,像一個(gè)一個(gè)的小島,而channel就是連接這些小島的橋梁,讓goroutine直接可以互相通信
和map一樣,聲明之后需要初始化使用,聲明后賦值報(bào)錯(cuò),make 就是 返回空值
package main
import "time"
func main() {
var c1 chan int
c1=make(chan int)
go func() {
c1<-1
c1<-2
c1<-3
c1<-4
}()
v1:=<-c1
v2:=<-c1
println(v1,v2)
time.Sleep(1*time.Second)
}
//輸出
/private/var/folders/z5/b9w5zj9s4n322m0pzt95hwf40000gn/T/___go_build_main_go
1 2
Process finished with exit code 0
chanle 是先進(jìn)先出的,這個(gè)以后會(huì)具體介紹用法
struct
結(jié)構(gòu)體是一種聚合的數(shù)據(jù)類型,是由零個(gè)或多個(gè)任意類型的值聚合成的實(shí)體。每個(gè)值稱為結(jié)構(gòu)體的成員。有點(diǎn)像我們phper常用的orm里面的定義,把它當(dāng)對(duì)象使用就行了

比如我們現(xiàn)在定義一個(gè)person,先來看一下 聲明方法,這個(gè)和interface和error 都一樣 ,這里再次提示一下,注意大小寫,小寫包外訪問不到呢!
package main
func main() {
p1 := person{"技術(shù)小蟲",20}
println(p1.name)
}
type person struct {
name string
age int
}
//輸出
/private/var/folders/z5/b9w5zj9s4n322m0pzt95hwf40000gn/T/___go_build_main_go
技術(shù)小蟲
interface
我們還是先問一下,為什么會(huì)有interface?我們知道,在go里面沒有對(duì)象和類的概念,那么如果我想賦值一組功能給某個(gè)結(jié)構(gòu)體,怎么辦呢?比如說,人,有吃喝拉撒的功能對(duì)吧,但是每個(gè)人吃點(diǎn) 還不一樣,我不能一個(gè)一個(gè)寫吧,那怎么辦嗯?這個(gè)時(shí)候,interface的功能就提現(xiàn)出來了
package main
import "strconv"
func main() {
p1 := person{"技術(shù)小蟲", 20}
res :=p1.eat("hello,")
res2 :=p1.say("hello,")
println(res,"\n",res2)
}
type person struct {
name string
age int
}
type behavor interface {
eat(s string) string
say(s string) string
}
func (p1 person) eat(s string)string {
return s + "i am " + p1.name
}
func (p1 person)say(s string )string {
return s + "i am " + strconv.Itoa(p1.age)
}
//輸出
/private/var/folders/z5/b9w5zj9s4n322m0pzt95hwf40000gn/T/___go_build_main_go
hello,i am 技術(shù)小蟲
hello,i am 20
Process finished with exit code 0
Error的自定義方法和使用
package main
import (
"errors"
"fmt"
)
func main() {
e1 :=errors.New("i am an error")
fmt.Println(e1.Error())
e2:= fmt.Errorf("i am an another error")
fmt.Println("\n",e2)
}
- 好了,go的常用一些 類型已經(jīng)介紹完畢了,下期我們會(huì)繼續(xù)一點(diǎn)帶線,以線帶面的方式來學(xué)習(xí)!


