Go
https://www.bilibili.com/video/BV1zR4y1t7Wj?t=9.53
https://www.bilibili.com/video/BV1ee411173m?t=577.1&p=97
Go语言数据类型转换
(biancheng.net)
简介
Go无类和继承的概念 通过接口实现多态性
go是编译型语言 自带编译器
go语言自带垃圾回收
go常见指令:
go run在编译后直接运行程序 不会生成可执行文件
go build编译
包和依赖管理
包
包名可以与目录名不同
一个go程序必须有且仅有一个main包
导入的包不能含有代码中没有用到的包
为了让本包中的函数能够被其他包调用,函数名的首字母需要大写。
值得注意的是,包名和文件夹名不一定相同。在import的地方写的是你的文件名,在调用的时候使用的是包名。
如果要编译成一个可执行程序文件,就需要将这个包声明为main;如果是写一个库,包名随意
标准库:
一般情况下,标准库会存放在 GOOS_$GOARCH/ 目录下。比如在
Windows 下,标准库的位置在Go语言根目录下的子目录 pkg_amd64 中;在 Linux
下,标准库在Go语言根目录下的子目录 pkg_amd64 中(如果是安装的是 32
位,则在 linux_386 目录中)。
GOPATH
GOPATH是最原始的依赖管理方式,具体来说,在go1.1.1后才引入gomodule逐步淘汰gopath
在使用gopath的时候,需要进行如下设置
1 2 set GO111MODULE=offset GOPATH=工程根目录
当在项目中引入了一个无用的包,go会按照如下的顺序寻找:
./vendor
$goroot/src
$gopath/src
为什么要引入vendor?因为在一个工程下可能有不同的项目,而每个项目可能会用到不同的依赖版本,当发生冲突时,就需要在本项目新建vendor文件夹,go也会优先在vendor中寻找依赖。
GOMODULE
类似Java里面的Maven,把所有的第三方库下载在一个统一的文件夹,然后项目去调用即可。
在Go的环境变量中,有一个变量GO111MODULE
负责Go Modules
的开关
auto:若当前项目不在GOPATH
下且当前或上一层文件夹有go.mod
则使用Go Modules
on:开启Go Modules
off:关闭Go Modules
采用Go Modules
后,下载的第三方库位于$GOPATH/pkg/mod
下
常见命令
1 2 3 4 go mod init:初始化go mod, 生成go.mod文件,后可接参数指定 module 名 go mod download:手动触发下载依赖包到本地cache(默认为$GOPATH /pkg/mod目录) go mod graph: 打印项目的模块依赖结构 go mod tidy :添加缺少的包,且删除无用的
为什么要引入go.sum
?因为GO没有中心仓库,而是将依赖托管在各大平台,因此若其他人对依赖进行了更改,但是没有更改版本号,则可能会出现异常。因此,需要将包下载时对应的哈希值进行存储。
官方gosumdb:
1 go env -w GOSUMDB=sum.golang.org
基本语法
变量
变量基本类型:
bool
string
int、int8 int16、int32、int64
uint、uint8、uint16、uint32、uint64、uintptr
byte // uint8 的别名
rune // int32 的别名 代表一个 Unicode 码
float32、float64
complex64、complex128
初始化:变量一旦声明就会全部自动初始化
变量声明:
作用域
局部变量
函数内的局部变量可以和全局变量名称相同,但是会优先考虑局部变量
全局变量
全局变量必须以var关键词开头
如果想在外部包中使用,则首字母必须大写
形式参数
就是定义函数时函数名后面括号中的变量,形式参数会作为函数的局部变量来使用
常量:const
输入输出
二进制%b
,八进制%o
,十六进制%x
字符串:
反引号代表多行
字符串拼接
使用+
使用fmt.Sprintf
使用strings.Join
使用bytes.Buffer
流程控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if a>b { qqq } else { yyy }switch var1 { case v1: xxx case vv2: yyy default : zzz }for i:=0 ,i<10 ,i++ { xxx }var arr = [3 ]int {1 , 2 , 3 }for k, v := range arr { fmt.Println(k, v) }
函数
普通函数
1 2 3 func 函数名(参数) [返回值类型]{ }
例-单个返回值:
1 2 3 4 5 6 7 8 9 10 func main () { fmt.Println(test(1 , 2 )) }func test (a int , b int ) int { if a > b { return a } else { return b } }
例-多个返回值:
1 2 3 4 5 6 7 8 9 10 func main () { fmt.Println(test(1 , 2 )) }func test (a int , b int ) (int , int ) { if a > b { return a, b } else { return b, a } }
方法(定义在struct上的函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" type Person struct { name string age int }func main () { var p Person p.name = "qy" p.test() }func (p Person) test() { fmt.Println(p.name) }
go中的函数特性
init函数:
执行顺序:变量初始化-->init函数-->main函数
没有输入值和返回值
每个包或源文件可以有多个init函数
不同包的init函数按照导入顺序执行
指针:
不能进行偏移和运算
声明:var var_name *int
数组/切片
数组和切片是类似的。数组的长度固定,切片的长度不固定,具有动态长度,可以通过append动态增加切片的长度。
另外,他们的初始化方式也有所不同。
数组
1 2 var arr [3 ]int arr = [3 ]int {1 , 2 , 3 }
切片
1 2 3 var slice []int slice = []int {1 , 2 , 3 } slice = append (slice, 4 , 5 , 6 )
面向对象
Golang是支持面向对象的特性,但实际上不是传统的面向对象,比如没有类的概念。
结构体
go没有面向对象的概念,但是可以用结构体来实现
普通结构体
1 2 3 4 5 6 7 8 9 10 11 type Person struct { age int name string email string }var tom Person tom.name = "qy" tom.age = 19 fmt.Println(tom.name)
匿名结构体
1 2 3 4 5 6 7 8 9 var tom struct { age int name string email string } tom.name = "qy" tom.age = 19 fmt.Println(tom.name)
结构体初始化
1 2 3 4 5 6 7 tom := Person{ age: 0 , name: "qy" , email: "" , } tom := Person{0 , "qy" , "" }
结构体指针
1 2 var p *Person = &tom fmt.Printf("%p\n" , p)
方法
值传递和引用传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type Person struct { name string age int }func (p Person) change1() { p.name = "z1" }func (p *Person) change2() { p.name = "z2" }func main () { var p Person p.name = "l" fmt.Println(p) p.change1() fmt.Println(p) p.change2() fmt.Println(p) }
Golang中的方法都是绑定在指定数据类型上,不一定是struct,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type integer int func (i integer) myPrint() { fmt.Println(i) }func (i *integer) change() { *i = 33 }func main () { var n integer = 3 n.myPrint() }
如果一个类型绑定了String()
方法,则fmt.Println
默认会调用此方法进行输出
方法的访问控制同函数一样,方法首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
继承
要想实现继承,只需要嵌入一个匿名结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type Animal struct { Name string }func (animal Animal) introduce() { fmt.Printf("I'm %s" , animal.Name) }type Cat struct { Animal Miaomiao string }func (cat Cat) shot() { fmt.Println("miao~" ) }func main () { c := Cat{} c.Miaomiao = "miao" c.Name = "sanhua" c.shot() c.introduce() }
错误处理
使用defer+recover机制处理错误
1 2 3 4 5 6 7 8 9 10 11 12 func test () { defer func () { err := recover () if err != nil { fmt.Println(err) } }() a := 1 b := 0 fmt.Println(a / b) }
自定义错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { err := test() if err != nil { fmt.Println(err) } }func test () (err error ) { a := 1 b := 0 if b == 0 { err = errors.New("除数不能为0" ) return } else { fmt.Println(a / b) err = nil return } }
并发
默认情况下,在main线程执行完毕后,所有的协程不管怎样都会终止。
WaitGroup
要想实现等待所有协程结束,可以使用Sleep,或者WaitGroup
1 2 3 4 5 6 7 8 9 10 11 12 func main () { var wg sync.WaitGroup for i := 0 ; i < 5 ; i++ { wg.Add(1 ) go func (n int ) { defer wg.Done() fmt.Println(n) }(i) } wg.Wait() }
锁
互斥锁
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 main () { var lock sync.Mutex var wg sync.WaitGroup var num = 0 for i := 0 ; i < 100000 ; i++ { wg.Add(1 ) go func () { defer wg.Done() lock.Lock() num++ lock.Unlock() }() } for i := 0 ; i < 100000 ; i++ { wg.Add(1 ) go func () { defer wg.Done() lock.Lock() num-- lock.Unlock() }() } wg.Wait() fmt.Println(num) }
读写锁:适用于读次数远高于写次数的场景
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 main () { var wg sync.WaitGroup var lock sync.RWMutex var num = 0 for i := 0 ; i < 1000 ; i++ { wg.Add(1 ) go func () { defer wg.Done() lock.Lock() num++ lock.Unlock() }() } for i := 0 ; i < 1000 ; i++ { wg.Add(1 ) go func () { defer wg.Done() lock.RLock() fmt.Println(num) lock.RUnlock() }() } wg.Wait() fmt.Println(num) }
管道
管道的本质是队列,本身线程安全,不需要加锁
管道是引用类型,必须使用make进行初始化后才能写入数据
管道的示例
1 2 3 4 5 6 7 8 9 10 11 12 func main () { que := make (chan int , 3 ) que <- 1 que <- 2 que <- 3 num := <-que fmt.Println(num) num = <-que fmt.Println(num) num = <-que fmt.Println(num) }
可以使用close关闭管道,但是仍然可以读数据
并发简述
将go
放到被调用的函数前,函数执行时便会作为一个独立的线程
并发通信
通道Channel
一个channel只能传递一种类型的值
域套接字
Unix domain
sockets,用于在同一主机操作系统上执行的进程之间交换数据。
server.go
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 46 package mainimport ( "fmt" "net" "os" )func main () { socketPath := "/var/run/mysocket.sock" os.Remove(socketPath) listener, err := net.Listen("unix" , socketPath) if err != nil { fmt.Println("Error listening:" , err) return } defer listener.Close() fmt.Println("Server listening on" , socketPath) conn, err := listener.Accept() if err != nil { fmt.Println("Error accepting:" , err) return } defer conn.Close() fmt.Println("Server accepted connection" ) buffer := make ([]byte , 1024 ) n, err := conn.Read(buffer) if err != nil { fmt.Println("Error reading:" , err) return } fmt.Println("Received message from client:" , string (buffer[:n])) }
client.go
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 package mainimport ( "fmt" "net" )func main () { socketPath := "/var/run/mysocket.sock" conn, err := net.Dial("unix" , socketPath) if err != nil { fmt.Println("Error connecting:" , err) return } defer conn.Close() fmt.Println("Connected to server" ) message := "Hello, server!" _, err = conn.Write([]byte (message)) if err != nil { fmt.Println("Error sending:" , err) return } fmt.Println("Message sent to server:" , message) }
连接MongoDB
上下文
对mongo
的任何操作都离不开一个上下文环境
contex.background
:创建默认的,无限期的上下文
context.todo
:
select语句类似switch,只能用于通道操作;select语句会监听所有指定的通道的操作,一旦一个通道准备好就会执行响应的代码块
当select为空的时候,有一个作用是可以防止程序在创建完协程后立即退出
defer:常用于资源的释放和异常的捕获,defer后的语句会在return后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 func CopyFile (dstName, srcName string ) (written int64 , err error ) { src, err := os.Open(srcName) if err != nil { return } dst, err := os.Create(dstName) if err != nil { return } dst.Close() src.Close() return }
当有异常,会直接return,此时资源未释放
panic:类似Exception,是用来抛出异常的,recover用来恢复异常,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func main () { f() fmt.Println("Returned normally from f." ) }func f () { defer func () { if r := recover (); r != nil { fmt.Println("Recovered in f" , r) } }() fmt.Println("Calling g." ) g(0 ) fmt.Println("Returned normally from g." ) }func g (i int ) { fmt.Println("Printing in g" , i) panic (i) fmt.Println("After panic in g" , i) }
数组:
1 var balance = [5 ]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
time模块
time.NewTicker:Ticker是周期触发的定时器,可以理解为特殊的通道,每隔一段时间就往这个通道发送时间
context:通过实例理解Go标准库context包
| Tony Bai
结构体中若变量类型是指针或slice或map,必须要用make,不然不会分配空间
管道:v, ok := <-ch中的ok代表是否读到数据
RPC:
通俗理解RPC:客户端可以像调用本地程序一样调用远程服务器的应用程序
bson:
bson.D:bson文档的有序表示
bson.M:bson.M与顺序无关
第三方库
Flag
flag用于解析命令行选项
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 package mainimport ( "flag" "fmt" )var ( age int )func init () { fmt.Print("init" ) flag.IntVar(&age, "a" , 19 , "your age" ) }func main () { fmt.Print("main" ) flag.Parse() fmt.Print(age) }
值得注意的是,flag.Parse
必须在所有选项定义后调用,且flag.Parse
后不能定义新的选项.
Msgp
msgp是Message Pack的缩写,是一种二进制序列化格式。
代理
1 2 3 4 5 go env -w GOPROXY=https://goproxy.cn,direct go env -w GOSUMDB=goproxy.cn/sumdb/sum.golang.org go env -u GOPROXY