Golang基础知识点整理

Golang基础知识点整理

Go语言strconv包实现字符串和数值类型的相互转换

Go语言strconv包实现字符串和数值类型的相互转换

1 Slice

1.1 定义

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内

切片是动态结构,只能与 nil 判定相等,不能互相判定相等。声明新的切片后,可以使用 append() 函数向切片中添加元素。

1.2 切片的底层原理

切片的底层结构,即 reflect.SliceHeader:

type SliceHeader struct {
    Data uintptr
    Len int
    Cap int
}

由切片的结构定义可知,切片的结构由三个信息组成:

指针:指向底层数组中切片指定的开始位置;
长度:切片的长度;
容量:当前切片的容量;

1.3 切片的创建方式

1.3.1 从已有的数组或切片生成新的切片

切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
从连续内存区域生成切片是常见的操作,格式如下:

slice [开始位置 : 结束位置]

语法说明如下:

  • slice:表示目标切片对象;
  • 开始位置:对应目标切片对象的索引;
  • 结束位置:对应目标切片的结束索引。

从数组生成切片,代码如下:

var a  = [3]int{1, 2, 3}
var aa = a[1:2]
fmt.Println(a, aa)

其中 a 是一个拥有 3 个整型元素的数组,被初始化为数值 1 到 3,使用 a[1:2] 可以生成一个新的切片 aa,代码运行结果如下:
[1 2 3] [2]

其中 [2] 就是 aa 切片操作的结果。

从数组或切片生成新的切片拥有如下特性:

  • 取出的元素数量为:结束位置 - 开始位置;
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
  • 当缺省开始位置时,表示从连续区域开头到结束位置;
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
  • 两者同时缺省时,与源切片本身等效;
  • 两者同时为 0 时,等效于空切片,一般用于切片复位。

1.3.2 直接声明新的切片

除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:

var name []Type

其中 name 表示切片的变量名,Type 表示切片对应的元素类型。

下面代码展示了切片声明的使用过程:

// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
// 输出3个切片
fmt.Println(strList, numList, numListEmpty)
// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的结果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)

代码输出结果:

[] [] []
0 0 0
true
true
false

1.3.3 使用 make() 函数构造切片

如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:

make([]Type, Len, Cap)

其中 Type 是指切片的元素类型,Len 指的是为这个类型的切片长度,Cap 为该切片预分配的容量大小,这个值设定后不影响 Len只是能提前分配空间,降低多次分配空间造成的性能问题

示例如下:

a := make([]int, 2)
b := make([]int, 2, 10)

fmt.Println(a, b)
fmt.Println(len(a), len(b))

代码输出如下:

[0 0] [0 0]
2 2

其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素。
容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2。

1.4 注意

使用 make()函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

1.5 append()为切片添加元素

Go语言的内建函数 append() 可以为切片动态添加元素,代码如下所示:

var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,例如 1、2、4、8、16……。

除了在切片的尾部追加,我们还可以在切片的开头添加元素:

var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多

1.6 切片复制(切片拷贝)

Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

copy() 函数的使用格式如下:

copy( destSlice, srcSlice []T) int

其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。

下面的代码展示了使用 copy() 函数将一个切片复制到另一个切片的过程:

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置

虽然通过循环复制切片元素更直接,不过内置的 copy() 函数使用起来更加方便,copy() 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题。

1.7 切片中删除元素

Go语言并没有对删除切片元素提供专用的方法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。

删除的原理是从已有的切片生成新的切片。切片中删除元素

1.8 循环迭代切片

切片是一个集合,可以使用 range 迭代其中的元素。

2 map

2.1 map的声明

map 是引用类型,可以使用如下方式声明

var mapname map[keytype]valuetype

其中:

  • mapname 为 map 的变量名。
  • keytype 为键类型。
  • valuetype 是键对应的值类型。

提示:[keytype] 和 valuetype 之间允许有空格。

在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的,未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 pair 的数目。

2.2 map的创建方式

	//方式1
	var mapLit = map[string]string{}
	
	//方式2
	mapCreated := make(map[string]float32)

方式1和方式2等价。但不能使用 new() 来构造 map,如果错误的使用 new() 分配了一个引用对象,会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:

mapCreated := new(map[string]float)

接下来当我们调用mapCreated["key1"] = 4.5的时候,编译器会报错:

invalid operation: mapCreated["key1"] (index of type *map[string]float).

2.3 K-V 一对多实现

既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示:

mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)

2.4 map元素的删除和清空

2.4.1 删除map中的元素

使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:

delete(map,)

其中 map 为要删除的 map 实例,键为要删除的 map 中键值对的键。

2.4.2 清空map中的所有元素

Go语言中并没有为 map 提供任何清空所有元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多。

2.5 sync.Map(在并发环境中使用的map)

Go语言sync.Map

3 Go语言list(列表)

Go语言list(列表)

在Go语言中,列表使用 container/list 包来实现,内部的实现原理是双向链表,列表能够高效地进行任意位置的元素插入和删除操作。
list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。

3.1 通过container/list包的New()函数初始化 list

变量名 := list.New()

4 Go语言nil:空值/零值

Go语言nil:空值/零值

5 Go语言make和new关键字的区别及实现原理

推荐 :Go语言make和new关键字的区别及实现原理

  • new 关键字只分配内存,当我们想要获取指向某个类型的指针时可以使用 new ;
  • make 关键字的主要作用是初始化内置的数据结构,也就是 slice、map 和 channel 的初始化。

6 函数

函数的基本组成:

  1. 关键字 func
  2. 函数名;
  3. 参数列表;
  4. 返回值列表;
  5. 函数体;
  6. 返回语句;

在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。

6.1 函数类型的变量

在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中。
注意:
在声明函数类型的变量时,要根据实际调用的具体函数来声明。主要分为三种情况。
代码示例:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	//1、调用无参、无返回值的函数
	var walkFun func()
	walkFun = walk
	walkFun()

	//2、调用有参、无返回值的函数
	var flyFun func(paramName string)
	flyFun = fly
	flyFun("鸟儿")

	//3、调用有参、有返回值的函数
	var runFun func(paramName string, paramDistance float64) (res string)
	runFun = run
	res := runFun("兔子", 500.00)
	fmt.Println(res)
}

func walk() {
	fmt.Println("walk")
}

func fly(name string) {
	fmt.Printf("%s 在飞\n", name)
}

func run(name string, distance float64) (result string) {
	result = name + ":跑了 " + strconv.FormatFloat(distance, 'f', 3, 64) + " 米"
	return result
}

6.2 匿名函数

匿名函数:没有函数名字的函数。

6.3 闭包

闭包: 是指匿名函数所使用的变量来自匿名函数外部时,就称这样的匿名函数为闭包(所谓的函数外部指的是不通过形参的方式使用变量,而是在匿名函数内部直接使用变量)。

闭包是由匿名函数及其相关引用环境组合而成的实体(即:闭包=匿名函数+引用环境)

闭包对它作用域上部的变量可以进行修改,修改引用的变量会对变量进行实际修改
Go语言闭包(Closure)——引用了外部变量的匿名函数
golang之闭包

闭包有一个特点,包含闭包的函数,它的返回类型都是函数类型,返回的实际上是匿名函数。

6.4 Go语言defer、panic、recover 详解

6.4.1 defer

  • Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

  • 处理业务或逻辑中涉及成对的操作是一件比较烦琐的事情,比如打开和关闭文件、接收请求和回复请求、加锁和解锁等。在这些操作中,最容易忽略的就是在每个函数退出处正确地释放和关闭资源。

  • defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放问题。

defer,panic,recover详解 go 的异常处理

7 Go语言Test功能

要开始一个单元测试,需要准备一个 go 源码文件,在命名文件时文件名必须以_test.go结尾,单元测试源码文件可以由多个测试用例(可以理解为函数)组成,每个测试用例的名称需要以 Test 为前缀,例如:

func TestXxx( t *testing.T ){
    //......
}

编写测试用例有以下几点需要注意:

  • 测试用例文件不会参与正常源码的编译,不会被包含到可执行文件中;
  • 测试用例的文件名必须以_test.go结尾;
  • 需要使用 import 导入 testing 包;
  • 测试函数的名称要以Test或Benchmark开头,后面可以跟任意字母组成的字符串,但第一个字母必须大写,例如 TestAbc(),一个测试用例文件中可以包含多个测试函数;
  • 单元测试则以(t *testing.T)作为参数,性能测试以(t *testing.B)做为参数;
  • 测试用例文件使用go test命令来执行,源码中不需要 main() 函数作为入口,所有以_test.go结尾的源码文件内以Test开头的函数都会自动执行。

8 结构体

Go 语言中的类型可以被实例化,使用new&构造的类型实例的类型是类型的指针。

结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:

  • 字段拥有自己的类型和值。
  • 字段名必须唯一。
  • 字段的类型也可以是结构体,甚至是字段所在结构体的类型。

8.1 实例化

8.1.1 基本的实例化形式

var ins T

其中,T 为结构体类型,ins 为结构体的实例。

8.1.2 创建指针类型的结构体来实例化

使用 new 关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。

ins := new(T)

其中:

  • T 为类型,可以是结构体、整型、字符串等。
  • ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。

8.1.3 取结构体的地址实例化

在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式如下:

ins := &T{}

其中:

  • T 表示结构体类型。
  • ins 为结构体的实例,类型为 *T,是指针类型。

取地址实例化是最广泛的一种结构体实例化方式

8.2 初始化结构体的成员变量

初始化有两种形式分别是以字段“键值对”形式多个值的列表形式

  • 键值对:键值对形式的初始化适合选择性填充字段较多的结构体;
  • 多个值:多个值的列表形式适合填充字段较少的结构体。

8.2.1 键值对形式的初始化

ins := 结构体类型名{
    字段1: 字段1的值,
    字段2: 字段2的值,}

键值之间以:分隔,键值对之间以,分隔。

提示:结构体成员中只能包含结构体的指针类型,包含非指针类型会引起编译错误。

示例:

type People struct {
    name  string
    child *People
}
relation := &People{
    name: "爷爷",
    child: &People{
        name: "爸爸",
        child: &People{
                name: "我",
        },
    },
}

8.2.2 多个值的列表初始化

ins := 结构体类型名{
    字段1的值,
    字段2的值,}

多个值使用逗号分隔初始化结构体。

8.3 Go语言方法和接收器、为任意类型添加方法

9 接口

接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。

接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节。接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构。

9.1 接口声明的格式

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2}

对各个部分的说明:

  • 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
  • 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略,例如:
type writer interface{
    Write([]byte) error
}

9.2 实现接口的条件

如果一个任意类型 T 的方法集是一个接口类型的方法集的超集【即包含了此接口里所有的方法】,则我们说类型 T 实现了此接口类型。T 可以是一个非接口类型,也可以是一个接口类型。

注意:

是通过另一个类型来实现接口中的方法的,这个类型可以是一个非接口类型,也可以是一个接口类型。一般是用结构体类型或者接口类型。

实现关系在Go语言中是隐式的。两个类型之间的实现关系不需要在代码中显式地表示出来。Go语言中没有类似于 implements 的关键字。Go编译器将自动在需要的时候检查两个类型之间的实现关系。

9.2.1 接口的方法与实现接口的方法格式要完全一致

在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。

9.2.2 接口中所有方法均均需被实现

当一个接口中有多个方法时,只有全部方法都被实现类实现时,接口才能被正确编译并使用。

9.3 一个类型可以实现多个接口

一个类型可以同时实现多个接口,而接口之间彼此独立。

9.4 多个类型可以实现相同的接口

一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。

9.5 Go语言实现日志系统

Go语言实现日志系统

9.6 接口的嵌套组合

  • 在Go语言中,不仅结构体与结构体之间可以嵌套,接口与接口间也可以通过嵌套创造出新的接口。

  • 一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。

9.7 空接口类型

空接口是接口类型的特殊形式,空接口没有任何方法,因此任何类型都无须实现空接口。从实现的角度看,任何值都满足这个接口的需求。因此空接口类型可以保存任何值,也可以从空接口中取出原值。

提示:

空接口类型类似于 C# 或 Java 语言中的 Object、C语言中的 void*、C++ 中的 std::any。在泛型和模板出现前,空接口是一种非常灵活的数据抽象保存和使用的方法。

空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。

10 Go项目的打包部署

Go项目的打包部署

11 并发之goroutine

11.1 goroutine

goroutine:是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。

Go 程序中使用 go 关键字为一个函数创建一个 goroutine。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数。

格式
为一个普通函数创建 goroutine 的写法如下:

go 函数名( 参数列表 )
  • 函数名:要调用的函数名。
  • 参数列表:调用函数需要传入的参数。

使用 go 关键字创建 goroutine 时,被调用函数的返回值会被忽略。如果需要在 goroutine 中返回数据,可以借助通道(channel),通过通道把数据从 goroutine 中作为返回值传出。

11.2 goroutine竞争状态

有并发,就有资源竞争,如果两个或者多个 goroutine 在没有相互同步的情况下,访问某个共享的资源,比如同时对该资源进行读写时,就会处于相互竞争的状态,这就是并发中的资源竞争。

解决办法就是 锁住共享资源 ,atomic 和 sync 包里的一些函数就可以对共享的资源进行加锁操作。比如:sync.Mutex。

12 并发之channel

12.1 channel简介

channel:是Go语言提供的 goroutine 间的通信方式我们可以使用 channel 在两个或多个 goroutine 之间传递消息,但同时只能有一个 goroutine 访问通道进行发送和获取数据。

channel:是类型相关的,也就是说,一个 channel 只能传递一种类型的值,这个类型需要在声明 channel 时指定。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。

Go语言提倡使用通道的方法代替共享内存,当一个资源需要在 goroutine 之间共享时,通道在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。

定义一个 channel 时,也需要定义发送到 channel 的值的类型,注意,通道是引用类型,必须使用 make 创建 channel,格式如下:

通道实例 := make(chan 数据类型)
  • 数据类型:通道内传输的元素类型。
  • 通道实例:通过make创建的通道句柄。

代码示例:

ch1 := make(chan int)                 // 创建一个整型类型的通道
ch2 := make(chan interface{})         // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)             // 创建Equip指针类型的通道, 可以存放*Equip

12.2 使用channel发送、接收数据

通道创建后,就可以使用通道进行发送和接收操作。

12.2.1 往channel里发送数据

发送使用特殊的操作符<-,发送到通道里的数据格式:

通道变量 <-
  • 通道变量:通过make创建好的通道实例。
  • 值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。

代码示例:

// 创建一个空接口通道
ch := make(chan interface{})
// 将0放入通道中
ch <- 0
// 将hello字符串放入通道中
ch <- "hello"

如果是无缓冲的通道,当把数据往通道中发送时,如果没有接收方或者接收方一直都没有接收,那么发送操作将持续阻塞。会报错:fatal error: all goroutines are asleep - deadlock!同样的如果发送方没有往通道中发送数据,接收方也将会一直阻塞,直到通道中有数据。

12.2.2 往channel里发送数据

使用通道接收数据同样使用<-操作符,通道接收注意事项:

  1. 通道的收发操作需在不同的两个 goroutine 间进行。
  2. 接收将持续阻塞直到发送方发送数据。
  3. 每次接收一个元素。
12.2.2.1 阻塞接收数据
data := <-ch

执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。

12.2.2.2 非阻塞接收数据
data, ok := <-ch
  • data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。
  • ok:表示是否接收到数据。

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel进行,可以参见后面的内容。

12.2.2.3 忽略接收到的数据
<-ch

执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步

12.2.2.4 循环接收channel中的数据
for data := range ch {
}

通道 ch 是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过 for 遍历获得的变量只有一个,即上面例子中的 data。
注意:在使用循环接收数据时,实际上是通过for循环一直阻塞监听channel中是否有数据,所以在程序中应该先运行接收方的goroutine程序,后运行发送方的程序。

12.3 Go语言channel超时机制

Go语言没有提供直接的超时处理机制,虽然 select 机制不是专门为超时而设计的,却能很方便的解决超时问题,因为 select 的特点是只要其中有一个 case 已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况。

select 的用法与 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由 case 语句来描述。

select主要是监听读取通道中是否有数据,然后在执行case语句。

与 switch 语句相比,select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个 IO 操作,大致的结构如下:

select {
    case <-chan1:
    // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
    // 如果成功向chan2写入数据,则进行该case处理语句
    default:
    // 如果上面都没有成功,则进入default处理流程
}

在一个 select 语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。

如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。

如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有如下两种可能的情况:

  • 如果给出了 default 语句,那么就会执行 default 语句,同时程序的执行会从 select 语句后的语句中恢复;
  • 如果没有 default 语句,那么 select 语句将被阻塞,直到至少有一个通信可以进行下去。
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值