golang基礎

Go介紹

併發支持,垃圾回收的編譯型系統編程語言。git

特色:express

  • 類型安全 和內存安全
  • 以很是直觀和極低代價的方案實現高併發
  • 高效的垃圾回收機制
  • 快速編譯 (同時解決C語言中頭文件太多的問題)
  • 爲多核計算機提供性能提高的方案
  • UTF-8編碼支持

Go環境變量編程

clipboard.png

GOEXE=.exe // 造成可執行文件的後綴
GOPATH // 工做目錄

GOPATH下約定俗成的目錄:數組

bin // 存放編譯後生成的可執行文件
pkg // 存放編譯後生成的包文件
src // 存放項目源碼

經常使用命令緩存

go get // 獲取遠程包 (git或hg(若是是谷歌code上託管))
go run // 直接運行程序
go build // 編譯,檢查是否有編譯錯誤
go fmt // 格式化源碼
go install // 編譯包文件而且編譯整個程序
go test // 運行測試文件
go doc // 查看文檔
godoc -http=:8080 // 查看文檔

GO程序結構

clipboard.png

mpathapp // 可執行文件存放位置
math.a // 包文件

基礎知識

hello.go安全

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

.go文件的通常結構數據結構

Go程序是經過package來組織的
只有package名稱爲main的包能夠包含main函數
一個可執行程序有且僅有一個main多線程

經過import關鍵字來導入其它非main
經過const關鍵字來定義常量
經過在函數體外部使用var關鍵字來進行全局變量的聲明和賦值
經過type關鍵字來進行結構struct或接口interface的聲明
經過func關鍵字來進行函數的聲明閉包

// 當前程序的包名
package main

// 導入其它的包
// import . "fmt" // 省略調用
import (
    "fmt"
    "io"
    "os"
    "time"
    "strings"
)

// 常量的定義
const PI = 3.141592653

// 全局變量的聲明與賦值
var name = "zf"

// 通常類型聲明
type newType int

// 結構的聲明
type struc struct{} // 接口關鍵字和大括號不能有空格

// 接口的聲明
type inter interface{} // 接口關鍵字和大括號不能有空格

// 由 main 函數做爲程序入口點啓動
func main () {
    fmt.Println("hello world!")
}

若是導入包以後,未調用其中的函數或者類型將會報出編譯錯誤.併發

package 別名

當使用第三方包時,包名可能會很是接近或者相同,此時就可使用別名來進行區別和調用

import std "fmt" // 別名

std.Println("hello world")

註釋

// 單行註釋
/* 多行註釋 */

可見性規則

Go語言中,使用大小寫來決定該常量,變量,類型,接口,結構,或函數是否能夠被外部包所調用:根據約定,函數名首字母小寫即爲private;函數名首字母大寫即爲public

類型與變量

基本類型

布爾型:bool

長度:1字節
取值範圍:true,false
注意:不能夠用數字表明true或false

整型:int/uint

根據運行平臺可能爲32或64位

  • 8位整型:int8/uint8

    長度:1字節
    取值範圍:-128~127/0~255
  • 16位整型:int16/uint16

    長度:2字節
    取值範圍:-32768~32767/0~65535
  • 32位整型:int32(rune)/uint32

    長度:4字節
    取值範圍:-2^32/2~2^32/2-1/0~2^32-1
  • 64位整型:int64/uint64

    長度:8字節
    取值範圍:-2^64/2~2^64/2-1/0~2^64-1
  • 字節型:byte(uint8別名)

從嚴格意義上講type newint int 這裏的newinting不能說是int的別名,而是底層數據結構相同,在這裏自定義類型,在進行類型轉換時扔舊須要顯示轉換,但byterune確實爲uint8int32的別名,能夠相互進行轉換。

浮點型:float32/float64

長度:4/8字節
小數位:精確到7/15小數位

複數:complex64/complex128

長度:8/16字節

足夠保存指針的 32 位或 64 位整數型:uintptr

其它值類型

array、struct、string

引用類型

slice、map、chan

接口類型

interface

函數類型

func

類型零值

零值並不等於空值,而是當變量被聲明爲某種類型後的默認值。

一般狀況下:

  • 值類型的默認值是爲0
  • bool爲false
  • string爲空字符串

變量

單個變量的聲明與賦值

變量的聲明格式:var <變量名稱> <變量類型>
變量的賦值格式: <變量名稱> = <表達式>
聲明的同時賦值:var <變量名稱> [變量類型] = <表達式>
寫法:

var a int // 變量聲明
a = 10 // 變量賦值

var b int = 20 // 變量聲明的同時賦值
var b = 1 // 變量聲明與賦值,由系統推薦是那種類型

b := 10 // 函數中的變量聲明與賦值的最簡寫法

var 是全局的變量
:= 只能在函數中使用,局部變量

多個變量的聲明與賦值

全局變量的聲明可以使用var()的方式進行簡寫
全局的變量的聲明不能夠省略var,但可以使用並行方式
全部變量均可以使用類型推斷
局部變量不可使用var()的方式簡寫,只能使用並行方式

var (
    // 使用常規方式
    aaa = "hello"
    // 使用並行方式以及類型推斷
    a, b = 1, 2
    // cc := 2 // 不能夠省略 var
)


func main () {
    // 多個變量的聲明
    var a, b, c, d int
    // 多個變量的賦值
    a, b, c, d = 1, 2, 3, 4

    // 多個變量聲明的同時賦值
    var e, f, g, h int = 5, 6, 7, 8
    // 省略變量類型,由系統推斷
    var i, j, k, l = 9, 10, 11, 12
    // 多個變量聲明與賦值的最簡寫法
    i, m, n, o := 13, 14, 15, 16

    _, dd = 10, 20 // 空白符號,省略該表達式賦值(應用函數返回值)
}

類型轉換

  • Go中不存在隱式轉換,都是顯示聲明(Go的類型安全)
  • 轉換隻能發生在兩種相互兼容的類型之間
  • 類型轉換的格式:

    <ValueA> [:]= <TypeOfValueA>(<ValueB>)
// 在相互兼容的兩種類型之間轉換
var a float32 = 1.1
b := int(a)


// 表達式沒法經過編譯
var c bool = true
d := int(c)

package main

import (
    "fmt"
)

func main () {
    var a float32 = 100.01
    fmt.Println(a) // 100.01
    b := int(a)
    fmt.Println(b) // 100
}

整型沒法和布爾型兼容
float類型沒法和字符串類型兼容

intstring互轉

var c int = 3
// d := string(c)
d := strconv.Itoa(c) // 字符串 3
c, _ = strconv.Atoi(d) // int 3

現象:

var a int = 65
string(a)
fmt.Println(a) // A

string()表示將數據轉換成文本格式,由於計算機中存儲的任何東西本質上都是數字,所以此函數天然的認爲須要的是用數字65表示文本A

常量

  • 常量的值在編譯時就已經肯定
  • 常量的定義格式與變量基本相同
  • 等號右側必須是常量或者常量表達式
  • 常量表達式中的函數必須是內置函數

常量的初始化規則與枚舉

  • 在定義常量組時,若是不提供初始值,則表示將使用上行的表達式
  • 使用相同的表達式不表明具備相同的值
  • iota是常量的計數器,從0開始,組中每定義1個常量自動遞增1
  • 經過初始化規則與iota能夠達到枚舉的效果
  • 每遇到一個const關鍵字,iota就會重置爲0
const (
    _A = "A"
    _B
    _C = iota
    _D
)
func main () {
    fmt.Println(_A, _B, _C, _D) // A A 2 3
}

const (
    a = "123"
    b = len(a)
    c
)

func main () {
    fmt.Println(a, b, c) // 123, 3, 3
}

編譯不經過:

var ss = "123"

const (
    a = len(ss)
    b
    c
)

func main () {
    fmt.Println(a, b, c)
}

錯誤信息:

# command-line-arguments
.\const.go:39: const initializer len(ss) is not a constant

const (
    a, b = 1, "xixi"
    c
)

func main () {
    fmt.Println(a, b, c)
}

錯誤信息:

# command-line-arguments
.\const.go:40: extra expression in const declaration

運算符

Go中的運算符從左至右結合

優先級(從高到低)

  • ^ ! (一元運算符)
  • * / % << >> & &^ (二元運算符)
  • + - | ^ (二元運算符)
  • == != < <= >= > (二元運算符)
  • <- (專門用於channel)
  • &&
  • ||
fmt.Println(1 ^ 2) // 二元運算符
fmt.Println(^2) // 一元運算符

/*

    6: 0110
 11: 1101
------------
  &     0010  // 2
  |  1111  // 15
  ^  1101  // 13
  &^ 0100  // 4

    6 -> 110
    5 -> 101
    4 -> 100

13 / 2 = 1 // 6
1101
 */

控制語句

指針

Go雖然保留了指針,但與其餘編程語言不一樣的是,在Go當中不支持指針運算以及->運算符,而是直接採用.選擇符來操做指針目標對象的成員

  • 操做符&取變量地址,使用*經過指針間接訪問目標對象
  • 默認值爲nil而非NULL
package main

import (
    "fmt"
)

func main () {
    a := 1
    var p *int = &a
    fmt.Println(p) // 0xc0420361d0
    fmt.Println(*p) // 1
}

遞增遞減語句
在Go當中,++--是做爲語句而並非做爲表達式

判斷if

  • 條件表達式沒有括號
  • 支持一個初始化表達式(能夠是並行方式)
  • 左大括號必須和條件語句或else在同一行
  • 支持單行模式
  • 初始化語句中的變量爲block級別,同時隱藏外部同名變量
func main () {
    a := 10
    if a := 0; a > 0 {
        fmt.Println(a)
    } else if a == 0 {
        fmt.Println(0111) // 73
    }
    fmt.Println(a) // 10
}

循環for

  • Go只有for一個循環語句關鍵字,但支持3中形式
  • 初始化和step表達式能夠是多個值
  • 條件語句每次循環都會從新檢查,所以不建議在條件語句中使用函數,儘可能提早計算好條件並以變量或常量代替
  • 左大括號必須和條件語句在同一行
// 第一種形式
func main () {
    a := 1
    for {
        a++
        if a > 3 {
            break
        }
        fmt.Println(a) // 2, 3
    }
    fmt.Println(a) // 4
}

// 第二種形式
func main () {
    a := 1
    for a <= 3 {
        a++
        fmt.Println(a) // 2, 3, 4
    }
    fmt.Println(a) // 4
}

// 第三種形式
func main () {
    a := 1
    for i := 0; i < 3; i++ {
        a++
        fmt.Println(a) // 2, 3, 4
    }
    fmt.Println(a) // 4
}

swtich

  • 可使用任何類型或表達式做爲條件語句
  • 不須要break,一旦條件符合自動終止
  • 如但願繼續執行下一個case,需使用fallthrough語句
  • 支持下一個初始化表達式(能夠是並行方式),右側需跟分號
  • 作大括號必須和條件語句在同一行
func main () {
    a := 1
    switch a {
    case 0:
        fmt.Println("a=0")
    case 1:
        fmt.Println("a=1")    
    }
    fmt.Println(a)
}

func main () {
    a := 1
    switch {
    case a >= 0:
        fmt.Println("a>=0")
        fallthrough
    case a >= 1:
        fmt.Println("a>=1")
    }
    fmt.Println(a)
}

func main () {
    switch a := 1; {
    case a >= 0:
        fmt.Println("a>=0")
        fallthrough
    case a >= 1:
        fmt.Println("a>=1")
    default:
        fmt.Println("none")
    }
    fmt.Println(a) // undefined: a //for,if,switch都具備塊級做用域
}

跳轉語句goto,break,continue

  • 三個語法均可以配合標籤使用
  • 標籤名區分大小寫,若不使用會形成編譯錯誤
  • breakcontinue配合標籤可用於多層循環的跳出
  • goto調整執行位置,與其它2個語句配合標籤的結果並不相同
func main () {
LABEL:
    for {
        for i := 0; i < 10; i++ {
            if i > 2 {
                break LABEL
            } else {
                fmt.Println(i)
            }
        }
    }
}

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

數組Array

  • 定義數組的格式:var <varName> [n]<type>, n>=0
  • 數組的長度也是類型的一部分,所以具備不一樣長度的數組爲不一樣類型
  • 數組在Go中爲值類型
  • 數組之間可使用==!=進行比較,但不可使用<或>``(相同類型之間,纔可使用相等或不能判斷。也就是數組長度也要相同,長度也是數組類型的一部分)
  • 可使用new來建立數組,此方法返回一個指向數組的指針
  • Go支持多維數組

建立數組

func main () {
    var a [2]string
    var b [1]int
    c := [2]int{11, 12}
    d := [20]int{19: 1}
    e := [...]int{1, 2, 3, 4, 5}
    f := [...]int{0: 11, 1: 22, 2: 33}
    b[0] = 10
    a[1] = "100"
    arr := [...]string{0: "xixi", 1: "hhh"}
    
    fmt.Println(a, b, c, d, e, f)
}

p := new([10]int)
p[1] = 2
fmt.Println(&p) // 取地址
fmt.Println(*p) // 取值

// 多維數組
a := [2][3]int{
    {1, 1, 1},
    {2, 2, 2},
}
fmt.Println(a)

冒泡排序:

func main () {
    a := [...]int{3, 4, 234, 2, 3, 5}
    fmt.Println(a)
    num := len(a)
    for i := 0; i< num; i++ {
        for j := i+1; j < num; j++ {
            if a[i] < a[j] {
                temp := a[i]
                a[i] = a[j]
                a[j] = temp
            }
        }                
    }
    fmt.Println(a)
}

切片Slice

  • 其自己並非數組,它指向底層的數組
  • 做爲變長數組的代替方案,能夠關聯底層數組的局部或所有
  • 爲引用類型
  • 能夠直接建立或從底層數組獲取生成
  • 使用len()獲取元素個數,cap()獲取容量
  • 通常使用make()建立
  • 若是多個slice指向相同底層數組,其中一個的值改變會影響所有
  • make([]T, len, cap)

    其中`cap`能夠省略,則和`len`的值相同
    `len`表示存數的元素個數,`cap`表示容量

聲明:

// 聲明方法:
var s1 []int // 中括號中沒有數字或`...`
fmt.Println(s1) // []

reslice方法: 從數組中截取Slice

a := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(a)
s1 := a[5: len(a)] // 包含起始索引,不包含終止索引 // a[5 6 7 8 9]
s2 := a[5: ] // 包含起始索引,不包含終止索引 // a[5 6 7 8 9]
fmt.Println(s1, s2)

make方法 (通常使用make建立)

s1 := make([]int, 3, 10) // 10小塊連續的內存,若是slice超過10,內存卡會繼續申請,從新生成內存地址
s2 := make([]int, 10) // cap不給定,是slice的最大長度
fmt.Println(len(s1), cap(s1), s1) // 3 10 [0 0 0]
fmt.Println(len(s2), cap(s2), s2) // 10 10 [0 0 0 0 0 0 0 0 0 0]

Reslice

  • reslice時索引以被slice的切片爲準
  • 索引不能夠超過被slice的切片的容量cap()
  • 索引越界不會致使底層數組的從新分配而引起錯誤

Append

  • 能夠在slice尾部追加元素
  • 能夠將一個slice追加在另外一個slice尾部
  • 若是最終長度爲超過追加到slice的容量則返回元素slice
  • 若是超過追加到的slice的容量則將從新分配數組並拷貝原始數據
s1 := make([]int, 3, 6)
fmt.Println("%p\n", s1)
s1 = append(s1, 1, 2, 3, 4)

fmt.Println("%v %p\n", s1) //  [0 0 0 1 2 3 4]

Copy

nt{1, 2, 3, 4, 5, 6, 7}
s2 := []int{8, 9}
copy(s1, s2)
// copy(s1, s2[1: 2])
fmt.Println(s1, s2) // [8 9 3 4 5 6 7] [8 9]

Map

  • 相似其它語言中的哈希表或者字典,以key-value形式存儲的數據
  • key必須是支持==!=比較運算符的類型,不能夠是函數mapslice
  • Map查找比線性搜索快不少,但比使用索引訪問數據的類型慢100倍
  • Map使用make()建立,支持:=簡寫方式
  • 鍵值對不存在時自動添加,使用delete()刪除某鍵值對
  • 使用for rangemapslice進行迭代操做
  • make([keyType]valueType, cap), cap表示容量,可省略

    超過容量時會自動擴容,但儘可能提供一個合理的初始值
    使用`len()`獲取元素個數

Map初始化

var m map[int]string
// m = map[int]string{}
m = make(map[int]string)
var m1 map[int]string = make(map[int]string)
m2 := make(map[int]string)

fmt.Println(m, m1) // map[]

刪除和經常使用Map方法賦值

m2 := make(map[int]string)
m2[1] = "OK"
delete(m2, 1)
a := m2[1]

fmt.Println(a)

迭代:

for i,v := range  slice {
    // slice[i]
}

for k,v := range map {
    // map[k]
}

取Map中的key

m := map[int]string{1: "A", 2: "B", 3: "C", 4: "D"}
s := make([]int, len(m))
i := 0
for k,_ := range m {
    s[i] = k
    i++
}
sort.Ints(s)
fmt.Println(s)

Map中的 key-value互換:

m1 := map[int]string{1: "A", 2: "B", 3: "C"}
m2 := make(map[string]int)
// m2 := map[string]int{"A": 1, "B": 2, "C": 3}

for k,v := range m1 {
    m2[v] = k
}
fmt.Println(m1)
fmt.Println(m2)

Function

函數function

Go 函數 不支持嵌套重載默認參數
支持的特性:

  • 無需聲明原型
  • 不定長度變參
  • 多返回值
  • 命名返回值參數
  • 匿名函數
  • 閉包

定義函數使用關鍵字 func,且左大括號不能另起一行
函數也能夠做爲一種類型使用

閉包:

func closure (x int) func (int) int {
    return func (y int) int {
        return x + y
    }
}

defer

  • defer的執行方式相似其它語言中的析構函數,在函數體執行結束後按照調用順序的相反順序逐個執行
  • 即便函數發生嚴重錯誤也會執行
  • 支持匿名函數的調用
  • 經常使用於資源清理文件關閉解鎖以及記錄時間等操做
  • 經過與匿名函數配合可在return以後修改函數計算結果
  • 若是函數體內某個變量做爲defer時匿名函數的參數,則在定義defer時即已經得到了拷貝,不然則是引用某個變量的地址
  • Go 沒有異常機制,但有panic/recover模式來處理錯誤
  • panic能夠在任何地方引起,但recover只有在defer調用的函數中有效

defer使用:

func main () {
    fmt.Println("a")
    defer fmt.Println("b")
    defer fmt.Println("c")
    // a, c, b
    
    
    for i := 0; i < 3; i++ {
        defer fmt.Println(i) // 2 1 0
    }
    
    for i := 0; i < 3; i++ {
        defer func () {
            fmt.Println(i) // 3 3 3
        }()
    }

}

defer要放在panic()以前:

func main () {
    A()
    B()
    C()
}

func A () {
    fmt.Println("func A")
}

func B () {
    defer func () {
        if err := recover(); err != nil {
            fmt.Println("Recover")
        }
    }()
    panic("Panic in B")
}

func C () {
    fmt.Println("func C")
}

結構struct

  • Go 中的struct與C中的struct很是類似,而且Go沒有class
  • 使用 type <Name> struct{} 定義結構,名稱遵循可見性規則
  • 支持指向自身的指針類型成員
  • 支持匿名結構,可用做成員或定義成員變量
  • 匿名結構也能夠用於map的值
  • 可使用字面值對結構進行初始化
  • 容許直接經過指針來讀寫結構成員
  • 相同類型的成員可進行直接拷貝賦值
  • 支持 ==!=比較運算符,但不支持 ><
  • 支持匿名字段,本質上是定義了以某個類型名爲名稱的字段
  • 嵌入結構做爲匿名字段看起來像繼承,但不是繼承
  • 可使用匿名字段指針
type person struct {
    name string
    age int
}

func main () {
    a := person{}
    a.name = "zf"
    a.age = 23
    fmt.Println(a) // { 0}
}

struct也是值類型

對初始化結構struct使用地址符

type person struct {
    name string
    age int
}
func main () {
    a := &person{ // 調用結構使用地址符  // 字面值初始化
        name: "zf",
        age: 24,
    }
    a.name = "pink"
    // a.name = "zf"
    // a.age = 23
    fmt.Println(a) // { 0}
    // A(&a)
    A(a)
    B(a)
    fmt.Println(a)
}

func A (per *person) {
    per.age = 18
    fmt.Println("A", per)
}

func B (per *person) {
    per.age = 20
    fmt.Println("B", per)
}

匿名結構:

func main () {
    a := &struct {
        name string
        age int
    } {
        name: "tan",
        age: 19,
    }
    fmt.Println(a)
}

外層結構:

type person struct {
    name string
    age int
    contact struct {
        phone,city string
    }
}

func main () {
    b := person {
        name: "yellow",
        age: 18,
    }
    b.contact.phone = "123123"
    b.contact.city = "xiamen"

    fmt.Println(b)
}

匿名字段:

type p1 struct {
    string
    int
}

func main () {
    c := p1{"cyan", 20} // 字段的類型嚴格按照結構聲明的字段

    fmt.Println(a, b, c)
}

匿名函數和匿名字段在函數中使用的次數很是少,沒有必要聲明,纔會使用到。

嵌入(繼承)結構:

type human struct {
    Sex int
}

type teacher struct {
    human
    name string
    age int
}

type student struct {
    human
    name string
    age int
}

func main () {
    // a := teacher{name: "cyan", age: 20, human{sex: 0}}
    a := teacher{name: "cyan", age: 20, human: human{Sex: 0}}
    b := student{name: "pink", age: 22, human: human{Sex: 1}}

    a.name = "xixi"
    a.age = 23
    // a.Sex = 100
    a.human.Sex = 200

    fmt.Println(a, b)
}

方法method

  • Go 中雖沒有class,但依舊有method
  • 經過顯示說明receiver來實現與某個類型的組合
  • 只能爲同一個包中的類型定義方法
  • Receiver 能夠是類型的值或者指針
  • 不存在方法重載
  • 可使用值或指針來調用方法,編譯器會自動完成轉換
  • 從某種意義上來講,方法是函數的語法糖,由於receiver其實就是
  • 方法所接收的第1個參數(Method Value vs. Method Expression)
  • 若是外部結構和嵌入結構存在同名方法,則優先調用外部結構的方法
  • 類型別名不會擁有底層類型所附帶的方法
  • 方法能夠調用結構中的非公開字段
type Test struct {
    name string
}

type Person struct {
    name string
}

func main () {
    t := Test{}
    t.Print()
    fmt.Println(t.name)

    p := Person{}
    p.Print()
    fmt.Println(p.name)
}

func (t *Test) Print() {
    t.name = "red"
    fmt.Println("Test")
}

func (p Person) Print() {
    fmt.Println("Person")
}

// 類型別名不會擁有底層類型所附帶的方法
type TZ int

func main () {
    var a TZ
    a.Print() 
    (*TZ).Print(&a)
}

func (a *TZ)  Print() {
    fmt.Println("TZ")
}

方法不一樣調用方式

type A struct {
    name string
}

func main () {
    a := A{}
    a.Print() 
    // (*TZ).Print(&a)
}

func (a *A)  Print() {
    a.name = "123"
    fmt.Println(a.name)
    // fmt.Println("TZ")
}

方法訪問權限
struct中的私有屬性,在方法中能夠訪問

// 屬性的訪問範圍是在`package`中的能夠訪問的,若是須要在外部包中訪問,須要大寫字母
type A struct {
    name string
}

func main () {
    a := A{}
    a.Print()
    fmt.Println(a.name)
}

func (a *A)  Print() {
    a.name = "123"
    fmt.Println(a.name)
}

接口interface

  • 接口是一個或多個方法簽名的集合
  • 只要某個類型擁有該接口的全部方法簽名,即算實現該接口,無需顯示聲明瞭哪一個接口,稱之爲:Structural Typing
  • 接口只有方法聲明,沒有實現,沒有數據字段
  • 接口能夠匿名嵌入其它接口,或嵌入到結構中
  • 將對象賦值給接口時,會發生拷貝,而接口內部存儲的是指向這個複製品的指針,即沒法修改複製品的狀態,也沒法獲取指針
  • 只有當接口存儲的類型和對象都爲nil時,接口才等於nil
  • 接口調用不會作receiver
  • 接口一樣支持匿名字段方法
  • 接口有能夠實現OOP中的多態
  • 空接口能夠做爲任何類型數據的容器
type USB interface {
    Name() string
    Connect()
}

type Phone struct {
    name string
}

func (pc Phone) Name() string {
    return pc.name
}

func (pc Phone) Connect() {
    fmt.Println("Connect: ", pc.name)
}

func main () {
    var a USB
    a = Phone{name: "phone"}
    a.Connect()
    Disconnect(a)
}

func Disconnect(usb USB) {
    fmt.Println("Disconnect")
}

接口嵌套:

type USB interface {
    Name() string
    // Connect()
    Connecter
}

type Connecter interface {
    Connect()
}

type Phone struct {
    name string
}

func (pc Phone) Name() string {
    return pc.name
}

func (pc Phone) Connect() {
    fmt.Println("Connect: ", pc.name)
}

func main () {
    var a USB
    a = Phone{name: "phone"}
    a.Connect()
    Disconnect(a)
}

func Disconnect(usb USB) {
    fmt.Println("Disconnect")
}

空接口的使用:

// Go語言中全部類型都實現空接口
// type empty interface {
// }

type USB interface {
    Name() string
    // Connect()
    Connecter
}

type Connecter interface {
    Connect()
}

type Phone struct {
    name string
}

func (pc Phone) Name() string {
    return pc.name
}

func (pc Phone) Connect() {
    fmt.Println("Connect: ", pc.name)
}

func main () {
    var a USB
    a = Phone{name: "phone"}
    a.Connect()
    Disconnect(a)

    // 空接口的判斷
    var b interface{}
    fmt.Println(b == nil) // true
}

func Disconnect(usb interface{}) { // interface{} 空接口
    // if pc,ok := usb.(Phone); ok { // 類型判斷
    //     fmt.Println("Disconnect:", pc.name)    
    //     return
    // }
    switch v := usb.(type) {
        case Phone:
            fmt.Println("Disconnect:", v.name)
        default :
            fmt.Println("Unknown")
    }
    fmt.Println("Disconnect")
}

只有當接口存儲的類型和對象都爲nil時,接口才等於nil

func main () {
    // 空接口的判斷
    var b interface{}
    fmt.Println(b == nil) // true

    var p *int = nil
    b = p
    fmt.Println(b == nil) // false
}

類型斷言

經過類型斷言的ok pattern能夠判斷接口中的數據類型
使用type switch則可針對空接口進行比較全面的類型判斷

type USB interface {
    Name() string
    // Connect()
    Connecter
}

type Connecter interface {
    Connect()
}

func Disconnect(usb interface{}) { // interface{} 空接口
    // if pc,ok := usb.(Phone); ok { // 類型判斷
    //     fmt.Println("Disconnect:", pc.name)    
    //     return
    // }
    switch v := usb.(type) {
        case Phone:
            fmt.Println("Disconnect:", v.name)
        default :
            fmt.Println("Unknown")
    }
    fmt.Println("Disconnect")
}

接口轉換

能夠將擁有超集的接口轉換爲子集的接口

反射reflection

  • 反射課大大提升程序的靈活性,使得interface{}有更大的發揮餘地
  • 反射使用TypeOfValueOf函數從接口中獲取目標對象信息
  • 反射會將匿名字段做爲獨立字段(匿名字段本質)
  • 想要利用反射修改對象狀態,前提是interface.datasettabelpointer-interface
  • 經過反射能夠"動態"調用方法
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id int
    Name string
    Age int
}

func (u User) Hello() {
    fmt.Println("Hello world")
}

func main () {
    u := User{1, "alogy", 12}
    Info(u)
}

func Info(o interface{}) {
    t := reflect.TypeOf(o)
    fmt.Println("Type: ", t.Name())

    v := reflect.ValueOf(o)
    fmt.Println("Fields: ")

    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Println(f.Name, f.Type, val)
    }

    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Println(m.Name, m.Type)
    }
}

若是是地址引用經過Kind()來獲取與reflect.Struct匹配的對象。

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id int
    Name string
    Age int
}

func (u User) Hello() {
    fmt.Println("Hello world")
}

func main () {
    u := User{1, "alogy", 12}
    Info(&u)
}

func Info(o interface{}) {
    t := reflect.TypeOf(o)
    fmt.Println("Type: ", t.Name())

    fmt.Println(t.Kind())

    if k := t.Kind(); k != reflect.Struct {
        fmt.Println("XX")
        return
    }

    v := reflect.ValueOf(o)
    fmt.Println("Fields: ")

    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Println(f.Name, f.Type, val)
    }

    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Println(m.Name, m.Type)
    }

}

匿名字段反射:

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id int
    Name string
    Age int
}

type Manager struct {
    User
    title string
}

func main () {
    // 反射會將匿名字段看成獨立字段來處理
    m := Manager{User: User{1, "OK", 12}, title: "123123"}
    t := reflect.TypeOf(m)
    fmt.Println(t.Field(0))

    // 取匿名當中的字段
    fmt.Println(t.FieldByIndex([]int{0, 0}))
}

指針操做:

package main

import (
    "fmt"
    "reflect"
)

func main () {
    x := 123
    v := reflect.ValueOf(&x)
    // fmt.Println(v)
    v.Elem().SetInt(999)
    fmt.Println(x)
}

類型判斷修改字段:

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id int
    Name string
    Age int
}

func main () {
    u := User{1, "Ok", 18}
    Set(&u)
    fmt.Println(u)
}

func Set(o interface{}) {
    v := reflect.ValueOf(o)

    if reflect.Ptr == v.Kind() && !v.Elem().CanSet() { // 指針是否正確
        fmt.Println("XXX")
        return
    } else {
        v = v.Elem() // 重寫賦值
    }

    f := v.FieldByName("Name") // 獲取字段
    if !f.IsValid() { // 判讀是否取到當前字段
        fmt.Println("BAD")
        return
    }
    if f.Kind() == reflect.String { // 類型判斷
        f.SetString("MM") // 從新賦值
    }

}

經過反射調用方法,動態調用方法:

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id int
    Name string
    Age int
}

func (u User) Hello(name string) {
    fmt.Println("Hello", name, ", my name is", u.Name)
}

func main () {
    u := User{1, "OK", 123}
    v := reflect.ValueOf(u)

    mv := v.MethodByName("Hello") // 獲取函數名
    args := []reflect.Value{reflect.ValueOf("alogy")} // 傳遞參數

    mv.Call(args)

    u.Hello("alogy")
}

併發concurrency

併發主要由切換時間片來實現「同時」運行,在並行則是直接利用多核實現多線程的運行,但Go能夠設置使用核數,以發揮多核計算機的能力。

Goroutine奉行經過通訊來共享內存,而不是共享內存來通訊。

package main

import (
    "fmt"
    "time"
)

func main () {
    fmt.Println(2 * time.Second) // 2s
    go Go()
    time.Sleep(2 * time.Second) // 延遲2s
    // 在main函數運行Sleep的時候,Go函數也運行了,執行完以後,退出。
}

func Go () {
    fmt.Println("Go...")
}

Channel

  • Channelgoroutine溝通的橋樑,大都是阻塞同步的.經過關鍵字go加函數的名稱,來實現goroutine
  • 經過make建立,close關閉
  • Channel是引用類型
  • 可使用for range來迭代不斷操做的Channel
  • 能夠設置單向或雙向通道
  • 能夠設置緩存大小,在未被填滿前不會發生阻塞(沒有設置爲0,就是阻塞的)

Select

  • 可處理一個或多個channel的發送與接收
  • 同時有多個可用的channel時按隨機順序處理
  • 可用空的select來阻塞main函數
  • 可設置超時

channel簡單使用:

package main

import (
    "fmt"
)

func main () {
    c := make(chan bool)
    go func () {
        fmt.Println("Go...")
        c <- true // 存 // 聲明的時候是bool
    }()
    <-c // 取 // 消息存取,阻塞執行
}

package main

import (
    "fmt"
)

func main() {
    c := make(chan bool)
    go func() {
        fmt.Println("Go...")
        c <- true
        close(c) // 關閉chan // 沒有明確關閉,會出現死鎖,崩潰退出
    }()
    // <- c
    for v := range c {
        fmt.Println(v)
    }
}

有無緩存區別

有緩存:異步,無緩存,同步。

package main

import (
    "fmt"
)

func main() {
    // c := make(chan bool, 1) // 有緩存 異步
    c := make(chan bool) // 無緩存的時候是阻塞的
    go func () {
        fmt.Println("Go...")    
        <- c // 讀取
    }()
    c <- true // 傳進去
}

多核分配任務

出現任務分配出現不必定的狀況,解決方法(多個goroutine的打印):

  1. 設置緩存
  2. 利用內置包sync
package main

import (
    "fmt"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) //  使用多核分配的時候,任務分配時不必定的
    c := make(chan bool, 10) // 設置緩存
    for i := 0; i < 10; i++ {
        go Go(c, i)
    }

    for i := 0; i < 10; i++ {
        <-c
    }
}

func Go(c chan bool, idx int) {
    a := 1
    for i := 0; i < 10000000; i++ {
        a += i
    }
    fmt.Println(idx, a)

    c <- true
}

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) //  使用多核分配的時候,任務分配時不必定的
    wg := sync.WaitGroup{} // 內置包sync使用
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go Go(&wg, i)
    }

    wg.Wait()
}

func Go(wg *sync.WaitGroup, idx int) {
    a := 1
    for i := 0; i < 10000000; i++ {
        a += i
    }
    fmt.Println(idx, a)

    wg.Done()
}

多個chan

經過select語句

package main

import (
    "fmt"
)

func main() {
    c1, c2 := make(chan int), make(chan string)
    o := make(chan bool, 2)
    go func() {
        for { // 經過死循環來不斷髮送和接收chan
            select {
            case v, ok := <-c1 :
                if !ok {
                    o <- true
                    break
                }
                fmt.Println("c1", v)
            case v, ok := <- c2 :
                if !ok {
                    o <- true
                    break
                }
                fmt.Println("c2", v)
            }
        }
    }()
        
    c1 <- 1
    c2 <- "hello"
    c1 <- 2
    c2 <- "zf"

    close(c1)
    close(c2)

    for i := 0; i < 2; i++ {
        <-o
    }

}

select做爲發送者的應用

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)

    go func() {
        for v := range c {
            fmt.Println(v)
        }    
    }()

    for i := 0; i < 10; i++ {
        select {
            case c <- 0:
            case c <- 1:    
        }
    }

    // select{} // 阻塞main函數退出,卡死main函數,場景常用在事件循環
}

select超時設置

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan bool)
    select {
        case v := <-c:
            fmt.Println(v)
        case t := <-time.After(3 * time.Second):
            fmt.Println(t)
            fmt.Println("Timeout")
    }
}
相關文章
相關標籤/搜索