go 從0開始學(xué)習(xí)筆記【1】

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"))
}

image.png
image.png
  • 可見 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é)果是什么呢?

  1. 執(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é)果,毫無相差


image.png
那么此處可能有小伙伴要問一下,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")
}

image.png

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é)果
  1. 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í)行


image.png
  • 我為什么要加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")
}

image.png

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")
}

image.png

最后一句,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é)果


image.png

我們看到,遇到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")
}
image.png

為什么會(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é)果


image.png

下面再補(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)


image.png

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


image.png

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


image.png

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


image.png

協(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
}

image.png

今天學(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é)果


image.png

先來看一下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)
}


看一下輸出


image.png

數(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

image.png
  • 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ì)象使用就行了


image.png

比如我們現(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í)!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容