• Go语言快速入门笔记



    取自B站视频:https://www.bilibili.com/video/BV1gf4y1r79E

    import匿名导包和别名导包的方式

    import (
        _ "GolangStudy/lib1" //匿名导包,这样不适用这个包也不会报错
        mylib1 "GolangStudy/lib1" //别名导包,可以直接用mylib1
        . "GolangStudy/lib1" //直接把这个包导入本地包。比如调用xx()函数就不用lib1.xx()了,可以直接写xx()
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    defer语句

    • 一般用来最后销毁一些东西。多个defer定义的话会压栈,先进后出
    • 如果又有defer又有return语句,那么先return,最后执行defer
    func main(){
    	defer fmt.Println("main1 end.")
        defer fmt.Println("main2 end.")
    	fmt.Println("1 end.")
    	fmt.Println("2 end.")
    }
    //执行结果
    1 end.
    2 end.
    main2 end.
    main1 end.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    数组和动态数组

    固定长度数组

    • go语言中固定长度的数组和切片是两种类型,并不兼容
    • 长度也是固定长度数组类型中的一部分,函数传参时数组长度也要严格匹配
    • go语言中数组作为函数参数是值传递而不是引用传递
    //固定长度数组
    var myArray [10]int
    myArray := [10]int{1,2,3,4} //golang中没有初始化的值默认为0
    fmt.Printf("type: %T", myArray) //输出为:types: [10]int go中定长数组的长度也是类型中的一部分
    func printArray(myArray [10]int){} //这个函数就只能接收长度为10的数组,不能接收其他长度的数组
    func printArray(myArray []int){} //这个函数不能接收myArray,因为定长数组和切片是不同类型
    
    func printArray(myArray [10]int){
        myArray[1] = 24 //这条语句不会影响传进来的原数组的值
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    切片(动态数组)

    • go语言中切片作为函数参数是引用传递而不是值传递(和固定长度数组做区分)
    //切片
    mySlice := []int{1,2,3} //声明并初始化一个长度为3的切片 
    var mySlice []int //声明一个切片,但是并未分配空间
    fmt.Printf("type: %T", mySlice) //输出为:types: []int
    
    • 1
    • 2
    • 3
    • 4
    • 注意slice的空间要分配了才能用,否则会出现越界错误
    var mySlice []int 
    mySlice[0] = 1 //由于前面slice并未被分配空间,因此直接访问下标会出现越界错误
    mySlice = make([]int, 3) //这样就分配了长度为3的空间,默认值为0 
    var mySlice2 = make([]int, 3) //切片的另一种定义方式
    
    • 1
    • 2
    • 3
    • 4
    切片的容量追加和截取
    • 切片追加到超过容量时,容量会扩充为原来的两倍
    • 切片截取的本质是引用传递,修改截取后的切片值会同步修改原切片
    • 可以使用copy函数进行切片拷贝
    var mySlice = make([]int, 3, 5) //定义一个长度为3,容量为5的切片
    mySlice = append(mySlice, 1) //向切片中追加一个元素1,此时长度为4,容量为5
    fmt.Printf("len=%d, cap=%d", len(mySlice), cap(mySlice)) //输出 len=4, cap=5
    mySlice = append(mySlice, 1) //向切片中追加一个元素1,此时长度为5,容量为5
    
    mySlice = append(mySlice, 1) //向切片中追加一个元素1,此时长度为6,容量为10
    
    s1 := mySlice[0:2] //截取前两个元素,左闭右开,引用传递
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    map

    • go中的map是O(1)的无序结构
    • map作为函数参数是引用传递
    var myMap map[string]string //声明map,并未实际分配空间
    myMap = make(map[string]string, 10)  //分配空间,空间长度可省略
    
    • 1
    • 2

    在这里插入图片描述

    面向对象

    struct

    • this有两种,一种是this指针,指向当前对象,第二种是this对象,是当前对象的一个拷贝

    • 方法名或成员名大写,表示其他包可以访问,否则只能在本包访问

      type Test struct{
          a int
          b string
      }
      func (this Test) SetA(c int){
          this.a = c //这时候这个类对象中的a并不会被更改,因为this只是一个对象的拷贝
      }
      func (this *Test) SetA(c int){
          this.a = c //这时这个类对象中的a会被修改为c,因为this是这个对象的指针
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    继承

    type Human struct{
    	name string
    	sex string
    }
    func (this *Human) Eat(){
        //...
    }
    type SuperMan struct{
    	Human //SuperMan类继承了Human类的方法
        level int
    }
    //重定义父类的方法Eat()
    func (this *SuperMan) Eat(){
        fmt.Println("SuperMan.Eat...")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    多态

    • Go语言的类是没有多态的,多态靠接口(interface)实现
    • interface本质上是一个指针,可以指向具体实现的类
    • 只要一个类实现了某接口的全部方法,就认为是实现了该接口
    package main
    
    //接口,本质上是一个指针
    type AnimalIf interface {
    	Sleep()
    	GetColor() string
    	GetType() string
    }
    
    //具体的类
    type Cat struct {
    	color string
    }
    func (this *Cat) Sleep() {
    	fmt.Println("Cat is Sleep")
    }
    func (this *Cat) GetColor() string {
    	return this.color
    }
    func (this *Cat) GetType() string {
    	return "Cat"
    }
    func main() {
    	var animal AnimalIF
    	animal = &Cat{"Green"}
    }
    
    • 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

    interface空接口万能类型与类型断言机制

    • 空接口类型的参数可以接收任何类型,包括基本类型:int、string、float32等
    • 要具体区分传入的参数是什么类型可以通过类型断言
    package main
    
    import "fmt"
    
    func myFunc(arg interface{}) {
    	fmt.Println(("a ...interface{}"))
    
    	value, ok := arg.(string)
    	if !ok {
    		fmt.Println("arg is not string type")
    	} else {
    		fmt.Println("arg is string type, value = ", value)
    	}
    }
    type Book struct {
    	auth string
    }
    func main() {
    	book := Book{"Golang"}
    
    	myFunc(book)
    	myFunc(100)
    	myFunc("abc")
    	myFunc(3.14)
    }
    
    • 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

    变量的内置pair结构

    变量结构

    • 变量内置了一个pair结构,用于存储数据类型和值

    • 变量的结构

      • type指针
        • static type:int、string等基本类型
        • concrete type:interface所指向的具体数据类型,系统看得见的类型(运行时确定的类型)
      • value指针
    • 变量在赋值时,会同时把自己的pair结构赋值过来

    package main
    
    func main(){
        var a string
        //pair
        a = "aceld"
        
        //pair
        vra allType interface{}
        allType = a
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    reflect包(反射)

    • 通过reflect包提供的函数获取变量的类型和值

    • reflect包

      • ValueOf函数
      • TypeOf函数
    func Valueof(i interface{}) Value {...}
    func Typeof(i interface{}) Type {...}
    
    • 1
    • 2
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type User struct {
    	Id   int
    	Name string
    	Age  int
    }
    
    func (this *User) Call() {
    	fmt.Println("user is called ..")
    }
    
    func main() {
    	user := User{1, "Aceld", 18}
    	DoFiledAndMethod(user)
    }
    
    func DoFiledAndMethod(input interface{}) {
    	inputType := reflect.TypeOf(input)
    	fmt.Println("inputType is :", inputType.Name())
    
    	inputValue := reflect.ValueOf(input)
    	fmt.Println("inputValue is : ", inputValue)
    
    	//通过type获取里面的字段
    	//1. 获取interface的reflect.Type,通过Type得到NumField,进行遍历
    	//2. 得到每个field,数据类型
    	//3. 通过field的Interface()方法得到对应的value
    	for i := 0; i < inputType.NumField(); i++ {
    		field := inputType.Field(i)
    		value := inputValue.Field(i).Interface()
    
    		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
    	}
        for i := 0; i < inputType.NumMethod(); i++ {
    		m := inputType.Method(i)
    		fmt.Printf("%s: %v\n", m.Name, m.Type)
    
    	}
    }
    
    • 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

    reflect反射解析结构体标签tag

    • 每个结构体成员可以指定tag
    • tag可以用于json解析
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"reflect"
    )
    type Movie struct {
    	Title  string   `json:"title"`
    	Year   int      `json:"year"`
    	Price  int      `json:"rmb"`
    	Actors []string `json:"actors"`
    }
    
    type resume struct {
    	Name string `info:"name" doc:"我的名字"`
    	Sex  string `info:"sex"`
    }
    
    func findTag(str interface{}) {
    	t := reflect.TypeOf(str).Elem()
    
    	for i := 0; i < t.NumField(); i++ {
    		taginfo := t.Field(i).Tag.Get("info")
    		tagdoc := t.Field(i).Tag.Get("doc")
    		fmt.Println("info: ", taginfo, " doc:", tagdoc)
    	}
    }
    func main() {
    	var re resume
    	findTag(&re)
        
    	movie := Movie{"喜剧之王", 2000, 10, []string{"xingye", "zhangbozhi"}}
    	//编码:将结构体编码为json的过程
    	jsonStr, err := json.Marshal(movie)
    	if err != nil {
    		fmt.Println("json marshal error ", err)
    		return
    	}
    
    	//解码:将json解码为结构体
    	myMovie := Movie{}
    	err = json.Unmarshal(jsonStr, &myMovie)
    	if err != nil {
    		fmt.Println("json unmarshal error ", err)
    		return
    	}
    
    	fmt.Printf("jsonStr = %s\n", jsonStr)
    	fmt.Printf("%v\n", myMovie)
    }
    
    • 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
    • 47
    • 48
    • 49
    • 50
    • 51

    Goroutine基本模型和调度设计策略

    • Go的协程(goroutine)就是用户态线程,M个协程对应N的内核级线程,通过协程调度器来调度
    • 一个goroutine只有几kb,可以大量、灵活切换

    在这里插入图片描述

    • 老版的调度器

      • 创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争
      • M转移G会造成延迟和额外的系统负载(当前G又创建了新的G)
      • 系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9DFs6ft-1669013131369)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121101734855.png)]

    GMP

    • 一个P对应一个真正的内核线程
    • 一个P包含一个本地的goroutine等待运行队列
    • 全局队列存放一些等待运行的goroutine,如果本地队列满了的话就会放到全局队列中
    • 内核级的最大并发数量实际上是GOMAXPROCS

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBZVRq2K-1669013131370)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121101415323.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-poDIFqGA-1669013131371)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121101650404.png)]

    调度器的设计策略

    • 复用线程

      • work stealing机制(工作窃取):当本地队列为空,可以从其他队列(其他本地队列或全局队列)窃取任务到本地队列中

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y7qU4F9u-1669013131372)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121102659710.png)]

      • hand-off机制:当正在执行的G1发生阻塞时,整个线程会被阻塞住。此时,再启动一个内核级线程,将当前本地队列挂到新的内核线程上,当前的G1继续在本地阻塞,执行完如果还要执行就加入其他队列中,否则销毁。

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3vuRRwL-1669013131373)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121103038447.png)]

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRGJB9Mt-1669013131377)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121103110453.png)]

    • 利用并行:通过GOMAXPROCS限定P的个数=CPU核数/2

    • 抢占:一个G最多10ms,时间片结束另一个G可以抢占

    • 全局G队列:

    创建Goroutine

    • main goroutine退出,所有goroutine也会死亡
    go func(){...}
    runtime.Goexit() //该函数用于退出当前goroutine
    
    • 1
    • 2

    channel机制

    channel的定义和使用

    • channel本身是实现了同步互斥的模型机制的(阻塞等待保证同步互斥)

    无缓冲的channel

    • 一次数据的发送和接收过程中两个goroutine都会被锁住,直到完全完成后两个goroutine才会被释放,可以执行其他任务

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ko9cqvNZ-1669013131379)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121113341791.png)]

    package main
    
    import "fmt"
    
    func main() {
    	//定义一个无缓冲channel
    	c := make(chan int)
    
    	go func() {
    		for {
    			defer fmt.Println("goroutine结束")
    			fmt.Println("goroutine正在运行...")
    			//由于是无缓冲channel,所以当c中数据未被消费时,此处会阻塞等待,直到channel为空再放入
    			c <- 666 //将666 发送给c
    		}
    	}()
    	i := 0
    	for {
    		i++
    	}
    	num := <-c //从c中接收数据,并赋值给num。等待是阻塞过程。
    	fmt.Printf("receive num from channel c: %d\n", num)
    	fmt.Println("main go routine 结束...")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    有缓冲的channel

    • 两个goroutine发送和接收是异步的,发送完就可以执行其他任务,接收完也可以去执行其他任务,除非管道满或者空(与生产者-消费者模型类似)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hrauu5w3-1669013131380)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121113748162.png)]

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	//定义一个有缓冲channel
    	c := make(chan int, 3)
    
    	fmt.Println("len(c) = ", len(c), ", cap(c)", cap(c))
    
    	go func() {
    		defer fmt.Println("goroutine结束")
    		for i := 0; i < 3; i++ {
    			c <- i
    			fmt.Println("子go程正在运行,发送的元素=", i, " len(c)=", len(c), ", cap(c)=", cap(c))
    		}
    	}()
    	time.Sleep(2 * time.Second)
    	for i := 0; i < 3; i++ {
    		num := <-c //从c中接收数据
    		fmt.Println("num=", num)
    	}
    	fmt.Println("main go routine 结束...")
    }
    
    • 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

    channel的关闭

    • 如果某goroutine一直等待channel的值,而没有goroutine再给channel写值,那么该goroutine会死锁,报错
    • 向关闭的channel发数据会引发panic
    • 关闭channel后,可以继续从channel接收数据
    • 对于nil channel,无论收发都会被阻塞
    c := make(char int)
    close(c) //关闭一个channel
    if data, ok := <-c; ok {...} //判断channel是否为打开状态,若ok为true表示没有关闭(注意这个不是判断是否为空,而是是否打开)
    
    • 1
    • 2
    • 3

    channel与range

    for data := range c {...}
    
    • 1

    channel与select

    • 单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态
    • select中哪个case先为真就执行哪个,都不为真就执行default
    package main
    
    import "fmt"
    
    func fibonacii(c, quit chan int) {
    	x, y := 1, 1
    	for {
    		select {
    		//如果c可写
    		case c <- x:
    			tmp := y
    			y = x + y
    			x = tmp
    		case <-quit: //如果quit可读
    			fmt.Println("quit")
    			return
    		}
    	}
    }
    func main() {
    	c := make(chan int)
    	quit := make(chan int)
    
    	go func() {
    		for i := 0; i < 6; i++ {
    			fmt.Println(<-c)
    		}
    
    		quit <- 0
    	}()
    
    	fibonacii(c, quit) //channel是引用传递
    }
    
    • 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

    Go Modules

    GOPATH

    • 目录结构

      • bin:一些编译好的二进制文件
      • pkg:一些依赖包之类的
      • src:自己写的go源代码
    • 弊端

      • 没有版本控制概念

        go get -u github.com/xxx //不能拉取指定版本,只拉取最新
        
        • 1
      • 无法同步一致第三方版本号:不同的go项目,引用的相同库的版本无法一致

      • 无法指定当前项目引用的库版本号

    Go Modules模式

    • 建议为了和GOPATH分开,不要将源码创建在GOPATH/src下

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ngnsGGbH-1669013131383)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121135209619.png)]

    • GO111MODULE:该环境变量为Go modules的开关

      • auto:只要项目包含了go.mod文件就启用Go Modules(在Go1.11至Go1.14中仍然是默认值)
      • on:启用
      • off:禁用
      go env -w GO111MODULE=on
      
      • 1
    • GOPROXY:该环境变量用于设置Go模块代理,其作用是用于使Go在后续拉取模块版本时直接通过镜像站点来快速获取(以前是手动下载,现在自动到GOPROXY下载)

      GOPROXY="https//goproxy.cn,direct" #这个direct表示默认去该网址拉取,如果该网址找不到则去包指定网址拉取
      import "githubs.com/xxx.json" #比如这个会先去GOPROXY拉取,否则去github上拉
      
      • 1
      • 2
    • GOSUMDB:它的值是一个Go checksum database,用于在拉取模块版本时保证拉取到的模块版本数据未经过篡改,若发现不一致,也就是可能存在篡改,将会立即中止。你在本地对依赖进行变动(更新/添加)操作时,Go 会自动去这个服务器进行数据校验,保证你下的这个代码库和世界上其他人下的代码库是一样的。和go.mod一样,Go 会帮我们维护一个名为go.sum的文件,它包含了对依赖包进行计算得到的校验值。如果你的代码仓库或者模块是私有的,那么它的校验值不应该出现在互联网的公有数据库里面,但是我们本地编译的时候默认所有的依赖下载都会去尝试做校验,这样不仅会校验失败,更会泄漏一些私有仓库的路径等信息,我们可以使用GONOSUMDB这个环境变量来设置不做校验的代码仓库, 它可以设置多个匹配路径,用逗号相隔。举例:

      GONOSUMDB=*.corp.example.com,rsc.io/private
      
      • 1
    • GOPRIVATE:go 命令会从公共镜像 http://goproxy.io 上下载依赖包,并且会对下载的软件包和代码库进行安全校验,当你的代码库是公开的时候,这些功能都没什么问题。但是如果你的仓库是私有的怎么办呢?

      环境变量 GOPRIVATE 用来控制 go 命令把哪些仓库看做是私有的仓库,这样的话,这些库会从私有仓库地址去拉取,并且跳过 proxy server 和校验检查(设置了GOPRIVATE之后,可以不用再设置GONOSUMDB和GONOPROXY),这个变量的值支持用逗号分隔,可以填写多个值,例如:

      GOPRIVATE=*.corp.example.com,rsc.io/private
      
      • 1

      这样 go 命令会把所有包含这个后缀的软件包,包括 http://git.corp.example.com/xyzzy , http://rsc.io/private, 和 http://rsc.io/private/quux 都以私有仓库来对待。

    Go Modules初始化项目

    开启Go Modules
    go env -w GO111MODULE=on
    或者
    export GO111MODULE=on
    
    • 1
    • 2
    • 3
    项目初始化
    • 注意尽量不要在GOPATH/src创建,否则可能有冲突
    • go get默认下载到$GOPATH/pkg/mod下面
    • go mod init github.com/aceld/modules_test,后面github.com/xxx是给当前项目起的模块名称,也可以不加。此时本目录会多一个go.sum文件。go.sum文件罗列当前项目直接或间接依赖的所有模块的版本,保证今后项目依赖的版本不会被篡改
    • 在拉取依赖包之后,会多一个go.sum文件
    mkdir module_test
    cd module_test
    
    #初始化Go Modules,自动创建go.mod
    go mod init github.com/aceld/modules_test #后面指定模块名称,别人就可以通过这一串来import这个模块
    
    #创建main.go文件,写代码,导入一些包
    go get xxx #下载导入的包,默认下载到$GOPATH/pkg/mod下
    #也可以自动下载
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    改变模块版本依赖关系

    • go.mod中require中指定了包的依赖版本

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUzowbMe-1669013131384)(C:\Users\12610\AppData\Roaming\Typora\typora-user-images\image-20221121143659656.png)]

    • 将xxx版本替换为yyy版本
    go mod edit -replace=xxx=yyy
    
    • 1

    在这里插入图片描述

    `

  • 相关阅读:
    BP神经网络python代码详细解答(来自原文)
    物联网网关可以采集水质传感器哪些数据?
    nohup安装和用法
    DeviceXPlorer OPC Server支持哪些设备?本文已列举出来了
    leetcode刷题日志-151反转字符串中的单词
    【密码加密原则三】
    Re61:读论文 PRP Get an A in Math: Progressive Rectification Prompting
    基于Jaccard相似度的推荐算法---示例
    Linux:生产消费模型 & 读者写者模型
    webgl 系列 —— 着色器语言
  • 原文地址:https://blog.csdn.net/qq_42968686/article/details/127964670