go 语言基础
包
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
每个 Go 程序都是由包组成的。
程序运行的入口是包 main
。
这个程序使用并导入了包 "fmt" 和 "math/rand"
。
按照惯例,包名与导入路径的最后一个目录一致。例如,"math/rand"
包由 package rand 语句开始。
这个代码用圆括号组合了导入,这是“打包”导入语句。
同样可以编写多个导入语句,例如:
import "fmt"
import "math"
不过使用打包的导入语句是更好的形式。
在导入了一个包之后,就可以用其导出的名称来调用它。
在 Go 中,首字母大写的名称是被导出的。
Foo 和 FOO 都是被导出的名称。名称 foo 是不会被导出的。
函数
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(add(42, 13))
}
函数可以没有参数或接受多个参数。
在这个例子中,add
接受两个 int 类型的参数。
注意类型在变量名 之后。
当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。
在这个例子中 ,
x int, y int
被缩写为
x, y int
多值返回
函数可以返回任意数量的返回值。
swap 函数返回了两个字符串。
命名返回值
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
Go 的返回值可以被命名,并且像变量那样使用。
返回值的名称应当具有一定的意义,可以作为文档使用。
没有参数的 return 语句返回结果的当前值。也就是直接
返回。
直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。
变量
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
var 语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。
就像在这个例子中看到的一样,var
语句可以定义在包或函数级别。
初始化变量
var c, python, java = true, false, "no!"
变量定义可以包含初始值,每个变量对应一个。
如果初始化是使用表达式,则可以省略类型;变量从初始值中获得类型。
短声明变量
k := 3
在函数中,:=
简洁赋值语句在明确类型的地方,可以用于替代 var 定义。
函数外的每个语句都必须以关键字开始(var
、func
、等等),:=
结构不能使用在函数外。
基本类型
Go 的基本类型有Basic types
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 代表一个Unicode码
float32 float64
complex64 complex128
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
这个例子演示了具有不同类型的变量。 同时与导入语句一样,变量的定义“打包”在一个语法块中。
零值
变量在定义时没有明确的初始化时会赋值为_零值_。
零值是:
数值类型为 0
,
布尔类型为 false
,
字符串为 ""
(空字符串)。
类型转换
表达式 T(v) 将值 v 转换为类型 T
。
一些关于数值的转换:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
或者,更加简单的形式:
i := 42
f := float64(i)
u := uint(f)
与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换
类型推导
在定义一个变量但不指定其类型时(使用没有类型的 var 或 := 语句), 变量的类型由右值推导得出。
当右值定义了类型时,新变量的类型与其相同:
var i int
j := i // j 也是一个 int
但是当右边包含了未指名类型的数字常量时,新的变量就可能是 int 、 float64 或 complex128
。 这取决于常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
常量
const Pi = 3.14
常量的定义与变量类似,只不过使用 const 关键字。
常量可以是字符、字符串、布尔或数字类型的值。
常量不能使用 := 语法定义。
数值常量
数值常量是高精度的 值。
一个未指定类型的常量由上下文来决定其类型。
for
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
Go 只有一种循环结构——for
循环。
基本的 for 循环除了没有了 ( )
之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中做的一样,而 { }
是必须的。
跟 C 或者 Java 中一样,可以让前置、后置语句为空。
sum := 1
for sum < 1000 {
sum += sum
}
基于此可以省略分号:C 的 while 在 Go 中叫做 for
。
if
if x < 0 {
return sqrt(-x) + "i"
}
if 语句除了没有了 ( )
之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中的一样,而 { }
是必须的。
if 的便捷语句
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
跟 for 一样,if
语句可以在条件之前执行一个简单的语句。
由这个语句定义的变量的作用域仅在 if 范围之内。
if 和 else
在 if 的便捷语句定义的变量同样可以在任何对应的 else 块中使用。
switch
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
一个结构体(struct
)就是一个字段的集合。
除非以 fallthrough 语句结束,否则分支会自动终止
switch 的执行顺序
switch 的条件从上到下的执行,当匹配成功的时候停止。
没有条件的 switch
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
没有条件的 switch 同 switch true
一样。
这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。
defer
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
defer 语句会延迟函数的执行直到上层函数返回。
延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。
defer 栈
延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。
指针
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
Go 具有指针。 指针保存了变量的内存地址。
类型 *T 是指向类型 T 的值的指针。其零值是 nil
。
var p *int
& 符号会生成一个指向其作用对象的指针。
i := 42
p = &i
- 符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“非直接引用”。
与 C 不同,Go 没有指针运算。
结构体
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
一个结构体(struct
)就是一个字段的集合。
(而 type 的含义跟其字面意思相符。)
结构体字段
v := Vertex{1, 2}
v.X = 4
结构体字段使用点号来访问。
结构体指针
v := Vertex{1, 2}
p := &v
p.X = 1e9
结构体字段可以通过结构体指针来访问。
通过指针间接的访问是透明的。
结构体文法
v1 = Vertex{1, 2} // 类型为 Vertex
v2 = Vertex{X: 1} // Y:0 被省略
结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
数组
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
}
类型 [n]T 是一个有 n 个类型为 T 的值的数组。
表达式
var a [10]int
定义变量 a 是一个有十个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小
slice
package main
import "fmt"
func main() {
p := []int{2, 3, 5, 7, 11, 13}
fmt.Println("p ==", p)
for i := 0; i < len(p); i++ {
fmt.Printf("p[%d] == %d\n", i, p[i])
}
}
一个 slice 会指向一个序列的值,并且包含了长度信息。
[]T 是一个元素类型为 T 的 slice。
对 slice 切片
package main
import "fmt"
func main() {
p := []int{2, 3, 5, 7, 11, 13}
fmt.Println("p ==", p)
fmt.Println("p[1:4] ==", p[1:4])
// 省略下标代表从 0 开始
fmt.Println("p[:3] ==", p[:3])
// 省略上标代表到 len(s) 结束
fmt.Println("p[4:] ==", p[4:])
}
slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
表达式
s[lo:hi]
表示从 lo 到 hi-1 的 slice 元素,左闭右开。因此
s[lo:lo]
是空的,而
s[lo:lo+1]
有一个元素。
构造 slice
slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:
a := make([]int, 5) // len(a)=5
为了指定容量,可传递第三个参数到 make
:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
nil slice
slice 的零值是 nil
。
一个 nil 的 slice 的长度和容量是 0。
向 slice 添加元素
向 slice 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append
。 内建函数的文档对 append 有详细介绍。
func append(s []T, vs ...T) []T
append 的第一个参数 s 是一个类型为 T 的数组,其余类型为 T 的值将会添加到 slice。
append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。
range
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
for 循环的 range 格式可以对 slice 或者 map 进行迭代循环
for _, value := range pow {
fmt.Printf("%d\n", value)
}
可以通过赋值给 _ 来忽略序号和值。
如果只需要索引值,去掉“, value”的部分即可。
map
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
map 映射键到值。
map 在使用之前必须用 make 而不是 new 来创建;值为 nil 的 map 是空的,并且不能赋值。
map 的文法
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
func main() {
fmt.Println(m)
}
map 的文法跟结构体文法相似,不过必须有键名。
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
如果顶级的类型只有类型名的话,可以在文法的元素中省略键名。
修改 map
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
在 map m 中插入或修改一个元素:
m[key] = elem
获得元素:
elem = m[key]
删除元素:
delete(m, key)
通过双赋值检测某个键存在:
elem, ok = m[key]
如果 key 在 m 中,ok
为 true 。否则, ok 为 false
,并且 elem 是 map 的元素类型的零值。
同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。
函数值
package main
import (
"fmt"
"math"
)
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3, 4))
}
函数也是值。
函数的闭包
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。
方法
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
Go 没有类。然而,仍然可以在结构体类型上定义方法。
方法接收者 出现在 func 关键字和方法名之间的参数中。
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
你可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。
但是,不能对来自其他包的类型或基础类型定义方法。
接收者为指针的方法
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
v.Scale(5)
fmt.Println(v, v.Abs())
}
方法可以与命名类型或命名类型的指针关联。
刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在 MyFloat 值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。
尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。
当 v 是 Vertex 的时候 Scale 方法没有任何作用。Scale
修改 v
。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。
Abs 的工作方式是一样的。只不过,仅仅读取 v
。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。
接口
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了 Abser
// 下面一行,v 是一个 Vertex(而不是 *Vertex)
// 所以没有实现 Abser。
a = v
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
接口类型是由一组方法定义的集合。
接口类型的值可以存放实现这些方法的任何值。
注意: 例子代码的 22 行存在一个错误。 由于 Abs 只定义在 *Vertex(指针类型) 上, 所以 Vertex(值类型) 不满足 Abser
。
隐式接口
package main
import (
"fmt"
"os"
)
type Reader interface {
Read(b []byte) (n int, err error)
}
type Writer interface {
Write(b []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func main() {
var w Writer
// os.Stdout 实现了 Writer
w = os.Stdout
fmt.Fprintf(w, "hello, writer\n")
}
类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
包 io 定义了 Reader 和 Writer
;其实不一定要这么做。
Stringers
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
一个普遍存在的接口是 fmt 包中定义的 Stringer。
type Stringer struct {
String() string
}
Stringer 是一个可以用字符串描述自己的类型。fmt
包 (还有许多其他包)使用这个来进行输出。
错误
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
Go 程序使用 error 值来表示错误状态。
与 fmt.Stringer 类似,error
类型是一个内建接口:
type error interface {
Error() string
}
(与 fmt.Stringer 类似,fmt
包在输出时也会试图匹配 error
。)
通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil
, 来进行错误处理。
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)
error 为 nil 时表示成功;非 nil 的 error 表示错误。
Readers
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
io 包指定了 io.Reader 接口, 它表示从数据流结尾读取。
Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。
io.Reader 接口有一个 Read 方法:
func (T) Read(b []byte) (n int, err error)
Read 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF 错误。
例子代码创建了一个 strings.Reader。 并且以每次 8 字节的速度读取它的输出。
Web 服务器
package main
import (
"fmt"
"log"
"net/http"
)
type Hello struct{}
func (h Hello) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
err := http.ListenAndServe("localhost:4000", h)
if err != nil {
log.Fatal(err)
}
}
包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求:
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
在这个例子中,类型 Hello 实现了 http.Handler
。
访问 http://localhost:4000/ 会看到来自程序的问候。
图片
Package image 定义了 Image 接口:
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
注意:Bounds
方法的 Rectangle 返回值实际上是一个 image.Rectangle, 其定义在 image 包中。
(参阅文档了解全部信息。)
color.Color 和 color.Model 也是接口,但是通常因为直接使用预定义的实现 image.RGBA 和 image.RGBAModel 而被忽视了。这些接口和类型由image/color 包定义。
goroutine
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
goroutine 是由 Go 运行时环境管理的轻量级线程。
go f(x, y, z)
开启一个新的 goroutine 执行
f(x, y, z)
f , x , y 和 z 是当前 goroutine 中定义的,但是在新的 goroutine 中运行 f
。
goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync 提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法。(在接下来的内容中会涉及到。)
channel
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // 将和送入 c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // 从 c 中获取
fmt.Println(x, y, x+y)
}
channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值。
ch <- v // 将 v 送入 channel ch。
v := <-ch // 从 ch 接收,并且赋值给 v。
(“箭头”就是数据流的方向。)
和 map 与 slice 一样,channel 使用前必须创建:
ch := make(chan int)
默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。
缓冲 channel
package main
import "fmt"
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
channel 可以是 带缓冲的。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:
ch := make(chan int, 100)
向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。
修改例子使得缓冲区被填满,然后看看会发生什么(会报错)。
range 和 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)
for i := range c {
fmt.Println(i)
}
}
发送者可以 close 一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过
v, ok := <-ch
之后 ok 会被设置为 false
。
循环 for i := range c
会不断从 channel 接收值,直到它被关闭。
注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。 还要注意: channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 range
。
select
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
select 语句使得一个 goroutine 在多个通讯操作上等待。
select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。
默认选择
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
当 select 中的其他条件分支都没有准备好的时候,default
分支会被执行。
为了非阻塞的发送或者接收,可使用 default 分支:
select {
case i := <-c:
// 使用 i
default:
// 从 c 读取会阻塞
}