Go 语言学习:流程控制 + 函数
- 一、条件判断语句
-
- [1.1 if 系列语句](#1.1 if 系列语句)
- [1.2 switch 语句](#1.2 switch 语句)
- [1.3 select 语句](#1.3 select 语句)
-
- [1.3.1 核心规则](#1.3.1 核心规则)
- [1.3.2 基础示例](#1.3.2 基础示例)
- [1.3.3 select 常见踩坑](#1.3.3 select 常见踩坑)
- 二、循环语句与跳转控制
-
- [2.1 for 循环四大写法](#2.1 for 循环四大写法)
-
- [2.1.1 标准 for 循环(初始化;条件;后置)](#2.1.1 标准 for 循环(初始化;条件;后置))
- [2.1.2 条件 for 循环(模拟 while)](#2.1.2 条件 for 循环(模拟 while))
- [2.1.3 无限循环](#2.1.3 无限循环)
- [2.1.4 for-range(遍历集合)](#2.1.4 for-range(遍历集合))
-
- 语法格式
- 代码示例(遍历切片)
- [忽略索引/值:使用空白标识符 `_`](#忽略索引/值:使用空白标识符
_)
- [2.2 循环嵌套](#2.2 循环嵌套)
- [2.3 跳转控制语句](#2.3 跳转控制语句)
-
- [2.3.1 break 语句](#2.3.1 break 语句)
-
- [示例1:单层循环 break](#示例1:单层循环 break)
- 示例2:标签跳出多层循环
- [2.3.2 continue 语句](#2.3.2 continue 语句)
-
- 代码示例(只打印奇数)
- [补充:`continue` 也支持标签,用于指定跳转到外层循环。](#补充:
continue也支持标签,用于指定跳转到外层循环。)
- [2.3.3 goto 语句](#2.3.3 goto 语句)
-
- 代码示例
- [踩坑:滥用 `goto` 会造成代码逻辑混乱,正规项目尽量不用。](#踩坑:滥用
goto会造成代码逻辑混乱,正规项目尽量不用。)
- [2.4 循环通用踩坑总结](#2.4 循环通用踩坑总结)
- 三、函数基础与高阶用法
-
- [3.1 函数基础定义与调用](#3.1 函数基础定义与调用)
- [3.2 函数多返回值](#3.2 函数多返回值)
-
- 代码示例(交换两个字符串)
-
- 运行结果
- 错误返回示例(实战常用)
- [忽略部分返回值:使用空白标识符 `_`](#忽略部分返回值:使用空白标识符
_)
- [3.3 函数参数传递方式](#3.3 函数参数传递方式)
- [3.4 匿名函数与闭包](#3.4 匿名函数与闭包)
-
- [3.4.1 匿名函数](#3.4.1 匿名函数)
- [3.4.2 闭包(Closure)](#3.4.2 闭包(Closure))
- [3.5 方法(Method)](#3.5 方法(Method))
- [3.6 函数常见踩坑总结](#3.6 函数常见踩坑总结)
目录
- 条件判断语句(if / if-else / switch / select)
- 循环语句与跳转控制(for / break / continue / goto)
- 函数基础与高阶用法(定义、调用、参数、返回值、闭包、方法)
一、条件判断语句
条件语句用于根据布尔表达式的结果(true/false)执行不同代码块。Go 语言不支持三目运算符 ?:,所有分支逻辑均使用标准条件语句实现。
1.1 if 系列语句
1.1.1 基础 if 语句
语法格式
go
if 布尔表达式 {
代码逻辑
}
语法说明
- 表达式结果必须是
bool类型(true/false),不能直接使用数字充当条件; - 左大括号
{必须紧跟在表达式末尾,禁止单独换行(Go 强制语法规范); - 条件表达式无需括号包裹(加括号也能运行,但不符合编码规范)。
代码示例
go
package main
import "fmt"
func main() {
age := 20
if age >= 18 {
fmt.Println("已成年")
}
}
运行结果
已成年
常见踩坑
- 错误写法:
if (age >= 18)不推荐(多余括号);if age >= 18 {以外的换行格式会语法报错; - 错误写法:
if 1,Go 不允许数字当作布尔条件,和 C/C++ 语法不同。
1.1.2 if-else 语句(二分支)
语法格式
go
if 布尔表达式 {
条件成立执行
} else {
条件不成立执行
}
代码示例
go
package main
import "fmt"
func main() {
score := 59
if score >= 60 {
fmt.Println("考试及格")
} else {
fmt.Println("考试不及格")
}
}
运行结果
考试不及格
1.1.3 if-else if-else 语句(多分支)
适用于多条件依次判断场景,从上到下匹配,命中后不再执行后续分支。
语法格式
go
if 条件1 {
// 条件1成立
} else if 条件2 {
// 条件2成立
} else {
// 所有条件都不成立
}
代码示例
go
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
}
运行结果
良好
1.1.4 嵌套 if
在 if/else 代码块内部继续使用 if,适用于多层筛选场景。
代码示例
go
package main
import "fmt"
func main() {
age := 22
gender := "男"
if age >= 18 {
if gender == "男" {
fmt.Println("成年男性")
} else {
fmt.Println("成年女性")
}
}
}
常见踩坑
嵌套层级过多会降低代码可读性,复杂场景建议改用 switch 语句。
1.2 switch 语句
switch 用于多值匹配分支 ,相比多 else if 代码更简洁,是 Go 高频语法。
1.2.1 基础特性
- 默认匹配成功后自动终止 ,不需要手动写
break(和 C/C++/Java 区别极大); - 支持任意数据类型(整型、字符串、布尔值等);
- 支持无表达式写法 ,等价于多
if-else; fallthrough关键字:强制执行下一个case(不判断条件)。
1.2.2 标准 switch(带表达式)
语法格式
go
switch 变量/表达式 {
case 值1:
逻辑1
case 值2, 值3: // 一个case匹配多个值
逻辑2
default: // 所有case都不匹配时执行(可选)
默认逻辑
}
代码示例
go
package main
import "fmt"
func main() {
day := 3
switch day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
case 3, 4, 5: // 多值匹配
fmt.Println("周三/周四/周五(工作日)")
case 6, 7:
fmt.Println("周末")
default:
fmt.Println("无效日期")
}
}
运行结果
周三/周四/周五(工作日)
1.2.3 无表达式 switch(等效 if-else)
不写 switch 后的表达式,每个 case 编写独立布尔条件,适合复杂逻辑判断。
代码示例
go
package main
import "fmt"
func main() {
score := 75
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
}
1.2.4 fallthrough 穿透(特殊用法)
fallthrough 会强制执行下一个 case,不会校验下一个 case 的条件,仅用于特殊业务场景,日常开发不推荐滥用。
代码示例
go
package main
import "fmt"
func main() {
num := 1
switch num {
case 1:
fmt.Println("数字1")
fallthrough // 穿透,继续执行下一个case
case 2:
fmt.Println("数字2")
default:
fmt.Println("其他数字")
}
}
运行结果
数字1
数字2
1.2.5 switch 常见踩坑
- 忘记 Go 默认自带
break,不要额外手写break(语法允许,但多余); fallthrough只能放在case代码块末尾,放在中间会报错;case中多个值必须用逗号分隔,不能用||。
1.3 select 语句
select 是 Go 独有语法,专门用于通道(channel)多路监听 ,语法类似 switch,仅在协程+通道场景使用。
1.3.1 核心规则
- 每个
case必须是通道读写操作; - 多个
case同时就绪时,随机选择一个执行(无优先级); - 无
default且所有case都未就绪时,阻塞等待; - 有
default时,无就绪case则立即执行default,不会阻塞。
1.3.2 基础示例
go
package main
import "fmt"
func main() {
// 创建两个缓冲通道
ch1 := make(chan int, 1)
ch2 := make(chan string, 1)
ch1 <- 100
ch2 <- "hello"
select {
case val := <-ch1:
fmt.Println("读取通道1数据:", val)
case val := <-ch2:
fmt.Println("读取通道2数据:", val)
default:
fmt.Println("暂无通道数据")
}
}
运行说明
多次运行会随机输出两个通道的内容,验证"随机选择"特性。
1.3.3 select 常见踩坑
case不能编写普通表达式,只能是通道读写;- 不要依赖
case顺序设置优先级,Go 会随机执行就绪分支; - 死循环+无
default的select会永久阻塞程序。
二、循环语句与跳转控制
Go 仅支持 for 循环 ,没有 while/do-while,通过 for 变体实现所有循环场景;搭配 break/continue/goto 实现跳转控制。
2.1 for 循环四大写法
2.1.1 标准 for 循环(初始化;条件;后置)
最常用写法,适用于已知循环次数的场景。
语法格式
go
for 初始化语句; 循环条件; 后置语句 {
循环体
}
代码示例(遍历 0~4)
go
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("当前索引:%d\n", i)
}
}
运行结果
当前索引:0
当前索引:1
当前索引:2
当前索引:3
当前索引:4
2.1.2 条件 for 循环(模拟 while)
省略初始化 和后置 语句,仅保留循环条件,等价于 while 循环,适用于未知循环次数。
语法格式
go
for 循环条件 {
循环体
}
代码示例
go
package main
import "fmt"
func main() {
i := 0
for i < 3 {
fmt.Println(i)
i++
}
}
2.1.3 无限循环
省略所有语句,for {} 代表无限循环,需配合 break 手动退出。
语法格式
go
for {
循环体
}
代码示例
go
package main
import "fmt"
func main() {
count := 0
for {
fmt.Println("无限循环", count)
count++
if count >= 2 {
break // 手动退出循环
}
}
}
2.1.4 for-range(遍历集合)
专门用于遍历字符串、数组、切片、map、通道,是 Go 遍历容器的标准写法。
语法格式
go
for 索引, 值 := range 容器 {
循环体
}
代码示例(遍历切片)
go
package main
import "fmt"
func main() {
arr := []string{"Go", "Java", "Python"}
for idx, val := range arr {
fmt.Printf("索引:%d,值:%s\n", idx, val)
}
}
忽略索引/值:使用空白标识符 _
go
// 只取值,忽略索引
for _, val := range arr {
fmt.Println(val)
}
2.2 循环嵌套
在 for 内部嵌套 for,常用于二维数据、九九乘法表等场景。
代码示例(九九乘法表)
go
package main
import "fmt"
func main() {
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d*%d=%d ", j, i, j*i)
}
fmt.Println()
}
}
2.3 跳转控制语句
2.3.1 break 语句
- 作用:终止当前循环/switch/select,跳出代码块;
- 普通用法:仅跳出当前单层循环;
- 标签用法:配合标签
label跳出多层嵌套循环(Go 推荐写法)。
示例1:单层循环 break
go
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
if i == 2 {
break // 终止循环
}
fmt.Println(i)
}
}
示例2:标签跳出多层循环
go
package main
import "fmt"
func main() {
// 定义标签
Outer:
for i := 1; i <= 3; i++ {
for j := 1; j <= 3; j++ {
if j == 2 {
break Outer // 直接跳出外层循环
}
fmt.Printf("i=%d,j=%d\n", i, j)
}
}
}
2.3.2 continue 语句
作用:跳过当前轮循环,直接进入下一轮迭代,不会终止整个循环。
代码示例(只打印奇数)
go
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i%2 == 0 {
continue // 偶数跳过,进入下一轮
}
fmt.Println("奇数:", i)
}
}
补充:continue 也支持标签,用于指定跳转到外层循环。
2.3.3 goto 语句
作用:无条件跳转到指定标签位置 ,破坏代码结构,日常开发禁止滥用,仅用于异常处理等特殊场景。
代码示例
go
package main
import "fmt"
func main() {
fmt.Println("开始执行")
goto End // 跳转到 End 标签
fmt.Println("这段代码不会执行")
End: // 定义标签
fmt.Println("跳转到此处")
}
踩坑:滥用 goto 会造成代码逻辑混乱,正规项目尽量不用。
2.4 循环通用踩坑总结
- 无限循环忘记加
break,程序卡死; for-range遍历中文字符串时,索引为字节位置,建议使用rune;- 嵌套循环中
break仅跳出当前层,多层退出必须使用标签; - 循环条件书写错误(如
i < 5写成i <= 5),导致循环次数异常。
三、函数基础与高阶用法
函数是可复用的代码块 ,Go 程序入口 main() 本身也是函数。Go 函数支持多返回值、值传递、指针传参、可变参数、闭包、方法等特性。
3.1 函数基础定义与调用
3.1.1 标准语法
go
// 无返回值
func 函数名(参数列表) {
函数体
}
// 单个返回值
func 函数名(参数列表) 返回值类型 {
函数体
return 返回数据
}
// 多个返回值
func 函数名(参数列表) (返回值1类型, 返回值2类型) {
函数体
return 数据1, 数据2
}
语法规则
- 函数必须以
func关键字开头; - 参数、返回值均需声明数据类型;
- 无返回值可省略返回值定义;
return用于终止函数并返回数据。
3.1.2 基础示例(单参数 + 单返回值)
go
package main
import "fmt"
// 定义函数:求两个整数的最大值
func max(num1, num2 int) int {
if num1 > num2 {
return num1
}
return num2
}
func main() {
a, b := 10, 20
res := max(a, b) // 调用函数
fmt.Println("最大值:", res)
}
运行结果
最大值: 20
3.2 函数多返回值
Go 核心特性,常用于"结果 + 错误信息"的组合返回(标准库高频用法)。
代码示例(交换两个字符串)
go
package main
import "fmt"
// 两个返回值
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := "Go", "Java"
a, b = swap(a, b)
fmt.Println(a, b)
}
运行结果
Java Go
错误返回示例(实战常用)
go
package main
import "errors"
import "fmt"
// 除法:返回结果 + 错误
func div(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
func main() {
res, err := div(10, 0)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", res)
}
}
忽略部分返回值:使用空白标识符 _
go
res, _ := div(10, 2) // 忽略错误返回值
3.3 函数参数传递方式
Go 所有参数默认值传递 ,如需修改原始数据,使用指针实现引用传递。
3.3.1 值传递(默认)
传递参数的副本,函数内部修改不会影响原始变量。
代码示例
go
package main
import "fmt"
func modify(x int) {
x = 100 // 修改副本
}
func main() {
num := 10
modify(num)
fmt.Println(num) // 原始值不变:10
}
3.3.2 指针传递(模拟引用传递)
传递变量内存地址,函数内部通过地址修改原始数据。
代码示例
go
package main
import "fmt"
// 接收指针类型
func modify(x *int) {
*x = 100 // 通过地址修改原值
}
func main() {
num := 10
modify(&num) // 取地址传参
fmt.Println(num) // 原始值被修改:100
}
3.3.3 可变参数
使用 ...类型 声明可变参数,函数内等价于切片,支持传入任意数量同类型参数。
语法 + 示例
go
package main
import "fmt"
// 可变参数:计算多个整数之和
func sum(nums ...int) int {
total := 0
for _, v := range nums {
total += v
}
return total
}
func main() {
fmt.Println(sum(1, 2))
fmt.Println(sum(1, 2, 3, 4))
}
3.4 匿名函数与闭包
3.4.1 匿名函数
无函数名的函数,可直接定义并调用,也可赋值给变量。
示例
go
package main
import "fmt"
func main() {
// 1. 定义并立即调用
func() {
fmt.Println("匿名函数执行")
}()
// 2. 赋值给变量,重复调用
f := func(a int) int {
return a * 2
}
fmt.Println(f(5))
}
3.4.2 闭包(Closure)
引用了外部局部变量的匿名函数,闭包会"捕获"外部变量,变量生命周期延长。
经典示例(累加器)
go
package main
import "fmt"
// 返回一个闭包
func counter() func() int {
count := 0 // 外部变量,被闭包捕获
return func() int {
count++
return count
}
}
func main() {
c1 := counter()
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
c2 := counter() // 新闭包,拥有独立count
fmt.Println(c2()) // 1
}
闭包踩坑:循环中使用闭包易出现变量复用问题。
3.5 方法(Method)
带有接收者的函数,绑定到结构体/自定义类型上,是 Go 实现面向对象的核心方式。
语法格式
go
func (接收者 类型) 方法名(参数) 返回值 {
方法体
}
代码示例
go
package main
import "fmt"
// 定义结构体
type User struct {
Name string
Age int
}
// 绑定到 User 的方法
func (u User) sayHello() {
fmt.Printf("你好,我是%s,年龄%d\n", u.Name, u.Age)
}
func main() {
u := User{Name: "张三", Age: 20}
u.sayHello() // 调用方法
}
3.6 函数常见踩坑总结
- 多返回值调用时,必须接收所有返回值 ,不需要的用
_忽略; - 值传递无法修改原始变量,修改变量必须使用指针传参;
- 可变参数必须放在参数列表最后;
- 函数名小写仅当前包可见,大写可跨包调用(Go 访问规则);
main函数无参数、无返回值,不能手动调用。