likes
comments
collection
share

go 语言基础设置环境变量: go.mod 和 go.sum go.mod 和 go.sum 的区别 go.mod:只记

作者站长头像
站长
· 阅读数 29

设置环境变量:

export GOPROXY=https://goproxy.cn

go env -w GOPROXY=https://goproxy.cn

go.mod 和 go.sum

go.modgo.sum 的区别

  • go.mod:只记录依赖包不含 go.mod 的间接依赖包
  • go.sum:记录所有的依赖包

go.mod 文件中,第一个 require 是直接依赖,第二个 require 是间接依赖

require 中后面的版本号是伪版本号,伪版本号分为三个部分:

  • v1.2.0:基础版本号
  • 20240408115609:提交时间戳(UTC 时间戳)
  • 8224c482b07e:提交的 commit id

go.sum 文件中,一般一个依赖会有两个,一个是有 go.mod 一个是没有 go.mod

github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= # 没有 go.mod
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= # 有 go.mod

go.sum 是用来做包校验的,在本地进行构建时,go 会从本地缓存中去查找 go.mod 中记录的包,并且计算出本地依赖包的哈希值,再去和 go.sum 文件中记录的 hash 值进行对比,如果 hash 值不一样,说明本地包的版本与 go.sum 中记录的版本是不一致的,构建就会被拒绝,从而保证在任何环境中构建的代码都是一致的

go.sum 中额外记录 go.mod 哈希值,是为了计算依赖树时不用下载完整的依赖包,只根据 go.mod 就可以计算出哈希

go.mod 哈希值是有算法 SHA-256 计算出来的

GOPROXY

GOPROXYgo 用来下载依赖的地址

当我们设置了这个 GOPROXY 环境变量之后,go 会根据 GOPROXY 设置的值去下载去拉取依赖包,GOPROXU 是在 go 1.11 版本开始支持的

GOPROXY='https://proxy.golang.org,direct'

GOPROXY 后面 direct 的意思是:如从前面的地址上拉不下来包,会去 go mod 中的地址去拉取

如果 GOPROXY 设置为 off,则不允许从任何源去拉取包

七牛云镜像:https://goproxy.cn

GOPRIVATE

GOPRIVATE 作用是不走代理,也不进行 go modules 校验,从 go 1.13 版本开始支持

GOPRIVATE 设置为 *.example.com 时,凡是通过 example.com 域名拉取的包都不会走 GOPROXY 代理

GONOPROXY 也是从 go 1.13 版本开始的,作用和 GOPRIVATE 相似,如果配置了域名,通过这些域名拉取的包不会走代理

在理解 GOPRIVATEGONOPROXY 区别之前,要先知道 GONOSUMDB 的作用

GONOUMDB 是用来设置不走 go modules 校验的,所以

GOPROXY='https://goproxy.cn'
GOPRIVATE='*.example.com'
# 等价于
GOPROXY='https://goproxy.cn'
GONOPROXY='*.example.com'
GONOSUMDB=$GONOPROXY

init 函数

init 执行的顺序

  • 在同一个 go 文件中,多个 init 方法,按照代码顺序依此执行
  • 同一个包中,按照文件名的属性执行(ascii 码顺序)
  • 不同包,且不相互依赖,按照 import 顺序执行
  • 不同包,且相互依赖,最后被依赖的最先被执行
    // main.go
    import (
      "pkg1"
      "pkg2"
    )
    // pkg2
    import (
      "pkg3"
    )
    // 执行顺序
    // pkg1 中的 init 先执行
    // pkg3 中的 init
    // pkg2 中的 init
    

包中初始化顺序

  • 引入的包,包中的顺序按照下面的顺序执行
  • 当前包中的常量
  • 当前包的 init
  • 执行 main 函数

一个包被引用多次,这个包的 init 函数只会执行一次,所有的 init 函数都在同一个 goroutine 内执行

获取项目的根目录

filepath.Abs(".") 获取的路径是执行命令的路径

func main() {
  path, _ := filepath.Abs(".")
  fmt.Println(path)
}

其他方式获取:

  • os.Getwd() 获取的是执行命令的路径
    func GetWorkPath() {
      // 获取当前执行命令的路径
      path, _ := os.Getwd()
      fmt.Println(path)  // /root/uccs/ls/a -> 文件所在的目录
    }
    
  • os.Args[0]
    • go run . 获取的是临时文件的路径
    • go build . && ./xxx 获取的是当前执行命令的路径
      func GetWorkPathByArg() {
        // 获取的是当前程序名
        fmt.Println(os.Args[0])  // ./a -> 文件名
        // 从相对路径或者环境变量 PATH 中递归找可执行文件,可以用来校验可执行文件是否存在
        filePath, _ := exec.LookPath(os.Args[0])
        fmt.Println(filePath)   // ./a -> 文件名
        absFilePath, _ := filepath.Abs(filePath)
        fmt.Println(absFilePath)   // /root/uccs/fd/a/a -> 文件的绝对路径
         // 获取当前执行命令的路径
        rootDir, _ := filepath.Abs(path.Dir(absFilePath))
        fmt.Println(rootDir)   // /root/uccs/fd/a -> 文件所在的目录
      }
      
  • os.Executable() 获取的是执行命令的路径(go run . 获取的是临时文件的目录)
    func GetWorkPathByExec() {
      execPath, _ := os.Executable()
      fmt.Println(execPath) // /root/uccs/fd/a/a -> 文件的绝对路径
      rootDir := filepath.Dir(execPath)
      fmt.Println(rootDir)  // /root/uccs/fd/a  -> 文件所在的目录
      // 将软连接转换为真实路径
      rootPath, _ := filepath.EvalSymlinks(rootDir)
      fmt.Println(rootPath)  // /root/uccs/fd/a  -> 文件所在的目录
    }
    
  • runtime.Caller(0) 获取文件的路径绝对路径,和命令在哪里执行无关
    func GetWorkPathByCaller() {
      _, file, _, _ := runtime.Caller(0)
      fmt.Println(file)  // /root/uccs/fd/a/a.go -> 文件的绝对路径
      rootDir := path.Dir(path.Dir(file))
      fmt.Println(rootDir)  // /root/uccs/fd -> 文件所在的根目录
    }
    
  • 通过设置环境变量,自定义环境变量或者 HOME/xxx

获取标准输入

通过命令注入,需要在文件中定义好全局变量,然后通过 go run 命令注入

go run -ldflags "-X 'main.AppName=uccs' -X 'main.AppVersion=1'" .
go build -ldflags "-X 'main.AppName=uccs' -X 'main.AppVersion=1'" .

通过 bufio.NewReader 获取标准输入

func main() {
  reader := bufio.NewReader(os.Stdin)
  text, _ := reader.ReadString('\n')
  fmt.Println(text)
}

通过 fmt.Scanf 获取标准输入

func main() {
  fmt.Scanf("%s %s", &AppName, &AppVersion)
}

new 和 make 的区别

make 不仅分配内存,还会初始化,new 分配的内存会被零值填充

make 只适用于 slicemapchannel 的初始化,返回的是这三个引用类型的实例,new 适用于值类型的初始化,返回的是指针

数值运算

数值类型的字面量在不需要四舍五入时,可以用来表示一个整数基本类型的值

var a int = 1.00
fmt.Println(reflect.TypeOf(a))  // int

如果声明变量时没有指定类型,go 会根据字面量自动推到它的类型

var b = 1.00
fmt.Println(reflect.TypeOf(b))  // float64

fmt.Println(strconv.IntSize) 可以判断当前系统是 32 位还是 64

fmt.Println(strconv.IntSize)  // 32

计算中文的 len

go 中直接使用 len 获取中文的长度是有问题的,因为在 go 中一个汉字占 3 个字节,所以计算出来的长度会是 3

str := "你好a"
fmt.Println(len(str))  // 7

3 种方法获取中文字符的长度

方法一:

str := "你好a"
runeLength := utf8.RuneCountInString(str)
fmt.Println(runeLength) // 3

方法二:

str := "你好a"
runeSlice := []rune(str)
fmt.Println(len(runeSlice)) // 3

方法三:

str := "你好a"
count := 0
for range str {
  count++
}
fmt.Println(count) // 3

指针的限制

  1. 指针不支持直接进行算术运算,如果要进行算术运算需要先解引用
    func main() {
      i := int64(5)
      p := &i
      *p++ // *p 获取 p 中的内容
      fmt.Println(*p)
    }
    
  2. 一个指针类型的值不能随意转化为另一个指针类型(除非满足下面一个)
    • 底层类型一致且有一个是无名类型
    • 都是无名类型,且他们的基类型的底层类型一致
      • 不属于无名类型
        • 预声明类型
        • 自定义类型
        • 泛型的实例化类型
        • 用在自定义泛型中的类型参数类型
          type bl = *bool
          type m = map[int]int
          var s string // 具名非指针类型
          var e error  // 具名接口类型
          var m1 m     // 无名类型的别名
          var b b1     // 无名指针类型的别名
          var iptr *int // 无名指针类型
          
  3. 一个指针值不能随意跟其他指针类型的值进行比较(除非满足下面一个)
    • 这两个指针类型相同
    • 这两个指针之间可以隐式转换
  4. 一个指针值不能随意被赋值给其他任意类型的指针值,除非这两个指针可以被比较

上述限制可以通过 unsafe 标准库包中的非类型安全指针 unsafe.Pointer 来打破

map

浮点数作为 mapkeygo 内部会做一些处理

a := map[float64]int{}
a[0.1] = 1
a[0.3] = 2
a[0.30000000000000000001] = 3
fmt.Println(a)  // map[0.1:1 0.3:3]

go 底层调用 math.Float64bits 方法,将 key 转换为 float64 类型的二进制位

fmt.Println(math.Float64bits(0.3) == math.Float64bits(0.30000000000000000001))  // true

如何使得一个结构体不能被比较

怎么更优雅的让一个结构体不可被比较?

给结构体一个非导出的零尺寸的不可比较类型的字段

type Person1 struct {
  Name string
  age  int
}
type Person2 struct {
  Name string
}
type Person3 struct {
	uncompare [0]func() // 不能放在结构体的最后
	Name      string
}
func main() {
  p1 := Person1{Name: "uccs"}
  p2 := Person2{Name: "uccs"}
  p3 := Person3{Name: "uccs"}
  fmt.Println("p1 size: ", unsafe.Sizeof(p1)) // 24
  fmt.Println("p2 size: ", unsafe.Sizeof(p2)) // 16
  fmt.Println("p3 size: ", unsafe.Sizeof(p3)) // 16
}

结构体的比较:

  1. 他们的子弹只能包含可比较类型
  2. 这两个结构体需要能隐式转换

p1p4 是不同的类型,所以不能比较

type Person1 struct {
  age  int
  Name string
}

type Person4 struct {
  age  int
  Name string
}

func main() {
  p1 := Person1{Name: "uccs"}
  p4 := Person4{Name: "uccs"}
  fmt.Println(p1 == p4) // 报错
}

如果需要比较两个结构体,应该使用匿名结构体

p1 := struct {
  age  int
  Name string
}{Name: "uccs"}
p4 := struct {
  age  int
  Name string
}{Name: "uccs"}

fmt.Println(p1 == p4) // true

比较

值为 [] 的两个 byte 切片,不能用 reflect.DeepEqual 进行比较,应该使用 byte.Equal 进行比较

var b1 []byte
b2 := []byte{}
fmt.Println(b1, b2) // [] []
fmt.Println(reflect.DeepEqual(b1, b2)) // false
fmt.Println(bytes.Equal(b1, b2)) // true

如果表达式 xy 的类型不相同,则函数调用 DeepEqual(x, y) 的结果总是 false,但 x == y 的结果可能为 true

type Person struct{ Name string }
p1 := Person{Name: "John"}
p2 := struct{ Name string }{Name: "John"}
fmt.Println(p1 == p2)
fmt.Println(reflect.DeepEqual(p1, p2))

如果同类型的 xy 是两个引用着不同其他值的指针值,则 x == y 的估值结果总为 false,但函数调用 reflect.DeepEqual(x, y) 的结果可能为 true

x := new(int)
y := new(int)
fmt.Println(x == y)      // false
reflect.DeepEqual(x, y)  // true

判断结构体中是否拥有某个方法

有两种方式判断一个结构体中是否拥有某个方法

type Person struct {
  Name string
}
func (p Person) Say(str string) string {
  return str
}

方法一:

func HasMethod(v interface{}) bool {
  _, has := v.(interface{ Say(str string) string })
  return has
}

方法二:

func HasMethod(v interface{}, name string) bool {
  t := reflect.TypeOf(v)
  _, ok := t.MethodByName(name)
  return ok
}
转载自:https://juejin.cn/post/7402845471645433896
评论
请登录