Go语言36讲笔记--18if语句、for语句和switch语句

156 阅读6分钟
1. 使用携带range子句的for语句时需要注意哪些细节?

要注意range后的对象是数组还是切片,二者在循环中对元素的操作结果,由于值传递问题,会有区别。

range表达式的结果值是会被复制的,实际迭代时并不会使用原值。至于会影响到什么,那就要看这个结果值的类型是值类型还是引用类型了。

demo

// 示例1。
numbers2 := [...]int{1, 2, 3, 4, 5, 6} //数组
maxIndex2 := len(numbers2) - 1
for i, e := range numbers2 {
        if i == maxIndex2 {
                numbers2[0] += e
        } else {
                numbers2[i+1] += e 
        }
}
fmt.Println(numbers2)
fmt.Println()

// 示例2。
numbers3 := []int{1, 2, 3, 4, 5, 6}
maxIndex3 := len(numbers2) - 1
for i, e := range numbers3 {
        if i == maxIndex3 {
                numbers3[0] += e
        } else {
                numbers3[i+1] += e
        }
}
fmt.Println(numbers3)

-------
result:

[7 3 5 7 9 11]

[22 3 6 10 15 21]

问题解析(为什么数组与切片的结果不同)

这里需要注意两点:

  1. range表达式只会在for语句开始执行时被求值一次,无论后边会有多少次迭代;
  2. range表达式的求值结果会被复制,也就是说,被迭代的对象是range表达式结果值的副本(e)而不是原值(numbers)。 分析示例1

基于这两个规则分析。在第一次迭代时,改变的是numbers2的第二个元素的值,新值为3,也就是12之和。

但是,被迭代的对象的第二个元素却没有任何改变,毕竟它与numbers2已经是毫不相关的两个数组了。因此,在第二次迭代时,我会把numbers2的第三个元素的值修改为5,即被迭代对象的第二个元素值2和第三个元素值3的和。

以此类推,之后的numbers2的元素值依次会是7911。当迭代到最后一个元素时,我会把numbers2的第一个元素的值修改为16之和。


2. switch语句中的switch表达式和case表达式之间有着怎样的联系?

基本原则

只要switch表达式的结果值与某个case表达式中的任意一个子表达式的结果值相等,该case表达式所属的case子句就会被选中。(判等操作)

并且,一旦某个case子句被选中,其中的附带在case表达式后边的那些语句就会被执行。与此同时,其他的所有case子句都会被忽略。 demo1(编译无法通过)

value1 := [...]int8{0, 1, 2, 3, 4, 5, 6}

switch 1 + 3 { //switch表达式

case value1[0], value1[1]: //case表达式
	fmt.Println("0 or 1")
        
case value1[2], value1[3]: 
	fmt.Println("2 or 3")
        
case value1[4], value1[5], value1[6]:
	fmt.Println("4 or 5 or 6")
}

问题分析

如果switch表达式的结果值是无类型的常量,比如1 + 3的求值结果就是无类型的常量4,那么这个常量会被自动地转换为此种常量的默认类型的值,比如整数4的默认类型是int,又比如浮点数3.14的默认类型是float64

demo1中的switch表达式类型会自动转换为int,与数组类型int8不符,故无法通过编译。

demo2(与demo1的区别是,case是无类型常量)

value2 := [...]int8{0, 1, 2, 3, 4, 5, 6}
switch value2[4] {
case 0, 1:
	fmt.Println("0 or 1")
case 2, 3:
	fmt.Println("2 or 3")
case 4, 5, 6:
	fmt.Println("4 or 5 or 6")
}

可以发生编译,case表达式的类型转换会以switch表达式为准。

image.png 结论

由于需要进行判等操作,所以switchcase中的子表达式的结果类型需要相同。

switch语句会进行有限的类型转换,但不能保证这种转换可以统一它们的类型。

如果这些表达式的结果类型有某个接口类型,要检查它们的动态值是否都具有可比性(或者说是否允许判等操作)。


3. switch语句对它的case表达式有哪些约束?

约束1:switch语句在case子句的选择上是具有唯一性的。

正因为如此,switch语句不允许case表达式中的子表达式结果值存在相等的情况,不论这些结果值相等的子表达式,是否存在于不同的case表达式中,都会是这样的结果。 demo1(case子表达式之间有重复,编译错误)

value3 := [...]int8{0, 1, 2, 3, 4, 5, 6}
switch value3[4] {
case 0, 1, 2:
	fmt.Println("0 or 1 or 2")
case 2, 3, 4:
	fmt.Println("2 or 3 or 4")
case 4, 5, 6:
	fmt.Println("4 or 5 or 6")
}

针对上面的约束1,还有一个附加约束,只针对结果值为常量的子表达式

demo2(绕过方式)

value5 := [...]int8{0, 1, 2, 3, 4, 5, 6}
switch value5[4] {
case value5[0], value5[1], value5[2]:
	fmt.Println("0 or 1 or 2")
case value5[2], value5[3], value5[4]:
	fmt.Println("2 or 3 or 4")
case value5[4], value5[5], value5[6]:
	fmt.Println("4 or 5 or 6")
}

问题分析

case表达式中的常量都换成了诸如value5[0]这样的索引表达式。

虽然第一个case表达式和第二个case表达式都包含了value5[2],并且第二个case表达式和第三个case表达式都包含了value5[4],但这已经不是问题了。这条switch语句可以成功通过编译。

普通case子句的编写顺序很重要,最上边的case子句中的子表达式总是会被最先求值,在判等的时候顺序也是这样。

因此,如果某些子表达式的结果值有重复并且它们与switch表达式的结果值相等,那么位置靠上的case子句总会被选中。

demo3(绕过方式对用于类型判断的switch语句无效)

value6 := interface{}(byte(127))
switch t := value6.(type) { //类型switch语句
case uint8, uint16:
	fmt.Println("uint8 or uint16")
case byte:
	fmt.Printf("byte")
default:
	fmt.Printf("unsupported type: %T", t)
}
---
无法通过编译

问题解析

变量value6的值是空接口类型的。该值包装了一个byte类型的值127。我在后面使用类型switch语句来判断value6的实际类型,并打印相应的内容。

这里有两个普通的case子句,还有一个default case子句。前者的case表达式分别是case uint8, uint16case byte

byte类型是uint8类型的别名类型。

因此,它们两个本质上是同一个类型,只是类型名称不同。

在这种情况下,这个类型switch语句是无法通过编译的,因为子表达式byteuint8重复了。

思考题

  1. 在类型switch语句中,我们怎样对被判断类型的那个值做相应的类型转换?

其实这个事情可以让 Go 语言自己来做,例如:

switch t := x.(type) {
// cases
}

当流程进入到某个case子句的时候,变量t的值就已经被自动地转换为相应类型的值了。

  1. if语句中,初始化子句声明的变量的作用域是什么?

如果这个变量是新的变量,那么它的作用域就是当前if语句所代表的代码块。注意,后续的else if子句和else子句也包含在当前的if语句代表的代码块之内。