61

Go语言基础05-Go流程控制

 5 years ago
source link: https://studygolang.com/articles/19218?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
文章转载请注明出处 www.leexide.com

希望每一位寻求转载的朋友都能够按照要求进行,鼓励原创,尊重原创。

微信公众号:DevOps运维运营之家

QQ号码:1045884038

E-mail:[email protected]

如有问题或建议,请关注微信公众号

vM3a2i7.jpg!web

1 概述

流程控制是顺序编程中必不可少的一部分,它是整个编程基础的重要一环。在顺序编程的流程控制部分,Go语言和其他主流语言有一些差别,主要体现在Go语言没有 do-while 语句,因此 for 语句拥有更广泛的含义与用途。另一方面 switch 语句也有一些扩展,例如支持类型判断和初始化子语句等。

除了这些常见的流程控制语法的关键字,Go语言还有三个特殊的关键字,分别是:

defer
select
go

2 条件语句

2.1 if判断

例子:

package main

import "fmt"

func main() {
    a := 1
    if a < 20 {
        fmt.Printf("a小于20\n")
    }
    fmt.Printf("a的值是:%d\n", a)
}

2.2 if-else判断

例子:

package main

import "fmt"

func main() {
    a := 100
    if a < 20 {
        fmt.Printf("a小于20\n")
    } else {
        fmt.Printf("a大于20\n")
    }
    fmt.Printf("a的值是:%d\n", a)
}

例子:

package main

import "fmt"

func main() {
    a := 100
    if a < 20 {
        fmt.Printf("a小于20\n")
        if a > 10 {
            fmt.Printf("a大于10\n")
        } else {
            fmt.Printf("a小于10\n")
        }
    } else {
        fmt.Printf("a大于20\n")
    }
    fmt.Printf("a的值是:%d\n", a)
} 
}

说明:

上面的代码嵌套了一层 if-else 语句,这在编程规范中可以说大忌,特别是在逻辑复杂的情况下,嵌套if语句将非常影响性能,因此就有了 else-if 语句。

2.3 else-if判断

else-if 语句是在前面 if-else 语句之上再度扩展的,为了解决多重判断的问题。例如:

package main

import "fmt"

func main() {
    a := 11
    if a > 20 {
        fmt.Printf("a大于20\n")
    } else if a < 10 {
        fmt.Printf("a小于10\n")
    } else {
        fmt.Printf("a大于10\n")
        fmt.Printf("a小于20\n")
    }
    fmt.Printf("a的值是:%d\n", a)
}

else-if 语句可以连续使用多个 else if 关键字,例如判断一个数字是否大于10小于20且不等于11:

package main

import "fmt"

func main() {
    a := 13
    if a > 20 {
        fmt.Printf("a大于20\n")
    } else if a < 10 {
        fmt.Printf("a小于10\n")
    } else if a == 11 {
        fmt.Printf("a等于11\n")
    } else {
        fmt.Printf("a大于10\n")
        fmt.Printf("a小于20\n")
        fmt.Printf("a不等于11\n")
    }
    fmt.Printf("a的值是:%d\n", a)
}

2.4 初始化子语句

if语句可以有一个子语句,用于初始化局部变量,例如:

package main

import "fmt"

func main() {
    if a := 10; a < 20 {
        fmt.Printf("a小于20\n")
    } else {
        fmt.Printf("a的值是:%d\n", a)
    }
}

注意1:

子语句只能有一个表达式,比如下面的例子是无法编译的。

func main() {
    if b := 10; a := 10; a < 20 {  //编译出错,初始化子语句只能有一个表达式
        fmt.Printf("a小于20\n")
    } else {
        fmt.Printf("a的值是:%d\n", a)
    }
}

注意2:

a的值是在if代码块中定义的,所以不能在代码块之外调用,例如下面代码也是无法编译的:

func main() {
    if a := 10; a < 20 {
        fmt.Printf("a小于20\n")
    } 
    //编译出错,a是在if代码块中定义的,所以不能再函数中调用
    fmt.Printf("a的值是:%d\n", a)
}

3 选择语句

在上面使用if条件语句时,一般用于二元判断,对于多元判断使用条件语句就会显得烦琐,所以就有了选择语句。例如判断一个数字属于哪个区间就可以用选择语句轻松实现,避免条件语句烦琐的嵌套过程。

3.1 switch语句

在Go语言中, switch 表示选择语句的关键字, switch 语句会根据初始化表达式得出一个值,然后根据 case 语句的条件,执行相应的代码块,最终返回特定内容。每个 case 被称为一种情况,只有当初始化语句的值符合 case 的条件语句时, case 才会被执行。

如果没有遇到符合的 case ,则可以使用默认的 case ( default case ),如果己经遇到了

符合的 case ,那么后面的 case 都不会被执行。

与其他编程语言不同的是,在Go语言编程中, switch 有两种类型。

  • 表达式 switch :在表达式 switch 中, case 包含与 switch 表达式的值进行比较的表达式。
  • 类型 switch :在类型 switch 中, case 包含与特殊注释的 switch 表达式的类型进行比较的类型。

3.1.1 表达式switch

例子:

package main

import "fmt"

func main() {
    grade := "B"
    marks := 90

    switch marks {
    case 90:
        grade = "A"
    case 80:
        grade = "B"
    case 60, 70:
        grade = "C"
    default:
        grade = "D"
    }
    fmt.Printf("你的成绩为%s\n", grade)
}

这种方式虽然简单,但一般不这样写,因为不容易扩展这个选择语句,例如把分数改为100或者91时,也会返回“你的成绩为D”,这明显不符合实际情况。这是因为选择语句中没有考虑值的范围的问题,现在假设90分到100分为A, 80分到89分为B, 60分到79分为C ,低于60分则为D 。这种情况下上面的单值判断己经不能满足我们此时的需求。

因此就需要完整的switch 表达式写法了:

package main

import "fmt"

func main() {
    grade := "E"
    marks := 90

    switch {
    case marks >= 90:
        grade = "A"
    case marks >= 80:
        grade = "B"
    case marks >= 70:
        grade = "C"
    case marks >= 60:
        grade = "D"
    default:
        grade = "E"
    }

    switch {
    case grade == "A":
        fmt.Printf("你的成绩优秀!\n")
    case grade == "B":
        fmt.Printf("表现良好!\n")
    case grade == "C", grade == "D": //case表达式可以有多个
        fmt.Printf("再接再厉!\n")
    default:
        fmt.Printf("成绩不合格!\n")
    }
    fmt.Printf("你的成绩为%s\n", grade)
}

上面例子中有两个 switch 相互关联,第一个 switch 语句判断成绩所属区间,并得出 grade 的值, 然后第二个 switch 根据 grade 的值返回相应的话语。

注意,每一个 case 都可以拥有多个表达式,它的含义与 fallthrough 一样,例如下面两种写法是一样的意思:

/*  多表达式写法   */
    case grade == "C", grade == "D": //case表达式可以有多个
        fmt.Printf("再接再厉!\n")
    /*  fallthrough写法    */
    case grade == "C":
        fallthrough
    case grade == "D":
        fmt.Printf("再接再厉!\n")

fallthrough 关键词可以把当前 case 控制权交给下一个 case 语句判断。

3.1.2 类型switch

类型 switch 语句针对变量类型判断执行哪个 case 代码块,下面是一个简单的例子:

package main

import "fmt"

var x interface{} //空接口

func main() {
    x = 1
    switch i := x.(type) { //这里表达式只有一句初始化子语句
    case nil:
        fmt.Printf("这里是nil,x的类型是%T", i)
    case int:
        fmt.Printf("这里是int,x的类型是%T", i)
    case float64:
        fmt.Printf("这里是float64,x的类型是%T", i)
    case bool:
        fmt.Printf("这里是bool,x的类型是%T", i)
    case string:
        fmt.Printf("这里是string,x的类型是%T", i)
    default:
        fmt.Printf("未知类型")
    }
}

类型 switch 的初始化子语句中需要判断的变量必须是具有接口类型的变量(如果是固定类型的变量就没有判断的意义了)。在语法上类型 switch 与表达式 switch 没有太大区别。

3.2 switch初始化语句

switch 语句同样拥有初始化子语句,和 if 一样均是写在关键字后面,只能有一句语句,例如:

/* 单值判断写法    */
    switch marks := 90; marks {
    case 90:
        grade = "A"
    case 80:
        grade = "B"
    case 70:
        grade = "C"
    case 60:
        grade = "D"
    default:
        grade = "E"
    }

    /* 范围表达式写法   */
    switch marks := 90; { //这里的分号不能省略
    case marks >= 90:
        grade = "A"
    case marks >= 80:
        grade = "B"
    case marks >= 70:
        grade = "C"
    case marks >= 60:
        grade = "D"
    default:
        grade = "E"
    }

3.3 select语句

在Go语言中,除了 switch 语句,还有一种选择语句一- select ,这种选择语句用于配合通道( channel )的读写操作,用于多个 channel 的并发读写操作。

switch 是按顺序从上到下依次执行的,而 select 是随机选择一个 case 来判断,直到匹配其中的一个 case ,举个例子:

package main

import "fmt"

func main() {
    a := make(chan int, 1024)
    b := make(chan int, 1024)

    for i := 0; i < 10; i++ {
        fmt.Printf("第%d次", i)
        a <- 1
        b <- 1

        select {
        case <-a:
            fmt.Println("from a")
        case <-b:
            fmt.Println("from b")
        }
    }
}

上述代码的意思是,同时在 ab 中选择,哪个有内容就从哪个读,由于 channel 的读写操作是阻塞操作,使用 select 语句可以避免单个 channel 的阻塞。此外 select 同样可以使用 default 代码块,用于避免所有 channel 同时阻塞。

4 循环语句

循环语句是编程中常使用的流程控制语句之一,在Go语言中,循环语句的关键字是 for ,没有 while 关键字。 for 语句可以根据指定的条件重复执行其内部的代码块,这个判断条件一般是由 for 关键字后面的子语句给出的。

例如一个简单的 for 循环例子:

package main

import "fmt"

func main() {
    for a := 0; a < 5; a++ {
        fmt.Printf("a的值是:%d\n", a)
    }
}

上面 for 关键字后面有三个子语句,初始化变量 a0 ,并判断当 a 小于 5 时执行下面代码块的内容,每次判断 a 的值都加 l ,直到不符合初始化语句的判断条件,进而退出循环。

4.1 for的子语句

for 语句后面的三个子语句我们称为:

  • 初始化子语句
  • 条件子语句
  • 后置子语句

这三者不能颠倒顺序,其中条件子语句是必需的,条件子语句会返回一个布尔型, true 则执行代码块, false 则跳出循环。

例如以下示例代码中就省略了初始化子语句和后置子语句:

package main

import "fmt"

func main() {
    a := 0
    b := 5
    for a < b {
        a++
        fmt.Printf("a的值是:%d\n", a)
    }
}

在上面的例子中, for 关键字后面只有一个 a&lt; b 的判断语句,这是典型的条件判断语

句,它实际上是三个子语句的简写:

for ; a < b ;
//Go语言编译器会自动判断三个子语句中是否存在条件子语句

//例如写成这样就会报错
for ; ; a < b

后置子语句的意思就是先进行条件子语句判断, for 代码块执行之后再对条件变量操作的语句进行判断,上面例子中的 a++ 就是一个后置子语句。

4.2 range子语句

每一个 for 语句都可以使用一个特殊的 range 子语句,其作用类似于迭代器,用于轮询数组或者切片值中的每一个元素,也可以用于轮询字符串的每一个字符,以及字典值中的每个键值对,甚至还可以持续读取一个通道类型值中的元素。

例子:

package main

import "fmt"

func main() {
    str := "abcz"

    for i, char := range str {
        fmt.Printf("字符串第%d个字符的值为%d\n", i, char)
    }
    for _, char := range str { //忽略第一个值(忽略index)
        println(char)
    }

    for i := range str { //忽略第二个值
        fmt.Println(i)
    }

    for range str { //忽略全部返回值,只执行下面代码块
        println("执行成功")
    }
}

range 关键宇右边是 range 表达式,表达式一般写在 for 语句前面,以便提高代码易读性。像上面的例子可以写成:

for i := range "abcz"
//把原来的str := "abcz"表达式写到range关键字后面

这样不仅降低了可读性,也不容易管理后续的循环代码块。

range 关键宇左边表示的是一对索引-值对,根据不同的表达式返回不同的结果,详见下表。

右边表达式返回的类型 第一个值 第二个值 string index str[index],返回类型为rune array/slice index str[index] map key m[key] channel element

从上表可以看出,除了轮询字符串,还支持其他类型,例如数组,切片,字典甚至通道等等。

例子:

package main

import "fmt"

func main() {
    m := map[string]int{"a": 1, "b": 2}
    for k, v := range m {
        println(k, v)
    }

    numbers := []int{1, 2, 3, 4}
    for i, x := range numbers {
        fmt.Printf("第%d次,x的值为%d。\n", i, x)
    }
}

返回第一个值为索引值(键值),有时候并不是我们所需要的,因此可以使用“ _ ”空标识符表示忽略第一个返回值。对于空字典或切片、空数组、空字符串等情况, for 语句会直接结束,不会循环。

但如果需要指定 for 执行循环的次数,例如需要获取数组(或者其他支持的类型)里面的值,而数组中有一个值是空字符串,则可以指定数组(或其他支持类型)长度,强制让 for 循环执行相应次数,例如将上面的例子稍微改一下:

package main

import "fmt"

func main() {
    numbers := [5]int{1, 2, 3, 4}
    for i, x := range numbers {
        fmt.Printf("第%d次,x的值为%d。\n", i, x)
    }
}

由于定义了 numbers 长度为 5 ,但 numbers 中只有 4 个值,因此最后一个为空值,从 for 循环返回的信息可以看到第 5x 的值为 0 ,代码块的确执行了 5 次。

5 延迟语句

defer 用于延迟调用指定函数, defer 关键字只能出现在函数内部。

例子:

package main

import "fmt"

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello")
}

上面例子会首先打印 Hello ,然后再打印 World ,因为第一句使用了 defer 关键字, defer 语句会在函数最后执行,被延迟的操作是 defer 后面的内容。

defer 后面的表达式必须是外部函数的调用,上面的例子就是针对 fmt.Println 函数的延迟调用。 defer 有两大特点:

  • 只有当 defer 语句全部执行, defer 所在函数才算真正结束执行。
  • 当函数中有 defer 语句时,需要等待所有 defer 语句执行完毕,才会执行 return 语句。

因为 defer 的延迟特点,可以把 defer 语句用于回收资源、清理收尾等工作。使用 defer 语句之后,不用纠结回收代码放在哪里,反正都是最后执行。

这里需要注意 defer 的执行时机,例如下面的例子:

package main

import "fmt"

var i = 0

func print() {
    fmt.Println(i)
}

func main() {
    for ; i < 5; i++ {
        defer print()
    }
}

上面例子返回的是 55 ,这是因为每个 defer 都是在函数轮询之后,最后才执行,此时 i 的值当然就是 5 了。如果要正确反向打印数字则应该这样写:

package main

import "fmt"

var i = 0

func print(i int) {
    fmt.Println(i)
}

func main() {
    for ; i < 5; i++ {
        defer print(i)
    }
}

上面例子引入了函数参数,虽然还没介绍函数的概念,但是不妨碍理解这里面的 defer 关键知识。这里之所以是一个反序的数字列表,是因为 defer 其实是一个栈,遵循先入后出,或者理解为后进先出。

i 等于0 时, defer 语句第一次被压栈,此时 defer 后面的函数返回 0 ; i 不断自增,一直到 i 等于 4 时, defer 语句第 5 次入栈, defer 后的函数返回 4 ;此时 i 的自增不再满足 for 条件,于是跳出循环,在结束之前,Go语言会根据 defer 后进先出原则逐条打印栈内的数值,于是就出现现在看到的结果了。

简单来说就是当一个函数内部有多个 defer 语句时,最后面的 defer 语句最先执行(当然是指在所有 defer 语句中) 。

6 标签

在Go语言中,有一个特殊的概念就是标签,可以给 forswitchselect 等流程控制代码块打上一个标签,配合标签标识符可以方便跳转到某一个地方继续执行,有助于提高编程效率。标签格式如下:

L1:
    for i := 0; i <= 5; i++ {
        //代码块
    }

//下面写法也可以,不过Go语言编译器会自动格式化为上面的格式
L2:switch i {
    //代码块
}

标签的名称是区分大小写的,为了提高代码易读性,建议标签名称使用大写字母和数字。标签可以标记任何语句, 并不限定于流程控制语句,未使用的标签会引发错误。

6.1 break

break 语句意思是打断,这个语句表示打断当前流程控制。

例子:

package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        fmt.Printf("i的值是:%d\n", i)
        if i > 4 {
            break
        }
    }
}

上面例子中 break 的用法与主流编程语言并无差别,当 i &gt;4 时, break 语句打断了后续的循环操作,跳出循环。

但是对于嵌套流程控制语句,例如下面这种情况:

package main

import "fmt"

func main() {
    for {
        x := 1
        switch {
        case x > 0:
            fmt.Println("A")
            break
        case x == 1:
            fmt.Println("B")
        default:
            fmt.Println("C")
        }
    }
}

上面的例子会一直返回 A ,无限循环。上面代码出现的 break 语句只是跳出了 switch 流程,并没有跳出 for 循环,所以这个程序会一直执行下去。

为了跳出指定的流程控制代码块,就需要标签出场了:

package main

import "fmt"

func main() {
LOOP1:
    for {
        x := 1
        switch {
        case x > 0:
            fmt.Println("A")
            break LOOP1
        case x == 1:
            fmt.Println("B")
        default:
            fmt.Println("C")
        }
    }
}

上面代码中的 break LOOPl 表示跳出到标签为 LOOPl 的代码块之外。

6.2 continue

break 相反, continue 用于跳转到指定代码块位置继续执行任务, continue 仅能用于 for 循环。例如与上面几乎一样的代码,只是改动了一个关键宇:

package main

import "fmt"

func main() {
LOOP1:
    for i := 0; i <= 5; i++ {
        switch {
        case i > 0:
            fmt.Println("A")
            continue LOOP1
        case i == 1:
            fmt.Println("B")
        default:
            fmt.Println("C")
        }
        fmt.Printf("i is:%d\n", i)
    }
}

上面代码中因为使用了 continue 语句跳转到了外围 for 循环中,与 break 不同的是, continue 表示跳转后继续执行操作。

6.3 goto

Go语言中的 goto 语句可以无条件跳转到相同函数中的带标签语句。标签、 goto 等关键字都并非Go语言独创,Go语言可以说是一门大量参考了其他语言优点的编程语言,在流程控制上做了一些扩增(如类型 switch ),同时也减少了一些关键宇(如 while ) 。

package main

func main() {
    var i int
    for {
        println(i)
        i++
        if i > 2 {
            goto BREAK
        }
    }
BREAK:
    println("break")
}

上面代码中, goto 语句指向了 BREAK 标签,所以当循环中遇到 i&gt;2 时,直接跳转打印 breakgoto 只能在同一个函数中跳转。

下面的是我的公众号二维码,欢迎关注。文章转载请注明出处www.leexide.com

vM3a2i7.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK