惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
IT之家
IT之家
D
Docker
博客园 - 叶小钗
A
About on SuperTechFans
博客园_首页
Apple Machine Learning Research
Apple Machine Learning Research
Recorded Future
Recorded Future
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
V
V2EX
S
SegmentFault 最新的问题
量子位
P
Proofpoint News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
Latest news
Latest news
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
有赞技术团队
有赞技术团队
The GitHub Blog
The GitHub Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
I
InfoQ
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
DataBreaches.Net
G
GRAHAM CLULEY
P
Proofpoint News Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Y
Y Combinator Blog
小众软件
小众软件
NISL@THU
NISL@THU
L
Lohrmann on Cybersecurity
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
I
Intezer
Last Week in AI
Last Week in AI
T
Threatpost
人人都是产品经理
人人都是产品经理
U
Unit 42
Security Latest
Security Latest
AWS News Blog
AWS News Blog
T
The Blog of Author Tim Ferriss
MongoDB | Blog
MongoDB | Blog
罗磊的独立博客
GbyAI
GbyAI
P
Palo Alto Networks Blog
G
Google Developers Blog
MyScale Blog
MyScale Blog
L
LangChain Blog

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第04章 - 流程控制与函数
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

第04章 - 流程控制与函数

本章讲解 Go 的流程控制语句(条件、循环、分支)以及函数的定义与高级用法,包括多返回值、可变参数、闭包和 defer 机制。

4.1 条件语句 if

Go 的 if 语句不需要括号包裹条件,但花括号是必须的:

score := 85
if score >= 90 {
    fmt.Println("优秀")
} else if score >= 60 {
    fmt.Println("及格")
} else {
    fmt.Println("不及格")
}

4.1.1 带初始化语句的 if

if 可以在条件前包含一个初始化语句,该语句声明的变量作用域仅限于 if-else 块内,这是 Go 非常地道的写法:

if value, err := strconv.Atoi("123"); err == nil {
    fmt.Println("转换成功:", value)
} else {
    fmt.Println("转换失败:", err)
}
// value 和 err 在这里已不可见

这种模式将变量作用域限制在最小范围,是处理错误的标准方式。

4.2 循环语句 for

Go 只有一种循环关键字 for,但它能表达所有循环形式。

4.2.1 经典三段式

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

4.2.2 类 while 循环

省略初始化和后置语句,for 就相当于 while

n := 1
for n < 100 {
    n *= 2
}

4.2.3 无限循环

for {
    // 永久循环,通常配合 break 或 return 退出
    if done {
        break
    }
}

4.2.4 range 遍历

range 用于遍历数组、切片、字符串、map 和 channel:

nums := []int{10, 20, 30}
for index, value := range nums {
    fmt.Printf("索引 %d = %d\n", index, value)
}

// 只要索引
for index := range nums {
    fmt.Println(index)
}

// 只要值,用下划线忽略索引
for _, value := range nums {
    fmt.Println(value)
}

// 遍历 map
m := map[string]int{"a": 1, "b": 2}
for key, val := range m {
    fmt.Println(key, val)
}

4.3 跳转语句

  • break:跳出当前循环或 switch
  • continue:跳过本次循环,进入下一次迭代。
  • goto:跳转到指定标签(极少使用)。
  • 标签(label):配合 break/continue 可跳出多层嵌套循环:
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i*j > 2 {
            break outer // 直接跳出外层循环
        }
    }
}

4.4 分支语句 switch

Go 的 switch 比 C 更强大,且默认不需要 break——每个 case 执行完自动跳出。

switch day {
case "Sat", "Sun": // 一个 case 可匹配多个值
    fmt.Println("周末")
default:
    fmt.Println("工作日")
}

4.4.1 表达式 switch

switch 后可以不跟变量,此时相当于 if-else 链:

switch {
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B")
default:
    fmt.Println("C")
}

4.4.2 fallthrough

若希望继续执行下一个 case(C 语言的默认行为),可使用 fallthrough

switch n {
case 1:
    fmt.Println("one")
    fallthrough
case 2:
    fmt.Println("two") // 当 n==1 时也会执行
}

4.4.3 类型 switch

用于判断接口的动态类型(详见第 6 章):

switch v := i.(type) {
case int:
    fmt.Println("整数:", v)
case string:
    fmt.Println("字符串:", v)
default:
    fmt.Println("未知类型")
}

4.5 函数定义

函数使用 func 关键字定义,基本语法为 func 函数名(参数列表) 返回值类型 { 函数体 }

func add(a int, b int) int {
    return a + b
}

// 相同类型的参数可合并书写
func add(a, b int) int {
    return a + b
}

4.5.1 多返回值

Go 函数可以返回多个值,这是其一大特色,广泛用于"返回结果 + 错误"的模式:

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

result, err := divide(10, 2)
if err != nil {
    fmt.Println("错误:", err)
    return
}
fmt.Println("结果:", result)

4.5.2 命名返回值

返回值可以命名,相当于在函数开头声明了这些变量,return 时可不带参数(裸返回):

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // 裸返回,返回 x 和 y
}

命名返回值能提升可读性,但裸返回在长函数中会降低可读性,应谨慎使用。

4.5.3 可变参数

参数类型前加 ... 表示可变参数,函数内部将其视为切片:

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

sum(1, 2, 3)         // 6
sum(1, 2, 3, 4, 5)   // 15

// 将切片展开传入
s := []int{1, 2, 3}
sum(s...)

4.6 函数是一等公民

在 Go 中,函数是一等公民(first-class citizen),可以赋值给变量、作为参数传递、作为返回值返回。

// 函数类型变量
var op func(int, int) int
op = add
fmt.Println(op(3, 4))

// 函数作为参数(回调)
func apply(a, b int, fn func(int, int) int) int {
    return fn(a, b)
}

4.6.1 匿名函数与闭包

匿名函数可以捕获外部变量,形成闭包:

func counter() func() int {
    count := 0
    return func() int {
        count++       // 捕获并修改外部变量
        return count
    }
}

c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3

每次调用 counter() 都会创建一个独立的 count 变量,闭包持有对它的引用。

4.7 defer 延迟执行

defer 用于延迟执行一个函数调用,被延迟的调用会在外层函数返回前执行,常用于资源释放、解锁、关闭文件等场景:

func readFile(name string) error {
    f, err := os.Open(name)
    if err != nil {
        return err
    }
    defer f.Close() // 函数返回前自动关闭文件

    // ... 处理文件 ...
    return nil
}

4.7.1 defer 的执行顺序

多个 defer后进先出(LIFO)的栈顺序执行:

func main() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}
// 输出: 2 1 0

4.7.2 defer 参数的求值时机

defer 语句的参数在 defer 出现时立即求值,而非执行时:

func example() {
    i := 0
    defer fmt.Println("defer 中的 i:", i) // 此时 i 为 0
    i = 100
    // 输出: defer 中的 i: 0
}

defer 还能配合命名返回值修改返回结果,这是实现 recover 错误恢复的关键(详见第 7 章)。

4.8 本章小结

本章学习了 Go 的流程控制(ifforswitch)和函数。Go 用单一的 for 表达所有循环,switch 默认自动 break 且支持类型判断。函数支持多返回值、可变参数、闭包,并通过 defer 优雅地管理资源释放。这些特性共同构成了 Go 简洁而强大的控制流。

下一章我们将深入复合数据类型:数组、切片、map 和结构体。