Go36-18-for和switch

栏目: Go · 发布时间: 5年前

内容简介:if语句、for语句和switch语句都属于Go语言的基本流程控制语句。下面主要讨论for语句和switch语句,不过不讲语法规则,而是一些要注意的细节,就是“坑”。下面会分别讲解迭代变量和range表达式的问题。

if语句、for语句和switch语句都属于 Go 语言的基本流程控制语句。

下面主要讨论for语句和switch语句,不过不讲语法规则,而是一些要注意的细节,就是“坑”。

带range子句的for语句

下面会分别讲解迭代变量和range表达式的问题。

迭代变量

对于不同种类的range表达式结果值,for语句的迭代变量的数量可以不同,下面是一个迭代变量的例子:

func main() {
    nums := []string{"A", "B", "C", "D", "E", "F"}
    for i := range nums {
        fmt.Println(i)
    }
}

当只有一个迭代变量的时候,数组、数组的指针、切片和字符串的元素值都是无处安放的,只能拿到按照从小到大顺序给出的一个个索引值。

如果是两个迭代变量,就可以去到元素值了:

for i, v := range nums {
    fmt.Println(i, v)
}

如果只要取元素只,可以用占位符:

for _, v := range nums {
    fmt.Println(v)
}

迭代数组和切片

这里主要讲range表达式的特性,下面是例子:

package main

import "fmt"

func main() {
    nums := [...]int{1, 2, 3, 4, 5, 6}
    for i, e := range nums {
        if i == len(nums) -1 {
            nums[0] += e
            fmt.Println(e, nums[0])
        } else {
            nums[i+1] += e
            fmt.Println(e, nums[i+1])
        }

    }
    fmt.Println(nums)
}
/* 执行结果
[7 3 5 7 9 11]
*/

首先要注意,这里要迭代的是一个数组(不是切片了)。

上面的代码是在每次迭代的时候,都会去修改下一个元素的值,把下一个元素的值加上当前位置的元素值。问题是在下次迭代的时候,取到的值不是之前上次迭代后修改的值,而是数组原始的那个值。结论概括如下:

  • range表达式只会在for语句开始执行时被求值一次,无论后边会有多少次迭代
  • range表达式的求值结果会被复制,就是说,被迭代的对象是range表达式结果值的副本而不是原值

我的理解是, for i, e := range nums 这句,拿去给range表达式处理的是nums的副本。

接下来把上面操作的数组改成切片,就是改成这样: nums := []int{1, 2, 3, 4, 5, 6} 。之前的数组是值类型,而现在切片是引用类型。再次运行后的结果就不一样了。结果应该是: [22 3 6 10 15 21] 。因为是引用类型,range表达式传递还是副本,不过这次是地址的副本,所以每次迭代对下一个元素的修改会在之后的迭代中取出上次修改后的值。

switch语句

在switch语句中,有switch表达式和case表达式。

判等操作

下面这段代码会有编译错误:

package main

import "fmt"

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

switch表达式需要做判等操作,switch表达式的结果类型,以及各个case表达式的结果类型是要求一样的。因为,在Go语言中,只有类型相同的值之间才被允许进行判等操作。

上面的例子中,数组中元素的值是int8类型,而switch表达式的结果是数字常量,如果switch表达式的结果值是无类型的常量,则会被自动断转换成此种常量的默认类型值。这里的常量是整数4,会转成int类型。然后两个类型是不同的,所以无法通过编译。

交换switch表达式和case表达式的类型下面这段代码,和上面的例子差不多,只是交换了switch和case里的类型:

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

这次switch表达式是int8类型,而case表达式是常量。如果case表达式的结果值是无类型的常量,则会被自动的转换为switch表达式的结果类型。所以这里case表达式的结果会转为int8类型,可以进行判断操作。

小结:

如果switch表达式的结果值是无类型的常量,那这个常量会被自动的转换为此种常量的默认类型的值。

如果case表达式的结果值是无类型的常量,那么这个常量会被自动的转换为switch表达式的结果类型。

最后还要注意,如果表达式的结果类型是接口类型,那么要小心检查他们的动态值是否允许判等操作。因为这个错误不会被编译器发现,也就是不会造成编译错误。但是这样的后果更加严重,会在运行时引发Panic。

case表达式的约束

switch语句在case子句的选择上是具有唯一性的,也就是switch语句不允许case表达式中的子表达式结果值存在相等的情况,不了这些值是否是在不同的case表达式中。下面的代码就是因为有重复的值存在产生了编译错误:

func main() {
    v := [...]int{1, 2, 3, 4, 5, 6}
    switch v[4] {
    case 1, 2 ,3:
        fmt.Println("1 or 2")
    case 3, 4, 7-1:
        fmt.Println("3 or 4")
    case 5, 6:
        fmt.Println("5 or 6")
    }
}

绕过约束不过上面的这种约束只能在常量里发现。也就是说有办法可以绕过这个限制:

package main

import "fmt"

func main() {
    v := [...]int{1, 2, 3, 4, 5, 6}
    switch v[4] {
    case v[0], v[1], v[2]:
        fmt.Println("1 or 2")
    case v[2], v[3], v[4]:
        fmt.Println("3 or 4")
    case v[3], v[4], v[5]:
        fmt.Println("5 or 6")
    }
}

上面用case表达式的常量换成了一个索引表达式,就绕过了编译的检查,并且程序也能正确的执行并返回。

类型判断的switch语句上面这种绕过约束的方式,对这种switch语句是无效的。因为,类型switch语句中的case表达式的子表达式,都必须直接由类型字面量表示,而无法通过间接的方式表示。下面是编程会出错的例子:

func main() {
    v := interface{}(byte(127))
    switch t := v.(type) {
    case uint8, uint16:
        fmt.Println("uint8 or uint16")
    case byte:
        fmt.Println("byte")
    default:
        fmt.Printf("%T\n", t)
    }
}

这里要知道byte类型是uint8类型的别名类型。这两个本质上是同一个类型,只是名称不同。这种情况是无法通过编译的,因为子表达式byte和uint8算是重复了。

类型判断

switch还可以用来做类型判断,这里直接给出例子:

package main

import "fmt"

func classifier(items ...interface{}) {
    for i, v := range items {
        switch v.(type) {
        case bool:
            fmt.Println("bool", i)
        case float64:
            fmt.Println("float64", i)
        case int:
            fmt.Println("int", i)
        case nil:
            fmt.Println("nil", i)
        case string:
            fmt.Println("string", i)
        default:
            fmt.Println("unknow", i)
        }
    }
}

func main() {
    classifier(1, "", nil, 1.234, true, int32(5))
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

实现领域驱动设计

实现领域驱动设计

Vaughn Vernon / 滕云 / 电子工业出版社 / 2014-3 / 99.00元

领域驱动设计(DDD)是教我们如何做好软件的,同时也是教我们如何更好地使用面向对象技术的。它为我们提供了设计软件的全新视角,同时也给开发者留下了一大难题:如何将领域驱动设计付诸实践?Vaughn Vernon 的这本《实现领域驱动设计》为我们给出了全面的解答。 《实现领域驱动设计》分别从战略和战术层面详尽地讨论了如何实现DDD,其中包含了大量的最佳实践、设计准则和对一些问题的折中性讨论。《实......一起来看看 《实现领域驱动设计》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具