學(xué)習(xí)筆記

1啥酱、Go Module:

go mod init:為當(dāng)前 Go 項目創(chuàng)建一個新的 module
go mod tidy:自動分析 Go 源碼的依賴變更
go mod vendor:可以支持 vendor 機制

2封恰、Go 包的初始化次序:
image.png
  • 依賴包按“深度優(yōu)先”的次序進行初始化;
  • 每個包內(nèi)按以“常量 -> 變量 -> init() -> main()”的順序進行初始化;
  • 包內(nèi)的多個 init 函數(shù)按出現(xiàn)次序進行自動調(diào)用(順序執(zhí)行)财喳。
3、init 函數(shù)的用途:
  • 重置包級變量值;
  • 實現(xiàn)對包級變量的復(fù)雜初始化允扇;
  • 在 init 函數(shù)中實現(xiàn)“注冊模式”。
4则奥、變量聲明規(guī)則:

包級變量:

  • 只能使用帶有 var 關(guān)鍵字的變量聲明形式考润,不能使用短變量聲明形式,但在形式細節(jié)上可以有一定靈活度读处。
var (
  a = 13
  b = int32(17)
  f = float32(3.14)
)
  • 可以將延遲初始化的變量聲明放在一個 var 聲明塊 (比如上面的第一個 var 聲明塊)糊治,然后將聲明且顯式初始化的變量放在另一個 var 塊中(比如上面的第二個 var 聲明塊)
var (
    netGo  bool 
    netCgo bool 
)

var (
    aLongTimeAgo = time.Unix(1, 0)
    noDeadline = time.Time{}
    noCancel   = (chan struct{})(nil)
)
  • iota常量計數(shù)器
    1、iota在const關(guān)鍵字出現(xiàn)時將被重置為0罚舱。
    2井辜、const中每新增一行常量聲明將使iota計數(shù)一次(iota可理解為const語句塊中的行索引)。
const (
        n1 = iota //0
        n2        //1
        n3        //2
        n4        //3
    )
const (
        n1 = iota //0
        n2        //1
        _         //丟棄該值管闷,常用在錯誤處理中
        n4        //3
    )
const (
        n1 = iota //0
        n2 = 100  //100
        n3 = iota //2
        n4        //3
    )

局部變量:

  • 延遲初始化的局部變量聲明采用通用的變量聲明形式
var err error
  • 聲明且顯式初始化的局部變量使用短變量聲明形式
a := 17
f := 3.14
s := "hello, gopher!"
5粥脚、條件判斷語句的特殊寫法
  • 正常寫法:
a := 10
if a == 1 {
    fmt.Println("a == 1")
} else if a == 2 {
    fmt.Println("a == 2")
} else if a == 3 {
    fmt.Println("a == 3")
} else {
    fmt.Println(a)
}
  • 簡略寫法:
if a := 10; a == 1 {
    fmt.Println("a == 1")
} else if a == 2 {
    fmt.Println("a == 2")
} else if a == 3 {
    fmt.Println("a == 3")
} else {
    fmt.Println(a)
}
6、for循環(huán)語句的特殊寫法
  • 正常寫法:
for i := 0; i < 10; i++ {
    fmt.Println(i)
}
  • 簡略寫法:
//省略初始語句和結(jié)束語句
var i = 10
for i > 0 {
    fmt.Println(i)
    i--
}
  • 死循環(huán):
for {
    fmt.Println("wuxian")
}
  • for range循環(huán):
for index, value := range list {
    fmt.Println("index:", index, ",", "value:", value)
}
7包个、數(shù)組刷允、切片、map
  • 數(shù)組的拷貝是深拷貝赃蛛,不會影響原數(shù)組:
list1 := [3]int{1, 2, 3}
list2 := list1
list2[0] = 100
fmt.Println(list1) //[1 2 3]
  • 切片的拷貝是淺拷貝恃锉,會影響原切片:
list1 := []int{1, 2, 3}
list2 := list1
list2[0] = 100
fmt.Println(list1) //[100 2 3]
  • 深拷貝切片:
a := []int{1, 2, 3, 4}
b := make([]int, 5)
copy(b, a)
b[0] = 100
fmt.Println(a) //[1 2 3 4]
  • 切片刪除索引index元素:append(a[:index], a[index+1:]...)
// 切片刪除下標(biāo)為2的元素
a := []int{1, 2, 3, 4}
a = append(a[0:2], a[3:]...)
fmt.Println(a)
  • 切片刪除多個元素
    a = append(a[:i], a[i+1:]...) // 刪除中間1個元素
    a = append(a[:i], a[i+N:]...) // 刪除中間N個元素

  • map基本使用
    直接在聲明的時候填充元素:

userInfo := map[string]string{
    "username": "沙河小王子",
    "password": "123456",
}
  • map的遍歷
func main() {
    scoreMap := make(map[string]int)
    scoreMap["張三"] = 90
    scoreMap["小明"] = 100
    scoreMap["娜扎"] = 60
    for k, v := range scoreMap {
        fmt.Println(k, v)
    }
}
8、結(jié)構(gòu)體struct
  • 1呕臂、定義
type person struct {
    name, city string
    gender     string
    age        int8
}

var p person
p.name = "panda"
p.age = 21
p.gender = "女"
p.city = "BeiJing"

// p2 為結(jié)構(gòu)體指針
var p2 = new(person)
fmt.Println(p2)

// 取結(jié)構(gòu)體的地址進行實例化
p3 := &person{}
fmt.Println(p3)

// 結(jié)構(gòu)體初始化
p4 := person{ //鍵值對初始化
    name:   "Panda",
    city:   "BeiJing",
    gender: "女",
    age:    21,
}
fmt.Println(p4)

p5 := person{ //列表初始化(按定義時的順序)
    "Panda", "BeiJing", "女", 21,
}
fmt.Printf("%#v\n", p5)
  • 2破托、構(gòu)造函數(shù)
//定義
func newPerson(name, city string, age int8) *person {
    return &person{
        name: name,
        city: city,
        age:  age,
    }
}

//調(diào)用構(gòu)造函數(shù)
p9 := newPerson("張三", "沙河", 90)
fmt.Printf("%#v\n", p9) //&main.person{name:"張三", city:"沙河", age:90}
  • 3、方法和接收者(相當(dāng)于結(jié)構(gòu)體的成員函數(shù))
    Go語言中的方法(Method)是一種作用于特定類型變量的函數(shù)歧蒋。
func (接收者變量 接收者類型) 方法名(參數(shù)列表) (返回參數(shù)) {
    函數(shù)體
}

例如:

//Person 結(jié)構(gòu)體
type Person struct {
    name string
    age  int8
}

//NewPerson 構(gòu)造函數(shù)
func NewPerson(name string, age int8) *Person {
    return &Person{
        name: name,
        age:  age,
    }
}

//Dream Person做夢的方法
func (p Person) Dream() {
    fmt.Printf("%s的夢想是學(xué)好Go語言土砂!\n", p.name)
}

func main() {
    p1 := NewPerson("小王子", 25)
    p1.Dream()
}
  • 4、結(jié)構(gòu)體-JSON 序列化谜洽、反序列化
type student struct {
    ID     int
    Gender string
    Name   string
}

type class struct {
    Title    string
    Students []student
}

c := &class{
    Title:    "101",
    Students: make([]student, 0, 20),
}
for i := 0; i < 10; i++ {
    stu := student{
        Name:   "stu" + string(i+48),
        Gender: "男",
        ID:     i,
    }
    c.Students = append(c.Students, stu)
}
data, err := json.Marshal(c) // 序列化
if err != nil {
    fmt.Println("json marshal failed!")
    return
}
fmt.Printf("json:%s\n", data)

str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu0"},{"ID":1,"Gender":"男","Name":"stu1"},{"ID":2,"Gender":"男","Name":"stu2"},{"ID":3,"Gender":"男","Name":"stu3"},{"ID":4,"Gender":"男","Name":"stu4"},{"ID":5,"Gender":"男","Name":"stu5"},{"ID":6,"Gender":"男","Name":"stu6"},{"ID":7,"Gender":"男","Name":"stu7"},{"ID":8,"Gender":"男","Name":"stu8"},{"ID":9,"Gender":"男","Name":"stu9"}]}`
c1 := &class{}
err = json.Unmarshal([]byte(str), c1) // 反序列化
if err != nil {
    fmt.Println("json unmarshal failed!")
    return
}
fmt.Printf("%#v\n", c1)
9萝映、接口interface
  • 1、語法
    接口的出現(xiàn)是為了支持多態(tài)阐虚,例如一個函數(shù)可能會接收多種結(jié)構(gòu)體作為參數(shù)序臂,如果此時參數(shù)類型還是傳統(tǒng)的結(jié)構(gòu)體,那么就只能支持傳入一種結(jié)構(gòu)體類型,這時可以使用接口作為參數(shù)類型奥秆。
    只要一個類型實現(xiàn)了接口中規(guī)定的所有方法逊彭,那么它就實現(xiàn)了這個接口。
package main

import "fmt"

type dog struct {
    name string
}

func (d dog) say() {
    fmt.Println(d.name + ":汪汪汪~")
}

type cat struct {
    name string
}

func (c cat) say() {
    fmt.Println(c.name + ":喵喵喵~")
}

// 接口為了實現(xiàn)多態(tài)构订,只要實現(xiàn)了say方法的結(jié)構(gòu)體都可以被視為sayer類型
type sayer interface {
    say()
}

// 這里傳入的參數(shù)因為既有狗也有貓侮叮,所以必須使用接口類型進行多態(tài)傳入
func hit(someone sayer) {
    someone.say()
}

func main() {
    dog1 := dog{
        "旺財",
    }
    cat1 := cat{
        "加菲",
    }
    hit(dog1)
    hit(cat1)
}
  • 2、接口組合
    接口與接口之間可以通過互相嵌套形成新的接口類型
// src/io/io.go

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

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

type Closer interface {
    Close() error
}

// ReadWriter 是組合Reader接口和Writer接口形成的新接口類型
type ReadWriter interface {
    Reader
    Writer
}

// ReadCloser 是組合Reader接口和Closer接口形成的新接口類型
type ReadCloser interface {
    Reader
    Closer
}

// WriteCloser 是組合Writer接口和Closer接口形成的新接口類型
type WriteCloser interface {
    Writer
    Closer
}

只需要實現(xiàn)新接口類型中規(guī)定的所有方法就算實現(xiàn)了該接口類型

  • 3悼瘾、空接口
    空接口是指沒有定義任何方法的接口類型囊榜。因此任何類型都可以視為實現(xiàn)了空接口。也正是因為空接口類型的這個特性亥宿,空接口類型的變量可以存儲任意類型的值卸勺。
    使用空接口實現(xiàn)可以接收任意類型的函數(shù)參數(shù):
// 空接口作為函數(shù)參數(shù)
func show(a interface{}) {
    fmt.Printf("type:%T value:%v\n", a, a)
}

使用空接口實現(xiàn)可以保存任意值的字典:

// 空接口作為map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "沙河娜扎"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
10、reflect 反射

reflect包提供了reflect.TypeOf和reflect.ValueOf兩個函數(shù)來獲取任意對象的Value和Type箩绍。

11孔庭、并發(fā) goroutine

區(qū)別于操作系統(tǒng)線程由系統(tǒng)內(nèi)核進行調(diào)度, goroutine 是由Go運行時(runtime)負責(zé)調(diào)度材蛛。例如Go運行時會智能地將 m個goroutine 合理地分配給n個操作系統(tǒng)線程,實現(xiàn)類似m:n的調(diào)度機制怎抛,不再需要Go開發(fā)者自行在代碼層面維護一個線程池卑吭。

Goroutine 是 Go 程序中最基本的并發(fā)執(zhí)行單元。每一個 Go 程序都至少包含一個 goroutine——main goroutine马绝,當(dāng) Go 程序啟動時它會自動創(chuàng)建豆赏。

啟動 goroutine 的方式非常簡單,只需要在調(diào)用函數(shù)(普通函數(shù)和匿名函數(shù))前加上一個go關(guān)鍵字:

go func(){
  // ...
}()

為了防止main函數(shù)主線程結(jié)束時創(chuàng)建的goroutine 還沒有運行完富稻,就需要使用sync.WaitGroup全局等待組變量掷邦,讓main函數(shù)等待其他線程運行結(jié)束

package main

import (
    "fmt"
    "sync"
)

// 聲明全局等待組變量
var wg sync.WaitGroup

func hello() {
    fmt.Println("hello")
    wg.Done() // 告知當(dāng)前goroutine完成
}

func main() {
    wg.Add(1) // 登記1個goroutine
    go hello()
    fmt.Println("你好")
    wg.Wait() // 阻塞等待登記的goroutine完成
}

啟動多個goroutine:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func hello(i int) {
    defer wg.Done() // goroutine結(jié)束就登記-1
    fmt.Println("hello", i)
}
func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1) // 啟動一個goroutine就登記+1
        go hello(i)
    }
    wg.Wait() // 等待所有登記的goroutine都結(jié)束
}

匿名函數(shù)版本:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1) // 啟動一個goroutine就登記+1
        go func(i int) {
            fmt.Println("hello", i)
            wg.Done() // goroutine結(jié)束就登記-1
        }(i) //必須要顯式的傳入i,不然閉包訪問到的 i 可能跟循環(huán)次數(shù)不對應(yīng)
    }
    wg.Wait() // 等待所有登記的goroutine都結(jié)束
}
12椭赋、channel通信
  • 1抚岗、初始化
    通道是一種消息隊列,總是遵循先進先出的原則哪怔;
    channel是一種類型宣蔚,一種引用類型(空值為nil),聲明格式如下:
var x1 chan int //聲明一個傳遞整型的通道
var x2 chan bool //聲明一個傳遞布爾型的通道
var x3 chan []int //聲明一個傳遞int切片的通道

聲明的通道需要使用make函數(shù)初始化之后才能使用认境,格式如下:

make(chan 元素類型, [緩沖大小])
x1 := make(chan int)
x2 := make(chan bool)
x3 := make(chan []int)
ch5 := make(chan bool, 1)  // 聲明一個緩沖區(qū)大小為1的通道
  • 2胚委、channel操作
    通道共有發(fā)送(send)、接收(receive)和關(guān)閉(close)三種操作叉信,發(fā)送和接收操作都使用<-符號亩冬。

先使用以下語句定義一個通道:

ch := make(chan int)

①發(fā)送
將一個值發(fā)送到通道中。

ch <- 10 // 把10發(fā)送到ch中

②接收
從一個通道中接收值硼身。

x := <- ch // 從ch中接收值并賦值給變量x
<-ch       // 從ch中接收值硅急,忽略結(jié)果

③關(guān)閉
我們通過調(diào)用內(nèi)置的close函數(shù)來關(guān)閉通道枢冤。

close(ch)
  • 3、無緩沖的通道
    無緩沖的通道又稱為阻塞的通道铜秆。
    ch := make(chan int)沒有分配緩沖區(qū)大小淹真,信息必須要手把手的同步傳輸
  • 4、有緩沖的通道
    ch := make(chan int, 1)
  • 5连茧、對通道的操作
len(ch1) //取通道中的元素數(shù)量
cap(ch1) //獲取通道的容量
  • 6核蘸、從通道中取值的兩種方式
for res := range ch2 {
    fmt.Println(res)
}
for {
    val, ok := <-ch1
    if !ok {
        break
    }
    ch2 <- val * val
}
  • 7、單向通道
    一般用于函數(shù)傳參時規(guī)定通道的方向
    ch1 chan<- int 只能接收值
    ch2 <-chan int 只能發(fā)送值
    image.png

    例如:
package main

import (
    "fmt"
)

func f1(ch1 chan<- int) {
    for i := 0; i < 100; i++ {
        ch1 <- i
    }
    close(ch1)
}

func f2(ch1 <-chan int, ch2 chan<- int) {
    for {
        val, ok := <-ch1
        if !ok {
            break
        }
        ch2 <- val * val
    }
    close(ch2)
}

func main() {
    ch1 := make(chan int, 100)
    ch2 := make(chan int, 100)

    go f1(ch1)
    go f2(ch1, ch2)

    for ret := range ch2 {
        fmt.Println(ret)
    }
}

  • 8啸驯、select多路復(fù)用:同時從多個通道接收數(shù)據(jù)
    類似于之前學(xué)到的 switch 語句客扎,但是如果有多個case符合條件則會隨機選取其中一條執(zhí)行而不是順序執(zhí)行
select {
case <-ch1:
    //...
case data := <-ch2:
    //...
case ch3 <- 10:
    //...
default:
    //默認操作
}

Select 語句具有以下特點。

  • 可處理一個或多個 channel 的發(fā)送/接收操作罚斗。
  • 如果多個 case 同時滿足徙鱼,select 會隨機選擇一個執(zhí)行。
  • 對于沒有 case 的 select 會一直阻塞针姿,可用于阻塞 main 函數(shù)袱吆,防止退出。
13距淫、并發(fā)安全和鎖
  • 1绞绒、互斥鎖
    互斥鎖是完全互斥的
var lock sync.Mutex //定義一個互斥鎖類型
lock.Lock() //加鎖
lock.Unlock() //釋放鎖
  • 2、讀寫互斥鎖:適用于讀的操作數(shù)量遠遠大于寫操作
    讀取時可以不加鎖榕暇,寫的時候才有必要加鎖
    加了讀鎖之后都可以讀蓬衡,不可以寫;
    加了寫鎖之后其他的線程都不能讀寫
var rwlock sync.RWMutex
rwlock .Lock()  //加寫鎖
rwlock .Unlock()  //釋放寫鎖
rwlock .RLock()  //加讀鎖
rwlock .RUnlock()  //釋放讀鎖
  • 3彤枢、sync.WaitGroup
    進程同步器
var wg   sync.WaitGroup
wg.Add(x) //計數(shù)器+x
wg.Done() //計數(shù)器-1
wg.Wait() //阻塞直到計數(shù)器變?yōu)?
  • 4狰晚、sync.Once:只執(zhí)行一次
    確保某些操作即使在高并發(fā)的場景下也只會被執(zhí)行一次,例如只加載一次配置文件等
func test(){
 ...
}
var once sync.Once
once.Do(test)
  • 5缴啡、sync.Map:并發(fā)安全版map
    go 語言中內(nèi)置的 map 不是并發(fā)安全的壁晒,當(dāng)過多的線程并發(fā)訪問map時會報錯:
    fatal error: concurrent map writes
// 并發(fā)安全的map
var m = sync.Map{} //不需要指定key、value類型(都是空接口類型)
m.Store(key, value)  // 存儲key-value
m.Load(key) // 根據(jù)key取值
image.png
  • 6盟猖、原子操作
    通常直接使用原子操作比使用鎖操作效率更高讨衣,但是只針對整數(shù)數(shù)據(jù)類型(int32、uint32式镐、int64反镇、uint64)
    方法:
    LoadInt32、StoreInt32娘汞、AddInt32歹茶、SwapInt32、CompareAndSwapInt32
import  "sync/atomic"
type AtomicCounter struct {
    counter int64
}

func (a *AtomicCounter) Inc() {
    atomic.AddInt64(&a.counter, 1)
}

func (a *AtomicCounter) Load() int64 {
    return atomic.LoadInt64(&a.counter)
}
14、網(wǎng)絡(luò)編程
  • 1惊豺、TCP:
    服務(wù)端:
package main

import (
    "bufio"
    "fmt"
    "net"
)

func process1(conn net.Conn) {
    defer conn.Close() //處理完畢之后要關(guān)閉該連接
    // 針對當(dāng)前連接做數(shù)據(jù)發(fā)送和接收操作
    for {
        reader := bufio.NewReader(conn)
        var buf [128]byte
        n, err := reader.Read(buf[:])
        if err != nil {
            fmt.Printf("read from conn failed, err:%v\n", err)
            break
        }
        recv := string(buf[:n])
        fmt.Println("接收到的數(shù)據(jù):", recv)
        conn.Write([]byte("ok")) //把收到的數(shù)據(jù)返回到客戶端
    }
}

func main() {
    // 1燎孟、開啟服務(wù)
    listen, err := net.Listen("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Printf("Listen failed, err:%v\n", err)
        return
    }
    for {
        // 2、等待客戶端建立連接
        conn, err := listen.Accept()
        if err != nil {
            fmt.Printf("Accept failed, err:%v\n", err)
            continue
        }
        // 3尸昧、啟動一個單獨的goroutine去處理連接
        go process1(conn)
    }
}

客戶端:

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func main() {
    // 1揩页、與服務(wù)端建立連接
    conn, err := net.Dial("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Printf("Dial failed, err:%v\n", err)
        return
    }

    // 2、利用該連接進行數(shù)據(jù)發(fā)送和接收
    input := bufio.NewReader(os.Stdin)
    for {
        s, _ := input.ReadString('\n')
        s = strings.TrimSpace(s)
        if strings.ToUpper(s) == "Q" {
            return
        }
        // 給服務(wù)端發(fā)消息
        _, err := conn.Write([]byte(s))
        if err != nil {
            fmt.Printf("Send failed, err:%v\n", err)
            return
        }
        // 從服務(wù)端接收回復(fù)的消息
        var buf [1024]byte
        n, err := conn.Read(buf[:])
        if err != nil {
            fmt.Printf("Read failed, err:%v\n", err)
            return
        }
        fmt.Println("收到服務(wù)端回復(fù):", string(buf[:n]))
    }
}
  • 2烹俗、TCP黏包:
    tcp流模式傳遞數(shù)據(jù)爆侣,當(dāng)數(shù)據(jù)量較多時,多條數(shù)據(jù)混在一起接收時不知道是哪一條

①由Nagle算法造成的發(fā)送端的粘包:Nagle算法是一種改善網(wǎng)絡(luò)傳輸效率的算法幢妄。簡單來說就是當(dāng)我們提交一段數(shù)據(jù)給TCP發(fā)送時兔仰,TCP并不立刻發(fā)送此段數(shù)據(jù),而是等待一小段時間看看在等待期間是否還有要發(fā)送的數(shù)據(jù)蕉鸳,若有則會一次把這兩段數(shù)據(jù)發(fā)送出去乎赴。
②接收端接收不及時造成的接收端粘包:TCP會把接收到的數(shù)據(jù)存在自己的緩沖區(qū)中,然后通知應(yīng)用層取數(shù)據(jù)潮尝。當(dāng)應(yīng)用層由于某些原因不能及時的把TCP的數(shù)據(jù)取出來榕吼,就會造成TCP緩沖區(qū)中存放了幾段數(shù)據(jù)。

解決辦法:對數(shù)據(jù)包進行封包衍锚、拆包操作

// socket_stick/proto/proto.go
package proto

import (
    "bufio"
    "bytes"
    "encoding/binary"
)

// Encode 將消息編碼
func Encode(message string) ([]byte, error) {
    // 讀取消息的長度友题,轉(zhuǎn)換成int32類型(占4個字節(jié))
    var length = int32(len(message))
    var pkg = new(bytes.Buffer)
    // 寫入消息頭
    err := binary.Write(pkg, binary.LittleEndian, length)
    if err != nil {
        return nil, err
    }
    // 寫入消息實體
    err = binary.Write(pkg, binary.LittleEndian, []byte(message))
    if err != nil {
        return nil, err
    }
    return pkg.Bytes(), nil
}

// Decode 解碼消息
func Decode(reader *bufio.Reader) (string, error) {
    // 讀取消息的長度
    lengthByte, _ := reader.Peek(4) // 讀取前4個字節(jié)的數(shù)據(jù)
    lengthBuff := bytes.NewBuffer(lengthByte)
    var length int32
    err := binary.Read(lengthBuff, binary.LittleEndian, &length)
    if err != nil {
        return "", err
    }
    // Buffered返回緩沖中現(xiàn)有的可讀取的字節(jié)數(shù)。
    if int32(reader.Buffered()) < length+4 {
        return "", err
    }

    // 讀取真正的消息數(shù)據(jù)
    pack := make([]byte, int(4+length))
    _, err = reader.Read(pack)
    if err != nil {
        return "", err
    }
    return string(pack[4:]), nil
}
  • 3戴质、UDP:
    UDP服務(wù)端:
// UDP/server/main.go

// UDP server端
func main() {
    listen, err := net.ListenUDP("udp", &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 30000,
    })
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    defer listen.Close()
    for {
        var data [1024]byte
        n, addr, err := listen.ReadFromUDP(data[:]) // 接收數(shù)據(jù)
        if err != nil {
            fmt.Println("read udp failed, err:", err)
            continue
        }
        fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
        _, err = listen.WriteToUDP(data[:n], addr) // 發(fā)送數(shù)據(jù)
        if err != nil {
            fmt.Println("write to udp failed, err:", err)
            continue
        }
    }
}

UDP客戶端

// UDP 客戶端
func main() {
    socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 30000,
    })
    if err != nil {
        fmt.Println("連接服務(wù)端失敗,err:", err)
        return
    }
    defer socket.Close()
    sendData := []byte("Hello server")
    _, err = socket.Write(sendData) // 發(fā)送數(shù)據(jù)
    if err != nil {
        fmt.Println("發(fā)送數(shù)據(jù)失敗踢匣,err:", err)
        return
    }
    data := make([]byte, 4096)
    n, remoteAddr, err := socket.ReadFromUDP(data) // 接收數(shù)據(jù)
    if err != nil {
        fmt.Println("接收數(shù)據(jù)失敗告匠,err:", err)
        return
    }
    fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}
15、單元測試

go test命令是一個按照一定約定和組織的測試代碼的驅(qū)動程序离唬。在包目錄內(nèi)后专,所有以_test.go為后綴名的源代碼文件都是go test測試的一部分,不會被go build編譯到最終的可執(zhí)行文件中输莺。

_test.go文件中有三種類型的函數(shù)戚哎,單元測試函數(shù)、基準(zhǔn)測試函數(shù)和示例函數(shù)嫂用。

image.png

go test命令會遍歷所有的
_test.go文件中符合上述命名規(guī)則的函數(shù)型凳,然后生成一個臨時的main包用于調(diào)用相應(yīng)的測試函數(shù),然后構(gòu)建并運行嘱函、報告測試結(jié)果甘畅,最后清理測試中生成的臨時文件。

  • 1、測試函數(shù):
    每個測試函數(shù)必須導(dǎo)入testing包疏唾,測試函數(shù)的基本格式(簽名)如下:
func TestName(t *testing.T){
    // ...
}

其中參數(shù)t用于報告測試失敗和附加的日志信息蓄氧。 testing.T的擁有的方法如下:

func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (t *T) Parallel()
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool
16、泛型

如果多個函數(shù)僅僅是參數(shù)類型不同槐脏,可以考慮使用泛型
觀看以下兩個函數(shù):

func reverseInt(s []int) []int {
    l := len(s)
    r := make([]int, l)

    for i, e := range s {
        r[l-i-1] = e
    }
    return r
}
func reverseFloat64(s []float64) []float64 {
    l := len(s)
    r := make([]float64, l)

    for i, e := range s {
        r[l-i-1] = e
    }
    return r
}

僅僅是處理的參數(shù)類型不同喉童,過程完全一樣,那么可以使用泛型語法:

func min[T int | float64](a, b T) T {
    if a <= b {
        return a
    }
    return b
}

這次定義的min函數(shù)就同時支持int和float64兩種類型:

m1 := min[int](1, 2)  // 1
m2 := min[float64](-0.1, -0.2)  // -0.2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末顿天,一起剝皮案震驚了整個濱河市堂氯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌露氮,老刑警劉巖祖灰,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異畔规,居然都是意外死亡局扶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門叁扫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來三妈,“玉大人,你說我怎么就攤上這事莫绣〕肫眩” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵对室,是天一觀的道長模燥。 經(jīng)常有香客問我,道長掩宜,這世上最難降的妖魔是什么蔫骂? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮牺汤,結(jié)果婚禮上辽旋,老公的妹妹穿的比我還像新娘。我一直安慰自己檐迟,他們只是感情好补胚,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著追迟,像睡著了一般溶其。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怔匣,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天握联,我揣著相機與錄音桦沉,去河邊找鬼。 笑死金闽,一個胖子當(dāng)著我的面吹牛纯露,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播代芜,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼埠褪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挤庇?” 一聲冷哼從身側(cè)響起钞速,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嫡秕,沒想到半個月后渴语,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡昆咽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年驾凶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掷酗。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡调违,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泻轰,到底是詐尸還是另有隱情技肩,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布浮声,位于F島的核電站虚婿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泳挥。R本人自食惡果不足惜雳锋,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羡洁。 院中可真熱鬧,春花似錦爽丹、人聲如沸筑煮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽真仲。三九已至,卻和暖如春初澎,著一層夾襖步出監(jiān)牢的瞬間秸应,已是汗流浹背虑凛。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留软啼,地道東北人桑谍。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像祸挪,于是被迫代替她去往敵國和親锣披。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 是一門編譯型語言贿条,運行效率高雹仿,開發(fā)高效,部署簡單整以;語言層面支持并發(fā)胧辽,易于利用多核實現(xiàn)并發(fā);內(nèi)置runtime(作用...
    dev_winner閱讀 288評論 0 3
  • Go go run xx.go 執(zhí)行g(shù)o文件 go build xx.go生成二進制文件 一. GO 基礎(chǔ) 1. ...
    為什么不讓我改名字閱讀 268評論 0 3
  • 使用go1.10版本公黑,在liteIde里開發(fā)邑商。 1,變量聲明后必須使用帆调,不然編譯不過(全局變量可以不用)奠骄。 2,變...
    adrian920閱讀 980評論 1 1
  • 1.for range結(jié)合指針如下寫法輸出的*v都是m的最后一個valuefor k,v := range m {...
    javid閱讀 571評論 0 0
  • 1.是一種靜態(tài)強類型番刊、編譯型語言含鳞,語法與C相近,功能更豐富:內(nèi)存安全芹务,GC(垃圾回收)蝉绷,結(jié)構(gòu)形態(tài)及并發(fā)計算2.go...
    hypercode閱讀 285評論 0 0