0%

Go基本语法

GoLang基本语法

环境配置

  • go下载地址

  • 配置环境变量,命令行输入

    1
    export PATH=/usr/local/go/bin:$PATH
  • 此时可在命令行中使用go命令

    新建一个test.go文件

    1
    2
    3
    4
    5
    6
    7
    package main

    import "fmt"

    func main() {
    fmt.Println("Hello, World!")
    }

    在命令行中输入,即可运行

    1
    go run test.go

入门

go的基本结构和语法

一个简单的go程序:

1
2
3
4
5
6
7
8
9
// 定义包
package main

// 调用包
import "fmt"

func main() {
fmt.Print("Hello, World!")
}
  • 包声明

    必须在非注释的第一行进行包声明

    1
    package main

    每个Go应用程序都必须包含一个main

  • 引入包

    1
    import "fmt"
  • 行分隔符

    go中每一行代表一个语句结束,结尾不需要加像C那样加;

输入输出

Printf 格式化字符串

使用fmt.Printf来格式化输出字符串

1
fmt.Printf(格式化样式, 参数列表…)
格 式 描 述
%s 字符串
%f 浮点数
%d 十进制整型
%b 二进制整型
%o 八进制整型
%x 十六进制整型
%X 十六进制整型,字母大写方式显示
1
2
3
name := "John"
age := 23
fmt.Printf("%s is %d", name, age)

输出:

John is 23

变量和常量

变量

变量名由字母、数字、下划线组成,其中首个字符不能为数字

使用 var 来声明变量

1
var identifier type

可以一次声明多个变量

1
var identifier1, identifier2 type

例如

1
2
var str string = "Hello"
var num1, num2 int = 1, 2

如果没有初始化,则变量默认为零值

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

输出:

0 0 false “”

可以根据值自行判断变量类型

1
var str, num = "Hello", 1

可省略var,使用关键字:=

1
str, num := "Hello", 1

常量

常量不会被修改,数据类型只可以是布尔型、数字型和字符串型。

1
const identifier [type] = value

因为可以通过value来判断数据类型,所有type可省略

  • 显式表示

    1
    const str string = "abc"
  • 隐式表示

    1
    const str = "abc"

可以一次声明多个常量

1
const str, num = "Hello", 1

或者用枚举:

1
2
3
4
5
const (
str = "Hello"
a = 1
b
)

这里,ab都是1b会继承a=1

iota

iota是一个可以被编译器修改的特殊常量,代表了位于const的第几行,如第 n 行 =iota 则为 n

1
2
3
4
5
6
7
8
const (
a = iota // 0
b // 1
c = "hello"
d // "hello"
e = iota // 4
f // 5
)

数据结构

数组

概述

数组,是相同元素类型的集合。

数组由两个维度描述:

  • 元素类型
  • 最多存储的元素个数

只有这两个条件都相同的数组才是同一类型

初始化

访问和赋值

声明数组

1
var variable_name [SIZE] variable_type

示例:

1
var nums [5] int

初始化数组

1
var nums = [5]int{1,2,3,4,5}
1
nums := [5]int{1, 2, 3, 4, 5}

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

1
nums := [...]int{1, 2, 3, 4, 5}

可以通过下标,只初始化特定的几个

1
2
//初始化 nums[1] = 2.1 , nums[3] = 1.6
nums := [5]float64{1: 2.1, 3: 1.6}

多维数组

以二维数组为例

1
var arrayName [ x ][ y ] variable_type

示例:

每行的结尾都要加,

1
2
3
4
nums := [2][3]int{
{0, 1, 2},
{3, 4, 5},
}

1
2
3
4
5
6
nums := [][]int{}

row1 := []int{0, 1, 2}
row2 := []int{3, 4, 5}
nums = append(nums, row1)
nums = append(nums, row2)

数组作为函数参数

一维数组做参数:

  • 设定数组大小

    1
    2
    3
    func f1(nums [10]int)  {
    ...
    }
  • 未设定数组大小

    1
    2
    3
    func f1(nums []int)  {
    ...
    }

指针

go中指针的规则与C类似

&用来取地址,*用来取内容

定义指针:

1
var var_name *var-type

空指针

当一个指针被定义后没有分配到任何变量时,即为一个空指针 nil

指针数组 & 数组指针

指针数组 数组指针
是一个数组 是一个指针
每个元素都是一个指针 指向一个数组
1
2
3
4
5
n1, n2, n3 := 1, 2, 3
nums := [3]int{n1, n2, n3} //普通数组
ptrs := [3]*int{&n1, &n2, &n3} //指针数组
nums_ptr1 := &nums //数组指针
var nums_ptr2 *[3]int = &nums //数组指针

结构体

定义结构体

1
2
3
4
5
6
type struct_variable_type struct {
member definition
member definition
...
member definition
}

声明结构体变量

1
variable_name := structure_variable_type {value1, value2...valuen}
1
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

通过 . 来访问结构体成员

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

type Book struct {
title string
author string
id int
}

func main() {
book := Book{id: 1, title: "BookName", author: "John"}
fmt.Println(book.id, book.title, book.author)
}

结构体指针

(与C不同)同样用 . 来访问结构体成员

1
2
book_ptr := &book
fmt.Println(book_ptr.id, book_ptr.title, book_ptr.author)

Slice 切片

与C++中的 vector 类似,长度不固定的动态数组,可以追加元素

定义切片

  • 定义一个未指定大小的数组

    1
    var identifier []type
  • 也可以使用 make() 函数来创建切片

    1
    slice1 := make([]type, len, capacity)

初始化切片

  • 直接初始化切片,声明一个未指定大小的数组

    1
    s := []int{1,2,3}
  • 通过引用数组/切片初始化切片

    如有一数组/切片arr,可以通过引用这个数组/切片来初始化切片

    引用全部的数组

    1
    s := arr[:]

    引用从下标 startIndexendIndex-1 的部分

    1
    s := arr[startIndex:endIndex]

    引用从下标 startIndex 到 最后一个元素 的部分

    1
    s := arr[startIndex:]

    引用从 第一个元素 到 endIndex-1 的部分

    1
    s := arr[:endIndex]

len() 和 cap()

  • len(s) 获取切片长度

  • cap(s) 获取切片容量

1
2
s := make([]int, 3, 5)
fmt.Println(len(s), cap(s), s) //3 5 [0 0 0]

append()

append扩展切片,原切片不变,返回的新切片在原切片上加上扩展项

1
new_slice = append(old_slice []Type, elems ...Type)

append()的原理:

如果 old slice 的 capacity够加,则 new slice 直接在 old slice 的内存上追加,共享内存;
如果capacity不够加,则 new slice 不与 old slice 共享内存,而是另开一片内存,复制 old slice 的数据

示例:

old slice 的 capacity 不够加

1
2
3
4
5
s1 := []int{0}
s2 := append(s1, 1, 2) //s1的cap不够,s2与s1不共内存
fmt.Println(s1, s2) //[0] [0 1 2]
s1[0] = -1 //s2与s1不共内存,s1修改,不影响s2
fmt.Println(s1, s2) //[-1] [0 1 2]

old slice 的 capacity 够加

1
2
3
4
5
s1 := make([]int, 1, 3)
s2 := append(s1, 1, 2) //s1的cap足够,s2与s1共内存
fmt.Println(s1, s2) //[0] [0 1 2]
s1[0] = -1 //s4与s3共内存,s3修改,s4也会相应变化
fmt.Println(s1, s2) //[-1] [-1 1 2]

copy()

copy复制切片,必须创造一个比原切片 capacity 更大的新切片,才能复制过来

1
copy(new_slice, old_slice)

copy= 的区别:

  • = 赋值拷贝,会将原来slice的地址拷贝,新旧slice共享内存
  • copy 将slice内容进行拷贝,新旧slice不共享内存

1
2
3
4
5
6
7
8
9
10
11
s1 := []int{0, 1, 2}

s2 := s1 //`=`赋值,指向同一片内存

s3 := make([]int, len(s1), 2*cap(s1))
copy(s3, s1) //copy复制,不共享内存

fmt.Println(s1, s2, s3) //[0 1 2] [0 1 2] [0 1 2]

s1[0] = -1
fmt.Println(s1, s2, s3) //[-1 1 2] [-1 1 2] [0 1 2]

Map 集合

无序的键值对,与C++中的map类似,但与C++中的map不同,

定义 Map

  • 使用 make 函数

    1
    map_variable := make(map[KeyType]ValueType, initialCapacity)

    initialCapacity 可选填,用于指定 Map 的初始容量。Map 的容量是指 Map 中可以保存的键值对的数量。

    示例:

    1
    2
    3
    4
    5
    // 创建一个空的 Map
    m1 := make(map[string]int)

    // 创建一个初始容量为 10 的 Map
    m2 := make(map[string]int, 10)
  • 使用 map 关键字

    1
    2
    3
    4
    5
    6
    7
    m1 := map[string]int{}

    m2 := map[string]int{
    "a": 1,
    "b": 2,
    "c": 3,
    }

常用操作

  • 获取元素

    1
    2
    3
    v1 := m["a"] 

    v2, ok := m["d"] // 如果键不存在,v2为该类型的零值,ok=false
  • 修改元素

    1
    m["a"] = 2
  • 删除元素

    1
    delete(m, "a")
  • 获取长度

    1
    l := len(m)
  • 遍历 map

    1
    2
    3
    for k, v := range m {
    ...
    }

控制结构

条件语句

if 语句

与C中类似,但是条件语句不需要用括号包住

1
2
3
4
5
6
7
if condition1 {
...
} else if condition2 {
...
} else {
...
}

switch 语句

1
2
3
4
5
6
7
8
switch expression {
case val1:
...
case val2:
...
default:
...
}
  • 先执行 expression
  • 从上往下依次找匹配的 case
  • go中的 switch 默认自带 break,匹配成功后,就不会执行其他 case
  • 如果希望匹配成功后继续执行后面的 case,可以使用 fallthrough

示例1:

1
2
3
4
5
6
7
8
switch tag {
case 0:
f1()
case 1, 2:
f2()
default:
f3()
}

示例2:

expression 可以为空

1
2
3
4
5
6
7
8
switch {
case tag == 0:
f1()
case tag == 1, tag == 2:
f2()
default:
f3()
}

Type Switch

switch还可以用来判断某个 interface 变量中实际存储的变量类型

1
2
3
4
5
6
7
8
9
10
11
12
var x interface{}

switch i := x.(type) {
case nil:
f1()
case int:
f2()
case float64:
f3()
default:
f4()
}

fallthrough

如果在 case 的最后加上了 fallthrough,则无论紧接着的下一条 case 为 ture 还是 false,都会执行。

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tag := 1

switch {
case tag == 0: // false
fmt.Println("0") // 不执行
fallthrough
case tag == 1: // true
fmt.Println("1") // 执行
fallthrough // 下一个case无条件执行
case tag == 2: // 不必判断,直接执行
fmt.Println("2") // 执行
// 这里其实有个默认的 break
case tag == 3:
fmt.Println("3") // 不执行
fallthrough
default:
fmt.Println("default") // 不执行
}

输出:
1
2

示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tag := 1

switch {
case tag == 0: // false
fmt.Println("0") // 不执行
fallthrough
case tag == 1: // true
fmt.Println("1") // 执行
fallthrough // 下一个case无条件执行
case tag == 2: // 不必判断,直接执行
fmt.Println("2") // 执行
fallthrough // 下一个case无条件执行
case tag == 3: // 不必判断,直接执行
fmt.Println("3") // 执行
fallthrough // 下一个case无条件执行
default: // 不必判断,直接执行
fmt.Println("default") // 执行
}

输出:
1
2
3
default

select 语句

select 类似于 switch,但是 select 只能用于通道操作

  • select 会监听所有通道,一旦有通道准备好,就随机选择其中一个通道执行
  • 如果所有通道都没准备好,则执行 default
  • 如果没有 defaultselect 将阻塞,直到某个通道可以运行

多个case匹配时,switch从上至下匹配第一个,select随机匹配一个

1
2
3
4
5
6
7
8
9
10
11
select {
// 随机执行一个准备好了的通道
case <- channel1:
...
case value := <- channel2:
...
case channel3 <- value:
...
default:
// 所有通道都没有准备好,执行的代码
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

func main() {
// 定义两个通道
ch1 := make(chan string)
ch2 := make(chan string)

// 启动两个 goroutine,分别从两个通道中获取数据
go func() {
for {
ch1 <- "from 1"
}
}()
go func() {
for {
ch2 <- "from 2"
}
}()

// 使用 select 语句非阻塞地从两个通道中获取数据
for i := 0; i < 10; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
// 如果两个通道都没有可用的数据,则执行这里的语句
fmt.Println("no message received")
}
}
}

输出:
no message received
from 1
from 2
from 1
from 2
no message received
no message received
from 2
from 1
no message received

循环语句

for 语句

go的 for 语句有多种用法:

  • 与C的 for 相似的用法

    1
    2
    3
    for init; condition; post {
    ...
    }
  • 与C的 while 相似的用法

    只留下 condition 项,则只需判断 condition

    1
    2
    3
    for condition {
    ...
    }
  • 与C的 while(1) 相似的用法

    三项都不填,则无限循环

    1
    2
    3
    for {
    ...
    }
  • range格式

    for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环

    1
    2
    3
    for key, value := range oldMap {
    newMap[key] = value
    }

    也可以只提取 key 或者 value

    1
    2
    3
    for key := range oldMap {
    ...
    }

    只提取 value 时,需要用 _, 占掉 key 的位置

    1
    2
    3
    for _, value := range oldMap {
    ...
    }

break

与C中的break用法相似,但多一个标号的功能:

在多重循环中,可以使用标号 label 跳出指定的循环。

示例:

不加标号时,与C一样,只会 break 掉最里面的那层循环

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ { // break掉这层循环
fmt.Printf("i = %d, j = %d\n", i, j)
break
}
}
}

输出:
i = 0, j = 0
i = 1, j = 0
i = 2, j = 0

加上标号后,会 break 掉标号的那层循环

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
re:
for i := 0; i < 3; i++ { // break掉标号re这层循环
for j := 0; j < 3; j++ {
fmt.Printf("i = %d, j = %d\n", i, j)
break re
}
}
}

输出:
i = 0, j = 0

continue

break 一样,可以通过标号 label 指定需要 continue 的循环

函数

函数的定义

1
2
3
func function_name( [parameter list] ) [return_types] {
...
}
  • parameter list 参数列表,选填,可以无参,格式为 parameter_name1 type1, parameter_name2 type2, ...
  • return_types 返回类型,选填,可以无返回值(相当于C中的void),也可以返回多个值,格式为 (type1, type2, ...)

示例1:

1
2
3
4
5
6
7
func max(num1, num2 int) int {
if num1 > num2 {
return num1
} else {
return num2
}
}

示例2:

1
2
3
func swap(x, y string) (string, string) {
return y, x
}
1
y, x := swap("hello", "world")

引用传递参数

与C中的指针类似,传递地址指针

1
2
3
4
5
func swap(x *int, y *int) {
temp := *x
*x = *y
*y = temp
}

函数变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
// 声明函数变量
findMax := func(num1, num2 int) int {
if num1 > num2 {
return num1
} else {
return num2
}
}

max := findMax(1, 2)
fmt.Println(max)
}

闭包

闭包包含两点:

  • 存在函数外部定义,但在函数内部引用的自由变量

  • 脱离了形成闭包的上下文,闭包也能照常使用这些自由变量,通常称这些自由变量为捕获变量

go语言中函数是头等对象,闭包可以充当C++中类的变量功能,也经常称闭包为有状态的函数

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

// getSequence函数的返回值为一个函数
func getSequence() func() int {
i := 0
// 闭包函数
return func() int {
//返回函数内部,使用了返回函数外部定义的变量i
i++
return i
}
}

func main() {
//声明一个函数变量,此时会执行getSequence函数,令i=0,并定义一个累加函数nextNumber
nextNumber1 := getSequence()
nextNumber2 := getSequence()

// 即使getSequence执行结束,依旧可以通过nextNumber调用闭包函数,并使用i
// nextNumber每调用一次,i加一
fmt.Println(nextNumber1()) // 输出1
fmt.Println(nextNumber1()) // 输出2
fmt.Println(nextNumber1()) // 输出3

//两个闭包函数互不影响
fmt.Println(nextNumber2()) // 输出1
fmt.Println(nextNumber2()) // 输出2
fmt.Println(nextNumber2()) // 输出3
}
  • getSequence返回一个闭包函数

  • 变量i就是在闭包外定义,闭包内使用的变量

  • 即使脱离了闭包的上下文,在main中,也可以调用nextNumber闭包函数,并使用定义在getSequence中的局部变量变量i

  • nextNumber1 := getSequence()nextNumber2 := getSequence() 分别独立,各自的捕获变量i不影响

方法

在其他语言中,函数和方法是一样的,但在go中有所区别

方法是一种有接收者的特殊函数,可以实现C++类的函数功能

接受者可以是命名类型或者结构体类型的一个值或者是一个指针

1
2
3
func (variable_name variable_data_type) function_name() [return_type]{
...
}
  • variable_name 接受者名
  • variable_data_type 接受者类型

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

// 圆的结构体
type Circle struct {
radius float64
}

// 方法,接受者必须为为 c Circle
func (c Circle) getArea() float64 {
return 3.14 * c.radius * c.radius
}

func main() {
var c1 Circle
c1.radius = 1
fmt.Println(c1.getArea()) //调用求面积的方法
}

类型转换

数值类型转换

格式:

1
type_name(expression)

示例:

1
2
var v1 int = 10
var v2 float64 = float64(v1)

字符串类型转换

  • string 转 int

    使用 strconv.Atoi 将 string 转换为 int,第二个返回值为可能发生的错误,可以用 _ 来忽略这个错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package main

    import (
    "fmt"
    "strconv"
    )

    func main() {
    str := "10"
    num, err := strconv.Atoi(str)

    if err != nil {
    fmt.Println(err)
    } else {
    fmt.Println(num)
    }
    }
  • int 转 string

    使用 strconv.Itoa 将 int 转换为 string

    1
    2
    num := 10
    str := strconv.Itoa(num)
  • string 转 float

    使用 strconv.ParseFloat 将 string 转换为 float,第二个参数为 bitSize,用来选择究竟是64位还是32位的 float

    1
    2
    str := "3.14"
    num, err := strconv.ParseFloat(str, 64)

接口类型转换

接口

go 通过接口实现了C++中多态的效果。

  • 接口把所有的具有共性的方法定义在一起

  • 隐式实现,不用声明某个struct实现了那个接口,如果一个struct实现了一个接口定义的所有方法,那么它就自动地实现了该接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 定义接口 */
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] {
/* 方法实现*/
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

// 接口声明
type Sleeper interface {
sleep() //声明方法
}

type Cat struct {
name string
}

type Dog struct {
name string
}

// Cat和Dog都分别实现了Sleeper的sleep方法
func (c Cat) sleep() {
fmt.Printf("Cat %s is sleeping\n", c.name)
}

func (d Dog) sleep() {
fmt.Printf("Dog %s is sleeping\n", d.name)
}

// AnimalSleep函数实现了一个函数多个状态,也就是多态
func AnimalSleep(s Sleeper) {
s.sleep()
}

func main() {
var s Sleeper

s = Cat{name: "cici"}
AnimalSleep(s) //Cat cici is sleeping

s = Dog{name: "dodo"}
AnimalSleep(s) //Dog dodo is sleeping
}

定义一个 Sleeper 接口,CatDog 都实现了Sleeper 接口的所有方法,所以隐式实现了接口。

如此 AnimalSleep() 函数实现了一个函数多个状态,也就是多态

错误处理

函数定义时,使用 errors.New 返回错误信息

1
2
3
4
5
6
7
8
9
func Sqrt(f float64) (float64, error) {
// 错误时,返回错误信息
if f < 0 {
return 0, errors.New("math: square root of negative number")
}

// 实现
...
}

调用函数时,接收返回的错误信息err

1
2
3
4
5
result, err:= Sqrt(-1)

if err != nil {
fmt.Println(err)
}

并发编程

goroutine 线程

goroutine 是轻量级线程,通过 go 关键字开启,如此可以同时多个线程并发进行。

同一个程序中的所有 goroutine 共享同一个地址空间。

格式:

1
go func(x, y, z)

如此,就开启了一个新的线程 func(x, y, z)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"time"
)

func eat() {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println("eating")
}
}

func drink() {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println("drinking")
}
}

func main() {
go eat() // 开启一个eat线程
drink() // eat和drink两个线程并发执行
}

输出:
drinking
eating
drinking
eating
eating
drinking

可以看出 eat 和 drink 两个线程并发执行,一下输出 eating,一下输出 drinking

channel 通道

通道是用来传递数据的一种数据结构,可用于两个 goroutine 之间通过传递值来同步运行和通讯

发送和接收

<- 来发送和接收数据

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

定义通道

利用 chan 关键字定义通道,使用 make 创建一个引用,当复制一个channel或用于函数参数传递时,实际只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象

  • 不带缓冲区的通道(默认)

    1
    ch := make(chan int)

    如此定义了一个不带缓冲区的通道,通道可以传递 int 参数

  • 带缓冲区的通道

    1
    ch := make(chan int, 100)

    如此定义了一个带缓冲区的通道,缓冲区大小为100

默认不带缓冲区,发送端发送数据,同时必须有接收端相应的接收数据。如果迟迟没有接收,发送方会阻塞直到接收方从通道中接收了值。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
ch := make(chan int) //没有缓冲区的通道
ch <- 1 //这里发送端会直接卡死,一直卡在这里等待接收端的出现
fmt.Println(<-ch) //无法运行到此
}

带缓冲区的通道允许发送端和接收端异步,发送端可以先把数据放进缓存区内,发送端进程进行向下进行,不需要等待接收端接收。

但如果缓冲区满了,发送端同样会像不带缓冲区的通道那样卡住,直到有接收方从通道中接收了值。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
ch := make(chan int, 1) //有大小为1缓冲区的通道
ch <- 1 //1放入缓冲区,不会卡住
fmt.Println(<-ch) //可以运行输出1
}

关闭通道

关闭通道后,不会再发送数据到通道上了

1
close(ch)

接收端在接收时,可以通过额外的第二个变量来判断通道是否关闭

1
v, ok := <- ch

遍历通道

与数组、切片相同,使用 range 关键字来遍历通道

1
2
3
for v := range ch {
...
}

规则:

  • 遍历一个空的通道(nil)时,阻塞
  • 遍历一个 阻塞 && 未关闭 的通道(nil)时,阻塞
  • 遍历一个 阻塞 && 已关闭 的通道(nil)时,不做任何操作
  • 遍历一个 非阻塞 && 未关闭 的通道(nil)时,接收所有缓存数据,然后阻塞
  • 遍历一个 非阻塞 && 已关闭 的通道(nil)时,接收所有缓存数据,然后返回

上下文

问题记录

单引号、双引号、反引号

  • 单引号 '':包裹字符
  • 双引号 "":包裹字符串,会解析其中的转义符
  • 反引号 ````:包裹字符串,不会解析其中的转义符
1
2
fmt.Print("Hello, World!\n") //双引号,会解析其中的转义符
fmt.Print(`Hello, World!\n`) //反引号,不会解析其中的转义符

输出:

Hello, World!
Hello, World!\n%

Go modules

go modules 是 go 的依赖包管理工具

go 的包管理发展

在引入 go modules 之前,go 管理依赖包先是用 GOPATH,后来用 go vender

GOPATH

go path 理论上并不算是包管理工具,需要手动管理依赖包,写程序代码必须放在 $GOPATH/src 目录下,且依赖包没有版本可言

go vendor

解决了包管理问题,所有依赖包下载到项目的 vendor 目录下

go modules 的使用

首先要确定 go 语言的版本,至少需是 v1.11 以上版本

1
go version

环境设置

查看 go 的环境变量

1
go env

其中与 Go Modules 相关的环境设置如下

GO111MODULE

1
go env -w GO111MODULE=on

参考