【Go语言-Day 7】循环控制全解析:从 for 基础到 for-range 遍历与高级控制

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的"USB-C",模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

Go语言系列文章目录

01-【Go语言-Day 1】扬帆起航:从零到一,精通 Go 语言环境搭建与首个程序
02-【Go语言-Day 2】代码的基石:深入解析Go变量(var, :=)与常量(const, iota)
03-【Go语言-Day 3】从零掌握 Go 基本数据类型:string, runestrconv 的实战技巧
04-【Go语言-Day 4】掌握标准 I/O:fmt 包 Print, Scan, Printf 核心用法详解
05-【Go语言-Day 5】掌握Go的运算脉络:算术、逻辑到位的全方位指南
06-【Go语言-Day 6】掌控代码流:if-else 条件判断的四种核心用法

07-【Go语言-Day 7】循环控制全解析:从 for 基础到 for-range 遍历与高级控制


文章目录

  • Langchain系列文章目录
  • Python系列文章目录
  • PyTorch系列文章目录
  • 机器学习系列文章目录
  • 深度学习系列文章目录
  • Java系列文章目录
  • JavaScript系列文章目录
  • Python系列文章目录
  • Go语言系列文章目录
  • 前言
  • [一、`for` 循环的基础形态:三段式循环](#一、for 循环的基础形态:三段式循环)
    • [1.1 语法结构与执行流程](#1.1 语法结构与执行流程)
      • [1.1.1 语法定义](#1.1.1 语法定义)
    • [1.2 实例:计算 1 到 100 的和](#1.2 实例:计算 1 到 100 的和)
  • [二、`for` 循环的变种:模拟 `while` 和无限循环](#二、for 循环的变种:模拟 while 和无限循环)
    • [2.1 模拟 `while` 循环](#2.1 模拟 while 循环)
      • [2.1.1 语法结构](#2.1.1 语法结构)
      • [2.1.2 实例:猜数字游戏](#2.1.2 实例:猜数字游戏)
    • [2.2 无限循环 `for {}`](#2.2 无限循环 for {})
      • [2.2.1 语法结构](#2.2.1 语法结构)
      • [2.2.2 应用场景与实例](#2.2.2 应用场景与实例)
  • 三、强大的迭代工具:`for...range`
    • [3.1 `for...range` 语法简介](#3.1 for...range 语法简介)
    • [3.2 遍历不同数据类型](#3.2 遍历不同数据类型)
      • [3.2.1 遍历字符串](#3.2.1 遍历字符串)
      • [3.2.2 遍历数组和切片](#3.2.2 遍历数组和切片)
      • [3.2.3 遍历 `map`](#3.2.3 遍历 map)
      • [3.2.4 遍历通道 (`channel`)](#3.2.4 遍历通道 (channel))
  • 四、循环的流程控制
    • [4.1 `break`:跳出循环](#4.1 break:跳出循环)
        • [(1) 普通 `break`](#(1) 普通 break)
        • [(2) 使用标签 `label` 跳出外层循环](#(2) 使用标签 label 跳出外层循环)
    • [4.2 `continue`:跳过本次迭代](#4.2 continue:跳过本次迭代)
        • [(1) 普通 `continue`](#(1) 普通 continue)
        • [(2) 使用标签 `label` 的 `continue`](#(2) 使用标签 labelcontinue)
    • [4.3 `goto`:谨慎使用的跳转](#4.3 goto:谨慎使用的跳转)
  • 五、总结

前言

大家好,欢迎来到【Go语言从入门到精通】专栏的第 7 篇。在上一篇文章中,我们学习了 Go 语言中的决策者------if-else 条件语句,掌握了如何让程序根据不同条件执行不同路径。今天,我们将深入探讨 Go 语言中一个极其重要且独特的特性:循环结构。

与其他许多编程语言(如 C, Java, Python)不同,Go 语言在设计上追求极致的简洁,因此它只提供了一种循环关键字------for 。然而,千万不要小看这个唯一的循环结构,它的设计非常灵活,足以涵盖其他语言中 forwhiledo-while 等多种循环的功能。

本文将带你全面解锁 for 循环的"百变"用法,从最基础的三段式结构,到模拟 while 循环和无限循环的变体,再到强大的 for...range 迭代利器,最后还会讲解 breakcontinuegoto 这三个循环控制语句。无论你是编程新手还是有一定经验的开发者,相信本文都能让你对 Go 语言的循环有更深刻的理解。

一、for 循环的基础形态:三段式循环

最经典、最常见的 for 循环形式,与 C++ 或 Java 中的 for 循环非常相似,我们称之为"三段式循环"。它将循环的初始化、条件判断和循环后的操作都定义在了一行,结构清晰明了。

1.1 语法结构与执行流程

1.1.1 语法定义

三段式 for 循环由三个部分组成,并用分号 ; 隔开,其基本语法如下:

go 复制代码
for 初始化语句; 条件表达式; 后续语句 {
    // 循环体代码
}
  • 初始化语句 (initialization statement) : 在第一次循环开始前执行,通常用于声明和初始化一个局部循环变量。这个变量的作用域仅限于该 for 循环。
  • 条件表达式 (condition expression) : 在每次循环迭代开始前进行求值。如果表达式的值为 true,则执行循环体;如果为 false,则退出循环。
  • 后续语句 (post-statement): 在每次循环体执行完毕后执行,通常用于更新循环变量(例如,自增或自减)。

这个流程清晰地展示了"初始化 -> 条件判断 -> 执行循环体 -> 执行后续语句 -> 条件判断..."的循环过程。

1.2 实例:计算 1 到 100 的和

这是一个经典的编程入门问题,使用三段式 for 循环解决起来非常方便。

go 复制代码
package main

import "fmt"

func main() {
    sum := 0
    // 使用标准的三段式 for 循环
    // 1. 初始化: i := 1
    // 2. 条件判断: i <= 100
    // 3. 后续语句: i++
    for i := 1; i <= 100; i++ {
        sum += i // 将当前 i 的值累加到 sum
    }
    fmt.Printf("1到100的和是: %d\n", sum) // 输出: 1到100的和是: 5050
}

代码解析

  1. 我们首先初始化一个变量 sum 用于存放累加结果。
  2. for i := 1; i <= 100; i++
    • i := 1:定义并初始化循环变量 i 为 1。
    • i <= 100:只要 i 不超过 100,循环就会继续。
    • i++:每次循环结束后,i 的值加 1。
  3. 循环体 sum += i 将当前的 i 值加到 sum 变量上。
  4. i 增加到 101 时,条件 i <= 100false,循环终止。

二、for 循环的变种:模拟 while 和无限循环

Go 的 for 循环设计非常灵活,通过省略三段式中的某些部分,可以轻松模拟出其他语言中的 while 循环和无限循环。

2.1 模拟 while 循环

在很多语言中,while 循环用于处理那些循环次数不确定,只知道循环终止条件的情况。在 Go 中,我们可以通过省略 for 循环的初始化语句和后续语句来实现同样的效果。

2.1.1 语法结构

go 复制代码
for 条件表达式 {
    // 循环体代码
}

这实际上是三段式循环的一个简化版本,只保留了条件判断部分。

2.1.2 实例:猜数字游戏

让我们看一个简单的例子,程序随机生成一个数字,用户需要不断输入猜测的数字,直到猜对为止。

go 复制代码
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // 设置随机数种子,否则每次运行结果都一样
    rand.Seed(time.Now().UnixNano())
    secretNumber := rand.Intn(100) + 1 // 生成 1-100 之间的随机数

    var guess int
    fmt.Println("猜一个1到100之间的数字。")

    // 只有条件判断的 for 循环,等价于 while(true)
    // 在这里我们用 for 循环模拟 while 循环,直到猜对为止
    for guess != secretNumber {
        fmt.Print("请输入你猜的数字: ")
        fmt.Scan(&guess) // 从键盘读取输入

        if guess < secretNumber {
            fmt.Println("太小了!")
        } else if guess > secretNumber {
            fmt.Println("太大了!")
        }
    }
    
    fmt.Println("恭喜你,猜对了!")
}

代码解析
for guess != secretNumber 这行代码完美地扮演了 while 循环的角色。只要用户猜测的数字 guess 不等于 secretNumber,循环就会一直持续,不断提示用户输入。

2.2 无限循环 for {}

如果将 for 关键字后的所有部分都省略,就得到了一个无限循环。

2.2.1 语法结构

go 复制代码
for {
    // 永不停止的循环体代码
    // (通常需要一个 break 语句来退出)
}

2.2.2 应用场景与实例

无限循环在需要持续运行的服务中非常常见,例如:

  • Web 服务器监听网络请求。
  • 后台任务持续处理数据。
  • 定时执行某个任务。

通常,无限循环内部会有一个明确的退出条件,通过 break 关键字来实现跳出。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func main() {
    i := 0
    // 无限循环
    for {
        fmt.Println("这是一个无限循环,但我们会在第5次停下来。当前是第", i+1, "次。")
        time.Sleep(500 * time.Millisecond) // 暂停半秒,方便观察

        i++
        if i >= 5 {
            fmt.Println("循环次数达到5次,准备退出。")
            break // 使用 break 关键字跳出无限循环
        }
    }
    fmt.Println("成功退出循环!")
}

代码解析
for {} 创建了一个无限循环。循环体内部通过 if i >= 5 来判断是否满足退出条件,一旦满足,break 语句就会立即终止循环。

三、强大的迭代工具:for...range

for...range 是 Go 语言中一个非常强大且方便的语法结构,专门用于遍历各种数据集合,如字符串、数组、切片、map 和通道(channel)。

3.1 for...range 语法简介

for...range 会从数据集合中依次取出元素,并可以同时返回索引和值。

go 复制代码
for index, value := range collection {
    // 使用 index 和 value
}
  • collection: 要遍历的数据集合。
  • index: 当前元素的索引(对于 map 则是键)。
  • value: 当前元素的值。

如果你不需要索引或值,可以使用匿名变量 _ 来忽略它。

3.2 遍历不同数据类型

3.2.1 遍历字符串

遍历字符串时,for...range 会智能地处理 Unicode 字符,它返回的是每个字符(rune)及其在字符串中的起始字节索引。

go 复制代码
package main

import "fmt"

func main() {
    str := "Go语言-你好"

    // 使用 for...range 遍历字符串
    for index, char := range str {
        // %c 用于打印字符
        fmt.Printf("索引: %d, 字符: %c, Unicode码点: %U\n", index, char, char)
    }
}

输出结果

复制代码
索引: 0, 字符: G, Unicode码点: U+0047
索引: 1, 字符: o, Unicode码点: U+006F
索引: 2, 字符: 语, Unicode码点: U+8BED
索引: 5, 字符: 言, Unicode码点: U+8A00
索引: 8, 字符: -, Unicode码点: U+002D
索引: 9, 字符: 你, Unicode码点: U+4F60
索引: 12, 字符: 好, Unicode码点: U+597D

注意 :中文字符(如"语")占 3 个字节,所以下一个字符"言"的索引从 2 跳到了 5。这正是 for...range 处理多字节字符的优势所在。

3.2.2 遍历数组和切片

遍历数组或切片时,返回的是索引和该索引位置的元素值。

go 复制代码
package main

import "fmt"

func main() {
    nums := []int{10, 20, 30, 40, 50}

    // 完整遍历,获取索引和值
    fmt.Println("--- 获取索引和值 ---")
    for i, v := range nums {
        fmt.Printf("索引: %d, 值: %d\n", i, v)
    }

    // 只关心值,忽略索引
    fmt.Println("\n--- 只获取值 ---")
    for _, v := range nums {
        fmt.Printf("值: %d\n", v)
    }

    // 只关心索引
    fmt.Println("\n--- 只获取索引 ---")
    for i := range nums {
        fmt.Printf("索引: %d\n", i)
    }
}

3.2.3 遍历 map

遍历 map 时,返回的是键和值。需要特别注意的是,map 的遍历顺序是随机的,不保证与插入顺序一致

go 复制代码
package main

import "fmt"

func main() {
    studentScores := map[string]int{
        "Alice": 95,
        "Bob":   88,
        "Cathy": 100,
    }

    for name, score := range studentScores {
        fmt.Printf("学生: %s, 分数: %d\n", name, score)
    }
}

每次运行上述代码,输出的顺序可能会不同。

3.2.4 遍历通道 (channel)

for...range 也可以用于遍历通道(Channel),这在并发编程中非常有用。它会持续从通道中接收数据,直到通道被关闭。我们将在后续的并发编程章节中详细讲解。

四、循环的流程控制

在循环过程中,有时我们需要更精细地控制其流程,例如提前退出或跳过某次迭代。Go 提供了 breakcontinuegoto 来实现这些控制。

4.1 break:跳出循环

break 语句用于立即终止其所在的最内层 forswitchselect 语句的执行。

(1) 普通 break

在单层循环中,break 会直接退出循环。

go 复制代码
package main

import "fmt"

func main() {
    // 寻找 1 到 10 之间第一个能被 3 整除的数
    for i := 1; i <= 10; i++ {
        if i%3 == 0 {
            fmt.Printf("找到了第一个能被3整除的数: %d\n", i)
            break // 找到后立即跳出循环
        }
        fmt.Printf("当前检查的数字是: %d\n", i)
    }
}
(2) 使用标签 label 跳出外层循环

在嵌套循环中,普通的 break 只能跳出内层循环。如果想直接跳出外层循环,需要使用标签 (label)

go 复制代码
package main

import "fmt"

func main() {
MyLoop: // 定义一个标签
    for i := 1; i <= 3; i++ {
        for j := 1; j <= 3; j++ {
            fmt.Printf("i = %d, j = %d\n", i, j)
            if i == 2 && j == 2 {
                fmt.Println("满足条件,使用 break MyLoop 跳出所有循环")
                break MyLoop // 跳出到 MyLoop 标签指定的循环
            }
        }
    }
    fmt.Println("循环结束")
}

输出

复制代码
i = 1, j = 1
i = 1, j = 2
i = 1, j = 3
i = 2, j = 1
i = 2, j = 2
满足条件,使用 break MyLoop 跳出所有循环
循环结束

可以看到,当 i=2, j=2 时,程序直接跳出了最外层的循环。

4.2 continue:跳过本次迭代

continue 语句用于跳过当前循环体的剩余部分,并立即开始下一次迭代。

(1) 普通 continue

continue 在单层循环中会跳过本次迭代。

go 复制代码
package main

import "fmt"

func main() {
    // 打印 1 到 10 之间的所有奇数
    for i := 1; i <= 10; i++ {
        if i%2 == 0 { // 如果是偶数
            continue // 跳过本次循环的剩余部分(即下面的 Printf),直接进入下一次 i++
        }
        fmt.Printf("%d 是奇数\n", i)
    }
}
(2) 使用标签 labelcontinue

break 类似,continue 也可以与标签配合使用,用于控制外层循环的迭代。

4.3 goto:谨慎使用的跳转

goto 语句可以无条件地跳转到当前函数内由标签标识的语句处。

强烈建议:在绝大多数情况下,都应避免使用 goto 因为它会破坏程序的正常执行流程,使代码难以阅读、理解和维护。在现代编程实践中,goto 通常被认为是"代码坏味道"。

不过,为了知识的完整性,我们还是看一个例子,了解其工作方式。

go 复制代码
package main

import "fmt"

func main() {
    fmt.Println("开始执行")
    i := 0

Here: // 定义一个标签
    fmt.Println("当前 i 的值是:", i)
    i++
    if i < 3 {
        goto Here // 无条件跳转到 Here 标签处
    }

    fmt.Println("执行结束")
}

在某些极其复杂的、需要从深度嵌套中一次性跳出的场景,goto 可能是(有争议的)一种选择,但通常使用带标签的 break 是更好、更清晰的方案。

五、总结

在本篇文章中,我们对 Go 语言中唯一的循环结构 for 进行了全面而深入的探讨。现在,让我们来回顾一下核心知识点:

  1. Go 的简洁哲学 :Go 语言仅提供 for 关键字作为循环结构,但其灵活性足以实现传统 forwhile 和无限循环等所有功能。

  2. for 循环的四种形态

    • 三段式 forfor init; condition; post {},用于固定次数的循环,结构清晰。
    • whileforfor condition {},用于循环次数不确定、但有明确终止条件的场景。
    • 无限循环 for {} :用于需要持续运行的服务,通常配合 break 使用。
    • for...range:用于遍历字符串、数组、切片、map 等数据集合,是 Go 中处理迭代的利器,尤其在处理 Unicode 字符串时表现出色。
  3. 循环的流程控制

    • break:用于立即终止循环。配合标签可以跳出嵌套的外层循环。
    • continue:用于跳过当前迭代的剩余部分,直接进入下一次迭代。
    • goto:提供无条件跳转能力,但因其破坏代码结构,应极力避免使用。

掌握 for 循环的各种用法是编写高效、地道 Go 代码的基础。在下一篇文章中,我们将学习 Go 语言中另一个重要的流程控制语句 switch-case,它为我们处理多分支选择提供了比 if-else 更优雅的方案。敬请期待!


相关推荐
NAGNIP8 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab9 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab9 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
点光12 小时前
使用Sentinel作为Spring Boot应用限流组件
后端
不要秃头啊13 小时前
别再谈提效了:AI 时代的开发范式本质变了
前端·后端·程序员
AngelPP13 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年13 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
有志13 小时前
Java 项目添加慢 SQL 查询工具实践
后端
九狼14 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS14 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能