Golang 學習日志

安裝

官網下載地址:https://golang.org/dl/ ,根據系統平臺下載對應資源包,安裝或解壓到對應目錄,然后配置環境變量

GOROOT:go安裝(非默認安裝目錄時需要配置)
PATH: go安裝目錄/bin(必需)
GOPATH:go項目目錄(命令安裝依賴時需要配置)

GOPATH目錄約定有三個子目錄:bin編譯后生成的可執行文件,pkg編譯后生成的文件,src存放源代碼


認知

命名
  1. 駝峰式命名,不要使用下劃線
  2. 根據首字母的大小寫來確定可以訪問的權限,無論是方法名、常量、變量名還是結構體的名稱,如果首字母大寫,則可以被其他的包訪問(公有);如果首字母小寫,則只能在本包中使用(私有)
變量
  1. var聲明變量,函數內可用 := 簡化聲明和初始化
  2. _ 忽略返回值
  3. const 用于定義常量
包導入
import (
    "test/pkg1"       //默認導入方式,調用包內函數:pkg1.func1()
    p2 "test/pkg2"    //別名方式,調用包內函數:p2.func2()
    . "test/pkg3"     //省略前綴,調用包內函數:func3()
    _ "test/pkg4"     //僅執行包內init()函數,無法調用包內其他函數
)

常使用命令行 go get xxx 可以從指定源上面下載或者更新指定的代碼和依賴,并對他們進行編譯和安裝

函數
  1. main() 一個包內只能有一個main函數
  2. init() 一個包內可以存在多個init函數,導入時都會被調用,在main函數之前被執行
  3. 常見寫法
package main

import (
    "errors"
    "fmt"
)

func f1(num1 int, num2 int) int {
    return num1 + num2
}

func f2(num1 int, num2 int) (rs int) {
    rs = num1 + num2
    return
}

func f3(num1 int, num2 int) (rs int, err error) {
    if num2 == 0 {
        err = errors.New("num2 is empty.")
        return
    }
    rs = int(num1 / num2)
    return rs, nil
}

func main() {
    num1 := 5
    num2 := 3
    if rs, err := f3(num1, num2); err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("%d / %d = %d", num1, num2, rs)
    }
}
  1. defer代碼塊,在函數內部使用,會在函數結束或返回時被調用(先進后出)
func main() {
    fmt.Println(0)
    for i := 1; i <= 3; i++ {
        defer fmt.Println(i)
    }
    fmt.Println(4)
    // 0 4 3 2 1 
}
類型
  1. string 字符串類型,相當于[]byte
  2. interface{} 空接口類型,可以放入其他所有類型,一般用來定義未知類型的變量

類型轉換

Golang是靜態類型的編程語言,所有數據的類型在編譯期確定。 Golang中即使是底層存的是同一個類型,聲明的類型不一樣,也要強制轉換才能互用。

  1. Go語言類型轉換基本格式如:type_name(expression)
    func main() {
        var a int = 1
        fmt.Printf("%T", a)             // int
        fmt.Printf("%T", float64(a))    // float64
    }
    
    如果強制轉換一些無法轉換的類型,將會報錯

控制語句

  1. if - else
    // demo1
    if a == 1 {
        fmt.Println(1)
    } else if a == 2 {
        fmt.Println(2)
    } else {
        fmt.Println(3)
    }
    
    // demo2
    if b := a; b == 1 {
        fmt.Println(1)
    } else {
        fmt.Println(2)
    }
    
  2. switch - case
    var a int = 1
    
    // demo1
    switch a {
    case 1:
        fmt.Println(1)
    case 2:
        fmt.Println(2)
        fallthrough //連接執行下一個case
    case 3, 4, 5:
        fmt.Println(345)
    default:
        fmt.Println(0)
    }
    
    // demo2
    switch b := a; b {
    case 1:
        fmt.Println(1)
    default:
        fmt.Println(0)
    }
    
    // demo3
    switch {
    case a == 1:
        fmt.Println(1)
    case a == 2, a == 3:
        fmt.Println(23)
    default:
        fmt.Println(0)
    }
    
    // demo4 接口類型判斷
    t := func() interface{} {
        return 1
    }()
    switch t.(type) {
    case int:
        fmt.Println("int")
    case string:
        fmt.Println("string")
    }
    
  3. for
    //基于計數器的迭代
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
    
    //基于條件判斷的迭代
    i := 10
    for i > 0 {
        fmt.Println(i)
        i--
    }
    
    //無限循環
    for {
        fmt.Println(1)
    }
    for true {
        fmt.Println(1)
    }
    
    //for-range迭代器
    //map,slice,array,channel
    for key, value := range array1 {
        fmt.Printf("%d => %v \n", key, value)
    }
    
  4. select - case
    可以用來處理channel操作
    select {
    case <-ch1:
        fmt.Println(1)
    case <-ch2:
        fmt.Println(2)
    case <-time.After(time.Minute):
        fmt.Println(9)
    default:
        fmt.Println(0)
    }
    

數組 Array

數組是類型相同的元素的集合

    var arr1 [5]int
    arr2 := [5]int{1, 2, 3}
    arr3 := [...]int{1, 2, 3}
    arr4 := [...]int{3: 3, 5: 5}
    arr5 := [4][2]int{{1, 2}, {3, 4}}

    fmt.Println(arr1, "\n", arr2, "\n", arr3, "\n", arr4, "\n", arr5)
    // [0 0 0 0 0]
    // [1 2 3 0 0]
    // [1 2 3]
    // [0 0 0 3 0 5]
    // [[1 2] [3 4] [0 0] [0 0]]

切片 Slice

切片是一個 長度可變的數組,是對數組一個連續片段的引用,是一個引用類型

//1.基于現有數組創建切片
var arr1 [10]int = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
//array[start:end] 不包含end,其中start和end在為第一或最后一個時可以省略
slice1 := arr1[3:7]
slice2 := arr1[:4]

//2,直接創建切片
//make([]T, length, capacity)
slice3 := make([]int, 2, 2)
slice4 := []int{1, 2, 3, 4}

//切片可以通過append,copy函數來進行追加和復制
slice4 = append(slice4, 5)
slice4 = append(slice4, 6)
slice5 := make([]int, len(slice4), cap(slice4)*2)
copy(slice5, slice4)
//在追加時,當切片的len超過cap時,切片的內存會重新分配,返回的新的切片不再指向原數組,
//新切片的cap會變為原來的2倍

集合map

Map是一種無序的鍵值對的集合
Map通過 key 來快速檢索數據,key 類似于索引, 指向數據的值

//聲明定義
var map1 map[string]int //nil
//map1 = make(map[string]int)
map1 = map[string]int{"a": 1, "b": 2}

//添加
map1["c"] = 3
//刪除
delete(map1, "b")

//是否存在
if _, ok := map1["c"]; ok {
    fmt.Println("C is exist")
} else {
    fmt.Println("C is not exist")
}

自定義結構體 Struct

結構體由一系列屬性組成,每個屬性都有自己的類型和值

package main

import (
    "fmt"
)

type A struct {
    b int
    c int
    d int
}

func main() {
    var a1 A
    a1.b = 2
    fmt.Println(a1)
    // {2 0 0} 值類型

    a2 := new(A)
    a2.b = 2
    fmt.Println(a2)
    // &{2 0 0} 引用類型

    a3 := A{b: 3, c: 4}
    fmt.Println(a3)
    // {3 4 0} 值類型

    a4 := &A{3, 4, 5} //此寫法需要全部參數賦值
    fmt.Println(a4)
    // &{3 4 5} 引用類型
}
  1. 初始化后, 字段默認為該類型的空值
  2. 直接定義A類型或使用A{}來完成初始化返回的是值類型,
    使用new()&A{}初始化返回的是引用類型, 兩者不一樣
struct 嵌套與函數擴展
package main

import (
    "fmt"
)

type A struct {
    a1 int
    a2 int
}

type B struct {
    A  // B嵌套A,B將可以使用A的參數和方法
    b1 int
    b2 int
}

func (a *A) Af() {
    fmt.Println("a func")
}

func (b *B) Bf() {
    fmt.Println("b func")
}

func main() {
    b := new(B)
    b.A.a1 = 1
    b.a2 = 2
    b.b1 = 3
    fmt.Println(b) // &{{1 2} 3 0}

    b.Af() //a func
    b.Bf() //b func
}

如果A、 B兩個struct不在同一個包中, B將無法讀寫A中的私有字段和方法


接口 Interface

接口是一些方法的集合, 只要實現了接口對應的方法, 就等于實現了此接口,
因此golang中所有的類型都實現了空接口interface{},在一些函數中如果傳入參數類型不固定,都可以使用interface{}代替傳入,但在具體使用該參數時仍需要將類型轉換回對應類型。

package main

import (
    "fmt"
)

type Ai interface {
    a()
    b()
}
type Ci interface {
    c()
}

type AC interface { // 接口嵌套
    Ai
    Ci
}

type T1 struct{}
type T2 struct{}

func (t *T1) a() {
    fmt.Println("a1")
}

func (t *T1) b() {
    fmt.Println("b1")
}

func (t *T1) c() {
    fmt.Println("c1")
}

func (t *T2) a() {
    fmt.Println("a2")
}

func (t *T2) c() {
    fmt.Println("c2")
}
func main() {

    //定義接口類型變量
    var test1 Ai
    test1 = new(T1) // T1實現了a()和b(),所以可以初始化T1類型賦值給Ai接口類型
    test1.a()       // 輸出 a1
    //  var test2 Ai
    //  test2 = new(T2) // 無法編譯通過,T2沒有實現Ai接口的b(),所以無法初始化T1類型賦值給Ai接口類型

    //定義接口類型變量
    var test3, test4 Ci
    test3 = new(T1) // T1實現了c(),所以可以初始化T1類型賦值給Ci接口類型
    test3.c()       // 輸出 c1
    test4 = new(T2) // T2實現了c(),所以可以初始化T2類型賦值給Ci接口類型
    test4.c()       // 輸出 c2

    //定義接口類型變量
    var test5 AC
    test5 = new(T1) // T1實現了a()、b()、c(),所以可以初始化T1類型賦值給AC接口類型
    test5.a()       // a1
    test5.b()       // b1
    test5.c()       // c1

    //  var test6 AC
    //  test6 = new(T2) // 無法編譯通過,T2沒有實現AC接口的b(),所以無法初始化T2類型賦值給AC接口類型
}
接口類型轉換
  • 斷言x.(T) , x是接口類型,如str, ok := value.(string)
  • Type switch,詳情見上述 控制語句 > switch-case > demo4

協程 Goroutines

協程是在一個線程中模擬多個協程并發執行
沒有優先級、 非搶占式調度: 任務不能主動搶占時間片
每個協程都有自己的堆棧和局部變量
golang調度學習: http://www.lxweimin.com/p/9db2dcb1ccb7
給調用函數使用 go 關鍵字即可將函數放到協程中執行

package main

import (
    "fmt"
)

func f1() {
    fmt.Println(1)
}
func f2() {
    fmt.Println(2)
}
func main() {
    go f1()
    f2()
}

反復執行上面代碼,我們會發現很大概率上只會輸出2,極小概率會同時輸出1、2,因為在使用go激活 f1 后,主線程直接就往下執行 f2 ,最后主線程退出,其中 f1 還沒來的及執行打印就已經結束了。

如何固定讓主線程的 f2 在 f1 執行只會在執行呢?

  1. sync.WaitGroup
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func f1() {
        fmt.Println(1)
        wg.Done() // 完成一個計數器,計數 -1
    }
    func f2() {
        fmt.Println(2)
    }
    
    var wg sync.WaitGroup
    
    func main() {
        wg.Add(1) // 添加一個等待計數器,計數 +1
        go f1()
        wg.Wait() // 主線程堵塞等待,在計數器為0時才會向下執行
        f2()
    }
    
  2. 信道Channel
    可以通過它們發送類型化的數據在協程之間通信, 可以避開一些內存共享導致的坑通過通道傳遞的數據同一時間只有一個協程可以訪問。
    package main
    
    import (
        "fmt"
    )
    
    func f1() {
        fmt.Println(1)
        ch <- 1 //將數據傳入ch
    }
    func f2() {
        fmt.Println(2)
    }
    
    var ch chan int //聲明信道和信道數據的類型
    
    func main() {
        ch = make(chan int) //初始化,分配內存
        go f1()
        <-ch //取出ch中的數據,若ch中沒有數據,則ch會堵塞,直到ch中有數據傳入
        f2()
    }
    
    在默認無緩沖通道情況下,如果通道中沒有數據, 那 <-ch 數據取出就阻塞,
    如果通道已有數據, 那 ch<-value 數據存入就阻塞。有緩沖通道是指在未填滿指定數量之前不會堵塞, 數量滿了之后就會像無緩沖一樣堵塞, 直到通道數據被取出
    ch := make(chan ch_type)
    // 無緩沖信道,ch_type可以為任意類型
    
    ch := make(chan ch_type, buf_num)
    //有緩沖信道,buf_num為緩沖數量
    
    
    ch <- 123
    // 將數據傳入信道
    // 無緩沖時,信道將會堵塞,直到ch取出數據
    // 有緩沖時,信道將無限制傳入,直到傳入數量大于緩沖數量,才會堵塞,直到ch取出數據才會繼續傳入
    
    data := <- ch
    // 將數據從信道中取出,并賦給data變量
    // 信道將會堵塞,直到ch有數據傳入
    

Http web服務

使用Golang的標準庫 net/http可以搭建一個Go Web服務器

package main

import (
    "fmt"
    "log"
    "net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", hello)
    err := http.ListenAndServe("127.0.0.1:80", nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err.Error())
    }
}

Request: 用戶請求的信息,用來解析用戶的請求信息,包括post、get、file等信息
Response: 服務器需要反饋給客戶端的信息
Handler: 處理請求和生成返回信息的處理邏輯

在編寫項目代碼時,有時會覺得每個函數都要寫(w http.ResponseWriter, req *http.Request),就會覺得很麻煩,所以做了以下測試

package main

import (
    "fmt"
    "log"
    "net/http"
)

type CTX struct {
    ResponseWriter http.ResponseWriter
    Request        *http.Request
}

var Ctx *CTX

func Handle(url string, f func()) {
    http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
        Ctx = &CTX{
            ResponseWriter: w,
            Request:        r,
        }
        f()
    })
}

func main() {
    Handle("/", home)
    Handle("/hello", hello)
    Handle("/ping", new(Ping).ping)
    err := http.ListenAndServe("127.0.0.1:80", nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err.Error())
    }
}

////////////////////////////////////////////

func home() {
    fmt.Fprintln(Ctx.ResponseWriter, "home")
}

func hello() {
    fmt.Fprintln(Ctx.ResponseWriter, "hello world")
}

type Ping struct{}

func (p *Ping) ping() {
    fmt.Fprintln(Ctx.ResponseWriter, "pong")
}

錯誤類型

  1. 實現error接口
    type error interface{
        Error() string
    }
    
    package main
    
    import (
        "fmt"
    )
    
    type Myerror struct {
        msg string
    }
    
    func (e *Myerror) Error() string {
        return e.msg
    }
    
    func main() {
        var err error
        err = &Myerror{"test error"}
        fmt.Println(err) //test error
    }
    
  2. 使用errors包函數
    package main
    
    import (
        "errors"
        "fmt"
    )
    
    func main() {
        var err error
        err = errors.New("test error")
        fmt.Println(err) //test error
    }
    
  3. 使用fmt包函數
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var err error
        err = fmt.Errorf("%s error", "test")
        fmt.Println(err) //test error
    }
    
  4. error可以使用在函數返回
    package main
    
    import (
        "errors"
        "fmt"
    )
    
    func main() {
        if v, err := div(2, 0); err != nil {
            fmt.Println("error:", err)
        } else {
            fmt.Println("2/0 = ", v)
        }
    }
    
    func div(a, b int) (rs int, err error) {
        if b == 0 {
            err = errors.New("division by zero")
            return
        }
        return a / b, nil
    }
    
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 使用定時器t := time.NewTicker(1 * time.Second)// 第一種方式for { ...
    qishuai閱讀 933評論 0 2
  • 一、數據類型轉換 https://studygolang.com/articles/10838 package m...
    蓓蓓的萬能男友閱讀 1,104評論 0 1
  • fmt格式化字符串 格式:%[旗標][寬度][.精度][arg索引]動詞旗標有以下幾種:+: 對于數值類型總是輸出...
    皮皮v閱讀 1,122評論 0 3
  • Golang是我最喜歡的一門語言,它簡潔、高效、易學習、開發效率高、還可以編譯成機器碼… 雖然它一出世,就飽受關注...
    盤木閱讀 3,580評論 0 7
  • 羨慕別人的七夕,有蠟燭、有擁抱、有鮮花、有掌聲。 再看我的七夕,只有可憐的七毛七 我內心一哂,終究是太高估這個男人...
    baicaochunsheng閱讀 227評論 0 0