0%

GOLANG

基础语法

第一个程序

package main

// import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数
import "fmt"

/*
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,
那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )
*/
func main(){
fmt.Println("Hello Go")
}

格式化字符串

Go 语言中使用 fmt.Sprintffmt.Printf 格式化字符串并赋值给新串:

  • Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
  • Printf 根据格式化参数生成格式化的字符串并写入标准输出。
// fmt.Sprintf实例 结果:Code=123&endDate=2020-12-31
package main
import "fmt"

func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)
}
// fmt.Printf实例 结果:Code=123&endDate=2020-12-31
package main
import "fmt"

func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
fmt.Printf(url,stockcode,enddate)
}

占位符

%v:只输出所有的值。
%+v:先输出结构体字段类型,在输出字段值。
%#v:先输出结构体名称,再输出结构体字段类型+值。
%T:输出结构体名称,或输出目标的类型。
%%:输出字面上的百分号。
%b:二进制表示
%c:相应的Unicode码所表示的字符。
%d:十进制表示
%o:八进制表示
%x:十六进制表示,字母形式a-f
%X:十六进制,字母形式A-F
%q:双引号围绕的字符串
%e:科学计数法1.020000e+01
%E:科学计数法1.020000E+01
%f:小数输出,有小数点而无指数
%p:十六进制输出,输出指针类型。
%g:末尾无零的小数输出。
%G:末尾无零的小数输出。
%t:布尔占位符。

%p%x区别:
%x打印的是十六进制数,%p也是十六进制,但%p以0x作为前缀,也就是说%p等于0x拼接%x。

例如:
同一个指针,用%x输出:c00000a0c8 用%p输出:0xc00000a0c8

变量

var identifier type
var identifier1, identifier2 type
package main
import "fmt"
func main() {
var a string = "Runoob"
fmt.Println(a)

var b, c int = 1, 2
fmt.Println(b, c)
}

变量声明

指定变量类型,如果没有初始化,则数值类型变量默认为零值

var v_name v_type
  • 数值类型(包括complex64/128)为 0
  • 布尔类型为 false
  • 字符串为 **””**(空字符串)
  • 以下几种类型为 nil
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

intVal := 1 相等于:

var intVal int 
intVal =1
var intVal int = 1

多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误


// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
var a, b int
var c string
a, b, c = 5, 7, "abc" // 也是允许的

// ----------------- 等价 a, b, c := 5, 7, "abc"

空白标识符

在函数返回值时的使用

package main
import "fmt"

func main() {
_,numb,strs := numbers() //只获取函数返回值的后两个
fmt.Println(numb,strs)
}

//一个可以返回多个值的函数
func numbers()(int,int,string){
a , b , c := 1 , 2 , "str"
return a,b,c
}

// 2 str

常量

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

常量可用len(), cap(), unsafe.Sizeof()计算表达式的值。常量表达式中, 函数必须是内置函数, 否则编译不过:

package main

import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a) // 不能是自定义函数
)

func main(){
println(a, b, c)
}

iota

ota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

package main
import "fmt"

func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
package main
import "fmt"

const (
i=1<<iota // 1 << 0
j=3<<iota // 3 << 1 = 6
k // 3 << 2 = 12
l // 3 << 3 = 24
)

func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}

运算符

位运算符

运算符 描述 实例
& 按位与运算符”&”是双目运算符。 其功能是参与运算的两数各对应的二进位相与。 (A & B) 结果为 12, 二进制为 0000 1100
| 按位或运算符”|”是双目运算符。 其功能是参与运算的两数各对应的二进位相或 (A | B) 结果为 61, 二进制为 0011 1101
^ 按位异或运算符”^”是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 (A ^ B) 结果为 49, 二进制为 0011 0001
<< 左移运算符”<<”是双目运算符。左移n位就是乘以2的n次方。 其功能把”<<”左边的运算数的各二进位全部左移若干位,由”<<”右边的数指定移动的位数,高位丢弃,低位补0。 A << 2 结果为 240 ,二进制为 1111 0000
>> 右移运算符”>>”是双目运算符。右移n位就是除以2的n次方。 其功能是把”>>”左边的运算数的各二进位全部右移若干位,”>>”右边的数指定移动的位数。 A >> 2 结果为 15 ,二进制为 0000 1111

赋值运算符

运算符 描述 实例
= 简单的赋值运算符,将一个表达式的值赋给一个左值 C = A + B 将 A + B 表达式结果赋值给 C
+= 相加后再赋值 C += A 等于 C = C + A
-= 相减后再赋值 C -= A 等于 C = C - A
*= 相乘后再赋值 C *= A 等于 C = C * A
/= 相除后再赋值 C /= A 等于 C = C / A
%= 求余后再赋值 C %= A 等于 C = C % A
<<= 左移后赋值 C <<= 2 等于 C = C << 2
>>= 右移后赋值 C >>= 2 等于 C = C >> 2
&= 按位与后赋值 C &= 2 等于 C = C & 2
^= 按位异或后赋值 C ^= 2 等于 C = C ^ 2
|= 按位或后赋值 C |= 2 等于 C = C | 2

其他运算符

运算符 描述 实例
& 返回变量存储地址 &a; 将给出变量的实际地址。
* 指针变量。 *a; 是一个指针变量

运算符优先级

由上至下代表优先级由高到低:

优先级 运算符
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

条件语句

Go 语言提供了以下几种条件判断语句:

语句 描述
if 语句 if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if…else 语句 if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
if 嵌套语句 你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。
switch 语句 switch 语句用于基于不同条件执行不同动作。
select 语句 select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

函数

函数定义

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
函数体
}

例子

func max(num1, num2 int) int { /* 函数返回两个数的最大值 */
var result int /* 声明局部变量 */

if (num1 > num2) result = num1
else result = num2

return result
}

函数返回多个值

Go 函数可以返回多个值,例如:

package main
import "fmt"

func swap(x, y string) (string, string) {
return y, x
}

func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}

函数参数

调用函数,可以通过两种方式来传递参数:

传递类型 描述
值传递 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

函数用法

函数用法 描述
函数作为另外一个函数的实参 函数定义后可作为另外一个函数的实参数传入
闭包 闭包是匿名函数,可在动态编程中使用
方法 方法就是一个包含了接受者的函数

数组

var arr_name [SIZE] arr_type

以上为一维数组的定义方式。例如以下定义了数组 balance 长度为 10 类型为 float32:

var balance [10] float32

初始化数组

以下演示了数组初始化:

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

我们也可以通过字面量在声明数组的同时快速初始化数组:

balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果数组长度不确定,可以使用 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果设置了数组的长度,我们还可以通过指定下标来初始化元素:

//  将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。

如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:

二维数组

package main
import "fmt"

func main() {
values := [][]int{} // Step 1: 创建数组

// Step 2: 使用 append() 函数向空的二维数组添加两行一维数组
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
values = append(values, row1)
values = append(values, row2)

// Step 3: 显示两行数据
fmt.Println("Row 1")
fmt.Println(values[0])
fmt.Println("Row 2")
fmt.Println(values[1])

// 同上 循环输出
for i := range values {
fmt.Printf("Row: %v\n", i)
fmt.Println(values[i])
}
}

指针

var ip *int        /* ip 指向整型*/
var fp *float32 /* fp 指向浮点型 */
package main
import "fmt"

func main() {
var a int= 20 /* 声明实际变量 */
ip := &a; /* 声明指针变量 */

fmt.Printf("a 变量的地址是: %p\n", &a )

/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %p\n", ip )

/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}

Go 空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。

package main
import "fmt"

func main() {
var ptr *int

fmt.Printf("ptr 的值为 : %x\n", ptr )
}

以上实例输出结果为:

ptr 的值为 : 0

空指针判断:

if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */

结构体

type struct_variable_type struct {
member definition
member definition
}
variable_name := structure_variable_type {value1, value2...valuen}
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
type Books struct {
title string
author string
subject string
book_id int
}

mybook := Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407}

访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

结构体.成员名

结构体指针

你可以定义指向结构体的指针类似于其他指针变量,格式如下:

var book *Books
book = &Book1

使用结构体指针访问结构体成员,使用 “.” 操作符:

book.title

切片(Slice)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义切片

你可以声明一个未指定大小的数组来定义切片:

var identifier []type

切片不需要说明长度。

或使用 make() 函数来创建切片:

var slice1 []type = make([]type, len)

slice1 := make([]type, len)

也可以指定容量,其中 capacity 为可选参数。

make([]T, length, capacity)

这里 len 是数组的长度并且也是切片的初始长度。

切片初始化

s :=[] int {1,2,3} 

直接初始化切片,**[]** 表示是切片类型,**{1,2,3}** 初始化值依次是 1,2,3,其 cap=len=3

s := arr[:] 

初始化切片 s,是数组 arr 的引用。

s := arr[startIndex:endIndex] 

将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。

s := arr[startIndex:] 

默认 endIndex 时将表示一直到arr的最后一个元素。

s := arr[:endIndex] 

默认 startIndex 时将表示从 arr 的第一个元素开始。

s1 := s[startIndex:endIndex] 

通过切片 s 初始化切片 s1。

s :=make([]int,len,cap) 

通过内置函数 make() 初始化切片s,**[]int** 标识为其元素类型为 int 的切片。

len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

以下为具体实例:

package main
import "fmt"

func main() {
var numbers = make([]int,3,5)
printSlice(numbers)
}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

// len=3 cap=5 slice=[0 0 0]

空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0,实例如下:

package main
import "fmt"

func main() {
var numbers []int
printSlice(numbers)

if(numbers == nil) fmt.Printf("切片是空的")
}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v**\n**",len(x),cap(x),x)
}

/*
len=0 cap=0 slice=[]
切片是空的
*/

切片截取

可以通过设置下限及上限来设置截取切片 *[lower-bound:upper-bound]*,实例如下:

package main
import "fmt"

func main() {
/* 创建切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers)

/* 打印原始切片 */
fmt.Println("numbers ==", numbers)

/* 打印子切片从索引1(包含) 到索引4(不包含) */
fmt.Println("numbers[1:4] ==", numbers[1:4])

/* 默认下限为 0 */
fmt.Println("numbers[:3] ==", numbers[:3])

/* 默认上限为 len(s) */
fmt.Println("numbers[4:] ==", numbers[4:])

numbers1 := make([]int,0,5)
printSlice(numbers1)

/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)

/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)
}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

执行以上代码输出结果为:

len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]

append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。

package main
import "fmt"

func main() {
var numbers []int
printSlice(numbers)

/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)

/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)

/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)

/* 创建切片 numbers1 是之前切片的两倍容量 */
numbers1 := make([]int, len(numbers), (cap(numbers))*2)

/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

以上代码执行输出结果为:

len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]

Map

定义 Map

可以使用内建函数 make 或使用 map 关键字来定义 Map:

/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)
// 创建一个空的 Map
m := make(map[string]int)

// 创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)

// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}

// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的nil值

修改元素:

m["apple"] = 5 // 修改键值对
len := len(m) // 获取 Map 的长度
delete(m, "banana") // 删除键值对

// 遍历 Map
for k, v := range m {
fmt.Printf("key=%s, value=%d\n", k, v)
}

循环读取:

// 读取 key 和 value
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}

// 读取 key
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}

// 读取 value
for _, value := range map1 {
fmt.Printf("value is: %f\n", value)
}

接口

/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
/* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}

例子:

package main
import "fmt"

type Phone interface {
call()
}

type NokiaPhone struct {}

func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {}

func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}

func main() {
var phone Phone

phone = new(NokiaPhone)
phone.call() // I am Nokia, I can call you!

phone = new(IPhone)
phone.call() // I am iPhone, I can call you!

}

类型转换

var a int = 10
var b float64 = float64(a)

字符串转换为整数

将一个字符串转换成另一个类型,可以使用以下语法:

var str string = "10"
var num int
num, _ = strconv.Atoi(str)

以上代码将字符串变量 str 转换为整型变量 num。

注意,strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,我们可以使用空白标识符 _ 来忽略这个错误

整数转换为字符串

package main

import (
"fmt"
"strconv"
)

func main() {
num := 123
str := strconv.Itoa(num)
fmt.Printf("整数 %d 转换为字符串为:'%s'\n", num, str)
}

字符串转换为浮点数

package main
import (
"fmt"
"strconv"
)

func main() {
str := "3.14"
num, err := strconv.ParseFloat(str, 64)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Printf("字符串 '%s' 转为浮点型为:%f\n", str, num)
}
}

浮点数转换为字符串

package main
import (
"fmt"
"strconv"
)

func main() {
num := 3.14
str := strconv.FormatFloat(num, 'f', 2, 64)
fmt.Printf("浮点数 %f 转为字符串为:'%s'\n", num, str)
}

接口类型转换

接口类型转换有两种情况:类型断言类型转换

类型断言

类型断言用于将接口类型转换为指定类型,其语法为:

value.(type) 
或者
value.(T)
package main
import "fmt"

func main() {
var i interface{} = "Hello, World"
str, ok := i.(string)
if ok {
fmt.Printf("'%s' is a string\n", str)
} else {
fmt.Println("conversion failed")
}
}
// 'Hello, World' is a string
类型转换

类型转换用于将一个接口类型的值转换为另一个接口类型,其语法为:

T(value)
package main
import "fmt"

type Writer interface {
Write([]byte) (int, error)
}

type StringWriter struct {
str string
}

func (sw *StringWriter) Write(data []byte) (int, error) {
sw.str += string(data)
return len(data), nil
}

func main() {
var w Writer = &StringWriter{}
sw := w.(*StringWriter)
sw.str = "Hello, World"
fmt.Println(sw.str)
}

以上实例中,我们定义了一个 Writer 接口和一个实现了该接口的结构体 StringWriter。然后,我们将 StringWriter 类型的指针赋值给 Writer 接口类型的变量 w。接着,我们使用类型转换将 w 转换为 StringWriter 类型,并将转换后的值赋值给变量 sw。最后,我们使用 sw 访问 StringWriter 结构体中的字段 str,并打印出它的值。

go并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

go 函数名( 参数列表 )  // goroutine 语法格式

Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

package main
import (
"fmt"
"time"
)

func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(10000 * time.Millisecond)
fmt.Println(s)
}
}

func main() {
go say("world") // 线程2
say("hello") // 线程1
}

// 每隔一段时间 几乎同时打印 hello 和 world 顺序不定

channel

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道

ch <- v    // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int) // 默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据

以下实例通过两个 goroutine 并行来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和

package main
import "fmt"

func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}

func main() {
s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收

fmt.Println(x, y, x+y)
}
通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

package main
import "fmt"

func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)

// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2

// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

package main
import "fmt"

func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}

执行输出结果为:

0
1
1
2
3
5
8
13
21
34