hello云胜

技术与生活

0%

控制结构

在go中代码控制的关键字只有三种,if,for和switch。

if

没什么好说的。

稍微特殊的是:go的if表达式中可以声明变量,称为if语句的自用变量。该变量的作用范围是这个if代码块。

1
2
3
4
a, b := 1, 1
if ok := (a == b); ok {
t.Log("go 的 if,格式可以是先声明变量,然后使用。这点和java不同")
}

for

几个特点

  1. go的for循环支持多循环变量

    1
    2
    3
    4
    5
    6
    7
    8
    	for i, j := 1, 2; (i < 10) && (j < 10); i, j = i+1, j+2 {
    t.Log(i, j)
    }
    ---------------------------------------------------------------------------
    loop_test.go:8: 1 2
    loop_test.go:8: 2 4
    loop_test.go:8: 3 6
    loop_test.go:8: 4 8
  2. 可以仅仅保留循环判断条件表达式

    1
    2
    3
    4
    5
    i := 0
    for i < 10 {
    println(i)
    i++
    }
  3. 无限循环

    1
    2
    3
    for {
    // 循环体代码
    }
    1. 支持continue和break,加label跳转

for range形式

对切片循环

1
2
3
4
slc := []string{"a", "b", "c"}
for index, v := range slc{
t.Log(index, v)
}
要重点说一下对string的循环
1
2
3
4
5
6
func TestString(t *testing.T) {
s := "你好中国"
for index, v := range s{
t.Log(index, v, string(v))
}
}

结果是

1
2
3
4
loop_test.go:23: 0 20320 你
loop_test.go:23: 3 22909 好
loop_test.go:23: 6 20013 中
loop_test.go:23: 9 22269 国

对于string的循环,每次得到的v是一个unicode字符码点。index是该字符码点在该字符串中的位置。一个汉字的unicode编码用utf8形式存储占3个字节。

重点说一个数组循环的一个大坑
1
2
3
4
5
6
7
8
9
10
11
12
13
func TestArray(t *testing.T) {
arr := [5]int{1, 2, 3, 4, 5}
var newArr [5]int
for index, value := range arr {
if index == 0 {
arr[1] = 12
arr[2] = 13
}
newArr[index] = value
}
t.Log("arr:", arr)
t.Log("newArr:", newArr)
}

结果:

1
2
loop_test.go:41: arr: [1 12 13 4 5]
loop_test.go:42: newArr: [1 2 3 4 5]

原因是:参与 for range 循环的是 range 表达式的副本。也就是说参与循环的是arr的副本,和原始的arr是完全两个东西。

使用切片就不会有这个问题,因为切片传递的是引用。

对map循环的坑

之前的文章说过map的循环顺序具有随机性,每次循环的顺序都不一样。

所以不要在map的循环里做任何修改或增删的操作。结果会是不确定的

switch

几个特点

  1. case语句可以接多个表达式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    switch initStmt; expr {
    case expr1:
    // 执行分支1
    case expr2:
    // 执行分支2
    case expr3_1, expr3_2, expr3_3:
    // 执行分支3
    default:
    // 执行默认分支
    }

  2. 不需要写break,和java不一样,go不会默认执行后面的分支。

  3. 如果需要继续执行下一个case,使用fallthrough关键字

  4. go的swaitch表达式支持各种类型,只要这个类型可以进行比较操作就可以。不像java,只支持整型,枚举和字符串。

  5. 如果case的表达式都是布尔型的,可以省略swicth的表达式

1
2
3
4
5
6
7
8
9
10
11
12
// 带有initStmt语句的switch语句
switch initStmt; {
case bool_expr1:
case bool_expr2:
... ...
}
// 没有initStmt语句的switch语句
switch {
case bool_expr1:
case bool_expr2:
... ...
}

type switch

“type switch”这是一种特殊的 switch 语句用法。用来根据变量类型的不同而执行不同的分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
func TestType(t *testing.T)  {
var x interface{} = 100
switch x.(type) {
case nil:
t.Log("type is nil")
case int:
t.Log("type is int")
case string:
t.Log("type is string")
default:
t.Log("other type")
}
}

switch 关键字后面跟着的表达式为x.(type),这种表达式形式是 switch 语句专有的,而 且也只能在 switch 语句中使用。

这个表达式中的 x 必须是一个接口类型变量,表达式的求 值结果是这个接口类型变量对应的动态类型。

Go 中所有类型都实现了 interface{}类型,所以case可以接各种类型。

如果x是指定的某个接口类型,那么case接的必须是实现了这个接口的类型。

另外还可以拿到x的具体值

1
2
3
4
5
6
7
8
9
10
11
12
13
func TestType(t *testing.T)  {
var x interface{} = 100
switch v := x.(type) {
case nil:
t.Log("type is nil")
case int:
t.Log("type is int", v)
case string:
t.Log("type is string")
default:
t.Log("other type")
}
}

这里的 v := x.(type),v得到的就是具体的值,不是x的类型。这点千万注意。这种写法也是诡异。