Go语言基础
基础语法
同一文件夹下的文件,包名得一致。
:=
表示声明+赋值,只能用于局部变量,不能用于全局变量。局部变量声明不使用会报错,全局变量声明后可以不使用。
指针和引用例子
1
2
3
4
5
6
7
8var a int = 4
var ptr *int
ptr = &a //指针指向a的实际值的地址
fmt.Printf("a 的值为 %d\n", a);
fmt.Printf("*ptr 为 %d\n", *ptr);
//*ptr和a此时等价了
iota
在 const
关键字出现时将被重置为 0(const
内部的第一行之前)
const
中每新增一行常量声明将使 iota
计数一次(iota
可理解为 const
语句块中的行索引)
1 | func main() { |
…操作符
- 第一个用法主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数。
- 第二个用法是slice可以被打散进行传递。
1 | func test1(args ...string) { //可以接受任意个string参数 |
switch
switch
默认只执行一次,成功就跳出。
1 | var grade string = "B" |
要继续执行下一个case用 fallthrough
,要跳出用 break
。
1 | switch { |
switch 从第一个判断表达式为 true 的 case 开始执行,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true。
for
1 | for i := 0; i <= 10; i++ { |
slice
切片是对数组的抽象。
len()
函数查看当前长度, cap()
函数查看容量。
数组是确定长度的, array := [2]int{1,2}
或者 array := [...]int{1,2}
,
切片可以不确定长度, slice:= []int{1,2}
切片初始化
1 | s :=[] int {1,2,3 } |
追加元素
numbers = append(numbers, 0)
切片扩容
1 | numbers1 := make([]int, len(numbers), (cap(numbers))*2) |
range
关键字
数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
Map
1 | //创建集合 |
string长度
string中有中文不能用 len()
函数来获取长度,因为golang中string底层是通过byte数组实现的。中文字符在utf-8编码下占3个字节。
如果有中文,需要用
1 | b := "hello 你好" |
strings.Count(a,b)
函数记录字符串a中出现了多少次字符串b,""
则代表字符串长度
结构体struct
结构体指针参数
1 | type Books struct { |
函数func
模板
1 | func function_name( [parameter list] ) [return_types] { |
例子
1 | func swap(x, y string) (string, string) { |
接口interface
接口也是 Go 语言中的一种类型,它能够出现在变量的定义、函数的入参和返回值中并对它们进行约束
有两种interface,一种是带有一组方法的接口,另一种是不带任何方法的 interface{}
,Go 语言使用 runtime.iface
表示第一种接口,使用 runtime.eface
表示第二种不包含任何方法的接口 interface{}
interface{}
类型不是任意类型,Go 语言的任意类型都可以转换成 interface{}
。
- 接口是方法的集合,方法作用于变量。
- 一个 struct 实现了某个接口里的所有方法,就叫做这个 struct 实现了该接口。
- 主意区分方法与函数,方法用
.
调用。
1 | /* 定义接口 */ |
一个例子:
项目结构:
1 | test |
结构体的方法实现了接口里的方法,即结构体实现了接口
New()
函数的返回参数是接口var _ Study = (*study)(nil)
要求 *study 去实现 Study,若 Study 接口被更改或未全部实现时,在编译时就会报错type study struct
结构体私有,接口公有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// study.go
package study
import "github.com/pkg/errors"
var _ Study = (*study)(nil)
type Study interface {
Read(msg string) string
Write(msg string) string
}
type study struct {
Name string
}
func (s *study) Read(msg string) string {
return s.Name + " 读 " + msg
}
func (s *study) Write(msg string) string {
return s.Name + " 写 " + msg
}
func New(name string) (Study, error) {
if name == "" {
return nil, errors.New("name required")
}
return &study{
Name: name,
}, nil
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// main.go
package main
import (
"test/study"
"fmt"
)
func main() {
name := "Tom"
s, err := study.New(name) //这里的study是包名,跨包调用函数
if err != nil {
fmt.Println(err)
}
fmt.Println(s.Read("english"))
fmt.Println(s.Write("english"))
}
// 输出
Tom 读 english
Tom 写 english
defer
声明一个延迟函数,该函数会放在一个列表中,在defer语句的外层函数返回之前系统会执行该延迟函数。
defer特点有:
- 函数返回之前执行
- 可以放在函数中任意位置
- 可以同时设置多个defer函数,多个defer函数执行遵循FILO顺序
- defer函数的传入参数在定义时就已经明确
- 可以修改函数中的命名返回值
- 用于文件资源,锁资源、数据库连接等释放和关闭
- 和recover一起处理panic
其他技巧
1 | a := 123 |
并发
go关键字
使用 go
关键字,开启轻量级线程goroutine
例子
1 | package main |
chan关键字
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
1 | //创建通道 |
操作 | nil channel | closed channel | channel |
---|---|---|---|
close | panic | panic | 成功 |
C<- | 阻塞 | panic | 阻塞或成功发送 |
<-C | 阻塞 | 永远不阻塞(读完数据读0值) | 阻塞或成功接收 |
close()
函数表示chan不再能有写入,只能读取数据,所有数据都读完后,再读取不会阻塞,会读零值。for ... range
用在读取chan时,必须先确保chan已经close了,否则会因为阻塞而导致死锁,即不断等待新的chan写入而死锁。或者可以改为for i := 0; i <= 10; i++
这种循环 。只读chan和只写chan
1
2
3ch := make(chan int,10)
var readch <-chan int = ch
var writech chan<- int = ch
使用技巧
- 检查chan是否关闭
1 | v, ok := <-a // 检查是否成功关闭(ok = false:已关闭) |
- 等待一个事件,通过 close 一个 channel。
1 | c := make(chan bool) |
- 搭配select做超时控制
1 | func main() { |
- 心跳
1 | func main() { |
- 多个goroutine同步响应
1 | func main() { |
context包
使用 context
可以更好的做并发控制,能更好的管理 goroutine
滥用。
创建context
context包主要提供了两种方式创建context:
context.Backgroud()
context.TODO()
这两个函数其实只是互为别名,没有差别,官方给的定义是:
context.Background
是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来。context.TODO
应该只在不确定应该使用哪种上下文时使用;
所以在大多数情况下,我们都使用 context.Background
作为起始的上下文向下传递。
上面的两种方式是创建根context,不具备任何功能,具体实践还是要依靠context包提供的四个With系列函数来进行派生
WithValue携带数据
一层一层地传递键值对数据。
1 | func WithValue(parent Context, key, val interface{}) Context |
- 不建议使用
context
值传递关键参数,关键参数应该显示的声明出来,不应该隐式处理,context
中最好是携带签名、trace_id
这类值。 - 在获取键值对时,先从当前
context
中查找,没有找到会在从父context
中查找该键对应的值直到在某个父context
中返回nil
或者查找到对应的值。 - 因为携带
value
也是key
、value
的形式,为了避免context
因多个包同时使用context
而带来冲突,key
建议采用内置类型。
1 | //例子 |
超时控制
当一次请求到达我们设置的超时时间,就会及时取消,不在往下执行。
使用函数 withTimeout
和 withDeadline
. 本质 withTimout
内部也是调用的 WithDeadline
。
会返回一个 cancelFunc
方法,通过调用这个方法可以达到提前进行取消,不过在使用的过程还是建议在自动取消后也调用 cancelFunc
去停止定时减少不必要的资源浪费。
1 | //例子:达到超时时间终止接下来的执行 |
1 | //例子:没有达到超时时间,手动终止接下来的执行 |
WithCancel取消控制
日常业务开发中我们往往为了完成一个复杂的需求会开多个 gouroutine
去做一些事情,这就导致我们会在一次请求中开了多个 goroutine
却无法控制他们,这时我们就可以使用 withCancel
来衍生一个 context
传递到不同的 goroutine
中,当我想让这些 goroutine
停止运行,就可以调用 cancel
来进行取消。
1 | //例子 |
我们使用 withCancel
创建一个基于 Background
的ctx,然后启动一个讲话程序,每隔1s说一话,main
函数在10s后执行 cancel
,那么 speak
检测到取消信号就会退出。
四大方法
Deadlne
方法:当Context
自动取消或者到了取消时间被取消后返回Done
方法:当Context
被取消或者到了deadline
返回一个被关闭的channel
Err
方法:当Context
被取消或者关闭后,返回context
取消的原因Value
方法:获取设置的key
对应的值
sync包
Mutex
1 | mutex := &sync.Mutex{} |
RWMutex
1 | mutex := &sync.RWMutex{} |
加了读锁(RLock)不会阻塞读,但是会阻塞写。
加了写锁(Lock)会阻塞读写。
锁定/解锁速度:RLock(RWMutex)
> Lock(Mutex)
> Lock(RWMutex)
。因此,只有在频繁读取和不频繁写入的场景里,才应该使用 sync.RWMutex
。
WaitGroup
使用场景:一个goroutine等待一组goroutine执行完成。
原理:sync.WaitGroup
拥有一个内部计数器。当计数器等于0时,则 Wait()
方法会立即返回。否则它将阻塞执行 Wait()
方法的goroutine,直到计数器等于0时为止。
增加计数器 Add(int)
,计数器减少1 Done()
。
1 | //一个例子 |
断言与反射
reflect包相关
gRPC
RPC远程过程调用,可以远程调用服务端实现的方法,如同调用本地方法一样,只需传入输入参数和返回参数即可。
一个简单的gRPC客户端和服务端的实现例子
Protocol Buffers
1 | syntax = "proto3"; |
- 建议使用
reserved
关键字来废弃不用字段,以免未来要重新启用
server传输模式
1 | //发请求,回响应 |
详细参考教程
gRPC GateWay
使得gRPC服务在外部可用(通过http请求)
参考教程
等待补充知识点
- 结构体里放接口