go学习
Go(又称 Golang)是由 Google 开发的一种开源编程语言。它于 2009 年首次发布,旨在解决当时软件开发中常见的挑战,特别是在大规模分布式系统和并发编程方面。Go 语言结合了静态类型和动态语言的优点,既保持了高效的性能,又提供了简单、清晰的语法。
定义包
当前文件中只要有main函数就是main包,这个main包跟文件名没有关系
package main //程序包名
导包
import "fmt"
导入多个包
import(
"fmt"
"time"
)
注意:导包后不使用的话会报错
匿名导包
导包后不使用但是可以执行包中的,init函数.
import _ "packageName"
注意:下划线(_)代表匿名的意思,此语法也可以定义匿名变量
别名导包
通过(别名.方法名)的方式调用导入包中的方法
import lib1 "packageName"
用. 把包里的方法全部导入(尽量不要使用)
使用时把包里的方法全部导入进当前类,此时不需要(包名.方法名)即可使用。
import . "package"
main函数
基本函数,无形参,无返回值
func main(){
fmt.Println("hello go"); //此处分号可加可不加都没有影响 -建议不加
}
花括号问题
go语言要求在函数中左大括号一定要与函数名在同一行 eg:正确
func main(){
}
eg:错误
func main()
{
}
syntax error:unexpected semicolon or newline before {
程序编译
go build hello.go
程序执行
./hello
编译+执行
go run hello.go
四种变量声明方式
方式一
声明一个变量
var a int
var a string
此时没有给初始化值,默认值是0
方式二
声明一个变量并给出初始值
var b int = 100
var b string = "abcd"
方式三
初始化时省略变量的数据类型,通过给定值来匹配当前变量的数据类型
var c = 100
var c = "abcd"
方式四
最常用的-省略var关键字直接自动匹配.冒等 表示既声明又赋值 类型通过冒等自动推出来
e:=100
注意:方式四不能声明全局变量 := 只能在函数体内来声明
打印出变量的数据类型
fmt.Printf("type of c = %T\n",c)
声明多个变量
同类型
var aa,bb int = 100,200
不同类型
var cc,dd = 100,"dd"
多行多变量声明
var(
ee int = 100
ff bool = true
)
常量
常量的用法(常量-只可读,不可重新赋值和修改)只需要把var 写成const即可。eg:
const width int = 100
常量定义枚举类型
const(
BEIJING=0
SHANGHAI=1
GUANGZHOU=2
SHENZHEN=3
)
常量中 利用iota自增
常规用法
我们可以在const()中添加一个关键字iota,每行的iota都会累加1,第一行的iota默认值是0。iota自增的作用减少了我们的手动赋值。
const(
BEIJING = iota // iota 默认为0 BEIJING = 0
SHANGHAI // 此行表示SHANGHAI=iota 此时iota自增1 所以 SHANGHAI = 1
GUANGZHOU // 同上 GUANGZHOU = iota = 2
SHENZHEN // 同上 SHENZHEN = iota = 3
)
赋值时增加运算
另当上面的某一个常量被iota赋值后,这个常量等号后面的规则就是下面常量的赋值规则,唯一不同的是下面的常量依次得到的是iota增加1之后的值eg:
const(
BEIJING = 10*iota // 此时规则是10*iota 由于第一行iota值为0,所以BEIJING = 0
SHANGHAI // 此时规则是10*iota 由于第一行iota值为1,所以SHANGHAI = 10
GUANGZHOU // 此时规则是10*iota 由于第一行iota值为2,所以GUANGZHOU = 20
SHENZHEN // 此时规则是10*iota 由于第一行iota值为3,所以SHENZHEN = 30
)
自增几行后修改规则
const(
a,b=iota+1,iota+2 //iota = 0 ,a = iota + 1,b = iota + 2,a=1,b=2
c,d //iota = 1 ,c = iota + 1,d = iota + 2,c=2,d=3
e,f //iota = 2 ,e = iota + 1,f = iota + 2,e=3,f=4
g,h=iota*2,iota*3 //iota = 3 ,g = iota * 2,h = iota * 3,g=6,h=9
i,k //iota = 4 ,g = iota * 2,h = iota * 3,g=8,h=12
)
iota 只能配合const()一起使用
函数
函数名首字母大写表示这个函数是对外开放的函数,其他包可以调用,如果小写的话则证明这个函数只能当钱包内调用其他包调用不到。大写函数名也可以理解为类的公用方法。
基本函数
func add(a int,b int) int{
return a+b
}
使用
func main(){
sum := add(1,2)
}
函数多返回值
返回多个返回值 (匿名)
fun muitReturn(a int,b string)(int,int){
return 666,777
}
使用
fun main(){
aaa,bbb:=muitReturn(111,"ssss")
}
返回多个返回值 (带形参名称)
带形参名称的返回值,如果方法体内没有给他赋值则走默认值 0。
fun muitReturn(a int,b string)(r1 int,r2 int){
//给有名称的返回值变量赋值
r1=100
r2=200
return
}
返回多个返回值 (带形参名称且返回值类型相同)
fun muitReturn(a int,b string)(r1,r2 int){
//给有名称的返回值变量赋值
r1=100
r2=200
return
}
导包问题与init方法的调用
同文件 方法执行逻辑
1-main包所在文件 》 2-常量 》 3-变量 》 4-init函数 》 5-main函数 》 6--退出
我们把此逻辑标记为A
含导包 方法执行逻辑
如果main包中有导入其他包,则程序执行逻辑递归导入子包并依次执行 初始化常量->初始化变量->执行init函数->返回上一个文件 执行 初始化常量->初始化变量->执行init函数->返回上一个文件执行,直到main方法执行完毕。程序结束
指针
当我们传递一个值时,经过在changeVal改变值后此时a仍然为1,我们只是把a的值1传递进了函数中。函数中的任何操作都不会改变外部变量a的具体值。
package main
import "fmt"
func changeVal(b int){
b = 10
}
func main(){
a:=1
changeValue(a)
fmt.print("a = "+a) //此时a打印为1
}
传递指针
package main
import "fmt"
func changeVal(b *int){
*b = 10
}
func main(){
a:=1
changeValue(&a)
fmt.print("a = "+a) //此时a打印为1
}
一级指针 通过跳转1下就能找到当前值(a变量里存了指针,想拿到当前值)
*a
二级指针 通过跳转2下找到具体值(a变量存了指针,此指针指向的是另一个地址)
**a
defer
defer使用
defer后面可以跟任何的表达式或者函数都可以。
defer执行顺序
defer采用压栈的形式,在使用defer的当前函数执行完后,先进栈的后执行。eg:
func main(){
defer fmt.Println("defer 1");
defer fmt.Println("defer 2");
fmt.Println("defer 3");
fmt.Println("defer 4");
}
输出结果
defer 3
defer 4
defer 2
defer 1
图例:
defer和return先后顺序
当return和defer同时出现在一个函数中,return先被调用 defer后面的语句后被调用
defer逻辑是在函数执行到最后一个大括号之后才去执行的,所以return逻辑会先于defer调用。
数组
数组定义
- 定义一个int类型的数组,长度是10
var arr[10]int
- 定义一个带初始值的数组,长度是10 带几个默认值
arr := [10]int{1,2,3,4}
- 查看数组类型
fmt.Printf("arr type = T% \n",arr) //输出[4]int
遍历数组
- 正常遍历
for i:=0;i<len(arr);i++{
}
- 带下标遍历
for index, value := range arr{
fmt.Println("index = ",index,"value = ",value)
}
数组当方法的参数
数组当参数,参数中数组的长度是多少,那么调用此函数的时候传递的数组长度就得是多少。否则会报类型不匹配的错误。
func bianLi([4]int){
}
数组当方法参数时 --是值拷贝
数组当方法参数时是值拷贝,尽管在方法中改变数组的值但是外界源数组内容并不会发生变化。
myArray:=[4]int{11,22,33,44}
func add(arr [4]int){
arr[0]=111
}
fmt.Print(arr[0])//此处打印依然是原值 11
动态数组(切片slice)
不用指定数组中元素的个数
创建动态数组
创建动态数组并赋初始值
arr:=[]int{1,2,3,4}
动态数组当函数参数
动态数组当函数的参数无需指定数组中元素的个数
func asum(arr []int){
}
动态数组遍历方式同数组相同
动态数组是 --引用传递
myArray:=[]int{11,22,33,44}
func add(arr []int){
arr[0]=111
}
fmt.Print(arr[0])//此处打印的是改变后的值 111
切片的4种声明方式
- 声明slice1是一个切片并且初始化,默认值是123,长度是len是3
slice:=[]int{1,2,3}
- 声明slice这个切片,但是并没有给slice分配空间,此时使用会报错
var slice []int
要想开辟空间
slice = make([]int,3)//类型[]int ,长度3
- 声明slice是一个切片同时给slice分配三个空间,初始化值是0
var slice []int = make([]int,3)
- := 声明slice是一个切片同时给slice分配三个空间,初始化值是0
slice:=make([]int,3)
切片判空
slice == nil
切片追加与截取
获取切片长度
len(slice)
获取切片容量
cap(slice)
切片定义
下面定义代表他的容量是5,长度是3
var number = make([]int,3,5)
切片追加
numbers=append(numbers,1)
执行此方法后,numbers容量还是5,但是len变成了4 此时其中元素由[0,0,0]变成了[0,0,0,1]
向一个容量已经满了的切片中追加元素,此时系统会动态的开辟此切片原始容量大小的空间
切片截取
截取前两个元素[0,2)
s:=[]int{1,2,3}
s1:=s[0:2]
从头开始截取(索引0包含)到(索引2不包含)的位置。
s1:=s[:2]
从索引2的位置截取到末尾
s1:=s[2:]
注意:切片截取后的到的新切片中的元素值改变后,源切片也会发生变化。也就是说原来的切片和截取后的切片元素的地址是相同的
切片的copy
上面的切片截取使我们得到了源个切片的一部分引用,如果我们想直接完整复制一个切片的元素,改变新切片不影响旧的,则需要用到copy函数。
s2 := make([]int,3)
copy(s,s2)
此时s2就是完全不同的地址空间,改变s2中的元素不影响s中的值、
map
map的声明
声明myMap是一种map类型,key是string ,值是string 。此时map是空的。
var myMap map[string]string
map分配数据空间
- 分配数据空间,并设置初始容量10
myMap = make(map[string]string,10)
- 分配数据空间,不设置容量
myMap = make(map[int]stirng)
- 声明并初始化
myMap:=map[string]string{
"zhangsan":"男",
"lisi":"男",
}
map的使用
1、添加
myMap:=make(map[string]string)
myMap["beijing"]="北京"
myMap["shanghai"]="上海"
2、遍历
for key,value := range myMap{
fmt.Println("key")
fmt.Println("value")
}
3、删除
delect(myMap,"beijing")
4、修改
myMap["beijing"]="china"
5、map传参(引用传递) map变量以参数形式传递进方法中,在方法内部改变变量值后。外部map中存储的值也会进行相应改变
func changeValue(cityMap map[string]string){
cityMap["beijing"]="上海"
}
//此时外部cityMap中的值也改变了
struct基本定义与使用
用type关键字定义一种新的类型
typeInt 是int的一个别名
//定义
type typeInt int
//使用
var a typeInt = 10
定义一个结构体
type Book struct{
bookName string
price int
}
使用一个结构体
1、结构体声明
var myBook Book
2、结构体使用
myBook.bookName = "8小时学会go语言"
myBook.price = 0
3、结构体打印 %v可以打印任意类型
fmt.print("%v \n",myBook)
4、结构体做函数的参数
值传递,传递进函数的是结构体的一个副本,此时在函数内改变结构体的值,外面的值不会变化
func changeBook(book Book){
//传递的book的一个副本
}
传指针
func changeBook(book *Book){
//此时传递进来的是book的一个指针,在函数体内改变book里面的一些值的话,外面也跟着改变
}
类的表示
1、 预备:结构体一个
// 类名首字母大写表示此类在其他包也可以访问到
type Human struct{
// 属性首字母大写表示该属性对外可以访问到,否则只能类的内部访问。
name string
age int
sex string
}
2、绑定方法到结构体(值拷贝)
func (this Human) getName() string{
fmt.Println("this name = ")
return this.name
}
func (this Human) setName(newName string){
//是调用该方法对象的一个副本(拷贝),并没有改边对象中存的值。
this.name=newName
}
3、绑定方法到结构体(引用传递)
// 方法首字母大写表示,你在其他的模块和其他的包中也可以访问到。
func (this *Human) GetName() string{
fmt.Println("this name = ")
return this.name
}
// 方法首字母小写表示,你在其他的模块和其他的包中不可以访问到。
func (this *Human) setName(newName string){
//是调用该方法对象的一个副本(拷贝),并没有改边对象中存的值。
this.name=newName
}
上面两步,定义结构体+定义方法并绑定到结构体就组成了普通意义上的类的概念
对象的创建
1、创建对象
hero:=Human{name:"zhangsan",age:10,sex:"男"}
2、调用方法
hero.getName()
hero.setName("张三")
类的继承
1、父类
type Human strct{
name string
sex string
}
func (this *Human) eat(){
fmt.Println("human eat()")
}
2、创建子结构体继承上面的Human类
type SuperMan struct{
Human //SuperMan类继承了Human类的方法
age int
}
3、重定义父类的方法
func (this *SuperMan) eat(){
fmt.Println("super fly")
}
4、定义子类的新方法
func (this *SuperMan) fly(){
fmt.Println("SuperMain flying")
}
5、定义一个子类对象
第一种方法
superMan:=SuperMan{Human{"超人","男"},88}
superMan.eat()
第二种方法
var superMan SuperMan
接口+多态
1、定义一个接口
接口本质是一个指针
type Animal interface{
Sleep()
GetColor() string
GetType() string
}
2、接口子类的实现
要实现一个接口,就是在类中把接口中所有的方法都实现就可以了。
type Cat struct{
color string//猫的颜色
type string//类型
}
func (this *Cat) Sleep(){
fmt.Println("小猫睡着了")
}
func (this *Cat) GetColor() string{
return this.color
}
func (this *Cat) GetType() string{
return this.type
}
3、给接口的声明中传递子类的对象
func main(){
var animal Animal//定义接口的数据类型 父类指针
animal = &Cat("red","三花")//多态的实现,
animal.Sleep()//调用的就是Cat方法
}
4、方法中定义接口参数,使用时传递子类对象
func showAnimal(animal Animal){
animal.Sleep()
}
调用
showAnimal(&Cat("red","三花"))
空接口(万能类型)及接口断言机制
int,string,float32,float64,struct都实现了空接口。所以用空接口类型就可以引用任意数据类型
空接口
1、空接口写法
interface{}
2、空接口作为参数
//此处的interface{}是万能数据类型,所以此方法可以传进来任何参数
func SetName(name interface{}){
}
3、使用上面的方法(可传递任意类型)
var cat=Cat{"red","三花"}
setName(cat)
setName("name")
setName(100)
setName(3.14)
断言机制
intface{}如何判断底层具体数据类型是什么?
interface{}的类型断言
的机制。
value,ok :=arg.(string)
if !ok{
fmt.Println("arg is not a string type")
}else{
fmt.Println("arg is a string type value=",value)
fmt.Println("value type is %T \n",value)
}
变量中的pair的存在
变量的结构
golang中变量的基本构造一共有两个部分 1,type 2,value
其中type又分为static type和concrete type
静态类型和具体类型(二选一)。见下图:
1、pair解释
var a string
//pair<type,value>
a="abcdefg"
实际上a内部就会有一个pair<type,value>此时type就应该是string,value就应该是"abcdefg"这个字符串 type是string的话此时type应该是statictype
2、不管把a赋值给谁此时的pair不变
var AllType interface{}
//allType的pair是pair<type,value>
allType=a
3、类型断言
类型断言用于将接口类型的变量转换为具体的类型。语法如下:
value, ok := interfaceValue.(ConcreteType)
interfaceValue
是接口类型的变量。ConcreteType
是你要断言的具体类型。ok
是一个布尔值,表示断言是否成功。
如果断言成功,value
将是 ConcreteType
类型的值,ok
为 true
。如果断言失败,value
是 ConcreteType
的零值,ok
为 false
。
类型断言示例:
package main
import "fmt"
func main() {
var i interface{} = "hello"
// 类型断言
s, ok := i.(string)
if ok {
fmt.Println("The string is:", s)
} else {
fmt.Println("Type assertion failed")
}
// 错误的类型断言
n, ok := i.(int)
if ok {
fmt.Println("The integer is:", n)
} else {
fmt.Println("Type assertion failed")
}
// 省略 ok 的用法
// 如果断言失败,会导致 panic
s = i.(string)
fmt.Println("The string is:", s)
}
反射
reflect包中提供了反射机制,他允许我们获取未知变量里面的type和value信息
1、返回任意类型的value
// reflect 方法源码
func ValueOf(i interface{}) Value{
}
2、返回任意类型的type
// reflect 方法源码
func TypeOf(i interface{}) Type{
}
3、反射一个基本类型
func reflectNum(arg intface{}){
fmt.Println("type = ",reflect.TypeOf(arg))
fmt.Println("value = ",reflect.ValueOf(arg))
}
func main(){
var num float64=1.2345
reflectNum(num)
}
反射一个类
1、定义一个基本类
type User struct{
Name string
Id long
Age int
}
func (this *User) Call(){
fmt.Println("user is called")
}
func main(){
user:=User{"张三",1000001,21}
}
2、获取类里面的type和value
//获取type
inputType:=inflect.TypeOf(user)
fmt.Println("inputType is",inputType)
//获取value
inputValue:=inflect.ValueOf(user)
fmt.Println("inputValue is",inputValue)
3、通过type获取里面的字段
//1. 获取interface的inflact.Type,通过type得到numberField,进行遍历
//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)
}
打印输出
Name:string = "张三"
Id:long = 1000001
Age:int = 21
4、通过type获取里面的方法调用
for i:=0;i<inputType.NumMethod();i++{
m := inputType.Method(i)
fmt.Printf(%s: %v \n",m.Name,m.Type)
}
打印输出
Call: func(main.User)
结构体标签
结构体标签表示对当前结构体内的属性进行解释和说明,当其他包导入这个属性的时候判断这个属性在这个包里具体该怎么用。它采用key:value的形式。其中可以出现多对key:value eg:
type resume struct{
//表示当前属性绑定了两个标签
Name string `info:"name" doc:"我的名字"`
sex string `info:"sex"`
}
通过反射解析结构体标签
func findTag(str interface{}){
t:=reflict.TypeOf(str).Elem()//表示当前结构体内的全部元素
for i:=0;i<t;i++{
tagInfo := t.Field(i).Tag().Get("info")
tagDoc := t.Field(i).Tag().Get("doc")
fmt.Println("info:",tagInfo," doc: ",tagDoc)
}
}
打印结果
info: name doc: 我的名字
info: sex doc:
结构体标签在json中的应用
1.准备
import(
"encoding/json"
)
type Movie struc{
Title string `json:"title"`
Year int `json:"year"`
Price int `json:price`
Actors []string 'json:actors`
}
movie:=Movie{"喜剧之王",2000,10,[]string{"xingye","zhangbozhi"}}
2.结构体-->json
jsonStr:err := json.Marshal(movie)
if err!=nil {
fmt.Println("json marshal err",err)
return
}else{
fmt.Printf("jsonStr = %s \n",jsonStr)
}
3.json-->结构体
myMovie:=Movie{}
err := json.Unmarshal(jsonStr,$myMovie)
if err != nil{
fmt.Println("unmashal json err",err)
return
}
fmt.Println("%v \n",myMovie)
go协程(goroutine)
协程的创建
//从协程
func newTask(){
i := 0
for{
i++
fmt.println("new goroutine : i = %d \n",i)
time.sleep(1*time.Second)
}
}
//主协程
func main(){
//创建一个go 协程去执行 newTask()流程
go newTask()
}
所有的从协程都是依赖于主协程的,如果当前main方法(也就是说主协程退出的话)其余的从协程也会自动退出。
用go 关键词 创建一个无参go协程
func main(){
go func(){
defer fmt.Pintln("A.defer")
func(){
defer fmt.Println("B.defer")
fmt.Println("B")
}
fmt.Println("A")
}
}
创建一个有参的并调用
go func(a int,b int) sum{
var sum int = a+b
fmt.Println("a+b=",sum)
return sum
}(10,20)//此处需要写上参数
退出当前go程
- 嵌套1层
如果只是嵌套1层,则直接用return退出
- 嵌套多层
runtime.Goexit()
利用channel进行go协程间通信
图例:
1、channel的定义
make(chan Type) //无缓存的channel,最多就是一个元素
make(chan Type,capacity)//capacity可以指定缓存容量。
2、简单通信方式
channel <- value 发送value到channel
<- channel //接收并将值丢弃
x := <- channel //接收并赋值给x
x , ok := <- channel //功能同上,同时检查通道是否已关闭或者是否为空
3、channel基本使用
func main(){
定义一个channel
c:=make(chan int)
go func(){
defer fmt.Println("goroutine 结束")
fmt.Println("goroutine 运行中")
c <- 666 //将666写入channel
}()
num := <- c //从c中接受数据,并赋值给num
fmt.Println("num 值为",num)
fmt.Println("main gorotine 结束...")
}
打印结果
gorotine 运行中
goroutine 结束
num 值为666
main gorotine 结束...
4、为何案例3的main总是等子协程结束后再结束
channel 本身是具有同步两个go协程的能力,案例中的两个协程(主协程(main)和子协程)同时异步执行(都有可能执行到想关语句),当主协程中执行到 num:= <- c 这行方法时会如果channel中没有返回数据,则主协程(main方法)就会阻塞在这,等待子协程执行到 c <- 666 把666放进channel中。然后主协程正常的执行从channel中拿出数据给num赋值。然后打印出num值
无缓冲的channel
- 第一步:两个协程都到达通道,但是哪个都没有开始执行发送或者接收
- 第二步:左侧的将他的手伸进了通道,这模拟了向通道发送数据的行为。这时这个协程会在通道中被锁住
- 第三步:右侧的手将他的协程放进通道,这模拟了从通道中接收数据。这个协程一样会在通道中被锁住,直到交换完成
- 第四步+第五步:进行交换
- 第六步:两个协程都把手从通道中拿出来,这模拟了被锁住的协程得到释放。两个协程都可以去做其他事情了
有缓冲的channel
- 第一步:右侧的协程正在从通道中接收1个值
- 第二步:右侧的这个协程独立完成了接收值的动作,而左侧的协程正在发送一个新值到通道中
- 第三步:左侧的协程还在向通道发送值,而右侧的协程正在从通道接收另外一个值,这个步骤里的两个操作既不是同步的,也不会互相阻塞操作。
- 第四步:最后所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值 注意:
左边放满了,就会阻塞。等待右边去取
通道空了,右边的不能取了,阻塞,等待左边去存
获取当前通道中的元素数量和容量
c:=make(chan int,3)
fmt.Println("通道中元素数量len=",len(c),"管道容量 cap=",cap(c))
关闭channel
close(channel)
从channel中获取值并判断当前channel是否关闭
if value,ok := <- c ; ok{
fmt.Println(value)
}else{
fmt.Println("channel 已关闭)
}
- channel 不像文件一样需要经常去关闭,只有当你确实没有任何数据发送了,或者你想显示的结束range循环之类的,才去关闭channel
- 关闭channel后无法向channel再次发送数据,会引发panic错误。导致接收立即返回
零
值 - 关闭channel后可以继续从channel中接收数据
- 对于nilchannel无论收发都会被阻塞
channel 与range
普通的从channel中等待数据的写法
for{
if data , ok := <- channel;ok{
fmt.Println("data=",data)
}else{
break
}
}
利用range简写从channel中等待数据的逻辑
for data := range c{
fmt.Println(data)
}
表示尝试从c中读取结果,如果有返回就赋值给data,并走进方法体执行打印,如果range中没有数据则阻塞等待结果
channel 与select
单流程下,一个go只能监控一个channel状态,select可以完成监控多个channel状态的任务
select{
case <- chanl:
//如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1
//如果成功的向chan2写入数据则进行该case处理语句
default
//如果上面都没有则进入default处理流程
}
GO mod基础环境说明
- 要运用gomod模式需要保证go的版本在1.11.0以上。在11之前是没有这个模式的。这个模块主要是对go module的依赖下载校验配置等内容。
执行 go version 命令进行检查
go mod 命令
下面表格中的命令可以通过go mod help
命令进行查看
命令 | 作用 |
---|---|
go mod init | 在当前文件夹初始化一个新的module |
go mod download | 下载module到本地缓存中 |
go mod tidy | 添加没有的或删除没用到的module |
go mod graph | 打印module需要的graph |
go mod edit | 从工具或者脚本中编辑go.mod文件 |
go mod vendor | 导出项目所有的依赖到vendor目录 |
go mod verify | 校验一个模块是否被篡改过 |
go mod why | 为什么需要依赖某模块 |
启用go mod模块
go env -w GO111MODULE=on
创建Go模块代理
设置成国内的地址,便于下载依赖。目前有阿里云和七牛云
go env -w GOPROXY=https://xxx.xxxx
- 阿里云
https://mirrors.aliyun.com/goproxy/
- 七牛云
https://goproxy.cn,direct
direct表示如果代理的镜像仓库中没有的话则去源GitHub上面去拉取
GOSUMDB
校验依赖是否被篡改的校验网站。如果设置镜像代理则默认走镜像的地址。
关闭校验(不建议)
go env -w GOSUMDB=off
go mod 环境变量 就是类似上面GOPROXY这些的配置。
go mod 初始化项目
可以不在GOPATH目录下创建项目
//在非GOPATH目录下新建文件夹并打开终端执行如下命令
go mod init github.com/zhou/modules_test //init后面决定了导包名称
下载依赖
go mod get
go mod 依赖指定版本
go mod edit -replace=原先的版本=新版本