go语言学习
go语言介绍
第1周–数据结构
变量
-
var 定义了就必须用
- var name int = 1
- name := 1
-
const 可以不用
- const name int = 1
- const name = 1
-
iota 自动自增
-
一般用在const中
1 2 3 4 5 6 7 8
const( name1 = iota //0 name2 = iota //1 name3 = iota //2 name4 = iota //3 name44 = 5 //5 name5 = iota //5 )
-
-
_ 匿名变量
- 不需要的变量用_接收
-
byte
- 位, uint8, asiic码
-
rune
- 位, int32, 一般表示万国码
-
strconv.Itoa(32) int转字符串
-
float, err := strconv.ParseFloat(“123”,64) 字符串转float64
字符串
-
len(“123你好”) = 9
- 英文字符1个byte, 中文字符3个byte
- 或者用切片转换成rune
-
%T 显示类型, %d输出int,
-
字符串拼接
-
printf(“姓名:” + name) 一般
-
name := sprintf(“姓名: %s”, name) 低性能
-
builder 高性能
1 2 3 4 5 6 7 8
var builder strings.builder builder.WriteString("用户名:") builder.WriteString(name) builder.WriteString("用户名:") builder.WriteString(name) re := builder.String() fmt.Println(re)
-
-
strings包里面的方法
- strings.Contains(name, “go”)
- strings.Count(name, “o”)
- strings.Index(name, “go”)
- 查找go在name中的位置, 是byte的位置
- strings.Replace(name, “go”, “java”,2)替换2次
- strings.ToLower(“go”)
条件判断与循环
-
1 2 3 4 5 6 7 8
for index := range name{ fmt.printf(index) //这是索引 } for index, key := range name{ fmt.printf(key) //这是拷贝来的值 } //如果name里面有中文一般用匿名函数接收, 否则用切片然后用索引找到接收 //不然的话就是乱码, 因为不匹配中文
-
switch
1 2 3 4 5 6
switch name { case "123" : .... case name == "345": ... }
第2周
简单数据结构
-
数组
-
var name [count]string
-
for _, value := range courses1{….}
-
初始化
1 2 3
coures1 := [3]string{"go", "grpc", "gin"} //传统 coures1 := [3]string{2: "gin"} //只赋值最后一个 coures1 := [...]string{"go", "grpc", "gin"} //自动判断
-
多维数组
1
var courseInfo [3][4]string
-
-
切片(怀疑是c++的stl) 不同的是, 切片了一个切片后, 是与原切片指向一个地址
-
var name [ ]int
-
1 2 3 4 5 6 7
append(name, 1) append(name, 2) append(name, 1) //追加 append(name, name1[:]...) //...的作用一般用来追加另一个切片 len(name) //查看有多少个元素 cap(name)//能装多少个元素
-
初始化
- 从数组/切片创建
- course := 数组[0:1] 左闭右开
- 使用string{}
- course := []string{1, 2, 3}
- make
- course := make([]int, 3) 确定大小
- 然后在0-2中就可以用=赋值了
- 从数组/切片创建
-
取值, 和python差不多, course[1:4] course[:4] course[1:]
-
-
map
-
var courseMap = map[string]string{ “go”: “go工程师”, } //初始化
-
var courseMap = make(map[string]string, 3) //初始化
-
courseMap[“mysql”] = “mysql原理” //增加值
-
查看元素
- courseMap[“java”] 看结果
- d, ok := courseMap[“java”]
- 如果有的话ok等于1, 否则为0
-
删除元素
- delete(courseMap, “java”)
-
-
list 需要导入inport “container/list”
- 初始化方式
- var mylist list.List
- mylist := list.New()
- mylist.PushBack(“go”)
- for i := my list.Front(); i != nil; i = i.Next() { i.Value}
- mylist.InsertBefore(mylist, i ) // i是一个索引, 用Front()+1或者+几来的来的
- 初始化方式
-
channel
函数
-
定义
1 2 3 4 5 6 7 8 9 10 11 12 13
func add(a,b int) (sum int, err error){ sum = a + b }//自动返回sum, err func add(a int, b int) (int, error){ return a + b, error }//显式返回 func add(items ...int) (sum int, err error){ for _, value := range items{ sum += value } }//可以多个传入参数
-
闭包
1 2 3 4 5 6 7 8 9
//利用函数可以赋值的特性, 可以实现一个变量可以不用全局变量来递增 func function(a,b int) (func int){ gobl := 0 return func function1() int { return gobl++ } } defin := function() //执行一次gobl=0 defin() //只执行function1()这个函数
-
defer功能 要import"sync"
1 2 3 4 5
mu.Lock() defer mu.Unlock() // defer这个语句会在return之前执行 //defer的执行顺序是栈, 可以看成从后往前执行 //在go语言中, 如果是命名返回值, 会先将返回值赋值给命名返回值, 然后在执行defer语句
-
错误处理, error, panic, recover
-
error
1 2 3
func A()(int, error) { return 0. errors.New("error infor") }
-
panic
1 2 3 4
//panic 会使程序直接退出, 一般不用. func A()(int, error) { panic("this is an panic") return 0. errors.New("error infor")
-
recover 捕捉panic的信号
- recover只有在defer中才会有效
- recover处理异常后, 逻辑不会恢复到panic的那个点去
-
结构体
-
type
1 2
type MyInt = int // 别名, 应该就是宏, 编译的时候会替换 type MyInt int // 自定义类型
-
定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
type Person struct{ name string age int address string height float32 }//定义 p1 := Person{"bobby",18,"网",1.80} p2 := Person{ name : "bobby2", age: 18, } //两种使用方法 address := struct{ province string city string }{ "黑龙江" "绥化" } //匿名结构体, 就使用一次
-
嵌套
- 有名嵌套
- 正常使用
- 匿名嵌套
- 使用的时候会优先使用外面的成员, 如果没有的话, 会展开里面的成员, 然后使用
- 有名嵌套
-
方法
1 2 3 4 5 6 7 8 9 10 11 12
//定义结构体方法 func (p Person) print(){ fmt.Printf(p.name) }//值传递 func (p *Person) print(){ fmt.Printf(p.name) //指针也可以用.来表示, 不需要用->来指向成员 }//指针传递 //结构体本来就是指针, 所以如果你获取到了一个指针的结构体, //那么就可以用这个指针的结构体来用值传递的结构体方法.
指针
-
不能运算, ++ 类似的操作, unsafe包可以运算
-
指针初始化的方式, 这样初始化可以用结构体里面的成员, 因为分配内存空间了
- ps := &Person{}
- var emptyPerson Person pi := &emptyPerson
- var pp = new(Person)
-
slice详解
-
var ps []Person 这个ps就是nil slice
-
var ps2 = make([]Person, 0) 这个ps2就是empty slice
-
slice是一个结构体里面有
- 一个指向数组的指针
- 里面的元素长度
- 最大容量
-
所有就有有上面的现象了
-
map和slice差不多, map中nil不能赋值, make的可以赋值
-
-
slice指针详解, slice本质就是结构体
-
如果不扩容, 传递值或者指针都一样. 就是但本质是指针传递, 性能较好
-
扩容的话, 就是总容量不够装了
-
传递指针
- 就是把结构体的地址传递进去了, 回到主函数, 结构体地址和内部变得结构体一样
-
传递值
- 就是吧结构体里面的值传递进去了, 回到主函数, 结构体地址保持不变, 但函数里面的结构体地址会改变
-
-
接口
鸭子类型, 一般在动态语言中使用. golang中处处都是interface, 所以都是鸭子类型
鸭子类型关心方法, 而不是内部结构
-
定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
type Duck interface { Gaga() Walk() } type pskDuck struct { legs int } //以上是定义 func (pd *pskDuck) Gage() { } func (pd *pskDuck) Walk() { } //以上是实现 var d Duck = &pskDuck{} d.Walk() //以上是实例化与使用
-
语法糖
1 2 3 4 5 6 7 8 9 10 11
func add(a,b interface{}) interface{}{ switch a.(type) { case int: ai, _ := a.(int) bi, _ := b.(int) return ai+bi } } //如果是interface{}类型, 可以用a.() //a.(type)是肯定成功的, 不需要接收 //a.(int)要接收是否成功
-
interface
1
//interface{} 推荐用any来代替
第3周–并发与包管理
包管理
-
一个文件夹中的go文件要package一个名称
-
如果要导入包, 那么必须用
-
如果不用, 要用匿名别名 _
1 2 3 4 5
//特殊函数 func init(){ } //导入包的时候会自动调用init()函数
-
-
如果要用别的包, 要用import
-
import可以用别名, 在包名称前面的名称就是别名
包管理命令
|
|
go代码规范
- 包名
- 尽量和目录保持一致
- 使用有意义的, 简短
- 不要和库名冲突
- 包名全部小写
- 文件名
- 多个单词用蛇形
- 变量名
- 驼峰命名
- 首字母命名 userName -> un string
- 结构体名
- 驼峰
- 接口名
- 和结构体差不多
- 接口用er结尾
- 加一个大写I, 和interface差不多
- 常量命名
- 全部大写
- 有多个单词用蛇行命名法
注释规范
- //
- /* */
import规范
会按照一下顺序来排序
- go自带的包
- 第三方包
- 自己内部包
单元测试
-
go test 命令, 所有以_test.go为后缀的源码文件都会被go test运行到
-
go buld 不会把test文件编译到一起
-
方法
1 2 3 4 5 6 7
func TestAdd(t *testing.T) { if ...{ t.Errorf("expect %d, actual %d"3, re) } } //只有Test后面的名称可以自定义 //go test .
-
如果有些太耗时了, 可以跳过
1 2 3 4 5 6 7 8 9
func TestAdd2(t *testing.T) { if testing.Short(){ t.Skip("short跳过") } if ...{ t.Errorf("expect %d, actual %d"3, re) } } //用go test . -short
-
表格测试, 就是你已经有结果了, 然后让机器算等于与否
-
benchmark
1 2 3 4 5
func BenchMarkAdd(bb *testing.B){ for i := 0; i<bb.N; i++ { } }
并发编程
go 后面接函数就可以了, 就异步运行了
-
原理
- GMP M:N M是thread线程数量, N是g的数量.
- 协程 – P – 线程
- P应该是GMP来处理的
-
子goroutine通知主goroutine
1 2 3 4 5 6
var wg sync.WaitGroup //不用实例 wg.Add(100)//监控多少个goroutine wg.Done() //一个子goroutine完成后, 调用一次 //一般用defer wg.Done() wg.Wait() //阻塞等待
-
同步锁
1 2 3
var lock sync.Mutex //不用实例, 锁不能复制, 就失去锁的作用的 lock.Lock() lock.Unlock()
-
原子包
1 2
atomic.AddInt32(&total,1) //原子操作
-
读写锁
1 2 3 4 5 6 7
var rwlock sync.RWMutex rwlock.Lock //写锁 defer rwlock.Unlock rwlock.RLock //读锁 defer rwlock.Unrlock
-
通信(channel), 消息队列
1 2 3 4 5 6
var msg chan string msg = make(chan string, 0)//无缓冲, 使用时要用另一个协程来消费 msg = make(chan string, 0)//大于0 就是有缓冲 msg <- "bobby" //放值, 这个会阻塞的, 如果空间满了, 或者make(, 0)的时候 data := <- msg //取值
1 2
//无缓冲, 一般用于通知 //有缓冲用于消息队列
- 消息传递
- 信号广播
- 时间订阅
- 任务分发
- 结果汇总
- 并发控制
- 同步与异步
|
|
-
select
1 2 3 4 5 6 7 8 9
select{ case <- g1Channel: xxxxx case <- g2Channel: xxxxx default: xxxx } //select 会阻塞等待至少一个完成, 才会继续往下走
-
context
- 创建
- context.Background() //父亲
- context.TODO()
- 定义
//如果里面穿父亲了, 那么就是父亲, 如果是子的话, 那么父亲cancel()的话, 所有子都会ctx.Done()会有反应
- context.WithTimeout() 不需要用cancel方法, 也就不接受那个cancel
- context.WithCancel() 需要用cancel方法
- context.WithValue() 用ctx.Value(key) 拿到value
- 使用
- 这两个是互相联系的, 一旦cancel()使用了, ctx.Done()这个channel会有信息.
- ctx的使用
- ctx.Done()
- cancel的使用
- cancel()
- 创建
微服务基础
第4周–理解rpc
rpc简介
Remote Procedure Call 远程过程调用
- 传输结构体的话
- 序列化
- 反序列化
|
|
内置rpc使用
- 实例化
- 注册处理逻辑
- 启动服务
|
|
|
|
内置的go的rpc是gob协议的, 不是json
grpc简介
第5周–grpc和protobuf
第6周–yapi文档管理、gorm详解
第7周 –gin快速入门
中间件的加载, 可以用c.netx()来执行函数, 然后会返回, 但return没用, 因为是队列, 一个一个执行的
tmpl 用{{.name}}来显示, router.LoadHTMLGlob(“PATH/*/”) , 可以找到子目录
模版中没有定义define, 就用文件名找
优雅的退出, 就是捕捉信号, 然后处理你想处理的完后退出
第8周–用户服务的grpc服务
自定义用户表结构
md5加密
crypto/md5
md5.sum(切片 )