GO系列—基礎知識指南

Go語言基礎知識點html

1. 說明

2. 環境安裝

2.1. 配置的三個環境變量說明

  • go env命令能夠查看Go語言的環境變量設置
  • GOROOT:這個是Go語言的安裝目錄
  • GOPATH:這個是Go語言的工做目錄
    • GOPATH能夠有多個用符號分割,(多個目錄的時候Windows是分號,Linux系統是冒號)。可是第下載的包會放在第一個下面
    • 從go 1.8開始,GOPATH環境變量如今有一個默認值,若是它沒有被設置。 它在Unix上默認爲$HOME/go,在Windows上默認爲%USERPROFILE%/go。
  • GOBIN:這個實際上指向的是%GOPATH%/bin目錄

2.2. linux

  • 首先,根據對應的操做系統選擇安裝包下載,在這裏我使用的是Centos 64位系統

wget https://studygolang.com/dl/golang/go1.9.2.linux-amd64.tar.gzmysql

sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/locallinux

  • 配置 /etc/profile

vi /etc/profilegit

  • 添加環境變量GOROOT和將GOBIN添加到PATH中

export GOROOT=/usr/local/gogithub

export PATH=$PATH:$GOROOT/bingolang

  • 添加環境變量GOPATH(這個可按實際狀況設置目錄位置)

export GOPATH=/usr/local/go/pathweb

  • 配置完畢後,執行命令令其生效

source /etc/profilesql

  • 在控制檯輸入go version,若輸出版本號則安裝成功

2.3. windows

2.4. 目錄結構介紹

首先,咱們在解壓的時候會獲得一個名爲go的文件夾,其中包括了全部Go語言相關的一些文件,在這下面又包含不少文件夾和文件,咱們來簡單說明其中主要文件夾的做爲:mongodb

  • api:用於存放依照Go版本順序的API增量列表文件。這裏所說的API包含公開的變量、常量、函數等。這些API增量列表文件用於Go語言API檢查
  • bin:用於存放主要的標準命令文件(可執行文件),包含go、godoc、gofmt
  • blog:用於存放官方博客中的全部文章
  • doc:用於存放標準庫的HTML格式的程序文檔。咱們能夠經過godoc命令啓動一個Web程序展現這些文檔
  • lib:用於存放一些特殊的庫文件
  • misc:用於存放一些輔助類的說明和工具
  • pkg:用於存放安裝Go標準庫後的全部歸檔文件(以.a結尾的文件)。注意,你會發現其中有名稱爲linux_amd64的文件夾,咱們稱爲平臺相關目錄。這類文件夾的名稱由對應的操做系統和計算架構的名稱組合而成。經過go install命令,Go程序會被編譯成平臺相關的歸檔文件存放到其中
  • src:用於存放Go自身、Go標準工具以及標準庫的全部源碼文件
  • test:存放用來測試喝驗證Go自己的全部相關文件

2.5. 其餘概念介紹

2.5.1. 工做區

這在Go中是一個很是重要的概念,在通常狀況下,Go源碼文件必須放在工做區中,也就是說,咱們寫的項目代碼都必須放在咱們所設定的工做區中,雖然對於命令源碼文件來講,這不是必須的。但咱們大多都是前一種狀況。工做區其實就是一個對應特定工程的目錄,它應包含3個子目錄:shell

  • src目錄:存放源代碼(好比:.go .c .h .s等)
  • pkg目錄:編譯後生成的文件(好比:.a)
  • bin目錄:編譯後生成的可執行文件(爲了方便,能夠把此目錄加入到 $PATH 變量中,若是有多個gopath,那麼使用${GOPATH//://bin:}/bin添加全部的bin目錄)

2.5.2. 命令源文件

若是一個源碼文件被聲明屬於main代碼包,且該文件代碼中包含無參數聲明喝結果聲明的main函數,則它就是命令源碼文件。命令源碼文件可經過go run命令直接啓動運行

2.5.3. .a文件

2.5.3.1. 編譯應用

  • 上面咱們已經創建了本身的應用包,如何進行編譯安裝呢?有兩種方式能夠進行安裝
  • 一、只要進入對應的應用包目錄,而後執行go install,就能夠安裝了
  • 二、在任意的目錄執行以下代碼go install mymath
安裝完以後,咱們能夠進入以下目錄

cd $GOPATH/pkg/${GOOS}_${GOARCH}
//能夠看到以下文件
mymath.a

這個.a文件是應用包,那麼咱們如何進行調用呢?

接下來咱們新建一個應用程序來調用這個應用包

新建應用包mathapp

cd $GOPATH/src
mkdir mathapp
cd mathapp
vim main.go

$GOPATH/src/mathapp/main.go源碼:

package main

import (
    "mymath"
    "fmt"
)

func main() {
    fmt.Printf("Hello, world.  Sqrt(2) = %v\n", mymath.Sqrt(2))
}

能夠看到這個的package是main,import裏面調用的包是mymath,這個就是相對於$GOPATH/src的路徑,若是是多級目錄,就在import裏面引入多級目錄,若是你有多個GOPATH,也是同樣,Go會自動在多個$GOPATH/src中尋找。

如何編譯程序呢?進入該應用目錄,而後執行go build,那麼在該目錄下面會生成一個mathapp的可執行文件

./mathapp

輸出以下內容

Hello, world.  Sqrt(2) = 1.414213562373095

如何安裝該應用,進入該目錄執行go install,那麼在$GOPATH/bin/下增長了一個可執行文件mathapp, 還記得前面咱們把$GOPATH/bin加到咱們的PATH裏面了,這樣能夠在命令行輸入以下命令就能夠執行

mathapp

也是輸出以下內容

Hello, world.  Sqrt(2) = 1.414213562373095

這裏咱們展現如何編譯和安裝一個可運行的應用,以及如何設計咱們的目錄結構。

2.5.4. bin和pkg目錄幹什麼用的 ??????

  • 目前說不清楚

2.5.5. go build和go install還有go run,go get的區別

  • go run:go run 編譯並直接運行程序,它會產生一個臨時文件(但不會生成 .exe 文件),直接在命令行輸出程序執行結果,方便用戶調試。
  • go build:go build 用於測試編譯包,主要檢查是否會有編譯錯誤,若是是一個可執行文件的源碼(便是 main 包),就會直接生成一個可執行文件到當前的命名執行目錄。
  • go install:go install 的做用有兩步:第一步是編譯導入的包文件,全部導入的包文件編譯完纔會編譯主程序;第二步是將編譯後生成的可執行文件放到 bin 目錄下($GOPATH/bin),編譯後的包文件放到 pkg 目錄下($GOPATH/pkg)。
  • go get:go get會作兩件事:
    • 從遠程下載須要用到的包
    • 執行go install
  • 下面的連接是各命令的執行結果:https://blog.csdn.net/zyz770834013/article/details/78656985

2.5.6. GO命令介紹

3. 關鍵字

25個關鍵字

break    default      func    interface    select
case     defer        go      map          struct
chan     else         goto    package      switch
const    fallthrough  if      range        type
continue for          import  return       var


var和const Go語言基礎裏面的變量和常量申明
package和import已經有太短暫的接觸
func 用於定義函數和方法
return 用於從函數返回
defer 用於相似析構函數
go 用於併發
select 用於選擇不一樣類型的通信
interface 用於定義接口
struct 用於定義抽象數據類型
break、case、continue、for、fallthrough、else、if、switch、goto、default這些參考2.3流程介紹裏面
chan用於channel通信
type用於聲明自定義類型
map用於聲明map類型數據
range用於讀取slice、map、channel數據

4. 包

package是最基本的分發單位和工程管理中依賴關係的體現

4.1. 包概念

  • 多個文件可能被打包在一塊兒,由於包名是同樣的,有些文件裏面的變量沒有定義,可是可能在其餘文件中定義了,只是包名同樣,打包到一塊兒了
  • 而後咱們在go中是按照文件夾引用的
  • Foo 和 FOO 都是被導出的名稱。名稱 foo 是不會被導出的。大寫的函數名是被導出的
  • 每一個Go語言源代碼文件開頭都必需要有一個package聲明,表示源代碼文件所屬包
  • 要生成Go語言可執行程序,必需要有名爲main的package包,且在該包下必須有且只有一個main函數
  • 同一個路徑下只能存在一個package,一個package能夠由多個源代碼文件組成

4.2. 包引用原理

  • 跟package相似,import原理遵照如下幾個原則:
  • 若是一個main導入其餘的包,包會被順序導入
  • 若是導入的包(pkg1)依賴其餘的包(包pkg2),會首先導入pkg2,而後初始化pkg2中的常量與變量,若是pkg2中有init函數,會自動執行init
  • 全部包導入完成後纔會對main的常量和變量進行初始化,而後執行main中的init函數(若是有的話),最後執行main函數
  • 若是一個包被導入屢次實際上只會導入一次

4.3. import

4.3.1. 普通引入

  • 相對路徑:import 「./model」 //當前文件同一目錄的model目錄,可是不建議這種方式來import
  • 絕對路徑:import 「shorturl/model」 //加載gopath/src/shorturl/model模塊
import "fmt"

    inport (
        "fmt"
    )

    import (
        "log"
        "fmt"

        "github.com/jinzhu/gorm"
        _ "github.com/jinzhu/gorm/dialects/mysql"

        "gin-blog/pkg/setting"
    )

4.3.2. 特殊用法

  • 一、.操做:這個點操做的含義就是這個包導入以後在你調用這個包的函數時,你能夠省略前綴的包名,也就是前面你調用的fmt.Println("hello world")能夠省略的寫成Println("hello world")
import(
     . "fmt"
 )
  • 二、別名操做:別名操做顧名思義咱們能夠把包命名成另外一個咱們用起來容易記憶的名字
import(
     f "fmt"
 )
  • 三、操做:操做實際上是引入該包,而不直接使用包裏面的函數,而是調用了該包裏面的init函數
import (
        "database/sql"
        _ "github.com/ziutek/mymysql/godrv"
    )

5. 變量

5.1. 變量定義

  • 函數外的每一個語句都必須以關鍵字開始(varfunc、等等)
  • 在函數中,:= 簡潔賦值語句在明確類型的地方,能夠用於替代 var 定義。
  • := 這種形式有個侷限只能用在函數內部,因此通常用var方式來定義全局變量
  • _(下劃線)是個特殊的變量名,任何賦予它的值都會被丟棄。
  • 對於沒有給類型的變量,Go會根據其相應值的類型推倒來幫你初始化它們
  • Go對於已聲明但未使用的變量會在編譯階段報錯,好比下面的代碼就會產生一個錯誤:聲明瞭i但未使用。
package main

func main() {
    var i int
}

5.2. 變量賦值

var variableName type
var vname1, vname2, vname3 type
var variableName type = value
var vname1, vname2, vname3 type= v1, v2, v3
// Go會根據其相應值的類型來幫你初始化它們
var vname1, vname2, vname3 = v1, v2, v3
vname1, vname2, vname3 := v1, v2, v3

5.3. 分組聲明

  • 在Go語言中,同時聲明多個常量、變量,或者導入多個包時,可採用分組的方式進行聲明。
import "fmt"
import "os"

const i = 100
const pi = 3.1415
const prefix = "Go_"

var i int
var pi float32
var prefix string

能夠分組寫成以下形式:

import(
    "fmt"
    "os"
)

const(
    i = 100
    pi = 3.1415
    prefix = "Go_"
)

var(
    i int
    pi float32
    prefix string
)

5.4. 常量

  • 所謂常量,也就是在程序編譯階段就肯定下來的值,而程序在運行時沒法改變該值。在Go程序中,常量可定義爲數值、布爾值或字符串等類型。
  • Go常量和通常程序語言不一樣的是,能夠指定至關多的小數位數(例如200位), 若指定給float32自動縮短爲32bit,指定給float64自動縮短爲64bit
  • 常量不能使用 := 語法定義。
  • 常量一般不須要指定類型,若是須要,也能夠明確指定常量的類型
const constantName = value
const Pi float32 = 3.1415926

5.5. 零值

  • 變量在定義時沒有明確的初始化時會賦值爲_零值_。
  • 關於「零值」,所指並不是是空值,而是一種「變量未填充前」的默認值,一般爲0。
  • 零值是
int     0
int8    0
int32   0
int64   0
uint    0x0
rune    0 //rune的實際類型是 int32
byte    0x0 // byte的實際類型是 uint8
float32 0 //長度爲 4 byte
float64 0 //長度爲 8 byte
bool    false
string  ""

6. 內置基礎類型

6.1. 基礎類型

  • bool
  • string

    • 字符串是用一對雙引號("")或反引號(`)括起來定義
    • 在Go中字符串是不可變的,例以下面的代碼編譯時會報錯:cannot assign to s[0]
    var s string = "hello"
      s[0] = 'c'
    • 但若是真的想要修改怎麼辦呢?下面的代碼能夠實現
    s := "hello"
      c := []byte(s)  // 將字符串 s 轉換爲 []byte 類型
      c[0] = 'c'
      s2 := string(c)  // 再轉換回 string 類型
      fmt.Printf("%s\n", s2)
    • Go中可使用+操做符來鏈接兩個字符串
    s := "hello,"
      m := " world"
      a := s + m
      fmt.Printf("%s\n", a)
    • 修改字符串也可寫爲
    s := "hello"
      s = "c" + s[1:] // 字符串雖不能更改,但可進行切片操做
      fmt.Printf("%s\n", s)
    • 聲明多行字符串,能夠經過`來聲明, 括起的字符串爲Raw字符串,即字符串在代碼中的形式就是打印時的形式,它沒有字符轉義,換行也將原樣輸出
    m := `hello
                    world`
  • 整數類型

    • 無符號:int int8 int16 int32 int64
    • 帶符號:uint uint8 uint16 uint32 uint64 uintptr
    • 其中rune是int32的別稱,byte是uint8的別稱
    • 這些類型的變量之間不容許互相賦值或操做,否則會在編譯時引發編譯器報錯。
  • 浮點型
    • float32 float64
    • (沒有float類型),默認是float64
  • 複數
    • complex64 complex128
    • 它的默認類型是complex128(64位實數+64位虛數)。若是須要小一些的,也有complex64(32位實數+32位虛數)。複數的形式爲RE + IMi,其中RE是實數部分,IM是虛數部分,而最後的i是虛數單位
  • 錯誤類型

    • Go內置有一個error類型,專門用來處理錯誤信息,Go的package裏面還專門有一個包errors來處理錯誤
    err := errors.New("emit macho dwarf: elf header corrupted")
      if err != nil {
          fmt.Print(err)
      }

6.2. 類型轉換

表達式 T(v) 將值 v 轉換爲類型 T

一些關於數值的轉換:

var i int = 42 var f float64 = float64(i) var u uint = uint(f)

或者,更加簡單的形式:

i := 42 f := float64(i) u := uint(f)

與 C 不一樣的是 Go 的在不一樣類型之間的項目賦值時須要顯式轉換。 試着移除例子中 float64 或 int 的轉換看看會發生什麼。

6.3. 類型推倒

在定義一個變量但不指定其類型時(使用沒有類型的 var 或 := 語句), 變量的類型由右值推導得出。

當右值定義了類型時,新變量的類型與其相同:

var i int j := i // j 也是一個 int

可是當右邊包含了未指名類型的數字常量時,新的變量就多是 int 、 float64 或 complex128。 這取決於常量的精度:

i := 42 // int f := 3.142 // float64 g := 0.867 + 0.5i // complex128

嘗試修改演示代碼中 v 的初始值,並觀察這是如何影響其類型的。

7. 複雜數據類型

7.1. iota枚舉

<http://www.cnblogs.com/zsy/p/5370052.html>
Go裏面有一個關鍵字iota,這個關鍵字用來聲明enum的時候採用,它默認開始值是0,const中每增長一行加1:

package main

import (
    "fmt"
)

const (
    x = iota // x == 0
    y = iota // y == 1
    z = iota // z == 2
    w        // 常量聲明省略值時,默認和以前一個值的字面相同。這裏隱式地說w = iota,所以w == 3。其實上面y和z可一樣不用"= iota"
)

const v = iota // 每遇到一個const關鍵字,iota就會重置,此時v == 0

const (
    h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
)

const (
    a       = iota //a=0
    b       = "B"
    c       = iota             //c=2
    d, e, f = iota, iota, iota //d=3,e=3,f=3
    g       = iota             //g = 4
)

func main() {
    fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}

    除非被顯式設置爲其它值或iota,每一個const分組的第一個常量被默認設置爲它的0值,第二及後續的常量被默認設置爲它前面那個常量的值,若是前面那個常量的值是iota,則它也被設置爲iota。

7.2. 指針

Go 具備指針。 指針保存了變量的內存地址。

類型 *T 是指向類型 T 的值的指針。其零值是 nil

var p *int int型的指針

& 符號會生成一個指向其做用對象的指針。

i := 42 p = &i

  • 符號表示指針指向的底層的值。

fmt.Println(p) // 經過指針 p 讀取 i p = 21 // 經過指針 p 設置 i

這也就是一般所說的「間接引用」或「非直接引用」。

與 C 不一樣,Go 沒有指針運算。

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

}

7.3. 結構體

Go語言中,也和C或者其餘語言同樣,咱們能夠聲明新的類型,做爲其它類型的屬性或字段的容器。例如,咱們能夠建立一個自定義類型person表明一我的的實體。這個實體擁有屬性:姓名和年齡。這樣的類型咱們稱之struct

7.3.1. 聲明

  • 普通
type person struct {
    name string
    age int
}

var P person  // P如今就是person類型的變量了

P.name = "Astaxie"  // 賦值"Astaxie"給P的name屬性.
P.age = 25  // 賦值"25"給變量P的age屬性
fmt.Printf("The person's name is %s", P.name)  // 訪問P的name屬性.
  • 按照順序提供初始化值:P := person{"Tom", 25}
  • 經過field:value的方式初始化,這樣能夠任意順序:P := person{age:24, name:"Tom"}
  • 固然也能夠經過new函數分配一個指針,此處P的類型爲*person:P := new(person)

7.3.2. struct的匿名字段(嵌入字段)

  • 當匿名字段是一個struct的時候,那麼這個struct所擁有的所有字段都被隱式地引入了當前定義的這個struct
package main

import "fmt"

type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human  // 匿名字段,那麼默認Student就包含了Human的全部字段
    speciality string
}

func main() {
    // 咱們初始化一個學生
    mark := Student{Human{"Mark", 25, 120}, "Computer Science"}

    // 咱們訪問相應的字段
    fmt.Println("His name is ", mark.name)
    fmt.Println("His age is ", mark.age)
    fmt.Println("His weight is ", mark.weight)
    fmt.Println("His speciality is ", mark.speciality)
    // 修改對應的備註信息
    mark.speciality = "AI"
    fmt.Println("Mark changed his speciality")
    fmt.Println("His speciality is ", mark.speciality)
    // 修改他的年齡信息
    fmt.Println("Mark become old")
    mark.age = 46
    fmt.Println("His age is", mark.age)
    // 修改他的體重信息
    fmt.Println("Mark is not an athlet anymore")
    mark.weight += 60
    fmt.Println("His weight is", mark.weight)
}
  • 嵌入字段也能夠做爲字段名去使用
mark.Human = Human{"Marcus", 55, 220}
mark.Human.age -= 1
  • 經過匿名訪問和修改字段至關的有用,可是不只僅是struct字段哦,全部的內置類型和自定義類型都是能夠做爲匿名字段的
package main

import "fmt"

type Skills []string

type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human  // 匿名字段,struct
    Skills // 匿名字段,自定義的類型string slice
    int    // 內置類型做爲匿名字段
    speciality string
}

func main() {
    // 初始化學生Jane
    jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
    // 如今咱們來訪問相應的字段
    fmt.Println("Her name is ", jane.name)
    fmt.Println("Her age is ", jane.age)
    fmt.Println("Her weight is ", jane.weight)
    fmt.Println("Her speciality is ", jane.speciality)
    // 咱們來修改他的skill技能字段
    jane.Skills = []string{"anatomy"}
    fmt.Println("Her skills are ", jane.Skills)
    fmt.Println("She acquired two new ones ")
    jane.Skills = append(jane.Skills, "physics", "golang")
    fmt.Println("Her skills now are ", jane.Skills)
    // 修改匿名內置類型字段
    jane.int = 3
    fmt.Println("Her preferred number is", jane.int)
}
  • 嵌入字段屬性名和原始屬性名重名處理規則是:最外層的優先訪問,固然若是咱們想訪問重載後對應匿名類型裏面的字段,能夠經過匿名字段名來訪問
package main

import "fmt"

type Human struct {
    name string
    age int
    phone string  // Human類型擁有的字段
}

type Employee struct {
    Human  // 匿名字段Human
    speciality string
    phone string  // 僱員的phone字段
}

func main() {
    Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
    fmt.Println("Bob's work phone is:", Bob.phone)
    // 若是咱們要訪問Human的phone字段
    fmt.Println("Bob's personal phone is:", Bob.Human.phone)
}

7.3.3. 結構體指針????????

  • 經過指針間接的訪問是透明的,其實我不是很明白這句話是作什麼的
package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    p := &v
    p.X = 1e9
    fmt.Println(v)
}
 結構體字段能夠經過結構體指針來訪問。

7.3.4. StructTag

7.3.4.1. 來源

  • 要先了解一下golang的基礎,在golang中,命名都是推薦都是用駝峯方式,而且在首字母大小寫有特殊的語法含義:包外沒法引用。可是由常常須要和其它的系統進行數據交互,例如轉成json格式,存儲到mongodb啊等等。這個時候若是用屬性名來做爲鍵值可能不必定會符合項目要求。
  • 因此呢就多了小米點的內容,在golang中叫標籤(Tag),在轉換成其它數據格式的時候,會使用其中特定的字段做爲鍵值
u := &User{UserId: 1, UserName: "tony"}
j, _ := json.Marshal(u)
fmt.Println(string(j))
// 輸出內容:{"user_id":1,"user_name":"tony"}

 若是在屬性中不增長標籤說明,則輸出:

{"UserId":1,"UserName":"tony"}

能夠看到直接用struct的屬性名作鍵值。

其中還有一個bson的聲明,這個是用在將數據存儲到mongodb使用的。

7.3.4.2. 語法

  • "號的貌似是註釋
  • `號的貌似是轉換的別名,和一些第三方的特性,經過反射實現

7.3.4.3. 取值

t := reflect.TypeOf(u)
field := t.Elem().Field(0)
fmt.Println(field.Tag.Get("json"))
fmt.Println(field.Tag.Get("bson"))
1 package main
 2 import (
 3     "fmt"
 4     "reflect" // 這裏引入reflect模塊
 5 )
 6 type User struct {
 7     Name   string "user name" //這引號裏面的就是tag
 8     Passwd string "user passsword"
 9 }
10 func main() {
11     user := &User{"chronos", "pass"}
12     s := reflect.TypeOf(user).Elem() //經過反射獲取type定義
13     for i := 0; i < s.NumField(); i++ {
14         fmt.Println(s.Field(i).Tag) //將tag輸出出來
15     }
16 }
1 package main
 2  
 3 import (
 4     "fmt"
 5     "reflect"
 6 )
 7  
 8 func main() {
 9     type S struct {
10         F string `species:"gopher" color:"blue"`
11     }
12  
13     s := S{}
14     st := reflect.TypeOf(s)
15     field := st.Field(0)
16     fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"))
17  
18 }

7.4. 數組

  • var arr [n]type:在[n]type中,n表示數組的長度,type表示存儲元素的類型
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)
    }
  • 因爲長度也是數組類型的一部分,所以[3]int與[4]int是不一樣的類型
  • 數組的長度不能改變
  • 數組之間的賦值是值的賦值,即當把一個數組做爲參數傳入函數的時候,傳入的實際上是該數組的副本,而不是它的指針,若是要使用指針,那麼就須要用到後面介紹的slice類型了
  • 數組可使用另外一種:=來聲明
a := [3]int{1, 2, 3} // 聲明瞭一個長度爲3的int數組
    b := [10]int{1, 2, 3} // 聲明瞭一個長度爲10的int數組,其中前三個元素初始化爲一、二、3,其它默認爲0
    c := [...]int{4, 5, 6} // 能夠省略長度而採用`...`的方式,Go會自動根據元素個數來計算長度☆☆☆☆☆
  • Go支持嵌套數組,即多維數組
// 聲明瞭一個二維數組,該數組以兩個數組做爲元素,其中每一個數組中又有4個int類型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}

// 上面的聲明能夠簡化,直接忽略內部的類型
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

7.5. 切片

7.5.1. 定義

  • 在不少應用場景中,數組並不能知足咱們的需求。在初始定義數組時,咱們並不知道須要多大的數組,所以咱們就須要「動態數組」。在Go裏面這種數據結構叫slice
  • slice的聲明也能夠像array同樣,只是不須要長度。
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])
        }
    }

7.5.2. 對數組和silce切片

  • slice能夠從一個數組或一個已經存在的slice中再次聲明。slice經過array[i:j]來獲取,其中i是數組的開始位置,j是結束位置,但不包含array[j],它的長度是j-i。
  • slice的默認開始位置是0,ar[:n]等價於ar[0:n]
  • slice的第二個序列默認是數組的長度,ar[n:]等價於ar[n:len(ar)]
  • 若是從一個數組裏面直接獲取slice,能夠這樣ar[:],由於默認第一個序列是0,第二個是數組的長度,即等價於ar[0:len(ar)]
// 聲明一個數組
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 聲明兩個slice
var aSlice, bSlice []byte

// 演示一些簡便操做
aSlice = array[:3] // 等價於aSlice = array[0:3] aSlice包含元素: a,b,c
aSlice = array[5:] // 等價於aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
aSlice = array[:]  // 等價於aSlice = array[0:10] 這樣aSlice包含了所有的元素

// 從slice中獲取slice
aSlice = array[3:7]  // aSlice包含元素: d,e,f,g,len=4,cap=7
bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
bSlice = aSlice[:3]  // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
bSlice = aSlice[0:5] // 對slice的slice能夠在cap範圍內擴展,此時bSlice包含:d,e,f,g,h
bSlice = aSlice[:]   // bSlice包含全部aSlice的元素: d,e,f,g

7.5.3. nil slice

  • slice 的零值是 nil
  • 一個 nil 的 slice 的長度和容量是 0。
package main

    import "fmt"

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

7.5.4. slice是引用類型

  • slice並非真正意義上的動態數組,而是一個引用類型。slice老是指向一個底層array
  • slice是引用類型,因此當引用改變其中元素的值時,其它的全部引用都會改變該值,例如上面的aSlice和bSlice,若是修改了aSlice中元素的值,那麼bSlice相對應的值也會改變。
  • slice像一個結構體,這個結構體包含了三個元素
    • 一個指針,指向數組中slice指定的開始位置
    • 長度,即slice的長度
    • 最大長度(容量),也就是slice開始位置到數組的最後位置的長度
  • slice內置的append函數會改變slice所引用的數組的內容,從而影響到引用同一數組的其它slice。 但當slice中沒有剩餘空間(即(cap-len) == 0)時,此時將動態分配新的數組空間。返回的slice數組指針將指向這個空間,而原數組的內容將保持不變;其它引用此數組的slice則不受影響。

7.5.5. 構造slice ????????

slice 由函數 make 建立。這會分配一個零長度的數組而且返回一個 slice 指向這個數組:

a := make([]int, 5) // len(a)=5

爲了指定容量,可傳遞第三個參數到 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

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

7.5.6. 三個參數????????

從Go1.2開始slice支持了三個參數的slice,以前咱們一直採用這種方式在slice或者array基礎上來獲取一個slice

var array [10]int slice := array[2:4]

這個例子裏面slice的容量是8,新版本里面能夠指定這個容量

slice = array[2:4:7]

上面這個的容量就是7-2,即5。這樣這個產生的新的slice就沒辦法訪問最後的三個元素。

若是slice是這樣的形式array[:i:j],即第一個參數爲空,默認值就是0。

7.5.7. 向slice添加元素

  • 對於slice有幾個有用的內置函數:

    • len 獲取slice的長度
    • cap 獲取slice的最大容量
    • copy 函數copy從源slice的src中複製元素到目標dst,而且返回複製的元素的個數
    • append 向slice裏面追加一個或者多個元素,而後返回一個和slice同樣類型的slice

      • append 的第一個參數 s 是一個類型爲 T 的數組,其他類型爲 T 的值將會添加到 slice。
      • 若是 s 的底層數組過小,而不能容納全部值時,會分配一個更大的數組。 返回的 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)
          }

7.5.8. 切片的擴容原理

  • append增長容量是按照若是容量不夠把以前切片的容量乘以2,若是乘以2還不夠就以前容量+1乘以2來遞增的

7.6. range

for 循環的 range 格式能夠對 slice 或者 map 進行迭代循環。

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

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

能夠經過賦值給 _ 來忽略序號和值。 若是隻須要索引值,去掉「, value」的部分便可。

package main

import "fmt"

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i)
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

7.7. map

7.7.1. 定義

  • map在使用以前必須用 make 而不是 new 來make初始化
  • 空map的值爲 nil,而且不能賦值
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"])
    }
  • map是無序的,每次打印出來的map都會不同,它不能經過index獲取,而必須經過key獲取
  • map的長度是不固定的,也就是和slice同樣,也是一種引用類型
  • map和其餘基本型別不一樣,它不是thread-safe,在多個go-routine存取時,必須使用mutex lock機制???

7.7.2. 修改map

// 一、初始化一個字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }

// 二、插入元素
m[key] = elem

// 三、得到元素
elem = m[key]

// 四、map有兩個返回值,第二個返回值,若是存在ok爲true,若是不存在key,那麼ok爲false,而且 elem 是 map 的元素類型的零值
csharpRating, ok := rating["C#"]
if ok {
    fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
    fmt.Println("We have no rating associated with C# in the map")
}

// 五、刪除元素
delete(rating, "C")  // 刪除key爲C的元素

7.7.3. make和new操做

  • make用於內建類型(map、slice 和channel)的內存分配。new用於各類類型的內存分配。

  • 內建函數new本質上說跟其它語言中的同名函數功能同樣:new(T)分配了零值填充的T類型的內存空間,而且返回其地址,即一個*T類型的值。用Go的術語說,它返回了一個指針,指向新分配的類型T的零值。有一點很是重要:

    new返回指針。

  • 內建函數make(T, args)與new(T)有着不一樣的功能,make只能建立slice、map和channel,而且返回一個有初始值(非零)的T類型,而不是*T。本質來說,致使這三個類型有所不一樣的緣由是指向數據結構的引用在使用前必須被初始化。例如,一個slice,是一個包含指向數據(內部array)的指針、長度和容量的三項描述符;在這些項目被初始化以前,slice爲nil。對於slice、map和channel來講,make初始化了內部的數據結構,填充適當的值。

    make返回初始化後的(非零)值。

  • 總結
    • make用於內建類型(只能用於建立map、slice 和channel)的內存分配。而且返回一個有初始值(非零)的T類型,而不是*T。
    • new用於各類類型的內存分配。new(T)分配了零值填充的T類型的內存空間,而且返回其地址,即一個T類型的值。用Go的術語說,它返回了一個指針,指向新分配的類型T的零值。有一點很是重要:*new返回指針。

8. 流程控制

8.1. for循環

Go裏面最強大的一個控制邏輯就是for,它既能夠用來循環讀取數據,又能夠看成while來控制邏輯,還能迭代操做

8.1.1. 標準寫法

package main

import "fmt"

func main(){
    sum := 0;
    for index:=0; index < 10 ; index++ {
        sum += index
    }
    fmt.Println("sum is equal to ", sum)
}
// 輸出:sum is equal to 45

8.1.2. 省略

  • 咱們能夠忽略前置、後置語句爲空。
sum := 1
for ; sum < 1000;  {
    sum += sum
}
  • 其中;也能夠省略,那麼就變成以下的代碼了,是否是似曾相識?對,這就是while的功能
package main

import "fmt"

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

8.1.3. 結合range

  • for配合range能夠用於讀取slice和map的數據
for k,v:=range map {
    fmt.Println("map's key:",k)
    fmt.Println("map's val:",v)
}
  • 因爲 Go 支持 「多值返回」, 而對於「聲明而未被調用」的變量, 編譯器會報錯, 在這種狀況下, 可使用_來丟棄不須要的返回值
for _, v := range map{
    fmt.Println("map's val:", v)
}

8.1.4. 死循環

  • 若是省略了循環條件,循環就不會結束,所以能夠用更簡潔地形式表達死循環。
package main

func main() {
    for {
    }
}

8.2. if語句

  • if有一個強大的地方就是條件判斷語句裏面容許聲明一個變量,這個變量的做用域只能在該條件邏輯塊內,其餘地方就不起做用了
if x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}
// 計算獲取值x,而後根據x返回的大小,判斷是否大於10。
if x := computedValue(); x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}

//這個地方若是這樣調用就編譯出錯了,由於x是條件裏面的變量
fmt.Println(x)
if integer == 3 {
    fmt.Println("The integer is equal to 3")
} else if integer < 3 {
    fmt.Println("The integer is less than 3")
} else {
    fmt.Println("The integer is greater than 3")
}

8.3. switch

8.3.1. 標準形式

switch sExpr {
case expr1:
    some instructions
case expr2:
    some other instructions
case expr3:
    some other instructions
default:
    other code
}

8.3.2. fallthrough

  • Go裏面switch默認至關於每一個case最後帶有break,匹配成功後不會自動向下執行其餘case,而是跳出整個switch, 可是可使用fallthrough強制執行後面的case代碼。
integer := 6
switch integer {
case 4:
    fmt.Println("The integer was <= 4")
    fallthrough
case 5:
    fmt.Println("The integer was <= 5")
    fallthrough
case 6:
    fmt.Println("The integer was <= 6")
    fallthrough
case 7:
    fmt.Println("The integer was <= 7")
    fallthrough
case 8:
    fmt.Println("The integer was <= 8")
    fallthrough
default:
    fmt.Println("default case")
}

8.3.3. 沒有條件的switch

  • 沒有條件的 switch 同 switch true 同樣。
  • 這一構造使得能夠用更清晰的形式來編寫長的 if-then-else 鏈
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.")
    }
}

8.4. goto

  • Go有goto語句——請明智地使用它。用goto跳轉到必須在當前函數內定義的標籤。例如假設這樣一個循環
  • 標籤名是大小寫敏感的
    func myFunc() {
      i := 0
    Here:   //這行的第一個詞,以冒號結束做爲標籤
      println(i)
      i++
      goto Here   //跳轉到Here去
    }

9. 函數

9.1. 定義

  • 函數是Go裏面的核心設計,它經過關鍵字func來聲明,它的格式以下
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    //這裏是處理邏輯代碼
    //返回多個值
    return value1, value2
}
  • 函數能夠有一個或者多個參數,每一個參數後面帶有類型,經過,分隔
  • 函數能夠返回多個值
  • 上面返回值聲明瞭兩個變量output1和output2,若是你不想聲明也能夠,直接就兩個類型
  • 若是隻有一個返回值且不聲明返回值變量,那麼你能夠省略 包括返回值 的括號
  • 若是沒有返回值,那麼就直接省略最後的返回信息
  • 若是有返回值, 那麼必須在函數的外層添加return語句
package main

import "fmt"

// 返回a、b中最大值.
func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    x := 3
    y := 4
    z := 5

    max_xy := max(x, y) //調用函數max(x, y)
    max_xz := max(x, z) //調用函數max(x, z)

    fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
    fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
    fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在這直接調用它
}

9.2. 函數空返回

  • 以下,函數沒有返回值,可是外面add和multiplied已經能夠用了,由於在返回的時候初始化了
func SumAndProduct(A, B int) (add int, Multiplied int) {
    add = A+B
    Multiplied = A*B
    return
}

9.3. 函數變參

  • Go函數支持變參。接受變參的函數是有着不定數量的參數的。爲了作到這點,首先須要定義函數使其接受變參:
func myfunc(arg ...int) {}
  • arg ...int告訴Go這個函數接受不定數量的參數。注意,這些參數的類型所有是int。在函數體中,變量arg是一個int的slice:
for _, n := range arg {
    fmt.Printf("And the number is: %d\n", n)
}

9.4. 傳值與傳指針

  • 當咱們傳一個參數值到被調用函數裏面時,其實是傳了這個值的一份copy,當在被調用函數中修改參數值的時候,調用函數中相應實參不會發生任何變化,由於數值變化只做用在copy上。若是傳指針,此時參數仍然是按copy傳遞的,只是copy的是一個指針。
  • 傳指針使得多個函數能操做同一個對象。
  • 傳指針比較輕量級 (8bytes),只是傳內存地址,咱們能夠用指針傳遞體積大的結構體。若是用參數值傳遞的話, 在每次copy上面就會花費相對較多的系統開銷(內存和時間)。因此當你要傳遞大的結構體的時候,用指針是一個明智的選擇。
  • Go語言中channel,slice,map這三種類型的實現機制相似指針,因此能夠直接傳遞,而不用取地址後傳遞指針。(注:若函數需改變slice的長度,則仍須要取地址傳遞指針)
package main

import "fmt"

//簡單的一個函數,實現了參數+1的操做
func add1(a *int) int { // 請注意,
    *a = *a+1 // 修改了a的值
    return *a // 返回新值
}

func main() {
    x := 3

    fmt.Println("x = ", x)  // 應該輸出 "x = 3"

    x1 := add1(&x)  // 調用 add1(&x) 傳x的地址

    fmt.Println("x+1 = ", x1) // 應該輸出 "x+1 = 4"
    fmt.Println("x = ", x)    // 應該輸出 "x = 4"
}

9.5. defer

  • Go語言中有種不錯的設計,即延遲(defer)語句,你能夠在函數中添加多個defer語句。當函數執行到最後時,這些defer語句會按照逆序執行,最後該函數返回
    • 本身的理解:按照上面這句話說的,defer必定會是在函數執行到最後,這個最後是函數立刻要結束了,包括提早返回。而後執行defer語句, 再返回要返回的內容
func ReadWrite() bool {
    file.Open("file")
// 作一些工做
    if failureX {
        file.Close()
        return false
    }

    if failureY {
        file.Close()
        return false
    }

    file.Close()
    return true
}

咱們看到上面有不少重複的代碼,Go的defer有效解決了這個問題。使用它後,不但代碼量減小了不少,並且程序變得更優雅。在defer後指定的函數會在函數退出前調用。

func ReadWrite() bool {
    file.Open("file")
    defer file.Close()
    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}
  • 若是有不少調用defer,那麼defer是採用後進先出模式
因此以下代碼會輸出4 3 2 1 0
for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

9.6. 函數的閉包

  • Go函數能夠是閉包的。閉包是一個函數值,它來自函數體的外部的變量引用。 函數能夠對這個引用值進行訪問和賦值;換句話說這個函數被「綁定」在這個變量上。
例如,函數 adder 返回一個閉包。每一個閉包都被綁定到其各自的 sum 變量上。 

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 < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

9.7. 函數做爲值、類型

  • 在Go中函數也是一種變量,咱們能夠經過type來定義它,它的類型就是全部擁有相同的參數,相同的返回值的一種類型
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
  • 將函數做爲參數進行傳遞,咱們能夠實現不少種的邏輯,這樣使得咱們的程序變得很是的靈活。
package main

import "fmt"

type testInt func(int) bool // 聲明瞭一個函數類型

func isOdd(integer int) bool {
    if integer%2 == 0 {
        return false
    }
    return true
}

func isEven(integer int) bool {
    if integer%2 == 0 {
        return true
    }
    return false
}

// 聲明的函數類型在這個地方當作了一個參數

func filter(slice []int, f testInt) []int {
    var result []int
    for _, value := range slice {
        if f(value) {
            result = append(result, value)
        }
    }
    return result
}

func main(){
    slice := []int {1, 2, 3, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd)    // 函數當作值來傳遞了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven)  // 函數當作值來傳遞了
    fmt.Println("Even elements of slice are: ", even)
}

函數當作值和類型在咱們寫一些通用接口的時候很是有用,經過上面例子咱們看到testInt這個類型是一個函數類型,而後兩個filter函數的參數和返回值與testInt類型是同樣的,可是咱們能夠實現不少種的邏輯,這樣使得咱們的程序變得很是的靈活。

9.8. Panic和Recover

  • Go沒有像Java那樣的異常機制,它不能拋出異常,而是使用了panic和recover機制。必定要記住,你應當把它做爲最後的手段來使用,也就是說,你的代碼中應當沒有,或者不多有panic的東西。這是個強大的工具,請明智地使用它。
  • Panic:是一個內建函數,能夠中斷原有的控制流程,進入一個panic狀態中。當函數F調用panic,函數F的執行被中斷,可是F中的延遲函數會正常執行,而後F返回到調用它的地方。在調用的地方,F的行爲就像調用了panic。這一過程繼續向上,直到發生panic的goroutine中全部調用的函數返回,此時程序退出。panic能夠直接調用panic產生。也能夠由運行時錯誤產生,例如訪問越界的數組。
  • Recover:是一個內建的函數,可讓進入panic狀態的goroutine恢復過來。recover僅在延遲函數中有效。在正常的執行過程當中,調用recover會返回nil,而且沒有其它任何效果。若是當前的goroutine陷入panic狀態,調用recover能夠捕獲到panic的輸入值,而且恢復正常的執行。
下面這個函數演示瞭如何在過程當中使用panic

var user = os.Getenv("USER")

func init() {
    if user == "" {
        panic("no value for $USER")
    }
}

下面這個函數檢查做爲其參數的函數在執行時是否會產生panic:

func throwsPanic(f func()) (b bool) {
    defer func() {
        if x := recover(); x != nil {
            b = true
        }
    }()
    f() //執行函數f,若是f中出現了panic,那麼就能夠恢復回來
    return
}

9.9. main函數和init函數

  • Go裏面有兩個保留的函數:init函數(可以應用於全部的package)和main函數(只能應用於package main)。這兩個函數在定義時不能有任何的參數和返回值。雖然一個package裏面能夠寫任意多個init函數,但這不管是對於可讀性仍是之後的可維護性來講,咱們都強烈建議用戶在一個package中每一個文件只寫一個init函數。
  • Go程序會自動調用init()和main(),因此你不須要在任何地方調用這兩個函數。每一個package中的init函數都是可選的,但package main就必須包含一個main函數。
  • 程序的初始化和執行都起始於main包。若是main包還導入了其它的包,那麼就會在編譯時將它們依次導入。有時一個包會被多個包同時導入,那麼它只會被導入一次(例如不少包可能都會用到fmt包,但它只會被導入一次,由於沒有必要導入屢次)。當一個包被導入時,若是該包還導入了其它的包,那麼會先將其它包導入進來,而後再對這些包中的包級常量和變量進行初始化,接着執行init函數(若是有的話),依次類推。等全部被導入的包都加載完畢了,就會開始對main包中的包級常量和變量進行初始化,而後執行main包中的init函數(若是存在的話),最後執行main函數。下圖詳細地解釋了整個執行過程:

10. 方法(面向對象)

10.1. 用方法實現類

  • Go沒有類。然而,仍然能夠在結構體類型上定義方法。
  • 前面咱們介紹了函數和struct,那你是否想過函數看成struct的字段同樣來處理呢?今天咱們就講解一下函數的另外一種形態,帶有接收者的函數,咱們稱爲method
  • 方法接收者 出如今 func 關鍵字和方法名之間的參數中。

10.2. 自定義類型

  • 實際上只是一個定義了一個類型別名
type ages int

type money float32

type months map[string]int

m := months {
    "January":31,
    "February":28,
    ...
    "December":31,
}

10.3. 任意類型均可以定義方法

  • 任何你自定義的類型、內置類型、struct等各類類型上面
  • 可是,不能對來自其餘包的類型或基礎類型定義方法
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 main() {
    v := &Vertex{3, 4}
    fmt.Println(v.Abs())
}
package main

import (
    "fmt"
    "math"
)

type MyFloat float64

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

func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}

10.4. 接收者爲指針的方法

  • 首先避免在每一個方法調用中拷貝值(若是值類型是大的結構體的話會更有效率)
  • 其次,方法能夠修改接收者指向的值
package main

import "fmt"

const(
    WHITE = iota
    BLACK
    BLUE
    RED
    YELLOW
)

type Color byte

type Box struct {
    width, height, depth float64
    color Color
}

type BoxList []Box //a slice of boxes

func (b Box) Volume() float64 {
    return b.width * b.height * b.depth
}

func (b *Box) SetColor(c Color) {
    b.color = c
}

func (bl BoxList) BiggestColor() Color {
    v := 0.00
    k := Color(WHITE)
    for _, b := range bl {
        if bv := b.Volume(); bv > v {
            v = bv
            k = b.color
        }
    }
    return k
}

func (bl BoxList) PaintItBlack() {
    for i := range bl {
        bl[i].SetColor(BLACK)
    }
}

func (c Color) String() string {
    strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
    return strings[c]
}

func main() {
    boxes := BoxList {
        Box{4, 4, 4, RED},
        Box{10, 10, 1, YELLOW},
        Box{1, 1, 20, BLACK},
        Box{10, 10, 1, BLUE},
        Box{10, 30, 1, WHITE},
        Box{20, 20, 20, YELLOW},
    }

    fmt.Printf("We have %d boxes in our set\n", len(boxes))
    fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
    fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
    fmt.Println("The biggest one is", boxes.BiggestColor().String())

    fmt.Println("Let's paint them all black")
    boxes.PaintItBlack()
    fmt.Println("The color of the second one is", boxes[1].color.String())

    fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
}

10.4.1. 指針和非指針的區別

  • 指針做爲Receiver會對實例對象的內容發生操做,而普通類型做爲Receiver僅僅是以副本做爲操做對象,並不對原實例對象發生操做
  • 也就是說:若是一個method的receiver是*T,你能夠在一個T類型的實例變量V上面調用這個method,而不須要&V去調用這個method
  • 相似的:若是一個method的receiver是T,你能夠在一個T類型的變量P上面調用這個method,而不須要 P去調用這個method

10.5. method繼承

  • method也是能夠繼承的。若是匿名字段實現了一個method,那麼包含這個匿名字段的struct也能調用該method
package main

import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
}

type Employee struct {
    Human //匿名字段
    company string
}

//在human上面定義了一個method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

10.6. method重寫

  • 上面的例子中,若是Employee想要實現本身的SayHi,怎麼辦?簡單,和匿名字段衝突同樣的道理,咱們能夠在Employee上面定義一個method,重寫了匿名字段的方法
package main

import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
}

type Employee struct {
    Human //匿名字段
    company string
}

//Human定義method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Employee的method重寫Human的method
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //Yes you can split into 2 lines here.
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

11. 接口

11.1. interface類型

  • 接口類型是由一組方法定義的集合
  • interface能夠被任意的對象實現
  • 一個對象能夠實現任意多個interface
type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段Human
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段Human
    company string
    money float32
}

//Human對象實現Sayhi方法
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Human對象實現Sing方法
func (h *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}

//Human對象實現Guzzle方法
func (h *Human) Guzzle(beerStein string) {
    fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee重載Human的Sayhi方法
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //此句能夠分紅多行
}

//Student實現BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
    s.loan += amount // (again and again and...)
}

//Employee實現SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
    e.money -= amount // More vodka please!!! Get me through the day!
}

// 定義interface
type Men interface {
    SayHi()
    Sing(lyrics string)
    Guzzle(beerStein string)
}

type YoungChap interface {
    SayHi()
    Sing(song string)
    BorrowMoney(amount float32)
}

type ElderlyGent interface {
    SayHi()
    Sing(song string)
    SpendSalary(amount float32)
}

11.2. interface值

  • 簡單總結就是,只要是實現了接口的類型均可以賦值給接口
package main

import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段
    company string
    money float32
}

//Human實現SayHi方法
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human實現Sing方法
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}

//Employee重載Human的SayHi方法
func (e Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone)
    }

// Interface Men被Human,Student和Employee實現
// 由於這三個類型都實現了這兩個方法
type Men interface {
    SayHi()
    Sing(lyrics string)
}

func main() {
    mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
    paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
    sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
    tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

    //定義Men類型的變量i
    var i Men

    //i能存儲Student
    i = mike
    fmt.Println("This is Mike, a Student:")
    i.SayHi()
    i.Sing("November rain")

    //i也能存儲Employee
    i = tom
    fmt.Println("This is tom, an Employee:")
    i.SayHi()
    i.Sing("Born to be wild")

    //定義了slice Men
    fmt.Println("Let's use a slice of Men and see what happens")
    x := make([]Men, 3)
    //這三個都是不一樣類型的元素,可是他們實現了interface同一個接口
    x[0], x[1], x[2] = paul, sam, mike

    for _, value := range x{
        value.SayHi()
    }
}

11.3. 空interface

  • 空interface(interface{})不包含任何的method,正由於如此,全部的類型都實現了空interface。空interface對於描述起不到任何的做用(由於它不包含任何的method),可是空interface在咱們須要存儲任意類型的數值的時候至關有用,由於它能夠存儲任意類型的數值。
  • 一個函數把interface{}做爲參數,那麼他能夠接受任意類型的值做爲參數
  • 若是一個函數返回interface{},那麼也就能夠返回任意類型的值
// 定義a爲空接口
var a interface{}
var i int = 5
s := "Hello world"
// a能夠存儲任意類型的數值
a = i
a = s

11.4. 接口類型判斷

咱們知道interface的變量裏面能夠存儲任意類型的數值(該類型實現了interface)。那麼咱們怎麼反向知道這個變量裏面實際保存了的是哪一個類型的對象呢?目前經常使用的有兩種方法:

11.4.1. Comma-ok斷言

  • Go語言裏面有一個語法,能夠直接判斷是不是該類型的變量: value, ok = element.(T),這裏value就是變量的值,ok是一個bool類型,element是interface變量,T是斷言的類型。
  • 若是element裏面確實存儲了T類型的數值,那麼ok返回true,不然返回false。
package main

import (
    "fmt"
    "strconv"
)
type Element interface{}
type List [] Element
type Person struct {
    name string
    age int
}
//定義了String方法,實現了fmt.Stringer
func (p Person) String() string {
    return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
    list := make(List, 3)
    list[0] = 1 // an int
    list[1] = "Hello" // a string
    list[2] = Person{"Dennis", 70}
    for index, element := range list {
        if value, ok := element.(int); ok {
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
        } else if value, ok := element.(string); ok {
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
        } else if value, ok := element.(Person); ok {
            fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
        } else {
            fmt.Printf("list[%d] is of a different type\n", index)
        }
    }
}

11.4.2. switch測試

  • 這裏有一點須要強調的是:element.(type)語法不能在switch外的任何邏輯裏面使用,若是你要在switch外面判斷一個類型就使用comma-ok
package main

import (
    "fmt"
    "strconv"
)
type Element interface{}
type List [] Element
type Person struct {
    name string
    age int
}
//打印
func (p Person) String() string {
    return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
    list := make(List, 3)
    list[0] = 1 //an int
    list[1] = "Hello" //a string
    list[2] = Person{"Dennis", 70}
    for index, element := range list{
        switch value := element.(type) {
            case int:
                fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
            case string:
                fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
            case Person:
                fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
            default:
                fmt.Println("list[%d] is of a different type", index)
        }
    }
}

11.5. 嵌入interface

  • Go裏面真正吸引人的是它內置的邏輯語法,就像咱們在學習Struct時學習的匿名字段,多麼的優雅啊,那麼相同的邏輯引入到interface裏面,那不是更加完美了。若是一個interface1做爲interface2的一個嵌入字段,那麼interface2隱式的包含了interface1裏面的method。
咱們能夠看到源碼包container/heap裏面有這樣的一個定義

type Interface interface {
    sort.Interface //嵌入字段sort.Interface
    Push(x interface{}) //a Push method to push elements into the heap
    Pop() interface{} //a Pop elements that pops elements from the heap
}

咱們看到sort.Interface其實就是嵌入字段,把sort.Interface的全部method給隱式的包含進來了。也就是下面三個方法:

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less returns whether the element with index i should sort
    // before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

另外一個例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer兩個interface:

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 實現了 Writer
    w = os.Stdout
    fmt.Fprintf(w, "hello, writer\n")
}

11.6. 接口反射

  • 使用reflect通常分紅三步,下面簡要的講解一下:要去反射是一個類型的值(這些值都實現了空interface),首先須要把它轉化成reflect對象(reflect.Type或者reflect.Value,根據不一樣的狀況調用不一樣的函數)。這兩種獲取方式以下:
t := reflect.TypeOf(i)    //獲得類型的元數據,經過t咱們能獲取類型定義裏面的全部元素
v := reflect.ValueOf(i)   //獲得實際的值,經過v咱們獲取存儲在裏面的值,還能夠去改變值
  • 轉化爲reflect對象以後咱們就能夠進行一些操做了,也就是將reflect對象轉化成相應的值,例如
tag := t.Elem().Field(0).Tag  //獲取定義在struct裏面的標籤
name := v.Elem().Field(0).String()  //獲取存儲在第一個字段裏面的值
  • 獲取反射值能返回相應的類型和數值
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
  • 最後,反射的話,那麼反射的字段必須是可修改的,咱們前面學習過傳值和傳引用,這個裏面也是同樣的道理。反射的字段必須是可讀寫的意思是,若是下面這樣寫,那麼會發生錯誤
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)

若是要修改相應的值,必須這樣寫

var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)

由於傳值,傳的是拷貝的副本,若是想改變就必須改變原始值

11.7. 隱式接口

  • 類型經過實現那些方法來實現接口。 沒有顯式聲明的必要;因此也就沒有關鍵字「implements「。
  • 隱式接口解藕了實現接口的包和定義接口的包:互不依賴。(也就是說接口能夠跨包實現)

11.8. 接口的幾個實際應用

11.8.1. Stringers

  • 一個廣泛存在的接口是 fmt 包中定義的 Stringer
type Stringer struct {
    String() string
}
package main
import (
    "fmt"
    "strconv"
)

type Human struct {
    name string
    age int
    phone string
}

// 經過這個方法 Human 實現了 fmt.Stringer
func (h Human) String() string {
    return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years -  ✆ " +h.phone+"❱"
}

func main() {
    Bob := Human{"Bob", 39, "000-7777-XXX"}
    fmt.Println("This Human is : ", Bob)
}
  • 這也是實現了fmt.Stringer這個interface,即若是須要某個類型能被fmt包以特殊的格式輸出,你就必須實現Stringer這個接口。若是沒有實現這個接口,fmt將以默認的方式輸出。
  • 實現了error接口的對象(即實現了Error() string的對象),使用fmt輸出時,會調用Error()方法,所以沒必要再定義String()方法了

11.8.2. 錯誤error

  • 這個地方有一個內建接口的概念,Go 程序使用 error 值來表示錯誤狀態。
type error interface {
    Error() string
}

(與 fmt.Stringer 相似,`fmt` 包在輸出時也會試圖匹配 `error`。)

一般函數會返回一個 error 值,調用的它的代碼應當判斷這個錯誤是否等於 `nil`, 來進行錯誤處理。

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

error 爲 nil 時表示成功;非 nil 的 error 表示錯誤。 

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

12. 併發

12.1. goroutine

  • goroutine是Go並行設計的核心。goroutine說到底其實就是協程,可是它比線程更小,十幾個goroutine可能體如今底層就是五六個線程,Go語言內部幫你實現了這些goroutine之間的內存共享。執行goroutine只需極少的棧內存(大概是4~5KB),固然會根據相應的數據伸縮。也正由於如此,可同時運行成千上萬個併發任務。goroutine比thread更易用、更高效、更輕便。
  • goroutine是經過Go的runtime管理的一個線程管理器。
  • goroutine經過go關鍵字實現了,其實就是一個普通的函數。
go f(x, y, z)
  • goroutine 在相同的地址空間中運行,所以訪問共享內存必須進行同步。sync 提供了這種可能,不過在 Go 中並不常常用到,由於有其餘的辦法
  • 不過設計上咱們要遵循:不要經過共享來通訊,而要經過通訊來共享。

12.2. channel(信道)

  • goroutine運行在相同的地址空間,所以訪問共享內存必須作好同步。那麼goroutine之間如何進行數據的通訊呢,Go提供了一個很好的通訊機制channel。channel能夠與Unix shell 中的雙向管道作類比:能夠經過它發送或者接收值。這些值只能是特定的類型:channel類型。定義一個channel時,也須要定義發送到channel的值的類型。注意,必須使用make 建立channel channel 是有類型的管道,能夠用 channel 操做符 <- 對其發送或者接收值。
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
  • channel經過操做符<-來接收和發送數據
ch <- v    // 發送v到channel ch.
v := <-ch  // 從ch中接收數據,並賦值給v

12.2.1. 無緩衝信道

  • 無緩衝的信道永遠不會存儲數據,只負責數據的流通,爲何這麼講呢?

    • 從無緩衝信道取數據,必需要有數據流進來才能夠,不然當前線阻塞
    • 數據流入無緩衝信道, 若是沒有其餘goroutine來拿走這個數據,那麼當前線阻塞
  • 默認狀況下,channel接收和發送數據都是阻塞的,除非另外一端已經準備好,這樣就使得Goroutines同步變的更加的簡單,而不須要顯式的lock。所謂阻塞,也就是若是讀取(value := <-ch)它將會被阻塞,直到有數據接收。其次,任何發送(ch<-5)將會被阻塞,直到數據被讀出。☆☆☆☆☆無緩衝channel是在多個goroutine之間同步很棒的工具☆☆☆☆☆

package main

import "fmt"

func sum(a []int, c chan int) {
    total := 0
    for _, v := range a {
        total += v
    }
    c <- total  // send total to 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  // receive from c

    fmt.Println(x, y, x + y)
}
  • 由上可知信道有阻塞主線程的做用,也就是無緩衝的信道在取消息和存消息的時候都會掛起當前的goroutine,除非另外一端已經準備好
var ch chan int = make(chan int)

func foo() {
    ch <- 0  // 向ch中加數據,若是沒有其餘goroutine來取走這個數據,那麼掛起foo, 直到main函數把0這個數據拿走
}

func main() {
    go foo()
    <- ch // 從ch取數據,若是ch中還沒放數據,那就掛起main線,直到foo函數中放數據爲止
}

12.2.2. 死鎖

  • 總結來看,爲何會死鎖?非緩衝信道上若是發生了流入無流出,或者流出無流入,也就致使了死鎖。或者這樣理解 Go啓動的全部goroutine裏的非緩衝信道必定要一個線裏存數據,一個線裏取數據,要成對才行
死鎖1
func main() {
    ch := make(chan int)
    <- ch // 阻塞main goroutine, 信道c被鎖
}
死鎖2
var ch1 chan int = make(chan int)
var ch2 chan int = make(chan int)

func say(s string) {
    fmt.Println(s)
    ch1 <- <- ch2 // ch1 等待 ch2流出的數據
}

func main() {
    go say("hello")
    <- ch1  // 堵塞主線
}

12.3. Buffered Channels(緩衝 channel)

  • Go也容許指定channel的緩衝大小,很簡單,就是channel能夠存儲多少元素。ch:= make(chan bool, 4),建立了能夠存儲4個元素的bool 型channel。在這個channel 中,前4個元素能夠無阻塞的寫入。當寫入第5個元素時,代碼將會阻塞,直到其餘goroutine從channel 中讀取一些元素,騰出空間。
ch := make(chan type, value)
當 value = 0 時,channel 是無緩衝阻塞讀寫的,當value > 0 時,channel 有緩衝、是非阻塞的,直到寫滿 value 個元素才阻塞寫入
package main

import "fmt"

func main() {
    c := make(chan int, 2)//修改2爲1就報錯,修改2爲3能夠正常運行
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
        //修改成1報以下的錯誤:
        //fatal error: all goroutines are asleep - deadlock!

12.4. range和close

  • 上面這個例子中,咱們須要讀取兩次c,這樣不是很方便,Go考慮到了這一點,因此也能夠經過range,像操做slice或者map同樣操做緩存類型的channel
package main

import (
    "fmt"
)

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

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

for i := range c可以不斷的讀取channel裏面的數據,直到該channel被顯式的關閉(這句話說明這個循環會一直存在,直到你調用了close)。上面代碼咱們看到能夠顯式的關閉channel,生產者經過內置函數close關閉channel。關閉channel以後就沒法再發送任何數據了,在消費方能夠經過語法v, ok := <-ch測試channel是否被關閉。若是ok返回false,那麼說明channel已經沒有任何數據而且已經被關閉。
  • 記住應該在生產者的地方關閉channel,而不是消費的地方去關閉它,這樣容易引發panic
  • 另外記住一點的就是channel不像文件之類的,不須要常常去關閉,只有當你確實沒有任何發送數據了,或者你想顯式的結束range循環之類的

12.5. select

  • 咱們上面介紹的都是只有一個channel的狀況,那麼若是存在多個channel的時候,咱們該如何操做呢,Go裏面提供了一個關鍵字select,經過select能夠監聽channel上的數據流動。
  • select默認是阻塞的,只有當監聽的channel中有發送或接收能夠進行時纔會運行,當多個channel都準備好的時候,select是隨機的選擇一個執行的。
    • (也就是用了select以後就一直處於監聽狀態了,只要有管道的讀寫操做就會執行select,由於select被包裹在for循環中了)
package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 1, 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 < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}
  • 在select裏面還有default語法,select其實就是相似switch的功能,default就是當監聽的channel都沒有準備好的時候,默認執行的(select再也不阻塞等待channel)
select {
case i := <-c:
    // use i
default:
    // 當c阻塞的時候執行這裏
}

12.6. 超時

  • 有時候會出現goroutine阻塞的狀況,那麼咱們如何避免整個程序進入阻塞的狀況呢?咱們能夠利用select來設置超時,經過以下的方式實現:
func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o
}

12.7. runtime goroutine

runtime包中有幾個處理goroutine的函數:

  • Goexit:退出當前執行的goroutine,可是defer函數還會繼續調用
  • Gosched:讓出當前goroutine的執行權限,調度器安排其餘等待的任務運行,並在下次某個時候從該位置恢復執行。
  • NumCPU:返回 CPU 核數量
  • NumGoroutine:返回正在執行和排隊的任務總數
  • GOMAXPROCS:用來設置能夠並行計算的CPU核數的最大值,並返回以前的值。

13. 免責說明

  • 本文檔中的部份內容摘自網上的衆多博客,僅做爲本身知識的補充和整理,並分享給其餘須要的coder,不會用於商用。
  • 由於不少博客的地址看完沒有及時作保存,因此不少不會在這裏標明出處,很是感謝各位大牛的分享,也但願你們理解。
  • 若是原文做者感受不適,能夠及時聯繫我shiguoqing999@163.com,我將及時刪除爭議部份內容

14. 追責聲明

  • 若有大段引用超過全文50%的內容,請在文檔結尾標明原文出處:龍馬行空-石國慶-朱庇特-https://my.oschina.net/u/1416844/blog,不然將視爲抄襲,予以法律追究,請各位尊重我的知識產權。
相關文章
相關標籤/搜索