golang_note

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,

  • 字符串拼接

    1. printf(“姓名:” + name) 一般

    2. name := sprintf(“姓名: %s”, name) 低性能

    3. 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)//能装多少个元素
      
    • 初始化

      1. 从数组/切片创建
        • course := 数组[0:1] 左闭右开
      2. 使用string{}
        • course := []string{1, 2, 3}
      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”

    • 初始化方式
      1. var mylist list.List
      2. 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的信号

      1. recover只有在defer中才会有效
      2. 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. 匿名嵌套
      • 使用的时候会优先使用外面的成员, 如果没有的话, 会展开里面的成员, 然后使用
  • 方法

     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包可以运算

  • 指针初始化的方式, 这样初始化可以用结构体里面的成员, 因为分配内存空间了

    1. ps := &Person{}
    2. var emptyPerson Person pi := &emptyPerson
    3. 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_point
接口

鸭子类型, 一般在动态语言中使用. 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可以用别名, 在包名称前面的名称就是别名

包管理命令
1
2
3
4
5
go get
go mod help
go mod tidy //add missing and remove unused modules
go get -U=patch
go replace //可以把获取的包的地址和获取包的名称用别名替换
go代码规范
  • 包名
    1. 尽量和目录保持一致
    2. 使用有意义的, 简短
    3. 不要和库名冲突
    4. 包名全部小写
  • 文件名
    1. 多个单词用蛇形
  • 变量名
    1. 驼峰命名
    2. 首字母命名 userName -> un string
  • 结构体名
    1. 驼峰
  • 接口名
    1. 和结构体差不多
    2. 接口用er结尾
    3. 加一个大写I, 和interface差不多
  • 常量命名
    1. 全部大写
    2. 有多个单词用蛇行命名法
注释规范
  • //
  • /* */
import规范

会按照一下顺序来排序

  1. go自带的包
  2. 第三方包
  3. 自己内部包
单元测试
  • 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
    
    //无缓冲, 一般用于通知
    //有缓冲用于消息队列
    
    1. 消息传递
    2. 信号广播
    3. 时间订阅
    4. 任务分发
    5. 结果汇总
    6. 并发控制
    7. 同步与异步
1
2
3
4
5
6
7
8
for data := rang msg{

}
//可以用for rang 来用channel取值, 并且还可以关闭消息

var ch1 chan int// 双向channel
var ch2 chan  <- int// 单项channel, 只能放
var ch3 <- chan int// 单向channel, 只能读
  • 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 远程过程调用

  1. 传输结构体的话
  2. 序列化
  3. 反序列化
1
2
json.Marshal()
json.Unmarshal()
内置rpc使用
  1. 实例化
  2. 注册处理逻辑
  3. 启动服务
 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
//service
package main

import (
	"net"
	"net/rpc" //内置rpc包
)

type HelloService struct{}
func (s *HelloService) Hello(request string, reply *string) error {
	*reply = "hello, " + request
	return nil
}

func main() {
	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
		panic("网络错误")
	}

	_ = rpc.RegisterName("HelloService", &HelloService{})

	conn, _ := listener.Accept()
	rpc.ServeConn(conn)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//client
package main
import (
	"fmt"
	"net/rpc"
)
func main() {
	client, err := rpc.Dial("tcp", "localhost:1234")
	if err != nil {
		panic("连接失败")
	}

	var reply *string
	err = client.Call("HelloService.Hello", "bobby", &reply)
	if err != nil {
		panic("call失败")
	}

	fmt.Println(*reply)
}

内置的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(切片 )

第9周–用户服务的web服务

第10周–服务注册发现、配置中心、负载均衡

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计