控制流和错误处理
1:Go的条件语句和循环语句
详细解答
1. if语句的特殊用法
代码实现
go
// 基本if语句
func basicIf(x int) {
if x > 0 {
fmt.Println("x是正数")
} else if x < 0 { // go语言不允许else if 和 else 换行
fmt.Println("x是负数")
} else {
fmt.Println("x是零")
}
}
// if语句中的初始化
func ifWithInit() {
// 在if条件中声明变量,后续的else if 和else可以访问,出了if不可访问
if num := rand.Intn(100); num < 50 {
fmt.Printf("随机数 %d 小于50\n", num)
} else {
fmt.Printf("随机数 %d 大于等于50\n", num)
}
// num在这里不可访问
// 常见的错误处理模式
if file, err := os.Open("test.txt"); err != nil {
fmt.Printf("打开文件失败: %v\n", err)
} else {
defer file.Close()
fmt.Println("文件打开成功")
}
}2. switch语句的多种形式
和其他语言相比,自带break,想要穿透,需要fallthrough
- switch表达式是可选的,如果省略,等价于switch true,即表达式switch
- case允许匹配多个条件
代码实现
go
// 基本switch语句
func basicSwitch(day int) {
switch day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
case 3, 4, 5: // 多个case值
fmt.Println("工作日")
case 6, 7:
fmt.Println("周末")
default:
fmt.Println("无效的日期")
}
}
// 表达式switch
func expressionSwitch(score int) {
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 70:
fmt.Println("中等")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
}
// switch初始化语句
func switchWithInit() {
switch hour := time.Now().Hour(); {
case hour < 12:
fmt.Println("上午好")
case hour < 18:
fmt.Println("下午好")
default:
fmt.Println("晚上好")
}
}
// 类型switch
func typeSwitch(v interface{}) {
switch x := v.(type) {
case nil:
fmt.Println("v是nil")
case bool:
fmt.Printf("v是bool类型,值为%t\n", x)
case int:
fmt.Printf("v是int类型,值为%d\n", x)
case string:
fmt.Printf("v是string类型,值为%s\n", x)
default:
fmt.Printf("v是%T类型\n", x)
}
}3. for循环的各种形式
for range循环遍历切片时,切片长度在开始遍历时就已经被固定下来,即使循环中使用append动态修改切片的长度,也不会影响range的遍历次数。
代码实现
go
// 经典for循环
func classicFor() {
for i := 0; i < 5; i++ {
fmt.Printf("%d ", i)
}
fmt.Println()
}
// while风格的for循环
func whileStyleFor() {
i := 0
for i < 5 {
fmt.Printf("%d ", i)
i++
}
fmt.Println()
}
// 无限循环
func infiniteFor() {
count := 0
for {
count++
if count > 3 {
break
}
fmt.Printf("计数: %d\n", count)
}
}
// for-range循环
func forRange() {
// 遍历切片
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("索引%d: 值%d\n", index, value)
}
// 只要索引
for index := range numbers {
fmt.Printf("索引: %d\n", index)
}
// 只要值
for _, value := range numbers {
fmt.Printf("值: %d\n", value)
}
// 遍历字符串
text := "Hello世界"
for index, char := range text {
fmt.Printf("字节索引%d: 字符%c\n", index, char)
}
// 遍历map
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
for name, age := range ages {
fmt.Printf("%s: %d岁\n", name, age)
}
// 遍历通道
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for value := range ch {
fmt.Printf("从通道接收: %d\n", value)
}
}2:Go的错误处理机制
1. 基本错误处理
代码实现
go
import (
"errors"
"fmt"
"strconv"
)
// 返回错误的函数
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
// 基本错误处理模式
func basicErrorHandling() {
result, err := divide(10, 2)
if err != nil {
fmt.Printf("错误: %v\n", err)
return
}
fmt.Printf("结果: %f\n", result)
// 处理除零错误
result, err = divide(10, 0)
if err != nil {
fmt.Printf("除法错误: %v\n", err)
// 可以进行错误恢复或其他处理
return
}
fmt.Printf("结果: %f\n", result)
}2. 自定义错误类型
代码实现
go
// 自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("验证失败 - %s: %s", e.Field, e.Message)
}
// 使用自定义错误
func validateUser(name string, age int) error {
if name == "" {
return ValidationError{
Field: "name",
Message: "姓名不能为空",
}
}
if age < 0 || age > 150 {
return ValidationError{
Field: "age",
Message: "年龄必须在0-150之间",
}
}
return nil
}
func demonstrateCustomError() {
if err := validateUser("", 25); err != nil {
fmt.Printf("用户验证失败: %v\n", err)
// 类型断言检查具体错误类型
if ve, ok := err.(ValidationError); ok {
fmt.Printf("验证错误字段: %s\n", ve.Field)
}
}
}3. 错误包装和解包(Go 1.13+)
代码实现
go
import (
"fmt"
"errors"
)
// 错误包装
func processFile(filename string) error {
if err := readFile(filename); err != nil {
return fmt.Errorf("处理文件 %s 失败: %w", filename, err)
}
return nil
}
func readFile(filename string) error {
// 模拟文件读取错误
return errors.New("文件不存在")
}
// 错误解包和检查
func demonstrateErrorWrapping() {
err := processFile("test.txt")
if err != nil {
fmt.Printf("错误: %v\n", err)
// 检查错误链中是否包含特定错误
var target error = errors.New("文件不存在")
if errors.Is(err, target) {
fmt.Println("错误链中包含文件不存在错误")
}
// 解包错误
if unwrapped := errors.Unwrap(err); unwrapped != nil {
fmt.Printf("原始错误: %v\n", unwrapped)
}
}
}4. 错误处理的最佳实践
代码实现
go
// 多返回值的错误处理
func parseAndValidate(input string) (int, error) {
// 首先解析
num, err := strconv.Atoi(input)
if err != nil {
return 0, fmt.Errorf("解析数字失败: %w", err)
}
// 然后验证
if num < 0 {
return 0, errors.New("数字不能为负数")
}
return num, nil
}
// 早期返回模式
func processData(data []string) error {
if len(data) == 0 {
return errors.New("数据为空")
}
for i, item := range data {
if item == "" {
return fmt.Errorf("第%d项数据为空", i)
}
num, err := parseAndValidate(item)
if err != nil {
return fmt.Errorf("处理第%d项数据失败: %w", i, err)
}
// 处理解析后的数字
fmt.Printf("处理数字: %d\n", num)
}
return nil
}
// 批量错误收集
func processDataBatch(data []string) []error {
var errors []error
for i, item := range data {
if _, err := parseAndValidate(item); err != nil {
errors = append(errors,
fmt.Errorf("第%d项: %w", i, err))
}
}
return errors
}
func demonstrateErrorPatterns() {
// 早期返回模式
data1 := []string{"10", "20", "-5", "30"}
if err := processData(data1); err != nil {
fmt.Printf("处理失败: %v\n", err)
}
// 批量错误收集
data2 := []string{"10", "abc", "-5", "30"}
if errs := processDataBatch(data2); len(errs) > 0 {
fmt.Println("批量处理错误:")
for _, err := range errs {
fmt.Printf(" %v\n", err)
}
}
}3:break、continue和标签
1. break和continue基础用法
代码实现
go
func basicBreakContinue() {
fmt.Println("=== break示例 ===")
for i := 0; i < 10; i++ {
if i == 5 {
break // 跳出循环
}
fmt.Printf("%d ", i) // 输出: 0 1 2 3 4
}
fmt.Println()
fmt.Println("=== continue示例 ===")
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // 跳过偶数
}
fmt.Printf("%d ", i) // 输出: 1 3 5 7 9
}
fmt.Println()
}2. 嵌套循环中的标签使用
代码实现
go
func labeledBreakContinue() {
fmt.Println("=== 标签break示例 ===")
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer // 跳出外层循环
}
fmt.Printf("(%d,%d) ", i, j)
}
}
fmt.Println()
fmt.Println("=== 标签continue示例 ===")
outer2:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer2 // 继续外层循环的下一次迭代
}
fmt.Printf("(%d,%d) ", i, j)
}
}
fmt.Println()
}3. 实际应用场景
代码实现
go
// 查找二维数组中的元素
func findInMatrix(matrix [][]int, target int) (row, col int, found bool) {
search:
for i, rowData := range matrix {
for j, value := range rowData {
if value == target {
row, col, found = i, j, true
break search
}
}
}
return
}
// 处理多层验证
func validateInput(data map[string]interface{}) error {
validation:
for key, value := range data {
switch key {
case "name":
if str, ok := value.(string); !ok || str == "" {
break validation
}
case "age":
if age, ok := value.(int); !ok || age < 0 {
break validation
}
case "email":
if email, ok := value.(string); !ok || !strings.Contains(email, "@") {
break validation
}
}
continue validation
}
return errors.New("验证失败")
}
func demonstrateRealWorldUsage() {
// 测试矩阵查找
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
if row, col, found := findInMatrix(matrix, 5); found {
fmt.Printf("找到元素5在位置(%d,%d)\n", row, col)
}
// 测试数据验证
testData := map[string]interface{}{
"name": "Alice",
"age": 30,
"email": "alice@example.com",
}
if err := validateInput(testData); err != nil {
fmt.Printf("验证失败: %v\n", err)
} else {
fmt.Println("验证通过")
}
}4:select语句和通道操作
1. select基础用法
go
func basicSelect() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "来自ch1的消息"
select {
case msg1 := <-ch1:
fmt.Printf("接收到: %s\n", msg1)
case msg2 := <-ch2:
fmt.Printf("接收到: %s\n", msg2)
default:
fmt.Println("没有可用的通道操作")
}
}2. select超时模式
go
import "time"
func selectWithTimeout() {
ch := make(chan string)
// 在另一个goroutine中延迟发送数据
go func() {
time.Sleep(2 * time.Second)
ch <- "延迟消息"
}()
select {
case msg := <-ch:
fmt.Printf("接收到消息: %s\n", msg)
case <-time.After(1 * time.Second):
fmt.Println("操作超时")
}
}
func selectNonBlocking() {
ch := make(chan int, 1)
// 非阻塞发送
select {
case ch <- 42:
fmt.Println("成功发送数据")
default:
fmt.Println("通道已满,无法发送")
}
// 非阻塞接收
select {
case value := <-ch:
fmt.Printf("接收到: %d\n", value)
default:
fmt.Println("通道为空,无法接收")
}
}3. 复杂的select应用
代码实现
go
func multiChannelProcessor() {
data := make(chan int, 5)
errors := make(chan error, 1)
done := make(chan bool, 1)
// 数据生产者
go func() {
defer close(data)
for i := 0; i < 10; i++ {
if i == 7 {
errors <- fmt.Errorf("处理第%d项时出错", i)
return
}
data <- i
time.Sleep(100 * time.Millisecond)
}
done <- true
}()
// 数据消费者
for {
select {
case value, ok := <-data:
if !ok {
fmt.Println("数据通道已关闭")
return
}
fmt.Printf("处理数据: %d\n", value)
case err := <-errors:
fmt.Printf("处理错误: %v\n", err)
return
case <-done:
fmt.Println("处理完成")
return
case <-time.After(1 * time.Second):
fmt.Println("处理超时")
return
}
}
}🎯 核心知识点总结
最佳实践要点
- 错误检查: 总是检查和处理错误
- 资源清理: 使用defer确保资源被清理
- 可读性: 保持控制流的清晰和简洁
- 并发安全: 在并发环境中正确使用select和通道
