golang快速入門(mén)

語(yǔ)言介紹

Go(又稱Golang)是Google開(kāi)發(fā)的一種靜態(tài)強(qiáng)類型、編譯型取逾、并發(fā)型,并具有垃圾回收功能的編程語(yǔ)言苹支。

羅伯特·格瑞史莫(Robert Griesemer)砾隅,羅勃·派克(Rob Pike)及肯·湯普遜(Ken Thompson)于2007年9月開(kāi)始設(shè)計(jì)Go,稍后Ian Lance Taylor债蜜、Russ Cox加入項(xiàng)目晴埂。Go是基于Inferno操作系統(tǒng)所開(kāi)發(fā)的。Go于2009年11月正式宣布推出寻定,成為開(kāi)放源代碼項(xiàng)目儒洛,并在LinuxMac OS X平臺(tái)上進(jìn)行了實(shí)現(xiàn),后來(lái)追加了Windows系統(tǒng)下的實(shí)現(xiàn)狼速。在2016年琅锻,Go被軟件評(píng)價(jià)公司TIOBE 選為“TIOBE 2016 年最佳語(yǔ)言”

Robert Griesemer, Rob Pike 和 Ken Thompson。Robert在開(kāi)發(fā)Go之前是Google V8向胡、Chubby和HotSpot JVM的主要貢獻(xiàn)者恼蓬;Rob主要是Unix、UTF-8捷枯、plan9的作者滚秩;Ken主要是B語(yǔ)言、C語(yǔ)言的作者淮捆、Unix之父郁油。

為什么會(huì)設(shè)計(jì)go語(yǔ)言

設(shè)計(jì)Go語(yǔ)言是為了解決當(dāng)時(shí)Google開(kāi)發(fā)遇到的以下這些問(wèn)題:

大量的C++代碼本股,同時(shí)又引入了Java和Python

成千上萬(wàn)的工程師

數(shù)以萬(wàn)計(jì)行的代碼

分布式的編譯系統(tǒng)

數(shù)百萬(wàn)的服務(wù)器

其主要有以下幾個(gè)方面的痛點(diǎn):

編譯慢

失控的依賴

每個(gè)工程師只是用了一個(gè)語(yǔ)言里面的一部分

程序難以維護(hù)(可讀性差、文檔不清晰等)

更新的花費(fèi)越來(lái)越長(zhǎng)

交叉編譯困難

所以桐腌,他們當(dāng)時(shí)設(shè)計(jì)Go的目標(biāo)是為了消除各種緩慢和笨重拄显、改進(jìn)各種低效和擴(kuò)展性。Go是由那些開(kāi)發(fā)大型系統(tǒng)的人設(shè)計(jì)的案站,同時(shí)也是為了這些人服務(wù)的躬审;它是為了解決工程上的問(wèn)題,不是為了研究語(yǔ)言設(shè)計(jì)蟆盐;它還是為了讓我們的編程變得更舒適和方便

語(yǔ)法規(guī)則介紹

包管理

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    fmt.Println("My favorite number is", rand.Intn(10))
}

output:
My favorite number is 1

每個(gè) Go 程序都是由包組成的承边。
程序運(yùn)行的入口是包 main
這個(gè)程序使用并導(dǎo)入了包 "fmt" 和 "math/rand"石挂。
按照慣例博助,包名與導(dǎo)入路徑的最后一個(gè)目錄一致。
例如痹愚,"math/rand" 包由 package rand 語(yǔ)句開(kāi)始富岳。
同一目錄下只能用同一個(gè)包名

  • 導(dǎo)出名

在導(dǎo)入了一個(gè)包之后,就可以用其導(dǎo)出的名稱來(lái)調(diào)用它拯腮。
在 Go 中窖式,首字母大寫(xiě)的名稱是被導(dǎo)出的。
Foo 和 FOO 都是被導(dǎo)出的名稱动壤。名稱 foo 是不會(huì)被導(dǎo)出的萝喘。
foo-相當(dāng)于php的private , Foo 和 FOO 相當(dāng)于public

package exports

import "fmt"

func Foo(){
    fmt.Println("it is Foo")
}

func FOO(){
    fmt.Println("it is FOO")
    foo()
}

func foo(){
    fmt.Println("it is foo")
}

函數(shù)

package main

import "fmt"

func add(x int, y int) int {
    return x + y
}

func add2(x, y int) int{
    return x+y
}

func add3(x, y int) (z int){
    z = x+y
    return
}

func main() {
    fmt.Println(add(42, 13))
    fmt.Println(add2(42, 13))
    fmt.Println(add3(42, 13))
}

output:
55
55
55

函數(shù)格式 func 函數(shù)名(無(wú)參/參數(shù)1, 參數(shù)2...)(返回結(jié)果|無(wú)){ 函數(shù)體}
執(zhí)行發(fā)現(xiàn)這三個(gè)函數(shù)返回結(jié)果相同狼电,只是格式不同
add-函數(shù)符合基本的函數(shù)格式
add2-對(duì)于同一類型參數(shù)蜒灰,可以在最后一個(gè)參數(shù)后面指明參數(shù)類型
add3-函數(shù)返回結(jié)果可以在返回結(jié)構(gòu)中聲明

  • 多值返回
package main

import "fmt"

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

output:
world hello

函數(shù)可以返回任意數(shù)量的返回值。
swap 函數(shù)返回了兩個(gè)字符串肩碟。

變量

package main

import "fmt"

var (
    a  bool
    b  string
    c  int        //有符號(hào)-等于cpu位數(shù)-- 如果是64位(-9223372036854775808 到 9223372036854775807)-`uname -m`
    c1 int8       //有符號(hào)-占用8bit(-128 到 127),int16,int32,int64類推
    d  uint       //無(wú)符號(hào)-等于cpu位數(shù)- 如果是64位(0 到 18446744073709551615)
    d1 uint8      //無(wú)符號(hào)-占用8bit(0 到 255), uint16,uint32,uint64類推
    e  rune       // int32 別名
    f  byte       // uint8 別名
    g  float32    //1.401298464324817070923729583289916131280e-45 -- 3.402823466385288598117041834516925440e+38 (23位小數(shù)f强窖,8位偏置指數(shù)e,1位符號(hào)s)
    h  float64    //4.940656458412465441765687928682213723651e-324 -- 1.797693134862315708145274237317043567981e+308(52位小數(shù)f削祈,11位偏置指數(shù)e翅溺,1位符號(hào)s)
    j  complex64  //32 位實(shí)數(shù)和虛數(shù)
    k  complex128 //64 位實(shí)數(shù)和虛數(shù)
)

var aa, bb, ee bool
var ff, gg int = 5, 6
var hh, ii = 5, true

func main() {
    var aaa, bbb bool = true, false
    ccc, ddd := true, 18
    fmt.Println(a, b, c, c1, d, d1, e, f, g, h, j, k)
    fmt.Println(aa, bb, ee)
    fmt.Println(ff, gg)
    fmt.Println(hh, ii)
    fmt.Println(aaa, bbb)
    fmt.Println(ccc, ddd)
}
output:  
false  0 0 0 0 0 0 0 0 (0+0i) (0+0i)
false false false
5 6
5 true
true false
true 18

go 基本的數(shù)據(jù)類型有bool類型,字符串類型髓抑,數(shù)字類型(復(fù)數(shù)類型)
默認(rèn)值分別為 false, "", 0(0+0i)
變量定義可以包含初始值咙崎,每個(gè)變量對(duì)應(yīng)一個(gè)。
如果省略類型吨拍;變量從初始值中獲得類型
在函數(shù)中褪猛,:= 簡(jiǎn)潔賦值語(yǔ)句在明確類型的地方,可以用于替代 var 定義羹饰。
函數(shù)外的每個(gè)語(yǔ)句都必須以關(guān)鍵字開(kāi)始(var伊滋、func碳却、等等),:= 結(jié)構(gòu)不能使用在函數(shù)外

  • 數(shù)字類型轉(zhuǎn)換
package main

import (
    "fmt"
    "math"
    "strconv"
)

//StringToInt 字符串轉(zhuǎn)整形
func StringToInt(valstr string) int {
    val, err := strconv.Atoi(valstr)
    if err != nil {
        val = 0
    }
    return val
}

//IntToString 整形轉(zhuǎn)字符串
func IntToString(intval int) string {
    return strconv.Itoa(intval)
}

func main() {
    var x, y int = 3, 4
    var f float64 = math.Sqrt(float64(x*x + y*y))
    var z int = int(f)
    fmt.Println(x, y, z)

    a := IntToString(x) + "string"
    b := "168"
    c := StringToInt(b)
    fmt.Println(a, b+"s", c)
}

output:
3 4 5
3string 168s 168

表達(dá)式 T(v) 將值 v 轉(zhuǎn)換為類型 T
數(shù)字類型一般可以直接顯式轉(zhuǎn)化笑旺,字符串和數(shù)字類型可以借助strconv包處理

  • 類型推導(dǎo)
package main

import "fmt"

func main() {
    i := 42           // int
    f := 3.142        // float64
    g := 0.867 + 0.5i // complex128
    var j bool
    k := j
    fmt.Printf("The types is %T, %T, %T, %T\n", i, f, g, k)
}
output:
The types is int, float64, complex128, bool

在定義一個(gè)變量但不指定其類型時(shí)(使用沒(méi)有類型的 var 或 := 語(yǔ)句)昼浦, 變量的類型由右值推導(dǎo)得出。當(dāng)右值定義了類型時(shí)筒主,新變量的類型與其相同

常量

package main

import "fmt"

const Pi = 3.14

const (
    Big   = 1 << 100
    Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
    return x * 0.1
}

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)

    fmt.Println(needInt(Small))
    fmt.Println(needFloat(Small))
    fmt.Println(needFloat(Big))
}
output:
Hello 世界
Happy 3.14 Day
Go rules? true
21
0.2
1.2676506002282295e+29

常量的定義與變量類似关噪,只不過(guò)使用 const 關(guān)鍵字。
常量可以是字符乌妙、字符串使兔、布爾或數(shù)字類型的值。
常量不能使用 := 語(yǔ)法定義冠胯。
一個(gè)未指定類型的數(shù)值常量可以作為不同數(shù)字類型傳參

循環(huán)

package main

import "fmt"

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    fmt.Println(sum)

    sum = 1
    for sum < 1000 {
        sum += sum
    }
    fmt.Println(sum)
}

output:
45
1024

Go 只有一種循環(huán)結(jié)構(gòu)——for 循環(huán)火诸。
基本的 for 循環(huán)除了沒(méi)有了 ( ) 之外(甚至強(qiáng)制不能使用它們)锦针,看起來(lái)跟 C 或者 Java 中做的一樣荠察,而 { } 是必須的。

  • 死循環(huán)
package main

import "fmt"

func main() {
    for {
        fmt.Println("hello world")
    }
}

output:
hello world
hello world
....

不手動(dòng)停止的話奈搜,代碼會(huì)一直運(yùn)行下去

if 語(yǔ)句

package main

import (
    "fmt"
    "math"
)

func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4))
}
output:
1.4142135623730951 2i

if 語(yǔ)句除了沒(méi)有了 ( ) 之外(甚至強(qiáng)制不能使用它們)悉盆,看起來(lái)跟 C 或者 Java 中的一樣,而 { } 是必須的馋吗。

  • if 的便捷語(yǔ)句
package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    }
    return lim
}

func pow2(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    } else {
        fmt.Printf("%g >= %g\n", v, lim)
    }
    // 這里開(kāi)始就不能使用 v 了
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
        pow2(3, 2, 10),
        pow2(3, 3, 20),
    )
}
output:
27 >= 20
9 20 9 20

跟 for 一樣焕盟,if 語(yǔ)句可以在條件之前執(zhí)行一個(gè)簡(jiǎn)單的語(yǔ)句。
由這個(gè)語(yǔ)句定義的變量的作用域僅在 if 范圍之內(nèi)宏粤。
在 if 的便捷語(yǔ)句定義的變量同樣可以在任何對(duì)應(yīng)的 else 塊中使用脚翘。

switch 語(yǔ)句

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}
output:
Go runs on OS X

switch 結(jié)構(gòu)同其他語(yǔ)言一樣
switch 的條件從上到下的執(zhí)行,當(dāng)匹配成功的時(shí)候停止绍哎。
每條case除非以 fallthrough 語(yǔ)句結(jié)束来农,否則分支會(huì)自動(dòng)終止。

  • 沒(méi)有條件的 switch
package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }
}
output:
Good evening.

沒(méi)有條件的 switch 同 switch true 一樣崇堰。
這一構(gòu)造使得可以用更清晰的形式來(lái)編寫(xiě)長(zhǎng)的 if-then-else 鏈沃于。

defer 語(yǔ)句

package main

import "fmt"

func echo(str string) string {
    defer fmt.Println("echo end")
    return "hello " + str
}
func main() {
    defer fmt.Println("world")

    fmt.Println("hello")

    echo("china")
}
output:
hello
echo end
world

defer 語(yǔ)句會(huì)延遲函數(shù)的執(zhí)行直到上層函數(shù)返回。
延遲調(diào)用的參數(shù)會(huì)立刻生成海诲,但是在上層函數(shù)返回前函數(shù)都不會(huì)被調(diào)用

  • defer 棧
package main

import "fmt"

func main() {
    fmt.Println("counting")

    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}
output:
counting
done
2
1
0

延遲的函數(shù)調(diào)用被壓入一個(gè)棧中繁莹。當(dāng)函數(shù)返回時(shí), 會(huì)按照后進(jìn)先出的順序調(diào)用被延遲的函數(shù)調(diào)用特幔。

派生類型

指針

package main

import "fmt"

func main() {
    i, j := 42, 2701

    p := &i         // point to i
    fmt.Println(*p) // read i through the pointer
    *p = 21         // set i through the pointer
    fmt.Println(i)  // see the new value of i

    p = &j         // point to j
    *p = *p / 37   // divide j through the pointer
    fmt.Println(j) // see the new value of j
}
output:
42
21
73

Go 具有指針咨演。 指針保存了變量的內(nèi)存地址。
類型 *T 是指向類型 T 的值的指針蚯斯。其零值是 nil薄风。
var p int
& 符號(hào)會(huì)生成一個(gè)指向其作用對(duì)象的指針零院。
i := 42
p = &i
* 符號(hào)表示指針指向的底層的值。
fmt.Println(
p) // 通過(guò)指針 p 讀取 i
*p = 21 // 通過(guò)指針 p 設(shè)置 i
這也就是通常所說(shuō)的“間接引用”或“非直接引用”村刨。

結(jié)構(gòu)體

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(v)
    v.X = 4
    fmt.Println(v)
    p := &v
    p.X = 1.5e9
    fmt.Println(v, v.X)
    fmt.Println(p, p.X)
    fmt.Println("------------------------")
    v1 := Vertex{1, 2}  // 類型為 Vertex
    v2 := Vertex{X: 1}  // Y:0 被省略
    v3 := Vertex{}      // X:0 和 Y:0
    p1 := &Vertex{1, 2} // 類型為 *Vertex
    fmt.Println(v1, v2, v3, p1)
    fmt.Printf("values is %+v, %+v, %+v, %+v \n", v1, v2, v3, p1)
    fmt.Printf("types is %T, %T, %T, %T \n", v1, v2, v3, p1)
    fmt.Printf("values is %#v, %#v, %#v, %#v \n", v1, v2, v3, p1)
}
output:
{1 2}
{4 2}
{1500000000 2} 1500000000
&{1500000000 2} 1500000000
------------------------
{1 2} {1 0} {0 0} &{1 2}
values is {X:1 Y:2}, {X:1 Y:0}, {X:0 Y:0}, &{X:1 Y:2} 
types is main.Vertex, main.Vertex, main.Vertex, *main.Vertex 
values is main.Vertex{X:1, Y:2}, main.Vertex{X:1, Y:0}, main.Vertex{X:0, Y:0}, &main.Vertex{X:1, Y:2} 

一個(gè)結(jié)構(gòu)體(struct)就是一個(gè)字段的集合告抄。
結(jié)構(gòu)體字段使用點(diǎn)號(hào)來(lái)訪問(wèn).
結(jié)構(gòu)體字段可以通過(guò)結(jié)構(gòu)體指針來(lái)訪問(wèn)。

數(shù)組

package main

import "fmt"

func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)
}

類型 [n]T 是一個(gè)有 n 個(gè)類型為 T 的值的數(shù)組嵌牺。
表達(dá)式 var a [10]int
定義變量 a 是一個(gè)有十個(gè)整數(shù)的數(shù)組打洼。
數(shù)組的長(zhǎng)度是其類型的一部分,因此數(shù)組不能改變大小
這看起來(lái)是一個(gè)制約逆粹,但是請(qǐng)不要擔(dān)心募疮; Go 提供了更加便利的方式來(lái)使用數(shù)組。

slice

package main

import "fmt"

func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)

    for i := 0; i < len(p); i++ {
        fmt.Printf("p[%d] == %d\n", i, p[i])
    }
}
output:
p == [2 3 5 7 11 13]
p[0] == 2
p[1] == 3
p[2] == 5
p[3] == 7
p[4] == 11
p[5] == 13

一個(gè) slice 會(huì)指向一個(gè)序列的值僻弹,并且包含了長(zhǎng)度信息阿浓。
[]T 是一個(gè)元素類型為 T 的 slice。

  • 對(duì) slice 切片
package main

import "fmt"

func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)
    fmt.Println("p[1:4] ==", p[1:4])

    // 省略下標(biāo)代表從 0 開(kāi)始
    fmt.Println("p[:3] ==", p[:3])

    // 省略上標(biāo)代表到 len(s) 結(jié)束
    fmt.Println("p[4:] ==", p[4:])
}

slice 可以重新切片蹋绽,創(chuàng)建一個(gè)新的 slice 值指向相同的數(shù)組芭毙。
表達(dá)式 s[lo:hi]表示從 lo 到 hi-1 的 slice 元素,含兩端卸耘。
因此s[lo:lo]是空的退敦,而 s[lo:lo+1]有一個(gè)元素。

  • 構(gòu)造 slice
package main

import "fmt"

func main() {
    a := make([]int, 5)
    printSlice("a", a)
    b := make([]int, 0, 5)
    printSlice("b", b)
    c := b[:2]
    printSlice("c", c)
    d := c[2:5]
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}
output:
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]

slice 由函數(shù) make 創(chuàng)建蚣抗。這會(huì)分配一個(gè)零長(zhǎng)度的數(shù)組并且返回一個(gè) slice 指向這個(gè)數(shù)組:
a := make([]int, 5) // len(a)=5
為了指定容量侈百,可傳遞第三個(gè)參數(shù)到 make
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4

  • nil slice
package main

import "fmt"

func main() {
    var z []int
    fmt.Println(z, len(z), cap(z))
    if z == nil {
        fmt.Println("nil!")
    }
}
output:
[] 0 0
nil!

slice 的零值是 nil
一個(gè) nil 的 slice 的長(zhǎng)度和容量是 0翰铡。

  • 向 slice 添加元素
package main

import "fmt"

func main() {
    var a []int
    printSlice("a", a)

    // append works on nil slices.
    a = append(a, 0)
    printSlice("a", a)

    // the slice grows as needed.
    a = append(a, 1)
    printSlice("a", a)

    // we can add more than one element at a time.
    a = append(a, 2, 3, 4)
    printSlice("a", a)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}
output:
a len=0 cap=0 []
a len=1 cap=1 [0]
a len=2 cap=2 [0 1]
a len=5 cap=6 [0 1 2 3 4]

向 slice 添加元素是一種常見(jiàn)的操作钝域,因此 Go 提供了一個(gè)內(nèi)建函數(shù) append。 內(nèi)建函數(shù)的文檔對(duì) append 有詳細(xì)介紹锭魔。

func append(s []T, vs ...T) []T

append 的第一個(gè)參數(shù) s 是一個(gè)類型為 T 的數(shù)組例证,其余類型為 T 的值將會(huì)添加到 slice。
append 的結(jié)果是一個(gè)包含原 slice 所有元素加上新添加的元素的 slice赂毯。
如果 s 的底層數(shù)組太小战虏,而不能容納所有值時(shí),會(huì)分配一個(gè)更大的數(shù)組党涕。
返回的 slice 會(huì)指向這個(gè)新分配的數(shù)組烦感。

map

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])

    m := make(map[string]int)

    m["Answer"] = 42
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 48
    fmt.Println("The value:", m["Answer"])

    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
}
output:
{40.68433 -74.39967}
The value: 42
The value: 48
The value: 0
The value: 0 Present? false

map 映射鍵到值。
map 在使用之前必須用 make 而不是 new 來(lái)創(chuàng)建膛堤;值為 nil 的 map 是空的手趣,并且不能賦值
在 map m 中插入或修改一個(gè)元素:
m[key] = elem
獲得元素:
elem = m[key]
刪除元素:
delete(m, key)
通過(guò)雙賦值檢測(cè)某個(gè)鍵存在:
elem, ok = m[key]
如果 key 在 m 中,ok 為 true 。否則绿渣, ok 為 false朝群,并且 elem 是 map 的元素類型的零值。
同樣的中符,當(dāng)從 map 中讀取某個(gè)不存在的鍵時(shí)姜胖,結(jié)果是 map 的元素類型的零值。

range

package main

import (
    "fmt"
    "sort"
)

var pow = []int{1, 2, 4, 8}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }

    m := make(map[string]int)
    m["Answer"] = 42
    m["welcome"] = 48

    var mp []string
    for k, v := range m {
        fmt.Printf("key:%s, value:%d \n", k, v)
        mp = append(mp, k)
    }

    sort.Strings(mp)
    for _, k := range mp {
        fmt.Printf("key:%s, value:%d \n", k, m[k])
    }
    pow := make([]int, 3)
    for i := range pow {
        pow[i] = 1 << uint(i)
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

output:
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
key:Answer, value:42 
key:welcome, value:48 
key:Answer, value:42 
key:welcome, value:48 
1
2
4

for 循環(huán)的 range 格式可以對(duì) slice 或者 map 進(jìn)行迭代循環(huán)淀散。
可以通過(guò)賦值給 _ 來(lái)忽略序號(hào)和值右莱。
如果只需要索引值,去掉“, value”的部分即可档插。

函數(shù)的閉包

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 3; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}
output:
0 0
1 -2
3 -6

Go 函數(shù)可以是閉包的慢蜓。閉包是一個(gè)函數(shù)值,它來(lái)自函數(shù)體的外部的變量引用。 函數(shù)可以對(duì)這個(gè)引用值進(jìn)行訪問(wèn)和賦值;換句話說(shuō)這個(gè)函數(shù)被“綁定”在這個(gè)變量上荐吵。
例如,函數(shù) adder 返回一個(gè)閉包翅萤。每個(gè)閉包都被綁定到其各自的 sum 變量上。

方法

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func (v Vertex) Scale2(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}



type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}


func main() {
    v := &Vertex{3, 4}
    fmt.Println(v.Abs())
    v.Scale(2)
    fmt.Printf("%#v \n", v)
    v.Scale2(2)
    fmt.Printf("%#v \n", v)
    v.Scale(2)
    fmt.Printf("%#v \n", v)

    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}
output:
5
&main.Vertex{X:6, Y:8} 
&main.Vertex{X:6, Y:8} 
&main.Vertex{X:12, Y:16} 
1.4142135623730951

Go 沒(méi)有類。然而,仍然可以在結(jié)構(gòu)體類型上定義方法帆谍。
方法接收者 出現(xiàn)在 func 關(guān)鍵字和方法名之間的參數(shù)中。
你可以對(duì)包中的 任意 類型定義任意方法轴咱,而不僅僅是針對(duì)結(jié)構(gòu)體。
但是烈涮,不能對(duì)來(lái)自其他包的類型或基礎(chǔ)類型定義方法朴肺。
方法可以與命名類型或命名類型的指針關(guān)聯(lián)。
剛剛看到的兩個(gè) Abs 方法坚洽。一個(gè)是在 *Vertex 指針類型上戈稿,而另一個(gè)在 MyFloat 值類型上。
有兩個(gè)原因需要使用指針接收者讶舰。

  • 首先避免在每個(gè)方法調(diào)用中拷貝值(如果值類型是大的結(jié)構(gòu)體的話會(huì)更有效率)鞍盗。
  • 其次,方法可以修改接收者指向的值跳昼。

嘗試修改 Abs 的定義般甲,同時(shí) Scale 方法使用 Vertex 代替 *Vertex 作為接收者。
當(dāng) v 是 Vertex 的時(shí)候 Scale 方法沒(méi)有任何作用鹅颊。Scale 修改 v敷存。當(dāng) v 是一個(gè)值(非指針),方法看到的是 Vertex 的副本堪伍,并且無(wú)法修改原始值锚烦。Abs 的工作方式是一樣的觅闽。只不過(guò),僅僅讀取 v涮俄。所以讀取的是原始值(通過(guò)指針)還是那個(gè)值的副本并沒(méi)有關(guān)系蛉拙。

接口

package main

import (
   "fmt"
   "os"
)

type Reader interface {
   Read(b []byte) (n int, err error)
}

type Writer interface {
   Write(b []byte) (n int, err error)
}

type ReadWriter interface {
   Reader
   Writer
}

func main() {
   var w Writer

   // os.Stdout 實(shí)現(xiàn)了 Writer
   w = os.Stdout

   fmt.Fprintf(w, "hello, writer\n")

   // os.Stdin 實(shí)現(xiàn)了 Writer, 但不是標(biāo)準(zhǔn)輸出
   w = os.Stdin

   fmt.Fprintf(w, "hello2, writer\n")
}
output:
hello, writer

接口類型是由一組方法定義的集合。
接口類型的值可以存放實(shí)現(xiàn)這些方法的任何值彻亲。
類型通過(guò)實(shí)現(xiàn)那些方法來(lái)實(shí)現(xiàn)接口刘离。 沒(méi)有顯式聲明的必要;所以也就沒(méi)有關(guān)鍵字“implements“睹栖。
隱式接口解藕了實(shí)現(xiàn)接口的包和定義接口的包:互不依賴硫惕。
因此,也就無(wú)需在每一個(gè)實(shí)現(xiàn)上增加新的接口名稱野来,這樣同時(shí)也鼓勵(lì)了明確的接口定義

  • Stringers
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person{"Arthur Dent", 42}
    z := Person{"Zaphod Beeblebrox", 9001}
    fmt.Println(a, z)
    fmt.Printf("%#v", a)
}

output:
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
main.Person{Name:"Arthur Dent", Age:42}

一個(gè)普遍存在的接口是 fmt 包中定義的 Stringer恼除。Stringer 是一個(gè)可以用字符串描述自己的類型。fmt包 (還有許多其他包)使用這個(gè)來(lái)進(jìn)行輸出

錯(cuò)誤

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}
output:
at 2019-06-26 20:43:20.529809 +0800 CST m=+0.000310142, it didn't work

Go 程序使用 error 值來(lái)表示錯(cuò)誤狀態(tài)曼氛。
與 fmt.Stringer 類似豁辉,error 類型是一個(gè)內(nèi)建接口:

type error interface {
      Error() string
}

(與 fmt.Stringer 類似,fmt 包在輸出時(shí)也會(huì)試圖匹配 error舀患。)

通常函數(shù)會(huì)返回一個(gè) error 值徽级,調(diào)用的它的代碼應(yīng)當(dāng)判斷這個(gè)錯(cuò)誤是否等于 nil, 來(lái)進(jìn)行錯(cuò)誤處理聊浅。

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)

error 為 nil 時(shí)表示成功餐抢;非 nil 的 error 表示錯(cuò)誤。

Readers

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
        fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        if err == io.EOF {
            break
        }
    }
}
output:
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""

io 包指定了 io.Reader 接口低匙, 它表示從數(shù)據(jù)流結(jié)尾讀取旷痕。
Go 標(biāo)準(zhǔn)庫(kù)包含了這個(gè)接口的許多實(shí)現(xiàn), 包括文件顽冶、網(wǎng)絡(luò)連接欺抗、壓縮、加密等等强重。
io.Reader 接口有一個(gè) Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 用數(shù)據(jù)填充指定的字節(jié) slice绞呈,并且返回填充的字節(jié)數(shù)和錯(cuò)誤信息。 在遇到數(shù)據(jù)流結(jié)尾時(shí)间景,返回 io.EOF 錯(cuò)誤佃声。
例子代碼創(chuàng)建了一個(gè) strings.Reader。 并且以每次 8 字節(jié)的速度讀取它的輸出拱燃。

Web 服務(wù)器

package main

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

type Hello struct{}

func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    err := http.ListenAndServe("localhost:4000", h)
    if err != nil {
        log.Fatal(err)
    }
}

包 http 通過(guò)任何實(shí)現(xiàn)了 http.Handler 的值來(lái)響應(yīng) HTTP 請(qǐng)求:

package http
type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

在這個(gè)例子中秉溉,類型 Hello 實(shí)現(xiàn)了 http.Handler
訪問(wèn) http://localhost:4000/ 會(huì)看到來(lái)自程序的問(wèn)候。

圖片

package main

import (
    "fmt"
    "image"
)

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}
output:
(0,0)-(100,100)
0 0 0 0

Package image 定義了 Image 接口:

package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

注意Bounds 方法的 Rectangle 返回值實(shí)際上是一個(gè) image.Rectangle召嘶, 其定義在 image 包中父晶。
(參閱文檔了解全部信息。)
color.Colorcolor.Model 也是接口弄跌,但是通常因?yàn)橹苯邮褂妙A(yù)定義的實(shí)現(xiàn) image.RGBAimage.RGBAModel而被忽視了甲喝。這些接口和類型由image/color 包定義。

多線程

goroutine

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 3; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}
output:
hello
world
world
hello
world
hello

goroutine 是由 Go 運(yùn)行時(shí)環(huán)境管理的輕量級(jí)線程铛只。

go f(x, y, z)

開(kāi)啟一個(gè)新的 goroutine 執(zhí)行

f(x, y, z)

f 埠胖, x , yz 是當(dāng)前 goroutine 中定義的淳玩,但是在新的 goroutine 中運(yùn)行 f直撤。

goroutine 在相同的地址空間中運(yùn)行,因此訪問(wèn)共享內(nèi)存必須進(jìn)行同步蜕着。sync 提供了這種可能谋竖,不過(guò)在 Go 中并不經(jīng)常用到,因?yàn)橛衅渌霓k法承匣。(在接下來(lái)的內(nèi)容中會(huì)涉及到蓖乘。)

channel

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // 將和送入 c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // 從 c 中獲取

    fmt.Println(x, y, x+y)
}
output:
-5 17 12

channel 是有類型的管道,可以用 channel 操作符 <- 對(duì)其發(fā)送或者接收值韧骗。

ch <- v    // 將 v 送入 channel ch嘉抒。
v := <-ch  // 從 ch 接收,并且賦值給 v袍暴。
(“箭頭”就是數(shù)據(jù)流的方向些侍。)

和 map 與 slice 一樣,channel 使用前必須創(chuàng)建:

ch := make(chan int)

默認(rèn)情況下容诬,在另一端準(zhǔn)備好之前娩梨,發(fā)送和接收都會(huì)阻塞。這使得 goroutine 可以在沒(méi)有明確的鎖或競(jìng)態(tài)變量的情況下進(jìn)行同步览徒。

  • 緩沖 channel
package main

import "fmt"

func main() {
    c := make(chan int, 2)
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
output:
1
2

channel 可以是 帶緩沖的。為 make 提供第二個(gè)參數(shù)作為緩沖長(zhǎng)度來(lái)初始化一個(gè)緩沖 channel:

ch := make(chan int, 100)

向緩沖 channel 發(fā)送數(shù)據(jù)的時(shí)候颂龙,只有在緩沖區(qū)滿的時(shí)候才會(huì)阻塞习蓬。當(dāng)緩沖區(qū)清空的時(shí)候接受阻塞。

range 和 close

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}

func main() {
    c := make(chan int, 4)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}
output:
0
1
1
2

range 和 close
發(fā)送者可以 close 一個(gè) channel 來(lái)表示再?zèng)]有值會(huì)被發(fā)送了措嵌。
接收者可以通過(guò)賦值語(yǔ)句的第二參數(shù)來(lái)測(cè)試 channel 是否被關(guān)閉:當(dāng)沒(méi)有值可以接收并且 channel 已經(jīng)被關(guān)閉躲叼,那么經(jīng)過(guò)

v, ok := <-ch

之后 ok 會(huì)被設(shè)置為 false
循環(huán) for i := range c 會(huì)不斷從 channel 接收值企巢,直到它被關(guān)閉枫慷。
注意: 只有發(fā)送者才能關(guān)閉 channel,而不是接收者。向一個(gè)已經(jīng)關(guān)閉的 channel 發(fā)送數(shù)據(jù)會(huì)引起 panic或听。 還要注意: channel 與文件不同探孝;通常情況下無(wú)需關(guān)閉它們。只有在需要告訴接收者沒(méi)有更多的數(shù)據(jù)的時(shí)候才有必要進(jìn)行關(guān)閉誉裆,例如中斷一個(gè) range顿颅。

select

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}
output:
0
1
1
2
3
quit

select 語(yǔ)句使得一個(gè) goroutine 在多個(gè)通訊操作上等待。
select 會(huì)阻塞足丢,直到條件分支中的某個(gè)可以繼續(xù)執(zhí)行粱腻,這時(shí)就會(huì)執(zhí)行那個(gè)條件分支。當(dāng)多個(gè)都準(zhǔn)備好的時(shí)候斩跌,會(huì)隨機(jī)選擇一個(gè)绍些。

  • 默認(rèn)選擇
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}
output:
    .
    .
tick.
    .
    .
tick.
    .
    .
BOOM!

當(dāng) select 中的其他條件分支都沒(méi)有準(zhǔn)備好的時(shí)候,default 分支會(huì)被執(zhí)行耀鸦。
為了非阻塞的發(fā)送或者接收柬批,可使用 default 分支:

select {
case i := <-c:
    // 使用 i
default:
    // 從 c 讀取會(huì)阻塞
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市揭糕,隨后出現(xiàn)的幾起案子萝快,更是在濱河造成了極大的恐慌,老刑警劉巖著角,帶你破解...
    沈念sama閱讀 210,835評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揪漩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吏口,警方通過(guò)查閱死者的電腦和手機(jī)奄容,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)产徊,“玉大人昂勒,你說(shuō)我怎么就攤上這事≈弁” “怎么了戈盈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,481評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谆刨。 經(jīng)常有香客問(wèn)我塘娶,道長(zhǎng),這世上最難降的妖魔是什么痊夭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,303評(píng)論 1 282
  • 正文 為了忘掉前任刁岸,我火速辦了婚禮,結(jié)果婚禮上她我,老公的妹妹穿的比我還像新娘虹曙。我一直安慰自己迫横,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布酝碳。 她就那樣靜靜地躺著矾踱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪击敌。 梳的紋絲不亂的頭發(fā)上介返,一...
    開(kāi)封第一講書(shū)人閱讀 49,729評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音沃斤,去河邊找鬼圣蝎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛衡瓶,可吹牛的內(nèi)容都是我干的徘公。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼哮针,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼关面!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起十厢,我...
    開(kāi)封第一講書(shū)人閱讀 37,633評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤等太,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蛮放,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缩抡,經(jīng)...
    沈念sama閱讀 44,088評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評(píng)論 2 326
  • 正文 我和宋清朗相戀三年包颁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞻想。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,563評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娩嚼,死狀恐怖蘑险,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岳悟,我是刑警寧澤佃迄,帶...
    沈念sama閱讀 34,251評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站贵少,受9級(jí)特大地震影響和屎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜春瞬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望套啤。 院中可真熱鬧宽气,春花似錦随常、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,712評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至涝影,卻和暖如春枣察,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背燃逻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,943評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工序目, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伯襟。 一個(gè)月前我還...
    沈念sama閱讀 46,240評(píng)論 2 360
  • 正文 我出身青樓猿涨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親姆怪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叛赚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評(píng)論 2 348