go 语言基础设置环境变量: go.mod 和 go.sum go.mod 和 go.sum 的区别 go.mod:只记
设置环境变量:
export GOPROXY=https://goproxy.cn
go env -w GOPROXY=https://goproxy.cn
go.mod 和 go.sum
go.mod
和 go.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
GOPROXY
是 go
用来下载依赖的地址
当我们设置了这个 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
相似,如果配置了域名,通过这些域名拉取的包不会走代理
在理解 GOPRIVATE
和 GONOPROXY
区别之前,要先知道 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
只适用于 slice
、map
和 channel
的初始化,返回的是这三个引用类型的实例,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
指针的限制
- 指针不支持直接进行算术运算,如果要进行算术运算需要先解引用
func main() { i := int64(5) p := &i *p++ // *p 获取 p 中的内容 fmt.Println(*p) }
- 一个指针类型的值不能随意转化为另一个指针类型(除非满足下面一个)
- 底层类型一致且有一个是无名类型
- 都是无名类型,且他们的基类型的底层类型一致
- 不属于无名类型
- 预声明类型
- 自定义类型
- 泛型的实例化类型
- 用在自定义泛型中的类型参数类型
type bl = *bool type m = map[int]int var s string // 具名非指针类型 var e error // 具名接口类型 var m1 m // 无名类型的别名 var b b1 // 无名指针类型的别名 var iptr *int // 无名指针类型
- 不属于无名类型
- 一个指针值不能随意跟其他指针类型的值进行比较(除非满足下面一个)
- 这两个指针类型相同
- 这两个指针之间可以隐式转换
- 一个指针值不能随意被赋值给其他任意类型的指针值,除非这两个指针可以被比较
上述限制可以通过 unsafe
标准库包中的非类型安全指针 unsafe.Pointer
来打破
map
浮点数作为 map
的 key
,go
内部会做一些处理
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
}
结构体的比较:
- 他们的子弹只能包含可比较类型
- 这两个结构体需要能隐式转换
p1
和 p4
是不同的类型,所以不能比较
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
如果表达式 x
和 y
的类型不相同,则函数调用 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))
如果同类型的 x
和 y
是两个引用着不同其他值的指针值,则 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