Golang語法

變量定義

變量定義語法

  • 使用var關(guān)鍵字,可放在函數(shù)內(nèi),也可放在包內(nèi)
// var + 變量名 + 數(shù)據(jù)類型(有默認(rèn)值)
var a int
var b string = "string"
// 通過賦值自動判斷類型
var c = true
// 集中定義
var (
    x = 1
    y = 2
)
  • 使用:=定義變量,只能在函數(shù)內(nèi)使用
// := 用于賦初值
a, b := 1, 2

內(nèi)建變量類型(builtin)

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

強(qiáng)制類型轉(zhuǎn)換

Golang要求強(qiáng)制類型轉(zhuǎn)換,無隱式轉(zhuǎn)換

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

常量

使用const關(guān)鍵字定義常量,const數(shù)值可作為各種類型使用。

// 不確定類型
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變量,后續(xù)變量依次加1
const (
    spring = iota
    summer
    autumn
    winter
)

變量定義要點(diǎn)

  • 變量類型寫在變量名之后
  • 編譯器可以推測變量類型
  • Golang沒有char,使用rune
  • 原生支持復(fù)數(shù)類型

分支

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后可以沒有表達(dá)式,case結(jié)束后自動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,除非出現(xiàn)fallthrough
        fallthrough
    case score >= 100:
        grade = "S"
    }
    fmt.Println(grade)
}

循環(huán)

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

函數(shù)

  • 函數(shù)聲明的順序?yàn)椋宏P(guān)鍵字,函數(shù)名,參數(shù),返回類型,函數(shù)體。
  • 函數(shù)允許有多個返回值,并能在函數(shù)體內(nèi)拿到返回值。
  • 函數(shù)可以作為另一個函數(shù)的參數(shù)
  • 函數(shù)支持可變參數(shù)列表
func div(a, b int) (q, r int) {
    // q = a / b
    // r = a % b
    // return
    return a / b, a % b
}

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

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

// 可變參數(shù)列表
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
}

數(shù)組

數(shù)組長度寫在類型之前,[10]int和[20]int是不同的類型。數(shù)組是值復(fù)制。

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

遍歷數(shù)組

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

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

// 按下標(biāo)和值遍歷
for i, v := range arr3 {
    fmt.Println(i, v)
}

切片

數(shù)組切片相當(dāng)于數(shù)據(jù)的一個視圖,可以通過切片改變數(shù)組的值。

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]
    
    // 切片擴(kuò)展
    s5 := s3[6:8]
    fmt.Println(s5)
    
    // 添加元素
    s6 := append(s5, 8)
    fmt.Println(s6, array)
    fmt.Println(array[:8])
    
    // 通過make創(chuàng)建切片
    // 長度為10的切片
    x := make([]int, 10)
    // 長度為10,容量為16的切片
    y := make([]int, 10, 16)

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

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

切片底層維護(hù)了一個數(shù)組,ptr指向切片的首個元素,len表示切片的長度,cap表示底層數(shù)組的從ptr到最末的元素?cái)?shù)量。切片可以向后擴(kuò)展,不可向前擴(kuò)展。切片添加元素如果會超過底層數(shù)組的cap,Golang會分配更大的底層數(shù)組(原容量*2進(jìn)行擴(kuò)展),由于值傳遞的關(guān)系,必須接收append方法的返回值。

map

通過map[K]v的形式定義map。除了slice、map、function的內(nèi)建類型和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相當(dāng)于java的char,中文字符在Golang中占3個字節(jié),使用 utf8.RuneCountInString 獲取字符數(shù),用 len 獲取字節(jié)長度,使用 []byte 獲取字節(jié)。

s := "學(xué)習(xí)Golang!"
fmt.Println(s)

// ch是rune類型,中文字符占3個字節(jié)
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()

面向?qū)ο?/h3>

Golang僅支持封裝,不支持繼承和多態(tài),沒有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支持顯式定義方法接收者,值/指針接收者均可接收值/指針。當(dāng)需要改變內(nèi)容或結(jié)構(gòu)過大時需要使用指針接收者,值接收者是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包包含可執(zhí)行入口。為結(jié)構(gòu)定義的方法必須放在同一個包內(nèi),可以是不同的文件。Golang提供了兩種擴(kuò)展系統(tǒng)類型和已封裝類型的方法:

  • 定義別名:通過type為已有類型定義一個別名,針對新的結(jié)構(gòu)體進(jìn)行擴(kuò)展。
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
}
  • 使用組合:定義新的結(jié)構(gòu)體,其中一個成員是需要擴(kuò)展的類型的指針。
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

接口

接口的實(shí)現(xiàn)是隱式的,只要實(shí)現(xiàn)接口的方法。

duck typing

“像鴨子走路,像鴨子叫(長得像鴨子),那么就是鴨子”,意為描述事物的外部行為而非內(nèi)部結(jié)構(gòu)。嚴(yán)格說go屬于結(jié)構(gòu)化類型系統(tǒng),類似duck typing。

接口變量里有什么

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

func download(r Retriever) string {
    return r.Get("http://www.baidu.com")
}
  • 實(shí)現(xiàn)接口
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)
}
  • 調(diào)用接口
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
}

函數(shù)式編程

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

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

錯誤處理和資源管理

defer

用于指定在函數(shù)結(jié)束之前執(zhí)行,可以用于關(guān)閉文件、釋放鎖等。

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

panic

  • 停止當(dāng)前函數(shù)執(zhí)行
  • 一直向上返回,執(zhí)行每一層的defer
  • 如果沒有遇見recover,程序退出

recover

  • 僅在defer調(diào)用中使用
  • 獲取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 結(jié)尾
  • 測試函數(shù)名為 TestXxxTest_xxx 的格式,使用其它類型測試,如性能測試則函數(shù)名為BenchmarkXxx 或 ```Benchmark_xxx`` 的格式

傳統(tǒng)測試

  • 傳統(tǒng)測試數(shù)據(jù)與邏輯混在一起
  • 傳統(tǒng)測試的出錯信息不明確
  • 傳統(tǒng)測試一旦出錯測試即全部結(jié)束

表格驅(qū)動測試

  • 測試代碼
func calcTriangle(a, b int) int {
    return int(math.Sqrt(float64(a*a + b*b)))
}
  • 表格驅(qū)動測試
    分離了測試數(shù)據(jù)和測試邏輯,明確了出錯信息,可以部分失敗。
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)
        }
    }
}

性能測試

執(zhí)行b.N次,由Golang決定具體次數(shù),測試完成后在控制臺打印執(zhí)行次數(shù)和平均執(zhí)行時間。

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 .
# 生成性能報(bào)告
go test -bench . -cpuprofile cpu.out

文檔

Golang提供 go doc 命令查看由注釋組成的文檔,通過 godoc -http :6060 命令在本地6060端口生成html文檔。在test文件中建立名為 ExampleXxx_xxx 的函數(shù),通過特定格式生成代碼實(shí)例文檔。

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

    // Output:
    // 1
}

go routine

協(xié)程Coroutine

  • 輕量級“線程”
  • 非搶占式多任務(wù)處理,由協(xié)程主動交出控制權(quán)
  • 編譯器/解釋器/虛擬機(jī)層面的多任務(wù)
  • 多個協(xié)程可能在一個或多個線程上運(yùn)行

goroutine定義

  • 調(diào)用任何函數(shù)前只需加上 go 關(guān)鍵字就可以交給調(diào)度器執(zhí)行
  • 不需要在定義時區(qū)分是否為異步函數(shù)
  • 調(diào)度器會在合適的點(diǎn)進(jìn)行切換(I/O、select、channel、等待鎖、函數(shù)調(diào)用等),或手動調(diào)用 runtime.Gosched() 進(jìn)行切換
  • 使用race來檢測數(shù)據(jù)訪問沖突, go run -race xxx.go

channel

相對于通過共享內(nèi)存來通信,channel通過通信來共享內(nèi)存。

  • chanel可以作為參數(shù)傳遞
  • 可以設(shè)置channel的buffer
  • 發(fā)送方負(fù)責(zé)關(guān)閉channel,接收方通過可以通過 range 判斷channel是否傳遞完畢

使用select進(jìn)行調(diào)度

select可以用來監(jiān)聽IO操作,可以同時監(jiān)聽多個channel的消息狀態(tài)。

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

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