阅读 533

Go学习小计

go学习

本篇文章是根据bilibili上的go学习视频,进行整理的。源视频地址 8小时转职Golang工程师(如果你想低成本学习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方法的调用

image.png

同文件 方法执行逻辑

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
复制代码

图例:

image.png

defer和return先后顺序

当return和defer同时出现在一个函数中,return先被调用 defer后面的语句后被调用defer逻辑是在函数执行到最后一个大括号之后才去执行的,所以return逻辑会先于defer调用。

数组

数组定义

  1. 定义一个int类型的数组,长度是10
var arr[10]int
复制代码
  1. 定义一个带初始值的数组,长度是10 带几个默认值
arr := [10]int{1,2,3,4}
复制代码
  1. 查看数组类型
fmt.Printf("arr type = T% \n",arr) //输出[4]int
复制代码

遍历数组

  1. 正常遍历
for i=0;i<len(arr);i++{
}
复制代码
  1. 带下标遍历

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种声明方式

  1. 声明slice1是一个切片并且初始化,默认值是123,长度是len是3
slice:=[]int{1,2,3}
复制代码
  1. 声明slice这个切片,但是并没有给slice分配空间,此时使用会报错
var slice []int
复制代码

要想开辟空间

slice = make([]int,3)//类型[]int ,长度3
复制代码
  1. 声明slice是一个切片同时给slice分配三个空间,初始化值是0
var slice []int = make([]int,3)
复制代码
  1. := 声明slice是一个切片同时给slice分配三个空间,初始化值是0
slice:make([]int,3)
复制代码

切片判空

slice == nil
复制代码

切片追加与截取

获取切片长度

len(slice)
复制代码

获取切片容量

cap(slice)
复制代码

切片定义

下面定义代表他的容量是5,长度是3

var number = make([]int,3,5)
复制代码

image.png

切片追加

numbers=append(numbers,1)
复制代码

执行此方法后,numbers容量还是5,但是len变成了4 此时其中元素由[0,0,0]变成了[0,0,0,1]

image.png

向一个容量已经满了的切片中追加元素,此时系统会动态的开辟此切片原始容量大小的空间

切片截取

截取前两个元素[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分配数据空间

  1. 分配数据空间,并设置初始容量10
myMap = make(map[string]string,10)
复制代码
  1. 分配数据空间,不设置容量
myMap = make(map[int]stirng)
复制代码
  1. 声明并初始化
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静态类型和具体类型(二选一)。见下图:

image.png

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、类型断言

str,_:=allType.(string)
fmt.Println(str)
复制代码

反射

reflect包中提供了反射机制,他允许我们获取未知变量里面的type和value信息

1、返回任意类型的value

func ValueOf(i interface{}) Value{
}
复制代码

2、返回任意类型的type

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层
如果只是嵌套1层,则直接用return退出
复制代码
  1. 嵌套多层
runtime.Goexit()
复制代码

利用channel进行go协程间通信

图例:

image.png

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值

image.png

无缓冲的channel

image.png

  • 第一步:两个协程都到达通道,但是哪个都没有开始执行发送或者接收
  • 第二步:左侧的将他的手伸进了通道,这模拟了向通道发送数据的行为。这时这个协程会在通道中被锁住
  • 第三步:右侧的手将他的协程放进通道,这模拟了从通道中接收数据。这个协程一样会在通道中被锁住,直到交换完成
  • 第四步+第五步:进行交换
  • 第六步:两个协程都把手从通道中拿出来,这模拟了被锁住的协程得到释放。两个协程都可以去做其他事情了

有缓冲的channel

image.png

  • 第一步:右侧的协程正在从通道中接收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 tudy添加没有的或删除没用到的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,directdirect表示如果代理的镜像仓库中没有的话则去源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=原先的版本=新版本
复制代码

时间和日期 -待补充

文件和目录操作 -待补充

文章分类
后端
文章标签