天天看點

golang基礎文法一

golang基礎文法一

變量定義

變量定義文法

  • 使用var關鍵字,可放在函數内,也可放在包内
// var + 變量名 + 資料類型(有預設值)
var a int
var b string = "string"
// 通過指派自動判斷類型
var c = true
// 集中定義
var (
    x = 1
    y = 2
)           
  • 使用:=定義變量,隻能在函數内使用
// := 用于賦初值
a, b := 1, 2           

内建變量類型(builtin)

  • bool, string
  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
  • byte, rune
  • float32, float64, complex64, complex128

強制類型轉換

Golang要求強制類型轉換,無隐式轉換

a, b := 3, 4
var c int
c = int(math.Sqrt(float64(a * a + b * b)))           

常量

使用const關鍵字定義常量,const數值可作為各種類型使用。

// 不确定類型
const a = 3
var b float64
b = a
// 合并定義
const (
    c = 1
    d = 2
)           

枚舉

const (
    spring = 0
    summer = 1
    autumn = 2
    winter = 3
)
// iota用在取值為0的const變量,後續變量依次加1
const (
    spring = iota
    summer
    autumn
    winter
)           

變量定義要點

  • 變量類型寫在變量名之後
  • 編譯器可以推測變量類型
  • Golang沒有char,使用rune
  • 原生支援複數類型

分支

if

const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println(contents)
}

// 可以在if條件中指派,指派變量的作用域在if語句中
if contents, err := ioutil.ReadFile(filename); err != nil {
    fmt.Println(err)
} else {
    fmt.Println(contents)
}           

switch

switch後可以沒有表達式,case結束後自動break,通過fallthrough不使用自動break。

func grade(score int) {
    grade := ""
    switch {
    case score < 0:
        panic(fmt.Sprintf("Wrong score: %d\n", score))
    case score < 60:
        grade = "C"
    case score < 80:
        grade = "B"
    case score < 90:
        grade = "A"
    case score < 100:
        // 自動break,除非出現fallthrough
        fallthrough
    case score >= 100:
        grade = "S"
    }
    fmt.Println(grade)
}           

循環

for條件沒有括号,沒有while。

func convertToBin(n int) string {
    result := ""
    for ; n > 0; n /= 2 {
        lsb := n % 2
        result = strconv.Itoa(lsb) + result
    }
    return result
}

func printFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        panic(err)
    }

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

func forever() {
    // while true
    for  {
        fmt.Println("forever")
    }
}           

函數

  • 函數聲明的順序為:關鍵字,函數名,參數,傳回類型,函數體。
  • 函數允許有多個傳回值,并能在函數體内拿到傳回值。
  • 函數可以作為另一個函數的參數
  • 函數支援可變參數清單
func div(a, b int) (q, r int) {
    // q = a / b
    // r = a % b
    // return
    return a / b, a % b
}

// 使用_抛棄傳回值
a, _ := div(12, 7)

// 使用另一個函數作為參數
func apply(operation func(a, b int), a, b int) {
    operation(a, b)
}
// 使用匿名函數
apply(func(a, b int) {
    fmt.Println(a, b)
}, 1, 2)

// 可變參數清單
func sum(numbers ...int) int {
    s := 0
    for i := range numbers {
        s += numbers[i]
    }
    return s
} 
           

指針

Golang隻有值傳遞一種方式。

// 使用指針操作
func change(pa *int) {
    *pa++
}
a := 5
change(&a)

func swap(x, y *int) {
    *x, *y = *y, *x
}           

數組

數組長度寫在類型之前,[10]int和[20]int是不同的類型。數組是值複制。

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

周遊數組

for i := 0; i < len(arr3); i++ {
    fmt.Println(arr3[i])
}

for i := range arr3 {
    fmt.Println(arr3[i])
}

// 按下标和值周遊
for i, v := range arr3 {
    fmt.Println(i, v)
}           

切片

數組切片相當于資料的一個視圖,可以通過切片改變數組的值。

func updateSlice(s []int) {
    s[0] = 100
}

func main() {
    array := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    s1 := array[2:6]
    s2 := array[2:]
    s3 := array[:6]
    s4 := array[:]
    fmt.Println(s1, s2, s3, s4) // [2 3 4 5] [2 3 4 5 6 7] [0 1 2 3 4 5] [0 1 2 3 4 5 6 7]

    updateSlice(s2)
    fmt.Println(s2) // [100 3 4 5 6 7]
    updateSlice(s3)
    fmt.Println(s3) // [100 1 100 3 4 5]
    
    // 切片擴充
    s5 := s3[6:8]
    fmt.Println(s5)
    
    // 添加元素
    s6 := append(s5, 8)
    fmt.Println(s6, array)
    fmt.Println(array[:8])
    
    // 通過make建立切片
    // 長度為10的切片
    x := make([]int, 10)
    // 長度為10,容量為16的切片
    y := make([]int, 10, 16)

    // 拷貝數組元素
    copy(x, y)

    // 截取數組元素
    z := append(x[:2], x[3:]...)
}           

切片底層維護了一個數組,ptr指向切片的首個元素,len表示切片的長度,cap表示底層數組的從ptr到最末的元素數量。切片可以向後擴充,不可向前擴充。切片添加元素如果會超過底層數組的cap,Golang會配置設定更大的底層數組(原容量*2進行擴充),由于值傳遞的關系,必須接收append方法的傳回值。

map

通過map[K]v的形式定義map。除了slice、map、function的内建類型和struct類型都可以作為map的key。

m := map[string]string{
        "name": "wch",
        "age":  "23",
    }
    
m2 := make(map[string]int)

// 無序周遊map
for k, v := range m {
    fmt.Println(k, v)
}

// map讀值
name := m["name"]


// 判斷key是否存在
grade, exist := m["grade"]

// 删除
delete(m, "name")           

rune

Golang的rune相當于java的char,中文字元在Golang中占3個位元組,使用 utf8.RuneCountInString 擷取字元數,用 len 擷取位元組長度,使用 []byte 擷取位元組。

s := "學習Golang!"
fmt.Println(s)

// ch是rune類型,中文字元占3個位元組
for i, ch := range s {
    fmt.Printf("(%d %X) ", i, ch)
}
fmt.Println()

fmt.Println("rune count in string:", utf8.RuneCountInString(s))

bytes := []byte(s)
for len(bytes) > 0 {
    ch, size := utf8.DecodeRune(bytes)
    bytes = bytes[size:]
    fmt.Printf("size= %d, rune=%c\n", size, ch)
}

for i, ch := range []rune(s) {
    fmt.Printf("(%d %c)", i, ch)
}
fmt.Println()           

面向對象

Golang僅支援封裝,不支援繼承和多态,沒有class,隻有struct,通過struct來定義對象。

type treeNode struct {
    value       int
    left, right *treeNode
}

func createNode(value int) *treeNode {
    return &treeNode{value: value}
}

func main() {
    // 通過多種方式聲明對象
    var root treeNode
    root = treeNode{value: 0}
    root.left = &treeNode{}
    root.right = &treeNode{1, nil, nil}
    root.right.left = new(treeNode)
    root.right.right = createNode(3)
}           

Golang支援顯式定義方法接收者,值/指針接收者均可接收值/指針。當需要改變内容或結構過大時需要使用指針接收者,值接收者是Golang特有的

// 指定方法接收者
func (node treeNode) print() {
    fmt.Println(node.value)
}

func (node *treeNode) setValue(value int) {
    node.value = value
}

func main() {
    node := treeNode{}
    node.setValue(100)
    node.print()
    
    pNode := &node
    pNode.setValue(101)
    pNode.print()
}           

封裝

Golang的可見性(針對包)使用首字母來控制,首字母大寫表示public,首字母小寫表示private。每個目錄都是一個包,main包包含可執行入口。為結構定義的方法必須放在同一個包内,可以是不同的檔案。Golang提供了兩種擴充系統類型和已封裝類型的方法:

  • 定義别名:通過type為已有類型定義一個别名,針對新的結構體進行擴充。
type Queue []int

func (q *Queue) Push(v int) {
    *q = append(*q, v)
}

func (q *Queue) Pop() int {
    head := (*q)[0]
    *q = (*q)[1:]
    return head
}

func (q *Queue) IsEmpty() bool {
    return len(*q) == 0
}           
  • 使用組合:定義新的結構體,其中一個成員是需要擴充的類型的指針。
type EnhanceNode struct {
    node *Node
}

func (enhanceNode *EnhanceNode) PostOrder() {
    if nil == enhanceNode || nil == enhanceNode.node {
        return
    }

    left := EnhanceNode{enhanceNode.node.Left}
    right := EnhanceNode{enhanceNode.node.Right}

    left.PostOrder()
    right.PostOrder()
    enhanceNode.PostOrder()
}           

GOROOT、GOPATH、go get

GOROOT一般為下載下傳的sdk路徑,GOPATH為使用者可定義的路徑,使用者源碼和通過go get指令下載下傳的第三方庫在GOPATH的src目錄下。

gopm是go get的鏡像工具,通過 go get -g -v github.com/gpmgo/gopm 下載下傳gopm。

// 下載下傳
gopm get -g -v Golang.org/x/tools/cmd/goimports
// 更新
gopm get -g -v -u Golang.org/x/tools/cmd/goimports
// 安裝到GOPATH的bin目錄
go install Golang.org/x/tools/cmd/goimports
           

接口

接口的實作是隐式的,隻要實作接口的方法。

duck typing

“像鴨子走路,像鴨子叫(長得像鴨子),那麼就是鴨子”,意為描述事物的外部行為而非内部結構。嚴格說go屬于結構化類型系統,類似duck typing。

接口變量裡有什麼

  • 接口自帶指針
  • 接口變量同樣采用值傳遞,幾乎不需要使用接口的指針
  • 指針接收者隻能以指針的方式使用,值接收者都可以
  • interface{}可以代表任何類型
  • 定義接口
type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return r.Get("http://www.baidu.com")
}           
  • 實作接口
type Retriever struct {
    UserAgent string
    Timeout   time.Duration
}

func (r *Retriever) Get(url string) string {
    resp, err := http.Get(url)
    if nil != err {
        panic(err)
    }

    result, err := httputil.DumpResponse(resp, true)
    resp.Body.Close()

    if nil != err {
        panic(err)
    }

    return string(result)
}           
  • 調用接口
var r Retriever
r = &real.Retriever{
    UserAgent: "Mozilla/5.0",
    Timeout:   time.Minute,
}
fmt.Println(download(r))

// Type Assertion
if retriever, ok := r.(*real.Retriever); ok {
    fmt.Printf("%T %v\n", retriever, retriever)
}           
  • 接口組合
type HttpExecute interface {
    Retriever
    Poster
}           

函數式程式設計

  • 函數是一等公民:參數,變量,傳回值都可以是函數。
  • 高階函數:函數的參數可以是函數
  • 函數->閉包
func adder() func(int) int {
    sum := 0
    return func(i int) int {
        sum += i
        return sum
    }
}

func main() {
    // f中不僅是一個函數,還有對變量sum的引用
    f := adder()
    for i := 0; i < 10; i++ {
        fmt.Println(f(i))
    }
}           

錯誤處理和資源管理

defer

用于指定在函數結束之前執行,可以用于關閉檔案、釋放鎖等。

func tryDefer() {
    // 在函數借結束之前執行
    defer fmt.Println("execute last...")
    defer fmt.Println("execute before last...")
    fmt.Println("execute")
}           

panic

  • 停止目前函數執行
  • 一直向上傳回,執行每一層的defer
  • 如果沒有遇見recover,程式退出

recover

  • 僅在defer調用中使用
  • 擷取panic的值
  • 如果無法處理,重新panic
func tryRecover() {
    defer func() {
        r := recover()
        if err, ok := r.(error); ok {
            fmt.Println("error:", err)
        } else {
            panic(fmt.Sprintf("unknown error: %v", r))
        }
    }()

    panic(errors.New("this is an error"))
}           

測試

測試格式

  • 測試檔案與待測試代碼放在同一目錄下
  • 測試檔案名以 _test.go 結尾
  • 測試函數名為 TestXxx 或 Test_xxx 的格式,使用其它類型測試,如性能測試則函數名為BenchmarkXxx 或 ```Benchmark_xxx`` 的格式

傳統測試

  • 傳統測試資料與邏輯混在一起
  • 傳統測試的出錯資訊不明确
  • 傳統測試一旦出錯測試即全部結束

表格驅動測試

  • 測試代碼
func calcTriangle(a, b int) int {
    return int(math.Sqrt(float64(a*a + b*b)))
}           
  • 表格驅動測試

    分離了測試資料和測試邏輯,明确了出錯資訊,可以部分失敗。

func TestCalcTriangle(t *testing.T) {
    tests := []struct{ a, b, c int }{
        {3, 4, 5},
        {5, 12, 13},
        {8, 15, 18},
    }

    for _, tt := range tests {
        if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
            t.Errorf("calcTriangle(%d %d); got %d; expected %d", tt.a, tt.b, actual, tt.c)
        }
    }
}           

性能測試

執行b.N次,由Golang決定具體次數,測試完成後在控制台列印執行次數和平均執行時間。

func BenchmarkTriangle(b *testing.B) {
    x, y, z := 8, 15, 17
    for i := 0; i < b.N; i++ {
        actual := calcTriangle(x, y)
        if actual != z {
            b.Errorf("calcTriangle(%d %d); got %d; expected %d", x, y, actual, z)
        }
    }
}           

測試指令行工具

# 測試指令
go test
# 代碼覆寫率測試
go test -cover
# 生成代碼測試覆寫率檔案
go test -coverprofile c.out
# 代碼覆寫率測試html
go tool cover -html=c.out
# 性能測試
go test -bench .
# 生成性能報告
go test -bench . -cpuprofile cpu.out
           

文檔

Golang提供 go doc 指令檢視由注釋組成的文檔,通過 godoc -http :6060 指令在本地6060端口生成html文檔。在test檔案中建立名為 ExampleXxx_xxx 的函數,通過特定格式生成代碼執行個體文檔。

func ExampleQueue_Pop() {
    queue := Queue{}
    queue.Push(1)
    fmt.Println(queue.Pop())

    // Output:
    // 1
}           

go routine

協程Coroutine

  • 輕量級“線程”
  • 非搶占式多任務處理,由協程主動交出控制權
  • 編譯器/解釋器/虛拟機層面的多任務
  • 多個協程可能在一個或多個線程上運作

goroutine定義

  • 調用任何函數前隻需加上 go 關鍵字就可以交給排程器執行
  • 不需要在定義時區分是否為異步函數
  • 排程器會在合适的點進行切換(I/O、select、channel、等待鎖、函數調用等),或手動調用 runtime.Gosched() 進行切換
  • 使用race來檢測資料通路沖突, go run -race xxx.go

channel

相對于通過共享記憶體來通信,channel通過通信來共享記憶體。

  • chanel可以作為參數傳遞
  • 可以設定channel的buffer
  • 發送方負責關閉channel,接收方通過可以通過 range 判斷channel是否傳遞完畢

使用select進行排程

select可以用來監聽IO操作,可以同時監聽多個channel的消息狀态。

連結:https://www.jianshu.com/p/ee509e86dc56