1啥酱、Go Module:
go mod init:為當(dāng)前 Go 項目創(chuàng)建一個新的 module
go mod tidy:自動分析 Go 源碼的依賴變更
go mod vendor:可以支持 vendor 機制
2封恰、Go 包的初始化次序:
- 依賴包按“深度優(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取值
-
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ù)嫂用。
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