本篇主要记录学习Golang过程的笔记
简介 Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。
Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。
Go语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势,目前国内诸多 IT 公司均已采用Go语言开发项目。
Go语言有时候被描述为“C 类似语言”,或者是“21 世纪的C语言”。Go 从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。
因为Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。
此外,很多重要的开源项目都是使用Go语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。
安装 1.下载安装包
https://go.dev/doc/install
2.查看版本
语法 关键字
break
常用于跳出循环,switch
default
配合switch、select
func
定义函数
interface
接口
select
选择流程
case
配合switch
defer
后置处理
go
并行
map
字典
struct
结构体
chan
管道
else
否则
goto
跳到某个代码块
package
包
switch
分支
const
常量
fallthrough
搭配switch
if
判断
range
范围
type
类型声明
continue
结束一次循环
for
循环
import
导包
return
返回
var
定义变量
基本类型 Go语言的基本类型有:
bool
string
int、int8、int16、int32、int64
uint、uint8、uint16、uint32、uint64、uintptr
byte // uint8 的别名
rune // int32 的别名 代表一个 Unicode 码
float32、float64
complex64、complex128(复数)
有符号 | int | 所占用的字节数与运行机器的CPU相关。在32位机器中,大小为4字节;在64位机器中,大小为8字节 | | | int8 | 占用一个字节存储(8位),范围是-128 ~ 127 | | | int16 | 占用两个字节存储(16位),范围是-32768 ~ 32767 | | | int32 | 占用四个字节存储(32位),范围是-2147483648 ~ 2147483647 | | | int64 | 占用八个字节存储(64位),范围是-9223372036854775808 ~ 9223372036854775807 | |
无符号 | uint | 所占用的字节数与运行机器的CPU相关。在32位机器中,大小为4字节;在64位机器中,大小为8字节 | | | uint8 | 占用一个字节存储(即8位),范围是0 ~ 255 | | | uint16 | 占用两个字节存储(16位),范围是0 ~ 65535 | | | uint32 | 占用四个字节存储(32位),范围是0 ~ 4294967295 | | | uint64 | 占用八个字节存储(64位),范围是0 ~ 18446744073709551615 | | | uintptr | 一个无符号整数类型 | | | byte | byte类型是uint8的别名 |
命名规范 在go中是区分大小写的,属性首字母大写表示公开,小写表示私有
变量声明 变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写
1 2 3 4 5 6 7 8 9 10 var a,b = 1 ,2 var ( a int b string c []float32 d func () bool e struct { x int } )
函数声明
1 2 3 4 5 6 7 8 9 func Hello (name string) string { message := fmt.Sprintf("Hi, %v. Welcome!" , name) return message } func moreRet (s string) (string,string) { return s,s }
变量赋值 1 2 3 声明时赋值 a:=100 var a = 100
变量交换
1 2 3 4 var a int = 100 var b int = 200 b, a = a, b fmt.Println(a, b)
匿名变量 在golang中_
表示空白标识符
1 2 3 4 5 6 7 8 9 10 func main () { a,_:=m() fmt.Println(a) _,b:=m() fmt.Println(b) } func m () (int , int ) { return 1 ,2 }
类型转换 1 2 3 4 5 6 7 8 func m () (int , int ) { a:=1000 b:=string(a) fmt.Println(b) c:=int (b[0 ]) fmt.Println(c) return 1 ,2 }
指针类型 1.创建指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { var s = "hello" fmt.Printf("s=%s\n" , s) ptr:=&s fmt.Printf("ptr type=%T\n" , ptr) fmt.Printf("ptr=%p\n" , ptr) val := *ptr fmt.Println(val) ptr2 := new (string) *ptr2 = "hello" fmt.Printf("ptr2 type=%T\n" , ptr2) fmt.Println("ptr2 value=" ,*ptr2) }
逃逸分析 todo
定义常量 1 2 3 4 5 6 7 const name = "mike" name = "hello" const intro string = "ffffff" const ( s = 'a' f float32 = 1 )
类型别名 1 2 3 4 5 6 7 8 9 10 11 12 type Car struct { No string Intro string driver string }type Bus = Car bus := Bus{ No: "粤B" , Intro: "abc" , }
注释使用
godoc 工具
1 go get golang.org/x/tools/cmd/godoc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Usage of [go ] doc: go doc go doc <pkg> go doc <sym>[.<methodOrField>] go doc [<pkg>.]<sym>[.<methodOrField>] go doc [<pkg>.][<sym>.]<methodOrField> go doc <pkg> <sym>[.<methodOrField>] For more information run go help doc Flags: -all show all documentation for package -c symbol matching honors case (paths not affected) -cmd show symbols with package docs even if package is a command -short one-line representation for each symbol -src show source code for symbol -u show unexported symbols as well as exported
依赖管理 go使用的是go mod进行依赖管理
Modules 是相关 Go 包的集合,是源代码交换和版本控制的单元。Go语言命令直接支持使用 Modules,包括记录和解析对其他模块的依赖性,Modules 替换旧的基于 GOPATH 的方法,来指定使用哪些源文件。
开启
1 export GO111MODULE=on 或者 export GO111MODULE=auto
详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Go mod provides access to operations on modules. Note that support for modules is built into all the go commands, not just 'go mod' . For example, day-to-day adding, removing, upgrading, and downgrading of dependencies should be done using 'go get' . See 'go help modules' for an overview of module functionality. Usage: go mod <command> [arguments] The commands are: download download modules to local cache edit edit go.mod from tools or scripts graph print module requirement graph init initialize new module in current directory tidy add missing and remove unused modules vendor make vendored copy of dependencies verify verify dependencies have expected content why explain why packages or modules are needed Use "go help mod <command>" for more information about a command.
分配内存
流程控制 分支语句(if 和 switch)、循环(for)和跳转(goto)语句。另外,还有循环控制语句(break 和 continue),前者的功能是中断循环或者跳出 switch 判断,后者的功能是继续 for 的下一个循环
1 2 3 4 5 6 7 8 9 switch 接口变量.(type ) { case 类型1 : case 类型2 : … default : }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 func main () { expr:=2000 switch expr { case 100 : fmt.Println(1 ) break case 200 : fmt.Println(2 ) break default : fmt.Println("default" ) } }func fore () { for i := 0 ; i < 10 ; i++ { fmt.Println(i) } arr:=[]int {1 ,2 ,3 ,4 } for i, v := range arr { fmt.Println(i,v) } i:=10 for i>0 { fmt.Println(i) i-- if i == 7 { continue } if i == 5 { break } } }func branch () { a := 100 if a == 10 { fmt.Println("case1" ) } else if a == 20 { fmt.Println("case2" ) } else { fmt.Println("case3" ) } }
函数 1.函数定义
1 2 3 func 函数名(形式参数列表) (返回值列表){ 函数体 }
1 2 3 4 5 6 7 8 9 10 11 12 func m () (int , int ) { a:=1000 b:=string (a) fmt.Println(b) c:=int (b[0 ]) fmt.Println(c) return 1 ,2 }func f (i, j, k int , s, t string ) { }func f (i int , j int , k int , s string , t string ) { }
2.函数返回值
3.函数也可以作为参数传递
1 2 3 4 5 6 7 8 9 10 func main () { s := exec(func (s string ) string { return s+" Mike" },"hello" ) fmt.Println(s) }func exec (f func (s string ) string ,s string ) string { return f(s) }
4.带有变量名的返回值
1 2 3 4 5 6 7 8 9 10 func m2 () (a, b int ) { a =1000 b =200 return }func m3 () { a,b := m2() fmt.Println(a,b) }
5.函数变量
1 2 3 4 5 6 7 8 func main () { var f func () f = m f() }func m () { fmt.Println("func" ) }
6.匿名函数
1 2 3 func (参数列表) (返回参数列表){ 函数体 }
1 2 3 4 5 6 func main () { ret:=func () string { return "Hi" }() fmt.Println(ret) }
1 2 3 4 5 6 7 func main () { f := func () string { return "Hi" } ret := f() fmt.Println(ret) }
参数 1.可变参数
1 2 3 4 5 func m (args... int ) { for i, v := range args { fmt.Println(i,v) } }
闭包
函数 + 引用环境 = 闭包 闭包(Closure)在某些编程语言中也被称为 Lambda 表达式。
定义:在函数内部引用了函数内部变量的函数
闭包内修改变量
1 2 3 4 5 6 7 8 9 10 11 12 func main () { str := "hello world" foo := func () { str = "hello golang" } foo() fmt.Println(str) }
闭包的记忆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func main () { accumulator := acc(1 ) fmt.Println(accumulator()) fmt.Println(accumulator()) fmt.Printf("%p\n" , &accumulator) accumulator2 := acc(10 ) fmt.Println(accumulator2()) fmt.Printf("%p\n" , &accumulator2) }func acc (value int ) func () int { return func () int { value++ return value } }
defer其实也是一种闭包
后置 defer关键字作为函数的后置执行,在return之前执行,主要用于释放资源
1 2 3 4 5 6 7 8 9 10 func acc (args... int ) { defer fmt.Println("defer" ) defer func () { fmt.Println("后置函数" ) }() for i, v := range args { fmt.Println(i,v) } }
异常 Error 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func Hello (name string ) (string , error ) { if name == "" { return "" , errors.New("empty name" ) } message := fmt.Sprintf("Hi, %v. Welcome!" , name) return message, nil }func main () { message, err := Hello("" ) if err != nil { log.Fatal(err) } fmt.Println(message) }
自定义错误
1 2 3 4 func main () { e := errors.New("xxxx" ) fmt.Println(e) }
Panic
panic手动触发宕机,让程序崩溃,这样开发者可以及时地发现错误
1 2 3 4 5 6 7 8 func main () { defer fmt.Println("宕机后要做的事情1" ) defer fmt.Println("宕机后要做的事情2" ) var s string if s == "" { panic ("s is nil" ) } }
Recover
让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入错误,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func handlePanic () { if a := recover (); a != nil { fmt.Println("RECOVER" , a) } }func entry (lang *string , name *string ) { defer handlePanic() if lang == nil { panic ("Error: Language cannot be nil" ) } if name == nil { panic ("Error: Author name cannot be nil" ) } fmt.Printf("Author Language: %s \n Author Name: %s\n" , *lang, *name) fmt.Printf("Return successfully from the entry function" ) }func main () { A_lang := "GO Language" entry(&A_lang, nil ) fmt.Printf("Return successfully from the main function" ) }
结构
结构体
定义 1 2 3 4 5 type 类型名 struct { 字段1 字段1 类型 字段2 字段2 类型 … }
案例
1 2 3 4 5 type Person struct { Name string Sex int16 Address,Phone string }
实例化 1.实例化结构体类型
1 2 3 4 5 6 var ins T ins := Struct{ XXX: XXX ... }
案例
1 2 3 4 5 p1 := Person{ Name: "Mike" , Sex: 1 , Address: "Guangdong province" , }
2.创建指针类型
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Person struct { Name string Sex int16 Address,Phone string }func main () { p2 := new (Person) p2.Phone = "122121" p2.Sex = 0 p2.Name = "Lily" fmt.Printf("%p\n" ,p2) fmt.Printf("%T\n" ,p2) fmt.Printf("%v\n" ,*p2) }
3.取结构体地址实例化
1 2 3 4 5 6 7 p3 := &Person{ Name: "Leo" , Phone: "121212" , Sex: 1 , Address: "xxxxx" , } fmt.Println(p3)
构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport "fmt" type Person struct { Name string Sex int16 Address,Phone string }func NewPersonName (name string ) *Person { return &Person{ Name: name, } }func NewPersonNameWithPhone (name ,phone string ) *Person { return &Person{ Name: name, Phone: phone, } }func main () { p1:=NewPersonName("mike" ) fmt.Println(p1) p2:=NewPersonNameWithPhone("Leo" ,"182121221" ) fmt.Println(p2) }
匿名属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Person struct { Name string int16 Address,Phone string bool }func NewPersonNameWithPhone (name ,phone string ) *Person { return &Person{ Name: name, Phone: phone, int16 : 1 , bool :false , } }
容器 数组 1 2 3 var 数组变量名 [元素数量]Typevar arr [10 ]int
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func main () { var arr [10 ]int arr2 := [10 ]int {10 ,9 ,8 ,7 ,6 ,5 ,4 ,3 ,2 ,1 } arr3 := [...]int {1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 } arr4 := []int {5 } arr5 := [][]int {{1 ,2 ,3 },{4 ,5 ,6 }} for i := 0 ; i < 10 ; i++ { arr[i] = i } for i := 0 ; i < 10 ; i++ { fmt.Print(arr[i]) } fmt.Println() for i, v := range arr { fmt.Printf("%d,%d\n" ,i,v) } fmt.Println() for _, v := range arr { fmt.Printf("%d\n" ,v) } fmt.Println(arr == arr2,arr2 == arr3) fmt.Println(arr3 == arr4) }
切片 Go 语言切片是对数组的抽象(基于数组)。 Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { arr := make ([]int ,10 ) fmt.Println(arr) for i := 0 ; i < 10 ; i++ { arr[i] = i } fmt.Println(len (arr)) fmt.Println(arr) array:=[]int {1 ,2 ,3 ,4 ,5 ,6 } fmt.Println(array[1 :4 ]) }
追加切片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { arr := make ([]int ,10 ) fmt.Println(arr) for i := 0 ; i < 15 ; i++ { arr = append (arr,i) arr = append (arr,[]int {1 ,2 ,3 }...) arr = append (arr[:2 ],[]int {1 ,2 ,3 }...) fmt.Printf("cap=%d\n" ,cap (arr)) fmt.Println(arr) } fmt.Println(len (arr)) fmt.Println(arr) }
切片复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { arr := make ([]int ,15 ) cpy := make ([]int ,15 ) fmt.Println(arr) for i := 0 ; i < 15 ; i++ { arr[i] = i } fmt.Println(len (arr)) fmt.Println(arr) copy (cpy,arr) fmt.Println(len (cpy)) fmt.Println(cpy) rge := make ([]int ,15 ) copy (rge[3 :8 ],arr[3 :8 ]) fmt.Println(len (rge)) fmt.Println(rge) }
删除切片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { arr := make ([]int ,15 ) for i := 0 ; i < 15 ; i++ { arr[i] = i } fmt.Printf("原始数据 arr=%v\n" ,arr) arr = arr[3 :] fmt.Printf("头部删除 arr=%v\n" ,arr) arr = arr[:10 ] fmt.Printf("尾部删除 arr=%v\n" ,arr) arr = append (arr[:4 ],arr[6 :]...) fmt.Printf("中间删除 arr=%v\n" ,arr) }
Map 1 var mapname map [keytype]valuetype
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func main () { var m map [string ]string m = map [string ]string {} for i := 97 ; i < 110 ; i++ { m["name" +string (i)] = string (i) } fmt.Println(len (m)) fmt.Println(m) m2 :=make (map [string ]string ) m3 :=make (map [string ]string ,100 ) for i := 97 ; i < 110 ; i++ { m2["name" +string (i)] = string (i) } fmt.Println(len (m2)) fmt.Println(m2) fmt.Println(m3) for k, v := range m { fmt.Println(k,v) } }
判断是否存在
1 2 3 4 5 6 7 8 9 10 func main () { m := make(map[string]string) m["one" ] = "one" fmt.Println(m) if _,ok := m["one" ];ok { fmt.Println("have" ) } else { fmt.Println("no have" ) } }
删除
并发map sync.Map
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { var cm sync.Map cm.Store("a" , 100 ) cm.Store(200 , "100" ) fmt.Println(cm) fmt.Println(cm.Load("a" )) cm.Delete(200 ) fmt.Println(cm) cm.Range(func (key, value any) bool { fmt.Println(key,value) return true }) }
列表 1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { list := list.New() fmt.Println(list) list.PushFront("hello" ) list.PushFront("Hi" ) fmt.Println(list.Len()) fmt.Println(list) fmt.Println(list.Front()) for i := list.Front(); i != nil ; i = i.Next() { fmt.Println(i.Value) } }
排序
golang 的sort包下提供一些排序的api提供给开发者使用
1.简单排序
1 2 3 4 5 6 func main () { arr:=[]string {"a" ,"d" ,"f" ,"b" ,"z" ,"K" ,"e" } fmt.Println(arr) sort.Strings(arr) fmt.Println(arr) }
2.自定义排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 type Interface interface { Len() int Less(i, j int ) bool Swap(i, j int ) }
案例
包 在golang中的所有源文件都必须有所归属的包
概念 Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰。
定义
只有在main包下的源文件才能独立运行
导包 1 2 3 4 5 6 7 8 9 10 import "包 1 的路径" import "包 2 的路径" import ( "包 1 的路径" "包 2 的路径" )import _ "fmt"
工具 go mod
govendor
已废弃
地址:github.com/kardianos/govendor
常用 1) fmt fmt 包实现了格式化的标准输入输出,这与C语言中的 printf 和 scanf 类似。其中的 fmt.Printf() 和 fmt.Println() 是开发者使用最为频繁的函数。
格式化短语派生于C语言,一些短语(%- 序列)是这样使用:
%v:默认格式的值。当打印结构时,加号(%+v)会增加字段名;
%#v:Go样式的值表达;
%T:带有类型的 Go 样式的值表达。
1 2 3 var input string fmt.Scanln(&input)
2) io 这个包提供了原始的 I/O 操作界面。它主要的任务是对 os 包这样的原始的 I/O 进行封装,增加一些其他相关,使其具有抽象功能用在公共的接口上。
3) bufio bufio 包通过对 io 包的封装,提供了数据缓冲功能,能够一定程度减少大块数据读写带来的开销。
在 bufio 各个组件内部都维护了一个缓冲区,数据读写操作都直接通过缓存区进行。当发起一次读写操作时,会首先尝试从缓冲区获取数据,只有当缓冲区没有数据时,才会从数据源获取数据更新缓冲。
4) sort sort 包提供了用于对切片和用户定义的集合进行排序的功能。
5) strconv strconv 包提供了将字符串转换成基本数据类型,或者从基本数据类型转换为字符串的功能。
6) os os 包提供了不依赖平台的操作系统函数接口,设计像 Unix 风格,但错误处理是 go 风格,当 os 包使用时,如果失败后返回错误类型而不是错误数量。
7) sync sync 包实现多线程中锁机制以及其他同步互斥机制。
8) flag flag 包提供命令行参数的规则定义和传入参数解析的功能。绝大部分的命令行程序都需要用到这个包。
9) encoding/json JSON 目前广泛用做网络程序中的通信格式。encoding/json 包提供了对 JSON 的基本支持,比如从一个对象序列化为 JSON 字符串,或者从 JSON 字符串反序列化出一个具体的对象等。
10) html/template 主要实现了 web 开发中生成 html 的 template 的一些函数。
11) net/http net/http 包提供 HTTP 相关服务,主要包括 http 请求、响应和 URL 的解析,以及基本的 http 客户端和扩展的 http 服务。
通过 net/http 包,只需要数行代码,即可实现一个爬虫或者一个 Web 服务器,这在传统语言中是无法想象的。
12) reflect reflect 包实现了运行时反射,允许程序通过抽象类型操作对象。通常用于处理静态类型 interface{} 的值,并且通过 Typeof 解析出其动态类型信息,通常会返回一个有接口类型 Type 的对象。
13) os/exec os/exec 包提供了执行自定义 linux 命令的相关实现。
14) strings strings 包主要是处理字符串的一些函数集合,包括合并、查找、分割、比较、后缀检查、索引、大小写处理等等。
strings 包与 bytes 包的函数接口功能基本一致。
15) regexp regexp包为正则表达式提供了官方支持,其采用 RE2 语法,除了\c、\C外,Go语言和 Perl、Python 等语言的正则基本一致。
16) bytes bytes 包提供了对字节切片进行读写操作的一系列函数。字节切片处理的函数比较多,分为基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数和子切片处理函数等。
17) log log 包主要用于在程序中输出日志。
log 包中提供了三类日志输出接口,Print、Fatal 和 Panic。
Print 是普通输出;
Fatal 是在执行完 Print 后,执行 os.Exit(1);
Panic 是在执行完 Print 后调用 panic() 方法。
18) time time包为时间相关api包
1 2 3 4 5 6 7 8 9 10 11 func main () { now := time.Now() fmt.Printf("current time:%v\n" , now) year := now.Year() month := now.Month() day := now.Day() hour := now.Hour() minute := now.Minute() second := now.Second() fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n" , year, month, day, hour, minute, second) }
19) context 接口 定义 1 2 3 4 5 type 接口类型名 interface { 方法名1 ( 参数列表1 ) 返回值列表1 方法名2 ( 参数列表2 ) 返回值列表2 … }
实例
1 2 3 4 type Animal interface { sleep(t int ) bool walk(d int ) bool }
实现 1 2 3 func (实现类) 方法名(参数) 返回类型 { }
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport "fmt" type Animal interface { sleep(t int ) bool walk(d int ) bool }type Dog struct { Name string Age int }func (d Dog) sleep(t int ) bool { fmt.Println("the dog sleep time = " ,t) return true }func (d Dog) walk(t int ) bool { fmt.Println("the dog walk time = " ,t) return true }func main () { var a1 Animal a1 = Dog{ Name: "labuladuo" , Age: 1 , } a1.sleep(1 ) a1.walk(1 ) fmt.Println(a1) }
类型转换
Go语言中使用接口断言(type assertions)将接口转换成另外一个接口,也可以将接口转换为另外的类型
1 2 t := i.(T) t,ok := i.(T)
其中,i 代表接口变量,T 代表转换的目标类型,t 代表转换后的变量。
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Car interface { driver() bool }type Animal interface { sleep(t int ) bool walk(d int ) bool }type Dog struct { Name string Age int }func main () { var dog Animal dog=Dog{Name: "labuladuo" } t,ok := dog.(Car) if ok { fmt.Println("转换成功" ,t) } else { fmt.Println("类型不匹配" ) } }
关于接口的实现
如果定义的是 (Type)Method,则该类型会隐式的声明一个 (*Type)Method;
如果定义的是 (*Type)Method ,则不会隐式什么一个 (Type)Method。
如果接收者是指针类型,在函数内修改接收者是直接修改原始的变量的,如果是结构体类型因为传递的值拷贝,修改不会影响原始对象。
并发 golang
协程 goroutine 是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务,它是Go语言并发设计的核心。
开启协程
案例
Channel Channel 是 Golang 在语言级别提供的 goroutine 之间的通信方式,可以使用 channel 在两个或多个 goroutine 之间传递消息。Channel 是进程内的通信方式,因此通过 channel 传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。使用通道发送和接收所需的共享资源,可以在 goroutine 之间消除竞争条件。 当一个资源需要在 goroutine 之间共享时,channel 在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。Channel 是类型相关的,也就是说,一个 channel 只能传递一种类型的值,这个类型需要在声明 channel 时指定。可以通过 channel 共享内置类型、命名类型、结构类型和引用类型的值或者指针。
结构
定义 1 2 3 4 5 6 7 8 9 10 11 var ChannelName chan ElementType ChannelName = make (chan ElementType,size) ChannelName := make (chan ElementType,size) ChannelName<-ElementType ret:=<-ChannelNamevar 通道实例 chan <- 元素类型 var 通道实例 <-chan 元素类型
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { fmt.Println(context.Background()) ch := make (chan int ,3 ) go func () { for i := 0 ; i < 10 ; i++ { ch <- i time.Sleep(1 *time.Second) } }() go func () { for true { v:=<-ch fmt.Println(v) time.Sleep(1 *time.Second) } }() time.Sleep(10 *time.Second) }
操作 关闭channel
1 2 3 4 5 6 7 8 func main () { ch := make (chan int ) close (ch) _,ok := <-ch if !ok { fmt.Println("channel closed" ) } }
buffered channel Go语言中有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
注意:如果在main线程中直接对无缓冲区chan操作时,会产生deadlock
1 通道实例 := make (chan 通道类型, 缓冲大小)
案例
1 ch := make (chan int ,100 )
unbuffered channel 无缓冲区是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。
遍历chan
1 2 3 4 5 6 7 go func () { ch := make (chan int , 3 ) for { fmt.Println(<-ch) } }()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 go func () { ch := make (chan int , 3 ) ch <- 1 ch <- 2 ch <- 3 cnt := 1 for v := range ch { fmt.Println(v) cnt++ if cnt == 3 { close (ch) } } }()
源码 1 2 3 4 5 6 7 8 9 10 11 12 13 type hchan struct { qcount uint dataqsiz uint buf unsafe.Pointer elemsize uint16 closed uint32 elemtype *_type sendx uint recvx uint recvq waitq sendq waitq lock mutex }
Context
又称上下文,golang 的 Context 包,是专门用来简化多个goroutine 之间传递数据、超时时间(deadline)、取消信号(cancellation signals)或者其他请求相关的信息
比如有一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine。这样的话, 我们就可以通过Context,来跟踪这些goroutine,并且通过Context来控制他们的目的
1 2 3 4 5 6 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key any) any }
Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。
Done方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。之后,Err 方法会返回一个错误,告知为什么 Context 被取消。
Err方法返回取消的错误原因,因为什么Context被取消。
Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的。
通过根Context派生出子Context
1 2 3 4 func WithCancel (parent Context) (ctx Context, cancel CancelFunc)func WithDeadline (parent Context, d time.Time) (Context, CancelFunc) func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc)func WithValue (parent Context, key, val any) Context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func contextCancel () { ctx := context.Background() childCtx, CancelFunc := context.WithCancel(ctx) go func (ctx context.Context) { for { select { case <-childCtx.Done(): fmt.Println("触发Done" ) return } } }(childCtx) time.Sleep(time.Second) fmt.Println("主函数运行" ) CancelFunc() fmt.Println("主函数完成" ) time.Sleep(5 * time.Second) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func contextDeadline () { ctx := context.Background() childCtx, CancelFunc := context.WithDeadline(ctx, time.Now().Add(3 *time.Second)) go func (ctx context.Context) { for { select { case <-childCtx.Done(): fmt.Println("触发Done" ) return } } }(childCtx) time.Sleep(time.Second) fmt.Println("主函数运行" ) CancelFunc() fmt.Println("主函数完成" ) time.Sleep(5 *time.Second) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func contextTimeout () { ctx := context.Background() childCtx, _ := context.WithTimeout(ctx, 3 *time.Second) go func (ctx context.Context) { for { select { case <-childCtx.Done(): fmt.Println("触发Done" ) return } } }(childCtx) time.Sleep(time.Second) fmt.Println("主函数运行" ) fmt.Println("主函数完成" ) time.Sleep(5 * time.Second) }
1 2 3 4 5 6 7 8 9 10 func contextValue () { ctx := context.Background() childCtx := context.WithValue(ctx, "param" , "Hello world" ) go func (ctx context.Context) { fmt.Println(ctx.Value("param" )) }(childCtx) time.Sleep(time.Second) fmt.Println("主函数完成" ) time.Sleep(5 * time.Second) }
Select
select是go提供类似io多路复用的功能,有的类似Switch语句
特点
监听的case中,没有满足条件的就阻塞
多个满足条件的就任选一个执行
select本身不带循环,需要外层的for
default通常不用,会产生忙轮询
break只能跳出select中的一个case1 2 3 4 5 6 7 8 9 select {case <- chan1:case chan2 <- 1 :default : }
简单案例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func main9 () { ch := make (chan int ) go func () { for { select { case v := <-ch: fmt.Println("向ch读取数据:" , v) case ch <- 0 : fmt.Println("向ch写入数据" ) } } }() go func () { for i := 0 ; i < 10 ; i++ { ch <- i fmt.Println("W" ) time.Sleep(time.Second * 2 ) } }() go func () { for i := 0 ; i < 10 ; i++ { <-ch time.Sleep(time.Second * 2 ) } }() time.Sleep(100 * time.Second) }
应用场景
锁 Lock
sync包下提供多种锁支持