一 GO语言简介
- 全称:Golang
- 起源:Google
- 三大作者:罗伯派克、肯汤普森、罗伯特
- 运行速度快、开发速度快、学习速度快
- 2009年被开源,2012年1.0正式版本发布,2015年的1.5版本移除了最后的C语言代码,目前更新到1.19
- https://studygolang.com/ 资源
- http://www.golang.ltd/ 官方文档
- 主要特点:
- 继承了C语言的一些理念,如指针、数据类型、语法等
- 函数可以有多个返回值,切片、延时执行、包管理
- 并发编程、网络编程支持性好
- 独特的内存分配及回收机制
- 编译型语言
VS Code
/Goland
hello world
package main import "fmt" func main() { fmt.Println("hello world") return }
- 编译/运行
> go build (-o myhello) hello.go > go build (-o myhello.exe) hello.go > go run hello.go hello world
二 包
-
管理项目
- 1.11版本之前 -> GOPATH 目录
- 1.11版本之后 -> go mod管理
- 使用
go mod
需要配置 ->go env
查看 -> 将GO111MODULE
配置改成auto
go env -w GO111MODULE=auto
- 使用
mod
go mod init Gone
go get xxx
go mod tidy
module Gone go 1.18 require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef )
- 使用
-
package xxx
–import "Gone/xxx"
> Test > C > S go.mod go.sum
-
import
import ( "fmt" _ "strings" //会执行包里的init()函数,其他隐藏 l "log" //取别名,取了别名则原包名不可用,使用包只能用别名 )
三 基本语法
3.1 书写规范
- 大小写敏感,首字母大小写表示公私有
type public_st struct { Test int32 } type private_st struct { test int32 } func main() { var st1 public_st res := st1.Test st1.Test = 2 fmt.Println(res, " ", st1.Test) //var st2 private_st //res1 := st2.test //err test字段私有,对于外部隐藏 }
0 2
- 一行一个语句
var str string = "aaaa" + "aaaa" + "aaaa" + "aaaa" + "aaaa" + "aaaa" + "aaaa" + "aaaa" + "aaaa"
- 语句后不加分号
- 大括号严格在上
- 声明的变量不使用,编译时报错
type xxx is unused
- 注释
//注释
/*注释*/
- hs[i],hs[j] = hs[j],hs[i]
3.2 函数
- 定义
func funcName(param) (returnparam){ }
- 程序的起点是main包
的main函数func init() {}
func main() { fmt.Println("ddd") } func init() { fmt.Println("ccc") }
- 仅做介绍,不要使用
init()
函数,会造成很多不必要的麻烦
- 仅做介绍,不要使用
3.3 关键字
3.3.1 type
type TypeDef_Int int
type TypeDef_St struct{}
type TypeDef_Inter interface{}
3.3.2 const
- 使用
const
可以达到枚举的效果,所以golang没有enum
字段
const (
Def_a TypeDef_Int = iota + 1 //固定写法iota = 0,iota可以省略
Def_b //在前者基础上+1
)
3.3.3 var
var name type
–>var a int
var ( a int32 b int32 )
- 声明变量还可以用
:=
,a := 1
3.3.4 defer
-
延迟执行
-
案例1:延迟执行关于锁的关闭操作(mutex、db、log等)
func Unlock(lock *sync.RWMutex) { fmt.Println("unlock") lock.RUnlock() } func main() { var ( test int = 10 lock sync.RWMutex ) lock.RLock() //加锁,加锁成功再修改test的值,否则阻塞 defer Unlock(&lock) //延迟解锁 test = 5 fmt.Println(test) }
5 unlock
-
案例2:关于defer内的变量值
func deferTest(){ num := 0 defer fmt.Println(num) num++ } func main(){ deferTest() }
defer语句在调用时候的值已经被确定了,本质上是拷贝了一份。仍然打印“0”。
-
案例3:多个defer的执行顺序
func main() { defer fmt.Println("1111111") defer fmt.Println("2222222") }
2222222 1111111
-
闭包概念在面向对象补充
3.3.5 if
...
//写法1
_, err := db.Exec(s, 3)
if err == nil {
}
//写法2
if _, err := db.Exec(s, 3); err == nil {
} else {
}
- 两种写法
err
作用域不同,写法1和if
同级的作用域,写法2是仅在if
内部 - 写法2定义的变量不一定要在
if
判断时候就用 - 在golang中,常用于错误判断
3.3.6 switch
- 不用
break
,需要穿透case
可以用fallthrough
,只透传一层 - 匹配到一个
case
之后不会进行后续case
判断 case
后直接写相同值会编译不过,但是变量值相同可以编过switch
可以不带表达式,case里面用作和if一样的操作switch i:= x.(type)
可以判断接口变量x
的实际类型,case
里写类型,在接口章节中补充func main() { var num int = 2 switch num { case 1: fmt.Println("one") case 2: fallthrough case 3: fmt.Println("three") default: fmt.Println("err") } switch { case num == 1: fmt.Println(1) case num == 2: fmt.Println(2) } }
3.3.7 for
- 基本用法
func main() { for n := 1; n <= 3; n++ { fmt.Println(n) } str := "abcde" for k, v := range str { fmt.Printf("%d,%c\n", k, v) } }
1 2 3 0,a 1,b 2,c 3,d 4,e
- 坑:
for...range
中的k,v
是值拷贝,若修改要通过数组下标修改值func main() { arr := [...]int{1, 2, 3} fmt.Println(arr) for k, v := range arr { fmt.Println(k, " ", v) if k == 2 { //v = 9 //无法修改,v为临时变量 arr[k] = 9 } } fmt.Println(arr) }
[1 2 3] 0 1 1 2 2 3 [1 2 9]
3.4 数据类型
3.4.1 指针
&
取地址、*
根据地址取值。
- 使用
type st struct {
Test int32
}
...
var s st
var ps *st = &s
res := s.Test
res1 := ps.Test //偷懒
res2 := (*ps).Test
3.4.2 string
- 支持库:
strconv
、strings
- golang没有
char
类型 - 底层结构
type stringStruct struct { str unsafe.Pointer //指向一个数组的指针 len int //实际长度 }
- 常用操作
str1 := "hello world" // 输出 str1[3] = 108 l的ascii码 fmt.Println(string(str1[3])) // 错误,unsafe.Pointer对于数组是只读的 //str1[3] = 'l' //获取字符串长度 fmt.Println(len(str1)) //输出:11 // 对于中文,可以使用中文的切片[]rune进行中文文字拆分 strChinese := "你好,世界" fmt.Println(len(strChinese)) //输出13,英文占1个字节,中文占3个字节 fmt.Println(len([]rune(strChinese))) //输出5 /***********************strings库常用方法***********************/ //匹配函数 //cmp = -1 str1字典序小于strChinese //cmp = 0 str1和strChinese相等 //cmp = 1,表示str1字典序大于strChinese fmt.Println(strings.Compare(str1, strChinese)) //输出:-1 //查找位置 fmt.Println(strings.Index(str1, "o")) //输出:4 fmt.Println(strings.LastIndex(str1, "o")) //输出:7 //出现次数 fmt.Println(strings.Count(str1, "o")) //输出:2 //重复多次 fmt.Println(strings.Repeat(str1, 3)) //输出:hello worldhello worldhello world //字符串替换 第四个参数是替换的次数,小于0为全部替换 fmt.Println(strings.Replace(str1, "o", "a", 1)) //输出:hella world fmt.Println(strings.Replace(str1, "o", "a", -1)) //输出:hella warld //字符串拆分,返回一个切片 fmt.Println(strings.Split(str1, ",")) //输出:[hello world] <--这是一个切片 //字符串拼接 sli := []string{"hello", "world", "tt"} fmt.Println(strings.Join(sli, ",")) //输出:hello,world,tt /***********************strconv库常用方法***********************/ //字符串解析 //字符串转整型 还有转别的类型不再举例 //参数:(字符串,进制,字符串的bit的大小) bit大小举例:8就是int8,16就是int16,具体用法查询文档 //返回类型:int64, error vParseInt, err := strconv.ParseInt("12345", 10, 0) if err != nil { fmt.Println("ParseInt error") } fmt.Println(vParseInt) //另一种写法,简单一些,返回int型 vAtoi, err := strconv.Atoi("12345") if err != nil { fmt.Println("error happens") } fmt.Println(vAtoi) //整型转字符串 同样用int举例 fmt.Println(strconv.FormatInt(int64(12345), 10)) //第二参数代表进制 fmt.Println(strconv.Itoa(12345))
3.4.3 数组
- 声明数组变量并初始化
var arr [4]int = [4]int{1,2,3,4}
arr := [...]int{1, 2, 3}
- 数组不会自动扩容
- 下标越界会崩溃
func main() { var arr [4]int = [4]int{1, 2, 3, 4} fmt.Println(arr) var arr2 [4]int = [4]int{1, 2, 3} fmt.Println(arr2) arr3 := [...]int{1, 2, 3} fmt.Println(arr3) }
[1 2 3 4] [1 2 3 0] [1 2 3]
3.4.4 切片
-
本质是对底层数组的封装:底层数组的指针、长度(len)、 容量(cap)
-
声明
var a []string
var b = []int{1,2,3}
sc := make([]int,2,10)
-
使用
- 添加
var s1 []int s1 = append(s1) s1 = append(s1,1) s1 = append(s1,2,3,4)
使用append可以对切片进行扩容: 1.len未超过cap,直接赋值 2.len超过cap,扩容 发生扩容的情况表示:
- 删、改、查
//删除 a := []int{11, 22, 33, 44, 55} a = append(a[:2], a[3:]...) //修改 s1[1] = 1000 //查询 num:=[3]int{0,1,2} sc1:=num[:] //[0.1.2] sc2:=num[0:2] //[0,1] --左闭右开 sc3:=num[1:] //[2] sc4:=num[:1] //[0]
3.4.5 可变参数
- 使用案例
func myfunc(args ...int) { for _, arg := range args { fmt.Println(arg) } } ... myfunc(2, 3, 4) myfunc(1, 3, 7, 13) tmp := []int{1,2,3} //针对切片的写法 myfunc(tmp...)
- 有确定的参数,必须写在可变参数前面
func myfunc(str string, args ...int) { } myfunc("test", 1, 2, 3)
- 针对不同类型的可变参数,可以使用空接口类型的可变参数,在函数内部进行判断,在接口章节补充
func myfunc(args ...interface{}) { } myfunc(1, 3.1, "test", false)
3.4.6 map
key-value
的键值对,key值无序- 声明:
map[key类型]value类型
- 初始化要
make
,m := make(map[int]int)
- 添加/修改:
mapTalkRuleCfg[1] = 1
- 键不存在,则会创建键,自动扩容
- 键存在:更新值
- 删除:
delete(mapNpcInfo, iNpcId)
- 查找:
info, ok := mapTalkRuleCfg[iRuleId] if ok{ //具体操作 }
- 坑1:修改。
cannot assign to struct field m[1].iLv in map
func main() { m := map[int]User{ 1: User{101, 10}, 2: User{102, 20}, } m[1].iLv = 11 //error } type User struct { iId int iLv int }
func main() { m := map[int]*User{ 1: &User{101, 10}, 2: &User{102, 20}, } m[1].iLv = 11 } type User struct { iId int iLv int }
- 坑2:将切片数据存入map操作不当,会导致数据丢失。
func main() { sliceUsers := []User{ User{101, 10}, User{102, 20}, } mu := make(map[int]*User) for _, u := range sliceUsers { mu[u.iId] = &u // mu[u.iId] = &sliceUsers[i] } fmt.Println(mu) } type User struct { iId int iLv int } func (u *User) String() string { return fmt.Sprintf("{Id: %v, Lv: %v}", u.iId, u.iLv) }
map
的底层原理:hashmap
-
hmap
底层结构:type hmap struct { count int //已存储多少结构 flags uint8 //map状态旗帜 B uint8 //buckets数量是(2^B)个 noverflow uint16 //溢出桶的数量 hash0 uint32 //哈希种子 buckets unsafe.Pointer //一个数组,数量是(2^B)个,哈希桶 oldbuckets unsafe.Pointer //迁移的时候的旧地址,平时都是nil nevacuate uintptr //迁移进度 extra *mapextra //溢出桶的情况 }
-
只关注
B
和buckets
字段简单的结构图: - 关于添加:对key
进行hash
算法处理,根据得出的哈希值选择对应的桶bucket
存入- 值已使用(哈希冲突):拉链法(Golang采用此方案,上图虚线表示)、开放寻址法
- 值未使用:
- 负载因子
LF
=已存键值count
/桶数量2^B
,用于判断扩容依据 - 不需要扩容,直接存入
- 需要扩容,
B++
,采用渐进式扩容,分配新的buckets
并发生数据迁移
- 负载因子
-
buckets
指向的bucket
,是一个bmap
结构。现版本中源代码结构中只列出了一个字段,此字段包含此buckets
中每个key
的哈希值的最高8位(1字节)。type bmap struct { tophash [bucketCnt]uint8 } bucketCntBits = 3 bucketCnt = 1 << bucketCntBits //0000 0001 -> 0000 1000 = 8
-
但是,map 在编译时已经确定了 map 中 key、value 及桶的大小,通过指针还会操作后续的内存空间,所以其实运行起来的
bmap
长这样。type bmap struct { tophash [8]uint8 keys [8]keytype values [8]valuetype overflow *bmap }
-
overflow
字段连接着一个溢出桶,桶满了之后的数据就会放在这儿 -
当
B>4
时,认为使用到溢出桶的概率较大,会分配2*(B-4)
个溢出桶,内存布局和常规桶是连续的
-
- 由
map
引出一个golang-GC
的引用计数原理- 每个内存数据单元都拥有一个引用数量,当引用数量为 0 时,将其回收。
- 以刚刚的例子做一个修改
var mu map[int]*User func doMap() { sliceUsers := []User{ User{101, 10}, User{102, 20}, } fmt.Printf("%p\n", sliceUsers) //0xc000010200 mu = make(map[int]*User) for i, u := range sliceUsers { mu[u.iId] = &sliceUsers[i] } } func main() { doMap() fmt.Println("执行这段话的时候,doMap函数中的sliceUsers地址,还没有被释放") fmt.Printf("%p\n", mu[101]) //0xc000010200 }
0xc000010200 执行这段话的时候,doMap函数中的sliceUsers地址,还没有被释放 0xc000010200
sliceUsers
是在doMap()
函数中被声明的,但是他的地址已经被mu
所引用,在doMap()函数结束之后没有释放。
3.5 面向对象
3.5.1 方法
- 定义
func (s structName)funcName(param) (returnparam){ } func (s *structName)funcName(param) (returnparam){ }
- 使用案例
type st struct { Param1 int } func (s *st) stCallPoint() { s.Param1 = 100 fmt.Println("stCallPoint()") } func (s st) stCall() { s.Param1 = 999 fmt.Println("stCall()") } func main() { var myst st fmt.Println("myst.Param1 = ", myst.Param1) myst.stCallPoint() fmt.Println("myst.Param1 = ", myst.Param1) myst.stCall() fmt.Println("myst.Param1 = ", myst.Param1) }
myst.Param1 = 0 stCallPoint() myst.Param1 = 100 stCall() myst.Param1 = 100
- 通过在函数名前加上
( s structName)
或者( s *structName)
声明结构体方法 - 使用指针可以修改相应结构的值
(s *structName)
相当于一个显式的this指针- 不建议写成
(this *structName)
或者(self *structName)
- 通过在函数名前加上
3.5.2 接口
- 接口基础
- 只含方法名、参数、返回值的方法集合,是抽象类型
- 声明
type 接口名 interface{ funcName1( param1 ) paramreturn1 funcName2( param2 ) paramreturn2 … }
- 实现一个接口,只需要用结构实现接口内相应的方法就行,不需要显式声明实现哪个接口
- 案例演示
type Sayer interface { say() } type dog struct{} type cat struct{} func (d dog) say() { fmt.Println("doooog") } func (c *cat) say() { fmt.Println("caaaat") } func main() { var x Sayer d1 := dog{} x = d1 x.say() d2 := &dog{} x = d2 x.say() //(*x).say() d = x or d = *x //c1 := cat{} //x = c1 //error c = c1 //x.say() c2 := &cat{} x = c2 x.say() } var _ Sayer = &cat{} var _ Sayer = dog{} var _ Sayer = &dog{}
doooog doooog caaaat
- 接口嵌套,将各个被包含的的接口函数进行结合,成为一个新的接口
- 空接口 (
any
)- 定义
type 接口名 interface{ }
- 因为里面没有方法,所以可以认为所有结构都已经实现了空接口
- 可以用来处理未知数据
- 使用案例
type inter interface { } func myfunc(args ...inter) { for k, v := range args { fmt.Println(k, " ", v) } } func main() { myfunc("lalala", 1.1, 666, false) }
0 lalala 1 1.1 2 666 3 false
- 空接口一般用更简便的写法:
func myfunc(args ...interface{}) { for k, v := range args { fmt.Println(k, " ", v) } } func main() { myfunc("lalala", 1.1, 666, false) }
- 定义
- 空接口类型
-
类型断言
b := a.(structName)
- 使用案例
func main() { var inter interface{} = "hello" str, ok := inter.(string) if ok { fmt.Println(str) } else { fmt.Println("error") } i, ok := inter.(int) if ok { fmt.Println(i) } else { fmt.Println("error2") } }
hello error2
- 类型断言返回值,可以是一个参数,也可以是两个参数
- 使用一个参数接收返回值,必须思路清晰知道要转成什么,类型不匹配会崩溃
- 使用两个参数接收返回值,必须校验成功与否,否则类型不匹配会崩溃
-
switch
func main() { var inter interface{} = "hello" switch inter.(type) { case int: fmt.Println("is int") case float64: fmt.Println("is float") case string: fmt.Println("is string") } }
is string
-
反射
func main() { var inter interface{} = "hello" reflectType := reflect.TypeOf(inter) //var reflectType reflect.Type reflectValue := reflect.ValueOf(inter) //var reflectValue reflect.Value fmt.Println("type: ", reflectType) fmt.Println("value: ", reflectValue) }
type: string value: hello
import "reflect"
- 自动化把空接口转化成想要的对象
- 注意反射返回值的类型分别是
reflect.Type
和reflect.Value
- 简单使用
type st struct { a int b int } func main() { var inter interface{} = st{} fmt.Println(reflect.TypeOf(inter).Name(), reflect.TypeOf(inter).NumField()) }
st 2
- 有兴趣可以以
TypeOf
、ValueOf
、Type
、Value
为核心进行拓展,深入学习
-
3.5.3 继承
- 单继承
type Father struct { FName string } func (this *Father) Say() string { return "父类 " + this.FName } type Child struct { Father } func main() { c := new(Child) c.FName = "AAA" fmt.Println(c.Say()) }
- 多继承
type Father struct { FName string } func (this *Father) Say() string { return "父类 " + this.FName } type Mother struct { MName string } func (this *Mother) Say() string { return "母类 " + this.MName } type Child struct { Father Mother } func main() { c := new(Child) c.FName = "AAA" c.MName = "BBB" fmt.Println(c.Father.Say()) fmt.Println(c.Mother.Say()) }
- 结构体中字段是接口名
=>type Inter interface { interTest() } func (this *Chlid) interTest() { } type Child struct { Father Mother Inter }
c.Inter = &Child{}
3.6 闭包
- 闭包 = 函数 + 引用环境
- 基础一些 – 匿名函数
func tast() { func() { fmt.Println("里") }() fmt.Println("外") }
- 复杂一些
- 案例1 – 匿名函数做回调
func call(i int, f func(int) int) { fmt.Println("call() i = ", i) res := f(i) fmt.Println("call() over res = ", res) } func main() { call(1, func(i int) int { fmt.Printf("it is a callback func!(i = %v)\n", i) i++ return i }) }
call() i = 1 it is a callback func!(i = 1) call() over res = 2
- 案例2 – 闭包
func add() func(int) int { var x int return func(y int) int { x += y return x } } func main() { var f = add() fmt.Println(f(1)) fmt.Println(f(1)) fmt.Println(f(1)) f = add() fmt.Println(f(1)) fmt.Println(f(1)) fmt.Println(f(1)) }
1 2 3 1 2 3
- 案例1 – 匿名函数做回调
3.7 多线程
3.7.1 多线程原理
- 进程、线程、协程
- 线程和协程区别:调度机制不同,线程需要内核调度,协程由程序调度(GMP)
- G-goroutine,go程
- M-machine,go程调度器
- P-processer,处理器
- 资源消耗:协程比线程更加轻量级
- Golang提倡 ---- “不通过共享内存来交流,要通过交流来共享内存”
3.7.2 chan
管道
-
先进先出、线程安全、引用类型
-
初始化要用
make
- 不带缓冲区:
intChan = make(chan int, 0)
- 写入后阻塞,直到被读取
- 读取后阻塞,直到被写入
- 带缓冲区:
intChan = make(chan int, 3)
- 写入阻塞:缓冲区满
- 读取阻塞:缓冲区没有数据
- 不带缓冲区:
-
遍历时要
close(c)
关闭管道,使管道不再写入,且要用for range
func main() { var intChan chan int intChan = make(chan int, 3) intChan <- 10 intChan <- 20 num := <-intChan fmt.Println("out num:", num) close(intChan) for v := range intChan { fmt.Println("num in chan:", v) } //num2 := <-intChan //fmt.Println("out num2:", num2) --遍历完之后管道数据没了 }
out num: 10 num in chan: 20
-
死锁
deadlock
:由channel引起的程序永久阻塞。fatal error: all goroutines are asleep - deadlock!
- 具体情况在
goroutine
章节后分析
- 具体情况在
-
可以声明为只读或只写,限制操作。
var ch chan<- int
、var ch <-chan int
func chanIn(In chan<- int) { In <- 10 In <- 20 In <- 30 //num := <-In } func chanOut(Out <-chan int) { num := <-Out fmt.Println("out num:", num) } func main() { var intChan chan int intChan = make(chan int, 3) chanIn(intChan) chanOut(intChan) close(intChan) //关闭管道 不能再推入数据 for v := range intChan { fmt.Println("num in chan:", v) } }
out num: 10 num in chan: 20 num in chan: 30
-
select
- 监听和 channel 有关的 IO 操作
- 使用
select { case <- chan1: // 处理语句 case chan2 <- num: // 处理语句 default: // 处理语句 }
- 匹配到
case
执行case
内的语句,否则执行default
内语句 - 没有
default
且没匹配到case
,永久阻塞 - 实际使用一般嵌套
for
循环,结合协程并加上流程控制
- 匹配到
3.7.3 goroutine
-
3.7.1
已经作出铺垫,直接看使用func main() { go say("Hello World") } func say(s string) { fmt.Println(s) }
- 这个例子,不会有输出 ---- 主协程优先于子协程结束
- 这个例子,不会有输出 ---- 主协程优先于子协程结束
-
sync.WaitGroup
import "sync"
- 解决主协程等待子协程问题
- 如果要传递参数要使用
wg *sync.WaitGroup
var wg sync.WaitGroup func main() { wg = sync.WaitGroup{} wg.Add(1) //设置计数 go say("Hello World") wg.Wait() //等待计数变成0 } func say(s string) { fmt.Println(s) wg.Done() //计数减一 }
Hello World
-
案例
func add(ya int, ch chan<- int) { func(y int) { ch <- y }(ya) } func opadd(ch chan<- int) { add(1, ch) time.Sleep(2 * time.Second) add(2, ch) time.Sleep(2 * time.Second) add(3, ch) } func main() { var ch chan int = make(chan int, 5) go opadd(ch) for { time.Sleep(time.Second) select { case v := <-ch: fmt.Println(v) default: fmt.Println("wait") } } }
1 wait 2 wait 3 wait wait wait ...
-
chan
死锁的情况- 无缓存管道
- 自导自演
- 案例
func main() { ch := make(chan int, 0) ch <- 10 num := <- ch fmt.Println(num) }
- 解决方案:读写分离
func in(in chan<- int) { for { time.Sleep(1 * time.Second) in <- 1 } } func out(out <-chan int) { for { time.Sleep(1 * time.Second) num := <-out fmt.Println(num) } } func main() { ch := make(chan int, 0) go in(ch) go out(ch) select {} }
- 案例
- 没有先开读线程,再开写线程
- 案例
func main() { ch := make(chan int, 0) ch <- 10 go func() { <-ch }() }
- 解决方案 ---- 先开读线程,再开写线程
func main() { ch := make(chan int, 0) go func() { <-ch }() ch <- 10 }
- 案例
- 嵌套依赖
- 案例
func main() { chA := make(chan int, 0) chB := make(chan int, 0) go func() { select { case <-chA: chB <- 1 } }() select { case <-chB: chA <- 2 } }
- 案例
- 自导自演
- 有缓冲区的管道越界
func main() { chA := make(chan int, 2) chA <- 10 chA <- 20 chA <- 30 close(chA) for i := 0; i < 3; i++ { <-chA } }
- 无缓存管道
3.8 Context
import "context"
- 一个接口:
context.Context
- 四种实现:
emptyCtv
、cancelCtx
、timerCtx
、valueCtx
- 六个函数:
Background
、TODO
、WithCancel
、WithDeadline
、WithTimout
、WithValue
- 使用案例1:
func myfunc(ctx context.Context) { fmt.Print(ctx.Value("begin")) fmt.Println("你是猪") } func main() { ctx := context.WithValue(context.Background(), "begin", "这部剧里,女主对男主说:") go myfunc(ctx) time.Sleep(time.Second) }
这部剧里,女主对男主说:你是猪
- 使用案例2:
func myfunc2(ctx context.Context) { fmt.Print(ctx.Value("end")) //fmt.Print(ctx.Value("begin")) //也能访问到值的 } func myfunc(ctx context.Context) { fmt.Print(ctx.Value("begin")) fmt.Println("你是猪") go myfunc2(context.WithValue(ctx, "end", "男主很开心")) } func main() { ctx := context.WithValue(context.Background(), "begin", "这部剧里,女主对男主说:") go myfunc(ctx) time.Sleep(time.Second) }
这部剧里,女主对男主说:你是猪 男主很开心
- 大部分业务函数参数需要带上context,并作为第一个参数
3.9 异常处理
- error,定义在errors包中
type error interface { Error() string } func Operate() error{ return errors.New("errrrrrr") } func main(){ err := Operate() if err != nil{} }
- 没有
try-catch
defer
+recover
可以让进入宕机流程中的 goroutine 恢复过来,防止崩溃func tast(x int) { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() var a [5]int a[x] = 666 fmt.Println(a) } func main() { tast(5) fmt.Println("golang not carsh") }
> go run .\main.go runtime error: index out of range [5] with length 5 golang not carsh
panic
自定义错误,终止程序if err != nil{ panic(err) }
> go run .\main.go panic: errrrrrr
3.10 泛型
- golang在1.18版本提出了对泛型的支持
- 用法
func funcName[T int|float64](params ...T)(sum T){ // 代码内容 return xxx }
- 自定义泛型类型
type TType interface { ~int | ~int32 } func funcName[T TType](params ...T)(sum T) { // 代码内容 return xxx }
- 案例
type TType interface { ~int | ~int32 | string } func myfunc[T TType, T2 TType](p1t T, p2t T, p1t2 T2, p2t2 T2) (res1 T, res2 T2) { return p1t + p2t, p1t2 + p2t2 } func main() { v1, v2 := myfunc(1, 2, "aaaa", "bbbbb") fmt.Println(v1, " ", v2) }
- 在函数内运行的T类型,由传入的类型决定,和传入的类型一致
- 和空接口的区别最大还是对类型的处理
- 泛型无法和
switch
一起使用 - 通常情况使用较少,例如通用工具模块