MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Go语言控制结构的嵌套与组合使用

2023-04-291.1k 阅读

Go 语言控制结构基础回顾

在深入探讨 Go 语言控制结构的嵌套与组合使用之前,我们先来回顾一下 Go 语言中基本的控制结构。Go 语言主要有三种基本控制结构:顺序结构、选择结构和循环结构。

顺序结构

顺序结构是程序中最基本的结构,代码按照编写的先后顺序依次执行。例如:

package main

import "fmt"

func main() {
    fmt.Println("第一步:打印这个消息")
    fmt.Println("第二步:再打印这个消息")
}

在上述代码中,先执行第一个 fmt.Println 语句,然后执行第二个,这就是典型的顺序执行。

选择结构

  1. if - else 语句
    • 简单的 if 语句用于根据条件决定是否执行某段代码。例如:
package main

import "fmt"

func main() {
    num := 10
    if num > 5 {
        fmt.Println(num, "大于5")
    }
}
  • if - else 语句则提供了两种分支选择。比如:
package main

import "fmt"

func main() {
    num := 3
    if num > 5 {
        fmt.Println(num, "大于5")
    } else {
        fmt.Println(num, "小于等于5")
    }
}
  • 还有 if - else if - else 结构,可以处理多个条件分支。例如:
package main

import "fmt"

func main() {
    score := 85
    if score >= 90 {
        fmt.Println("成绩等级:A")
    } else if score >= 80 {
        fmt.Println("成绩等级:B")
    } else if score >= 70 {
        fmt.Println("成绩等级:C")
    } else {
        fmt.Println("成绩等级:D")
    }
}
  1. switch - case 语句 switch - case 语句用于基于不同条件执行不同的代码块,它比 if - else if - else 结构在某些情况下更简洁明了。例如:
package main

import "fmt"

func main() {
    day := 3
    switch day {
    case 1:
        fmt.Println("星期一")
    case 2:
        fmt.Println("星期二")
    case 3:
        fmt.Println("星期三")
    default:
        fmt.Println("其他日期")
    }
}

switch 语句还可以不带表达式,这种情况下,它的行为类似于 if - else if - else 结构,但语法更紧凑。例如:

package main

import "fmt"

func main() {
    num := 15
    switch {
    case num < 10:
        fmt.Println(num, "小于10")
    case num >= 10 && num < 20:
        fmt.Println(num, "在10到19之间")
    default:
        fmt.Println(num, "其他情况")
    }
}

循环结构

  1. for 循环 Go 语言中只有 for 一种循环结构,但它通过不同的形式可以实现类似其他语言中 forwhiledo - while 的功能。
    • 经典的 for 循环形式:
package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
}
  • 类似 while 循环的形式:
package main

import "fmt"

func main() {
    i := 0
    for i < 5 {
        fmt.Println(i)
        i++
    }
}
  • 无限循环形式:
package main

import "fmt"

func main() {
    for {
        fmt.Println("无限循环中...")
        // 这里可以添加退出循环的条件,例如:
        // break
    }
}

控制结构的嵌套使用

if 语句的嵌套

在实际编程中,我们经常会遇到需要在一个条件判断内部再进行另一个条件判断的情况,这就需要用到 if 语句的嵌套。

例如,假设我们要判断一个学生是否通过考试并且是否获得优秀成绩。成绩大于等于 60 分为通过,大于等于 90 分为优秀。代码如下:

package main

import "fmt"

func main() {
    score := 85
    if score >= 60 {
        fmt.Println("通过考试")
        if score >= 90 {
            fmt.Println("并且获得优秀成绩")
        }
    } else {
        fmt.Println("未通过考试")
    }
}

在上述代码中,外层 if 语句判断学生是否通过考试,内层 if 语句在学生通过考试的基础上,进一步判断是否获得优秀成绩。

再比如,在一个电商系统中,我们可能需要根据用户的会员等级和购物金额来判断是否给予折扣。代码如下:

package main

import "fmt"

func main() {
    memberLevel := 2
    purchaseAmount := 200.0
    if memberLevel >= 2 {
        if purchaseAmount >= 100.0 {
            fmt.Println("您是高级会员且购物金额满足条件,享受 10% 折扣")
        } else {
            fmt.Println("您是高级会员,但购物金额不足,暂不享受折扣")
        }
    } else {
        fmt.Println("您不是高级会员,暂不享受折扣")
    }
}

这里外层 if 语句判断用户是否为高级会员,内层 if 语句在用户是高级会员的前提下,判断购物金额是否满足折扣条件。

for 循环的嵌套

for 循环的嵌套常用于处理多维数据结构,例如二维数组。假设我们要打印一个九九乘法表,就可以使用 for 循环的嵌套来实现。

package main

import "fmt"

func main() {
    for i := 1; i <= 9; i++ {
        for j := 1; j <= i; j++ {
            result := i * j
            fmt.Printf("%d×%d = %d\t", j, i, result)
        }
        fmt.Println()
    }
}

在这段代码中,外层 for 循环控制行数,从 1 到 9。对于每一行,内层 for 循环控制列数,列数从 1 到当前行号 i。这样就可以逐行打印出九九乘法表。

再比如,在图像处理中,我们可能需要遍历一个二维像素矩阵,对每个像素点进行某种操作。假设我们有一个简单的二维数组表示图像像素,每个像素值是 0 到 255 之间的整数,我们要对每个像素值进行平方操作。代码如下:

package main

import "fmt"

func main() {
    image := [][]int{
        {10, 20, 30},
        {40, 50, 60},
        {70, 80, 90},
    }
    for i := 0; i < len(image); i++ {
        for j := 0; j < len(image[i]); j++ {
            image[i][j] = image[i][j] * image[i][j]
        }
    }
    for _, row := range image {
        for _, value := range row {
            fmt.Printf("%d ", value)
        }
        fmt.Println()
    }
}

这里外层 for 循环遍历二维数组的行,内层 for 循环遍历每一行中的列,从而对每个像素值进行平方操作。

if 与 for 循环的嵌套

iffor 循环的嵌套结合可以实现更复杂的逻辑。例如,我们要在一个整数数组中找出所有大于某个阈值且是偶数的数,并计算它们的和。代码如下:

package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    threshold := 5
    sum := 0
    for _, num := range numbers {
        if num > threshold && num%2 == 0 {
            sum += num
        }
    }
    fmt.Printf("满足条件的数的和为:%d\n", sum)
}

在上述代码中,for 循环遍历数组中的每个数,if 语句在每次循环中判断当前数是否大于阈值且为偶数,如果满足条件,则将其加到 sum 中。

再比如,在一个文本处理程序中,我们要统计一篇文章中每个段落中单词数量大于 10 的行数。假设文章以二维字符串数组表示,每个子数组表示一个段落,每个字符串表示一行。代码如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    article := [][]string{
        {"这是第一段的第一行,单词数量较少", "这是第一段的第二行,单词多一些"},
        {"第二段的第一行,单词数量多", "第二段的第二行,单词较少"},
    }
    count := 0
    for _, paragraph := range article {
        for _, line := range paragraph {
            words := strings.Fields(line)
            if len(words) > 10 {
                count++
            }
        }
    }
    fmt.Printf("单词数量大于10的行数为:%d\n", count)
}

这里外层 for 循环遍历文章的每个段落,内层 for 循环遍历每个段落中的每一行。if 语句判断每一行的单词数量是否大于 10,如果是,则增加计数器 count

switch 与 for 循环的嵌套

switchfor 循环的嵌套可以处理一些根据不同条件进行循环操作的场景。例如,我们有一个游戏角色,根据角色的职业不同,在升级时获得不同的属性提升。假设角色职业用整数表示,1 为战士,2 为法师,3 为刺客。代码如下:

package main

import "fmt"

func main() {
    characterClass := 2
    level := 5
    for i := 1; i <= level; i++ {
        switch characterClass {
        case 1:
            fmt.Printf("战士升级,力量 + 5,生命值 + 50\n")
        case 2:
            fmt.Printf("法师升级,智力 + 5,魔法值 + 50\n")
        case 3:
            fmt.Printf("刺客升级,敏捷 + 5,暴击率 + 5%%\n")
        default:
            fmt.Println("未知职业")
        }
    }
}

在这段代码中,for 循环模拟角色升级的过程,switch 语句根据角色的职业类型,在每次升级时输出不同的属性提升信息。

再比如,在一个文件处理程序中,根据文件类型(用字符串表示),对文件内容进行不同的解析操作。假设我们有一个文件列表,每个文件的类型存储在一个字符串数组中,我们要对每个文件进行相应的处理。代码如下:

package main

import (
    "fmt"
)

func main() {
    fileTypes := []string{"txt", "csv", "jpg"}
    for _, fileType := range fileTypes {
        switch fileType {
        case "txt":
            fmt.Println("处理文本文件...")
            // 这里可以添加具体的文本文件处理逻辑
        case "csv":
            fmt.Println("处理 CSV 文件...")
            // 这里可以添加具体的 CSV 文件处理逻辑
        case "jpg":
            fmt.Println("处理图片文件...")
            // 这里可以添加具体的图片文件处理逻辑
        default:
            fmt.Println("未知文件类型")
        }
    }
}

这里 for 循环遍历文件类型列表,switch 语句根据不同的文件类型,输出相应的处理信息。

控制结构的组合使用

多层嵌套组合

在复杂的程序逻辑中,我们常常需要多层控制结构的嵌套组合。例如,在一个小型数据库查询系统中,我们要根据用户输入的查询条件,从一个二维数据表格中筛选出符合条件的数据。假设数据表格是一个二维字符串数组,第一行是表头,后续行是数据记录。我们可以这样实现:

package main

import (
    "fmt"
    "strings"
)

func main() {
    dataTable := [][]string{
        {"姓名", "年龄", "性别"},
        {"张三", "25", "男"},
        {"李四", "30", "女"},
        {"王五", "28", "男"},
    }
    searchName := "张"
    minAge := 20
    for i := 1; i < len(dataTable); i++ {
        if strings.Contains(dataTable[i][0], searchName) {
            age, _ := strconv.Atoi(dataTable[i][1])
            if age >= minAge {
                fmt.Println("找到符合条件的记录:", dataTable[i])
            }
        }
    }
}

在这段代码中,外层 for 循环遍历数据表格的每一行数据(从第二行开始,因为第一行是表头)。内层第一个 if 语句判断姓名是否包含指定的字符,第二个 if 语句在姓名符合条件的基础上,判断年龄是否满足最小值要求。这就是 for 循环与多个 if 语句的多层嵌套组合。

再比如,在一个图形绘制程序中,我们要绘制一个复杂的图形,这个图形由多个不同类型的子图形组成,每个子图形又有不同的绘制参数。假设子图形类型用整数表示,1 为圆形,2 为矩形,3 为三角形。绘制参数包括位置、大小等。代码如下:

package main

import (
    "fmt"
)

func drawCircle(x, y, radius int) {
    fmt.Printf("绘制圆形,圆心位置:(%d, %d),半径:%d\n", x, y, radius)
}

func drawRectangle(x, y, width, height int) {
    fmt.Printf("绘制矩形,左上角位置:(%d, %d),宽:%d,高:%d\n", x, y, width, height)
}

func drawTriangle(x1, y1, x2, y2, x3, y3 int) {
    fmt.Printf("绘制三角形,顶点位置:(%d, %d), (%d, %d), (%d, %d)\n", x1, y1, x2, y2, x3, y3)
}

func main() {
    subShapes := [][]int{
        {1, 100, 100, 50}, // 圆形参数:类型,圆心x,圆心y,半径
        {2, 200, 200, 100, 50}, // 矩形参数:类型,左上角x,左上角y,宽,高
        {3, 300, 300, 350, 350, 300, 350}, // 三角形参数:类型,顶点1x,顶点1y,顶点2x,顶点2y,顶点3x,顶点3y
    }
    for _, subShape := range subShapes {
        switch subShape[0] {
        case 1:
            drawCircle(subShape[1], subShape[2], subShape[3])
        case 2:
            drawRectangle(subShape[1], subShape[2], subShape[3], subShape[4])
        case 3:
            drawTriangle(subShape[1], subShape[2], subShape[3], subShape[4], subShape[5], subShape[6])
        }
    }
}

这里外层 for 循环遍历每个子图形的参数列表,switch 语句根据子图形的类型调用相应的绘制函数。这是 for 循环与 switch 语句的组合使用,并且在 switch 的每个 case 中执行不同的绘制逻辑,体现了多层控制结构的组合。

利用控制结构组合实现复杂业务逻辑

  1. 电商订单处理 在电商系统中,订单处理涉及多个复杂的逻辑判断。例如,根据用户的会员等级、订单金额、商品库存等条件,决定订单是否能够成功提交,以及是否给予相应的优惠和赠品。假设会员等级用整数表示,1 为普通会员,2 为高级会员,3 为 VIP 会员。商品库存存储在一个整数数组中,对应每个商品的库存数量。订单信息存储在一个二维数组中,每行表示一个订单,包含商品 ID、购买数量等信息。代码如下:
package main

import (
    "fmt"
)

func main() {
    memberLevel := 2
    orderAmount := 300.0
    productStocks := []int{10, 20, 15}
    orders := [][]int{
        {0, 5}, // 商品ID为0,购买数量为5
        {1, 3}, // 商品ID为1,购买数量为3
        {2, 2}, // 商品ID为2,购买数量为2
    }
    canSubmit := true
    for _, order := range orders {
        productID := order[0]
        quantity := order[1]
        if quantity > productStocks[productID] {
            canSubmit = false
            fmt.Println("商品", productID, "库存不足")
            break
        }
    }
    if canSubmit {
        var discount float64
        var gift string
        switch memberLevel {
        case 1:
            if orderAmount >= 200.0 {
                discount = 0.05
            }
        case 2:
            if orderAmount >= 200.0 {
                discount = 0.1
                gift = "小礼品"
            }
        case 3:
            discount = 0.15
            gift = "豪华礼品"
        }
        fmt.Printf("订单可以提交,折扣:%.2f%%,赠品:%s\n", discount*100, gift)
    } else {
        fmt.Println("订单无法提交")
    }
}

在这段代码中,首先通过 for 循环检查每个订单商品的库存是否足够。如果有任何商品库存不足,则设置 canSubmitfalse 并跳出循环。然后,在库存足够的情况下,通过 switch 语句根据会员等级和订单金额决定折扣和赠品。这是 for 循环、if 语句和 switch 语句的组合使用,实现了电商订单处理的复杂业务逻辑。

  1. 网络爬虫任务调度 在一个网络爬虫项目中,我们需要根据不同的网站类型和页面优先级,调度爬虫任务。假设网站类型用字符串表示,例如 "news" 表示新闻网站,"blog" 表示博客网站等。页面优先级用整数表示,1 为最高优先级,2 为次高优先级等。我们有一个任务列表,每个任务包含网站类型、页面 URL 和优先级信息。代码如下:
package main

import (
    "fmt"
)

func crawlNewsPage(url string) {
    fmt.Printf("正在爬取新闻页面:%s\n", url)
}

func crawlBlogPage(url string) {
    fmt.Printf("正在爬取博客页面:%s\n", url)
}

func main() {
    tasks := [][]interface{}{
        {"news", "http://news.example.com", 1},
        {"blog", "http://blog.example.com", 2},
        {"news", "http://anothernews.example.com", 2},
    }
    for _, task := range tasks {
        siteType := task[0].(string)
        url := task[1].(string)
        priority := task[2].(int)
        if priority == 1 {
            switch siteType {
            case "news":
                crawlNewsPage(url)
            case "blog":
                crawlBlogPage(url)
            }
        } else {
            fmt.Printf("优先级较低的任务:%s,稍后处理\n", url)
        }
    }
}

在上述代码中,for 循环遍历任务列表。对于每个任务,先通过 if 语句判断优先级是否为最高。如果是最高优先级,则通过 switch 语句根据网站类型调用相应的爬虫函数。这是 for 循环、if 语句和 switch 语句的组合,实现了网络爬虫任务的调度逻辑。

控制结构组合中的注意事项

  1. 代码可读性 随着控制结构嵌套和组合的层数增加,代码的可读性会迅速下降。为了保持代码的可读性,可以适当添加注释,说明每层控制结构的作用。例如:
package main

import (
    "fmt"
)

func main() {
    // 外层循环遍历数组中的每个数
    for _, num := range []int{1, 2, 3, 4, 5} {
        // 判断是否为偶数
        if num%2 == 0 {
            // 如果是偶数,进一步判断是否大于3
            if num > 3 {
                fmt.Println(num, "是大于3的偶数")
            }
        }
    }
}
  1. 避免过度嵌套 过度嵌套会使代码难以维护和调试。尽量通过提取函数等方式,将复杂的嵌套逻辑分解为更简单的部分。例如,上述电商订单处理的代码可以将库存检查和优惠计算分别提取到不同的函数中:
package main

import (
    "fmt"
)

func checkStock(productStocks []int, orders [][]int) bool {
    canSubmit := true
    for _, order := range orders {
        productID := order[0]
        quantity := order[1]
        if quantity > productStocks[productID] {
            canSubmit = false
            fmt.Println("商品", productID, "库存不足")
            break
        }
    }
    return canSubmit
}

func calculateDiscountAndGift(memberLevel int, orderAmount float64) (float64, string) {
    var discount float64
    var gift string
    switch memberLevel {
    case 1:
        if orderAmount >= 200.0 {
            discount = 0.05
        }
    case 2:
        if orderAmount >= 200.0 {
            discount = 0.1
            gift = "小礼品"
        }
    case 3:
        discount = 0.15
        gift = "豪华礼品"
    }
    return discount, gift
}

func main() {
    memberLevel := 2
    orderAmount := 300.0
    productStocks := []int{10, 20, 15}
    orders := [][]int{
        {0, 5},
        {1, 3},
        {2, 2},
    }
    if canSubmit := checkStock(productStocks, orders); canSubmit {
        discount, gift := calculateDiscountAndGift(memberLevel, orderAmount)
        fmt.Printf("订单可以提交,折扣:%.2f%%,赠品:%s\n", discount*100, gift)
    } else {
        fmt.Println("订单无法提交")
    }
}

这样代码结构更加清晰,每个函数的职责明确,便于维护和扩展。

  1. 逻辑正确性 在组合控制结构时,要仔细检查逻辑的正确性。特别是在涉及多个条件判断和循环的情况下,一个小的逻辑错误可能导致程序出现意想不到的结果。可以通过编写单元测试来验证控制结构组合的逻辑正确性。例如,对于上述电商订单处理的代码,可以编写如下单元测试:
package main

import (
    "testing"
)

func TestCheckStock(t *testing.T) {
    productStocks := []int{10, 20, 15}
    orders := [][]int{
        {0, 5},
        {1, 3},
        {2, 2},
    }
    result := checkStock(productStocks, orders)
    if!result {
        t.Errorf("库存检查结果错误")
    }
}

func TestCalculateDiscountAndGift(t *testing.T) {
    memberLevel := 2
    orderAmount := 300.0
    discount, gift := calculateDiscountAndGift(memberLevel, orderAmount)
    if discount != 0.1 || gift != "小礼品" {
        t.Errorf("折扣或赠品计算结果错误")
    }
}

通过这些单元测试,可以确保控制结构组合的逻辑在各种情况下都能正确运行。

通过合理地使用 Go 语言控制结构的嵌套与组合,我们能够实现各种复杂的程序逻辑。但在实际编程中,要注意保持代码的可读性、避免过度嵌套以及确保逻辑的正确性,这样才能编写出高质量的 Go 语言程序。