一、 前言

Go编程语言是一个开源项目,它使程序员更具生产力。Go语言具有很强的表达能力,它简洁、清晰而高效。得益于其并发机制,用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型系统则使程序结构变得灵活而模块化。

Go代码编译成机器码不仅非常迅速,还具有方便的垃圾收集机制和强大的运行时反射机制,它是一个快速的、静态类型的编译型语言,感觉却像动态类型的解释型语言。

第一个go程序
1
2
3
4
5
6
7
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world!!!gogogogo")
}

解释:如果是为了将代码编译成一个可执行程序,那么package必须时main如果是为了将代码编译成库,那么pacakge则没有限制Go中所有的代码都应该隶属一个包。

fmtgo的一个系统库fmt.Println()则可以打印输出,如果想要运行程序,执行命令go run 程序名 在一个可执行程序中只能只有一个main函数。

go的结构开发规范

好的代码规范非常重要,这样当你看别人的代码或者别人看你的代码的时候就能很清楚的明白,下面是go程序基本的结构规范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//当前程序的包名
package main
//导入其他的包
import "fmt"
//常量的定义
const PI =3.14
//全局变量的声明和赋值
var name = "gopher"
//一般类型声明
type newType int
//结构的声明
type gopher struct{}
//接口的声明
type golang interface{}
//函数
func funcName(a type1, b type2) (c type1, d type2) {
return c, d
}

二、基础语法

2.1 变量

函数内声明的为局部变量,作用域为函数体,函数外声明的变量为全局变量,作用域为包。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 声明:
var a int // 声明 int 类型的变量
var b [10] int // 声明 int 类型数组
var c []int // 声明 int 类型的切片
var d *int // 声明 int 类型的指针
// 赋值:
a = 10
b[0] = 10
// 同时声明与赋值
var a = 10
//等价于
a := 10
a,b,c,d := 1,2,true,"goodboy"

2.2 常量

1
2
3
4
5
6
7
8
9
10
11
12
const filename =  "ab"
const a,b = 3,4 //常量可作为各种类型调用,
const(
java = 1
php = 2
python = 3
)
const(
java = iota // 自增值,初始为0
php
python
)

2.3 条件语句与循环

if

1
2
3
4
5
6
7
8
9
10
11
if a > 100 {
return 100
}else if a >50 {
return 50
}else{
return 0
}
if a,b := 1,2; a+b>3{
fmt.Println(a,b)
}
fmt.Println(a,b) // 错误! a,b的是 if 条件里定义的,作用域仅限于 if 中使用

利用if语句判断读取文件时是否有异常
1
2
3
4
5
6
7
8
9
10
11
12
13
import (
"io/ioutil"
"fmt"
)
func main() {
const filename = "2.txt"
content, err := ioutil.ReadFile(filename)
if(err!=nil){
fmt.Println(err)
}else {
fmt.Printf("%s", content)
}
}

switch go中的switch不需要手动break

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var grade string = "B"
switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
switch {
case grade == "A" :
fmt.Printf("优秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" );
}
fmt.Printf("你的等级是 %s\n", grade );

for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 赋值语句;判断语句;递增语句
for i:=100; i>0; i--{
fmt.Println(i)
}
// 无赋值
func test(n int){
for ; n>0 ; n/=2 {
fmt.Println(n);
}
}
// 仅赋值
scanner := bufio.NewScanner(file)
for scanner.Scan(){
fmt.Println(scanner.Text);
}
// 死循环
for{
fmt.Println(1);
}

for range语句

1
2
3
4
5
str := "hello 世界"
for i,v := range str {
fmt.Println("index[%d] val[%c] len[%d]\n",i,v,len([]byte(string(v))))
}
//这里需要注意一个问题,range str 返回的是两个值,一个是字符串的下标,一个是字符串中单个字符

label

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

import "fmt"

func main() {
LABEL1:for i:=0;i<5;i++{
for j:=0;j<5;j++{
if j == 4{
continue LABEL1
}
fmt.Printf("i is :%d and j is:%d\n",i,j)
}
}
}

代码中我们在continue 后面添加了一个LABEL1这样当循环匹配到j等于4的时候,就会跳出循环,重新回到最外成i的循环,而如果没有LABEL1则就会跳出j的本次循环,执行j++进入到j的下次循环

2.4 函数

2.4.1 函数声明

1
2
3
4
5
6
7
//语法:func 函数名(参数列表)(返回列表){}
//一些实例
func add(){}
func add(a int,b int){}
func add(a int,b int) int {}
func add(a int,b int) (int,int) {}
func add(a,b int)(int,int){}

2.4.2 go函数的特点

  • 不支持重载,一个包不能包含两个名字一样的函数
  • 函数是一等公民,函数也是一种类型,一个函数可以赋值给变量
  • 匿名函数
  • 多返回值

下面通过例子来演示第二个特点

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

import (
"fmt"
)

type op_func func(int,int) int

func add(a,b int) int {
return a + b
}

func operator(op op_func,a,b int) int{
return op(a,b)
}

func main() {
c := add
sum := operator(c,100,200)
fmt.Println(sum)
}

2.4.3 可变参数

表示0个或多个参数

1
func add(arg ...int) int {}

表示1个或多个参数

func add(a int,arg ...int) int {}

其中arg是一个slice,我们可以通过arg[index]获取参数,通过len(arg)可以判断参数的个数

2.5 指针

go语言的参数传递是值传递,无论是值传递还是引用传递,传递给函数的都是变量的副本,不过值传递的是值的拷贝,引用传递是传递的地址的拷贝,一般来说,地址拷贝更为高效,而值拷贝取决于拷贝的对象的大小,对象越大,则性能越低
1
2
3
4
5
6
7
8
func main() {
a,b:=1,2
swap_test(&a,&b)
}
func swap_test(a,b *int) {
fmt.Println(a, *b) // 0xc420014050 2
}
// 理解: a,b *int 存的是 int 类型值的地址,当对指针类型的变量 *a 时,就是取出地址对应的值

普通的类型,变量存的就是值,也叫值类型 获取变量的地址,用&, 指针类型,变量存的是一个地址,这个地址存的才是真正的值 获取指针类型所指向的值,用,例如:var p int, 使用 *p获取p指向值 通过下面的代码例子理解:

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

import "fmt"

func main() {
var a int = 10
//通过&a打印a的指针地址
fmt.Println(&a)
//定义一个指针类型的变量p
var p *int
//讲a的指针地址复制给p
p = &a
fmt.Println(*p)
//给指针p赋值
*p = 100
fmt.Println(a)
}

三、内建容器

3.1 数组

定义数组

1
2
3
4
5
6
7
8
var arr [3]int              // 会初始化为 [0,0,0]
arr := [3]{1,2,3}
arr := [...]{1,2,3,4,5}
arr := [2][4]int // 2行4列

b := [2]string{"Penn", "Teller"}
//等价
b := [...]string{"Penn", "Teller"}

遍历数组

1
2
3
4
arr := [3]{1,2,3}
for k,v := range(arr){
fmt.Println(k, v) // k为索引,v为值,0,1 1,2 2,3
}

数组作为函数参数
按值传递,,注意,[5]int[10]int 是不同的类型 ,go 语言一般不直接使用数组,而是使用切片。

1
2
3
4
5
6
7
8
9
func printArr(arr [5]int)  {
for k,v:=range (arr){
fmt.Println(k,v)
}
}
func main() {
arr :=[5] int {6,7,8,9,10}
printArr(arr)
}

3.2 切片

Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。 切片有些类似于其他语言中的数组,但是有一些不同寻常的特性。类似于python中的list用法。 本节将深入切片的本质,并讲解它的用法。

切片是数组的“视图”,即引用
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
// 前开后闭
arr := [...]{0,1,2,3,4,5,6,7}
arr1 := arr[1:2] // 1
arr2 := arr[:5] // 0,1,2,3,4
arr3 := arr[2:] // 2,3,4,5,6,7
arr4 := arr[:] // 0,1,2,3,4,5,6,7

// 切片的切片依然是对一个数组的引用
func updateArr(arr []int) {
arr[0] = 100
}
func main() {
arr :=[5] int {0,1,2,3,4}
arr1 := arr[1:3]
arr2 := arr1[0:3]
fmt.Println(arr,arr1,arr2) // [0 1 2 3 4] [1 2] [1 2 3]
updateArr(arr1)
fmt.Println(arr,arr1,arr2) // [0 100 2 3 4] [100 2] [100 2 3]
}
// 查看扩展
arr :=[5] int {0,1,2,3,4}
arr1 := arr[1:3]
arr2 := arr1[0:3]
fmt.Println(arr1,len(arr1),cap(arr1)) // [1 2] 2 4
fmt.Println(arr2,len(arr2),cap(arr2)) // [1 2 3] 3 4

直接创建切片

1
2
3
s := []int{1,2,3}       
var s []int // 会初始化为 nil
s := make([]int, 16, 32) // make(切片类型,切片长度,切片cap长度)

添加元素

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
// 若添加元素个数不超过cap值,则对原数组进行修改
arr :=[5] int {0,1,2,3,4}
arr1 := arr[1:3]
arr2 := append(arr1, 10, 11)
fmt.Println(arr1,arr2,arr) // [1 2] [1 2 10 11] [0 1 2 10 11]
// 若添加元素个数超过cap值,则开辟新数组,拷贝并添加
arr :=[5] int {0,1,2,3,4}
arr1 := arr[1:3]
arr2 := append(arr1, 10, 11, 12)
fmt.Println(arr1,arr2,arr) // [1 2] [1 2 10 11 12] [0 1 2 3 4]
func main() {
var s []int
for i:=0; i<10; i++ {
s = append(s,i)
fmt.Println(s, cap(s))
}
}
结果:(当cap超出,就会重新分配cap值更大的新数组)
[0] 1
[0 1] 2
[0 1 2] 4
[0 1 2 3] 4
[0 1 2 3 4] 8
[0 1 2 3 4 5] 8
[0 1 2 3 4 5 6] 8
[0 1 2 3 4 5 6 7] 8
[0 1 2 3 4 5 6 7 8] 16
[0 1 2 3 4 5 6 7 8 9] 16

copy

1
2
3
4
s1 := []int{0,1,2,3}
s2 := make([]int, 6)
copy(s2,s1)
fmt.Println(s1,s2) // [0 1 2 3] [0 1 2 3 0 0]

3.3 Map

定义

1
2
3
4
5
6
7
8
m := map[string]int{}            // nil
var m map[string]string // nil
m := make(map[string]string) // empty map
m2 := map[string]string{
"name":"zy",
"age":"10",
}
fmt.Println(m2) // map[name:zy age:10]

遍历

`map`是无序的`hash map`,所以遍历时每次输出顺序不一样
1
2
3
4
5
6
7
m := map[string]string{
"name":"zy",
"age":"10",
}
for k,v := range m{
fmt.Println(k,v)
}

取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
m := map[string]string{
"name":"zy",
"age":"10",
}
name := m["name"]
fmt.Println(name) // “zy”
// 获取一个不存在的值
value := m["aaa"]
fmt.Println(value) // “” 返回一个空值

// 判断key是否存在
value, ok := m["aaa"]
fmt.Println(value, ok) // "" false

// 标准用法:
if v,ok:= m["name"]; ok{
fmt.Println(v)
}else{
fmt.Println("key not exist!")
}
//del
delete(m, "name")

四、面向对象

go语言的面向对象仅支持封装,不支持继承和多态

4.1 结构体

定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义一个结构体
type treeNode struct {
value int
left,right *treeNode
}
func main() {
root := treeNode{1,nil,nil}
node1 := treeNode{value:3}
root.left = &node1
root.left.right = new(treeNode) // 内建函数初始化node
nodes := []treeNode{
{1,nil,nil},
{2,&root,&node1},
}
fmt.Println(nodes[1].left.left.value) // 3
}

自定义工厂函数

由于没有构造函数,所以可以用工厂函数代替
1
2
3
4
5
6
7
func createNode(value int) *treeNode {
return &treeNode{value:value}
}
func main(){
node := createNode(10)
fmt.Println(node) // &{10 <nil> <nil>
}

结构体方法

结构体方法并不是写在结构体中,而是像函数一样写在外面,它实际上久是定义了[接收对象]的函数
由于本质依然是函数,所以也是按值传递,若要改变对象,用指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type treeNode struct {
value int
left,right *treeNode
}
// func (接收对象) 方法名(参数) 返回值{}
func (node treeNode) get() int{
return node.value
}
func (node *treeNode) set(value int) {
node.value = value
}
func main() {
root := treeNode{2,nil,nil}
res := root.get()
fmt.Println(res) // 2
}

4.2 封装

名字一般用 CamelCase
首字母大写是 public 方法
首字母小写是 private 方法(2和3 对于变量常量也依然适用)

每个目录只有一个包 (package) 
main包包含程序入口 
为某结构体定义的方法必须放在同一个包内,但可以放不同文件
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

/* 目录结构
go
|__tree1 // 为了讲解,目录名,文件名,包名不同
|——treeNode.go
|__main
|_main.go
*/
// treeNode.go
package tree
type TreeNode struct { // 外部可用的结构体
value int // 私有属性
left TreeNode
right TreeNode
}
func (node TreeNode) Get() int{ // 公有方法
return node.Value
}
func (node *treeNode) Set(value int) {
node.value = value
}
// main.go
package main
import "../../tree1"
func main() {
node := tree.TreeNode{}
node.Set(2)
res := node.Get(); // 2
}
// 总结:import 包所属目录名,就可以使用 包名.结构体/方法 ,与目录下的文件名无关

没有继承

go语言没有继承,如何扩展系统类型或者自定义类型呢?
1. 定义别名
2. 使用组合

4.3 接口

接口定义

1
2
3
type xxx interface{
FuncName() string // 定义接口方法与返回类型
}

实现

结构体不需要显示“实现”接口,只要定义好接口方法即可
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
// interface/interface.go
package file
type File interface {
Read() string
Write(str string)
}
// interface/implament.go
// File1 结构体实现了接口规定的方法
package file
type File1 struct {
Content string
}
func (file File1) Read() string{
return file.Content
}
func (file File1) Write(str string) {
file.Content = str
}
// interface/entry/main.go
package main
import (
"../../interface"
"fmt"
)
func get(f file.File) string { // 只有实现了 File 接口的结构体实例才能调用此方法
res := f.Read()
return res
}
func main() {
f := file.File1{"good"}
fmt.Println(get(f))
}

类型

查看类型: i.(type)
1
2
3
4
5
var i AnimalInterface       // 定义变量 i 是动物接口类型
i = Cat{"cat"} // 假设 Cat 结构体实现了 AnimalInterface 接口
i.(type) // Cat
i = Dog{"dog"} // 假设 Dog 结构体实现了 AnimalInterface 接口
i.(type) // Dog
约束接口类型:i.(xxx)
1
2
3
4
5
6
7
8
var i AnimalInterface       // 定义变量 i 是动物接口类型
i = Cat{"cat"} // 假设 Cat 结构体实现了 AnimalInterface 接口
cat := i.(Cat) // 如果 i 是Cat类型的,则拷贝赋值给 cat变量,否则报错)
if dog, ok := i.(Dog); ok{
// ok
}else{
// i isn't dog
}
泛型: interface{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Queue []int            // 定义了一个 int 类型的切片
func (q *Queue) Push(v int){
*q = append(*q, v)
}
func (q *Queue) Pop() int{
head := (*q)[0]
*q = (*q)[1:]
return head
}
// 将上面的切片改成可以接受任意类型:
type Queue []interface{} // 定义了一个 int 类型的切片
func (q *Queue) Push(v interface{}){
*q = append(*q, v)
}
func (q *Queue) Pop() interface{}{
head := (*q)[0]
*q = (*q)[1:]
return head
}
// 强制类型转换:
head.(int)

组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Cat interface{
cat()
}
type Dog interface{
dog()
}
type Animal interface{ // 要实例既实现 Cat 又实现 Dog
Cat
Dog
}
func get(i Animal){
i.cat()
i.dog()
}

常用系统接口

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 类似 toString() 的信息打印接口
type Stringer interface{
String() string
}
// 2. Reader
type Reader interface{
Read(p []byte) (n int, err error)
}
// 3. Writer
type Writer interface{
Write(p []byte) (n int, err error)
}

五、goroutine 并发

5.1 goroutine

`go func(){}()` 
用 `go` 关键字开启协程,协程是非抢占式多任务处理,由协程主动交出控制权
1
2
3
4
5
6
7
8
9
10
11
func main() {
for i:=0; i<1000; i++{
go func(i int) {
for {
fmt.Println(i); // IO 操作,会主动交出控制权
}
}(i)
}
time.Sleep(time.Microsecond);
}
// 如果没有 time.Sleep,在for执行完 i 的自增后就会立刻结束程序,此时协程还没来得及处理就结束了,没有任何打印
1
2
3
4
5
6
7
8
9
10
11
func main() {
var arr [10]int
for i:=0; i<1000; i++{
go func(i int) {
for {
arr[i]++ // 不会交出控制权,所以会在这里死循环
}
}(i)
}
time.Sleep(time.Microsecond)
}

goroutine可能交出控制权的点:

  • I/O操作,select
  • channel
  • 等待锁
  • 函数调用时(有时,不一定)
  • runtime.Gosched()

5.2 channel

channel其实就是传统语言的阻塞消息队列,可以用来做不同goroutine之间的消息传递

定义

  • 声明一个channel:
    var c chan int
    c := make(chan int)

  • 使用 channel:
    c <- 1 // 向管道中写入
    d := <-c // 从管道读出

1
2
3
4
5
6
7
8
9
10
11
12
13
func worker(c chan int)  {
for {
r := <-c
fmt.Println(r)
}
}
func main() {
c := make(chan int)
go worker(c)
c <- 1
c <- 2
time.Sleep(time.Microsecond)
}

更进一步:channel工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func workerFactory(i int)  chan int{
c := make(chan int)
go func(c chan int) {
fmt.Println( <-c )
}(c)
return c
}
func main() {
var arr [10] chan int
for i:=0; i<10 ; i++ {
arr[i] = workerFactory(i) // 创建 channel 并监听
}
for i:=0; i<10 ; i++ {
arr[i] <- i // 向各channel中输入
}
time.Sleep(time.Microsecond)
}

channel 方向(只读与只写)

1
2
3
4
5
6
7
8
9
10
func workerFactory()  chan <- int{     // 返回的是只写channel
c := make(chan int)
go func(c chan int) {
fmt.Println( <-c )
}(c)
return c
}
c := workerFactory(0)
r := <- c // 报错!
// 同理, `<- chan int` 是只读channel

缓冲区

向管道中写入就必须定义相应的输出,否则会报错 
有缓冲区与无缓冲区的区别是 一个是同步的 一个是非同步的,即阻塞型队列和非阻塞队列 详解:https://blog.csdn.net/samete/article/details/52751227
1
2
3
4
5
c := make(chan int, 3)      // 缓冲区长度3
c<-1
c<-2
c<-3 // 在这之前都在缓冲区中,不报错
c<-4 // 报错

关闭管道

当确定不再向缓冲区中发送数据,可以关闭管道,若还有协程在不断接收 管道数据,则会源源不断的收到 0 即空串,直到程序结束。
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
func workerFactory()  chan int{
c := make(chan int, 3)
go func(c chan int) {
for {
fmt.Println( <-c ) // 一直读取管道
}
}(c)
return c
}
func main() {
var c chan int
c = workerFactory()
c <- 1
c <- 2
close(c) // 关闭管道
time.Sleep(time.Second)
}
// 结果:
// 1,2,0,0,0,0,0...
// 改进:
for {
n, ok := <-c
if !ok {
break
}
fmt.Println( n )
}
// 或
for n := range c{
fmt.Println( n )
}

用channel等待任务结束
上面的例子使用 time.Sleep(time.Microsecond)来等待任务结束,不精确且耗时

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
41
42
package main
import (
"fmt"
)
type worker struct {
in chan int // in 用来读写
done chan bool // done 用来表示已读取完成
}
func createWorker() worker{
worker := worker{
in:make(chan int),
done:make(chan bool),
}
doWorker(worker);
return worker
}
func doWorker(w worker) {
go func(w worker) {
for {
fmt.Println(<-w.in)
go func(w worker) { // 注意此处也要 go,不然阻塞
w.done<-true
}(w)
}
}(w)
}
func main() {
var arr [10] worker
for i:=0; i<10; i++ {
arr[i] = createWorker()
}
for i:=0; i<10; i++ {
arr[i].in<-i
}
for i:=0; i<10; i++ {
arr[i].in<-i
}
for i:=0; i<10; i++ {
<-arr[i].done
<-arr[i].done
}
}

用 sync.WaitGroup 等待任务结束

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
41
package main
import (
"fmt"
"sync"
)
type worker struct {
in chan int
wg *sync.WaitGroup // *
}
func createWorker(wg *sync.WaitGroup) worker{
worker := worker{
in:make(chan int),
wg:wg,
}
doWorker(worker);
return worker
}
func doWorker(w worker) {
go func(w worker) {
for {
fmt.Printf("%c \n", <-w.in)
w.wg.Done() // 发送任务结束的信号
}
}(w)
}
func main() {
var wg sync.WaitGroup // 定义WaitGroup
var arr [10] worker
for i:=0; i<10; i++ {
arr[i] = createWorker(&wg) //按址传递,用一个引用来开始和结束
}
for i:=0; i<10; i++ {
wg.Add(1) // 开始一个任务前,计时器加一(一定要在开始前加)
arr[i].in <- 'a'+i
}
for i:=0; i<10; i++ {
wg.Add(1)
arr[i].in <- 'A'+i
}
wg.Wait() // 阻塞等待所有任务 done
}

5.3 select

  • 有多个 case 语句,只要有一个 case 处于非阻塞可执行状态,就执行,否则一直阻塞
  • 如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行
    用法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func main() {
    var c1,c2 chan int
    select {
    case n:=<-c1:
    fmt.Println(n)
    case n2:=<-c2:
    fmt.Println(n2)
    default:
    fmt.Println("no value") // ✔️
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func create(i int)  chan int{
c := make(chan int)
go func(c chan int, i int) {
for {
c <- 'a'+i
}
}(c,i)
return c
}
func main() {
c1,c2 := create(1),create(2)
for {
select { // 由于没有 default,会一直阻塞读
case n1 := <-c1:
fmt.Printf("%c \n",n1)
case n2 := <-c2:
fmt.Printf("%c \n",n2)
}
}
}

定时器

  • time.After() 设置一个定时器,返回一个 channel,到一段时间后,向channel发送一条当前时间
  • time.Tick() 返回一个 channel,每过一段时间向channel发送一条当前时间
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    func main() {
    c1,c2 := create(1),create(2)
    tm := time.After(2*time.Second) // 定时器,2秒后触发
    tm2 := time.Tick(1*time.Second) // 每1秒触发一次
    for {
    select {
    case n1 := <-c1:
    fmt.Printf("%c \n",n1)
    case n2 := <-c2:
    fmt.Printf("%c \n",n2)
    case t := <- tm2:
    fmt.Println("------- ",t," -----------")
    case <- tm:
    fmt.Println("bye")
    return
    }
    }
    }

六、结束语

哎~终于完了,还是有很多没有弄明白,先总结在这里,方便以后查阅。