首页 > 其他分享 >Go基础之条件语句,For循环,错误处理

Go基础之条件语句,For循环,错误处理

时间:2024-12-29 09:01:22浏览次数:10  
标签:语句 case fmt Println func error Go 错误处理 main

目录

1 条件语句

1.1 if 语句

if 语句使用特点:

  • 不需使用 小括号() 将条件包含起来
  • 大括号{}必须存在,即使只有一行语句
  • 左括号必须在if或else的同一行
  • 在if之后,条件语句之前,可以添加变量初始化语句,使用 ; 进行分隔
  • 在有返回值的函数中,最终的 return 不能在条件语句中

Go 编程语言中 if 语句的语法如下:

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else if 布尔表达式 {
  /* 在布尔表达式为 false 时执行 */
}else {
  /* 在布尔表达式为 false 时执行 */
}
或者
if statement; condition {  
}

例如:

 if a < 20 {
       /* 如果条件为 true 则执行以下语句 */
       fmt.Printf("a 小于 20\n" )
   }

如果在条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示:

package main
  
import "fmt"
func main() {
    if num := 9; num < 0 {
        fmt.Println(num, "is negative")
    } else if num < 10 {
        fmt.Println(num, "has 1 digit")
    } else {
        fmt.Println(num, "has multiple digits")
    }
}

1.2 switch

1.2.1 switch

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
switch 语句执行的过程 从上至下,直到找到匹配项,匹配项后面也不需要再加 break。因为 switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough
switch 的 default 不论放在哪都是最后执行,不会受位置影响
Go 编程语言中 switch 语句的语法如下:

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

示例

package main

import "fmt"
func main() {
   /* 定义局部变量 */
   var grade string = "B"
   var marks int = 90

   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }
switch 中的表达式是可选的,可以省略。
如果省略表达式,则相当于 switch true,这种情况下会将每一个 case 的表达式的求值结果与 true 做比较,如果匹配,则执行相应的代码块
   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}

可以在一个 case 中包含多个表达式,每个表达式用逗号分隔。

letter := "i"
	switch letter { 
	case "a", "e", "i", "o", "u": //multiple expressions in case
		fmt.Println("vowel")
	default:
		fmt.Println("not a vowel")
	}

1.2.2 Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

实例

package main

import "fmt"

func main() {
   var x interface{}
     
   switch i := x.(type) {
      case nil:  
         fmt.Printf(" x 的类型 :%T",i)                
      case int:  
         fmt.Printf("x 是 int 型")                      
      case float64:
         fmt.Printf("x 是 float64 型")          
      case func(int) float64:
         fmt.Printf("x 是 func(int) 型")                      
      case bool, string:
         fmt.Printf("x 是 bool 或 string 型" )      
      default:
         fmt.Printf("未知型")    
   }  
}
以上代码执行结果为:

x 的类型 :<nil>

1.2.3 fallthrough

使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。

package main

import "fmt"

func main() {

    switch {
    case false:
            fmt.Println("1、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("2、case 条件语句为 true")
            fallthrough
    case false:
            fmt.Println("3、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("4、case 条件语句为 true")
    case false:
            fmt.Println("5、case 条件语句为 false")
            fallthrough
    default:
            fmt.Println("6、默认 case")
    }
}

结果为:

2、case 条件语句为 true
3、case 条件语句为 false
4、case 条件语句为 true

2 for循环

2.1 简介

Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。

和 C 语言的 for 一样:
for init; condition; post { }
init: 一般为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 一般为赋值表达式,给控制变量增量或减量。

和 C 的 while 一样:
for condition { }

和 C 的 for(;;) 一样:
for { }

示例:

for i := 1; i <= 10; i++ { 
	fmt.Printf(" %d", i)
}

无限循环示例:

for {
        fmt.Println("Hello World")
    }

也可以在for循环中声明和操作多个变量。让我们编写一个程序,使用多个变量声明:

for no, i := 10, 1; i <= 10 && no <= 19; i, no = i+1, no+1 {
    //multiple initialisation and increment
	fmt.Printf("%d * %d = %d\n", no, i, no*i)
}

2.2 For-each range 循环

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。
格式如下:

for key, value := range oldMap {
    newMap[key] = value
}
如果只想读取 key,格式如下:
for key := range oldMap

或者这样:
for key, _ := range oldMap

如果只想读取 value,格式如下:
for _, value := range oldMap

在遍历时可以使用 _ 来忽略索引或值。

package main

import "fmt"

func main() {
    nums := []int{2, 3, 4}
    
    // 忽略索引
    for _, num := range nums {
        fmt.Println("value:", num)
    }
    
    // 忽略值
    for i := range nums {
        fmt.Println("index:", i)
    }
}

2.3 示例

package main
import "fmt"

func main() {
   strings := []string{"google", "runoob"}
   for i, s := range strings {
      fmt.Println(i, s)
   }
   numbers := [6]int{1, 2, 3, 5}
   for i,x:= range numbers {
      fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
   }  
}

for 循环内对同名变量继续声明赋值

package main

import "fmt"

func main(){
  var a int = 0
  fmt.Println("for start")
  for a:=0; a < 10; a++ {
    fmt.Println(a)
  }
  fmt.Println("for end")

  fmt.Println(a)
}

在 for 循环的 initialize(a:=0) 中,此时 initialize 中的 a 与外层的 a 不是同一个变量,initialize 中的 a 为 for 循环中的局部变量,因此在执行完 for 循环后,输出 a 的值仍然为 0。

3 错误处理

3.1 简介

Go 通过内置的错误接口提供了非常简单的错误处理机制。
Go 语言的错误处理采用显式返回错误的方式,而非传统的异常处理机制。这种设计使代码逻辑更清晰,便于开发者在编译时或运行时明确处理错误。

Go 的错误处理主要围绕以下机制展开:

  • error 接口:标准的错误表示。
  • 显式返回值:通过函数的返回值返回错误。
  • 自定义错误:可以通过标准库或自定义的方式创建错误。
  • panic 和 recover:处理不可恢复的严重错误。

3.2 error 接口

3.2.1 error接口

Go 标准库定义了一个 error 接口,表示一个错误的抽象。
error 类型是一个接口类型,这是它的定义:

type error interface {
    Error() string
}

任何实现了 Error() 方法的类型都可以作为错误,Error() 方法返回一个描述错误的字符串。

3.2.2 使用 errors 包创建错误

我们可以在编码中通过实现 error 接口类型来生成错误信息。

创建一个简单错误:

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("this is an error")
    fmt.Println(err) // 输出:this is an error
}

函数通常在最后的返回值中返回错误信息,使用 errors.New 可返回一个错误信息:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

3.2.3 errors 包原理

接口 error 的作用是为错误值提供一个标准的字符串表示方法,即实现了 Error() 方法的任何类型都可以被当作 error 使用。

errors.New 是 Go 标准库中的一个函数,它的作用是创建并返回一个实现了 error 接口的值。下面是 errors.New 的源码实现:

package errors

import "strconv"

// 定义错误类型
type errorString struct {
    s string
}

// 实现 error 接口的方法
func (e *errorString) Error() string {
    return e.s
}

// 创建并返回一个 error
func New(text string) error {
    return &errorString{s: text}
}

工作原理:

  • errorString:是一个结构体,用来存储错误信息(s 字段)。
  • errorString:实现了 Error() 方法,返回错误信息 s。
  • errors.New:函数返回的是 errorString 的指针,因为 *errorString 实现了 error 接口,因此可以作为 error 类型返回。

示例解析

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("this is an error")
	fmt.Println(err)         // 输出:this is an error
	fmt.Println(err.Error()) // 输出:this is an error
}
  • errors.New("this is an error"):创建了一个 *errorString 实例,赋值给 err。
  • fmt.Println(err):隐式调用了 Error() 方法,输出 this is an error。
  • err.Error():是显式调用 Error() 方法,输出同样的内容

3.3 显式返回错误

Go 中,错误通常作为函数的返回值返回,开发者需要显式检查并处理。

package main

import (
        "errors"
        "fmt"
)

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, errors.New("division by zero")
        }
        return a / b, nil
}

func main() {
        result, err := divide(10, 0)
        if err != nil {
                fmt.Println("Error:", err)
        } else {
                fmt.Println("Result:", result)
        }
}
输出:
Error: division by zero

3.4 自定义错误

3.4.1 自定义

通过定义自定义类型,可以扩展 error 接口。

package main

import (
        "fmt"
)

type DivideError struct {
        Dividend int
        Divisor  int
}

func (e *DivideError) Error() string {
        return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, &DivideError{Dividend: a, Divisor: b}
        }
        return a / b, nil
}

func main() {
        _, err := divide(10, 0)
        if err != nil {
                fmt.Println(err) // 输出:cannot divide 10 by 0
        }
}

3.4.2 使用 errors.Is 和 errors.As

从 Go 1.13 开始,errors 包引入了 errors.Iserrors.As 用于处理错误链:

errors.Is:检查某个错误是否是特定错误或由该错误包装而成。

package main

import (
        "errors"
        "fmt"
)

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
        return fmt.Errorf("database error: %w", ErrNotFound)
}

func main() {
        err := findItem(1)
        if errors.Is(err, ErrNotFound) {
                fmt.Println("Item not found")
        } else {
                fmt.Println("Other error:", err)
        }
}

errors.As:将错误转换为特定类型以便进一步处理。

package main

import (
        "errors"
        "fmt"
)

type MyError struct {
        Code int
        Msg  string
}

func (e *MyError) Error() string {
        return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}

func getError() error {
        return &MyError{Code: 404, Msg: "Not Found"}
}

func main() {
        err := getError()
        var myErr *MyError
        if errors.As(err, &myErr) {
                fmt.Printf("Custom error - Code: %d, Msg: %s\n", myErr.Code, myErr.Msg)
        }
}

3.5 panic 和 recover

3.5.1 简介

Go 的 panic 用于处理不可恢复的错误,recover 用于从 panic 中恢复。

  • panic
    导致程序崩溃并输出堆栈信息。
    常用于程序无法继续运行的情况。
  • recover:捕获 panic,避免程序崩溃。
    只有在同一个协程中调用 recover 才管用。recover 不能恢复一个不同协程的 panic

引发panic有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。
发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数。即:立即按照逆序执行 defer,并逐级往外层函数栈扩散;defer 就类似 finally
panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panicdefer里面的panic能够被后续执行的defer捕获。
recover用来捕获panic,阻止panic继续向上传递。recover()defer一起使用,但是defer只有在后面的函数体内直接被掉用才能捕获panic来终止异常,否则返回nil,异常继续向外传递
利用 recover 捕获 panic 时,defer 需要再 panic 之前声明,否则由于 panic 之后的代码得不到执行,因此也无法 recover

3.5.2 示例

简单示例:

package main

import "fmt"

func safeFunction() {
        defer func() {
                if r := recover(); r != nil {
                        fmt.Println("Recovered from panic:", r)
                }
        }()
        panic("something went wrong")
}

func main() {
        fmt.Println("Starting program...")
        safeFunction()
        fmt.Println("Program continued after panic")
}

结果为:
Starting program...
Recovered from panic: something went wrong
Program continued after panic

包含defer 的示例


package main

import (
"fmt"
)

func main() {
  fmt.Println("外层开始")
  defer func() {
    fmt.Println("外层准备recover")
    if err := recover(); err != nil {
      fmt.Printf("%#v-%#v\n", "外层", err) // err已经在上一级的函数中捕获了,这里没有异常,只是例行先执行defer,然后执行后面的代码
    } else {
      fmt.Println("外层没做啥事")
    }
    fmt.Println("外层完成recover")
  }()
  fmt.Println("外层即将异常")
  f()
  fmt.Println("外层异常后")
  defer func() {
    fmt.Println("外层异常后defer")
  }()
}

func f() {
  fmt.Println("内层开始")
  defer func() {
    fmt.Println("内层recover前的defer")
  }()

  defer func() {
    fmt.Println("内层准备recover")
    if err := recover(); err != nil {
      fmt.Printf("%#v-%#v\n", "内层", err) // 这里err就是panic传入的内容
    }

    fmt.Println("内层完成recover")
  }()

  defer func() {
    fmt.Println("内层异常前recover后的defer")
  }()

  panic("异常信息")

  defer func() {
    fmt.Println("内层异常后的defer")
  }()

  fmt.Println("内层异常后语句") //recover捕获的一级或者完全不捕获这里开始下面代码不会再执行
}

结果:
外层开始
外层即将异常
内层开始
内层异常前recover后的defer
内层准备recover
"内层"-"异常信息"
内层完成recover
内层recover前的defer
外层异常后
外层异常后defer
外层准备recover
外层没做啥事
外层完成recover

3.5.3 recover 恢复后获得堆栈跟踪

当我们恢复 panic 时,我们就释放了它的堆栈跟踪。实际上,在上述程序里,恢复 panic 之后,我们就失去了堆栈跟踪。
有一种办法可以打印出堆栈跟踪,就是使用 Debug 包中的 PrintStack 函数。

package main

import (  
    "fmt"
    "runtime/debug"
)

func r() {          
    if r := recover(); r != nil {        
        fmt.Println("Recovered", r)
        debug.PrintStack()
    }
}

func a() {          
    defer r()
    n := []int{ 5, 7, 4}
    fmt.Println(n[3])
    fmt.Println("normally returned from a")
}

func main() {          
    a()
    fmt.Println("normally returned from main")
}

在上面的程序中,我们使用了 debug.PrintStack() 打印堆栈跟踪。

标签:语句,case,fmt,Println,func,error,Go,错误处理,main
From: https://www.cnblogs.com/jingzh/p/18638383

相关文章

  • Go基础之结构体,接口
    目录1结构体1.1简介1.2定义结构体1.3声明结构体1.3.1new声明1.3.2直接声明1.3.3与new的对比1.3.4new和&操作符的区别1.3.5结构体指针1.4结构体标签1.4.1标签的语法1.4.2标签的工作机制1.4.3常见用途1.4.3.1JSON序列化和反序列化1.4.3.2数据库映射1.4.3.3表......
  • Go 并发之goroutine和Channel讲解
    目录1并发1.1简介1.2Goroutine1.2.1简介1.2.2特点1.2.3检测数据访问冲突1.2.4示例1.3通道(Channel)1.3.1普通通道1.3.1.1简介1.3.1.2声明通道1.3.1.3普通通道示例1.3.2带缓冲区通道1.3.2.1简介1.3.2.2带缓冲区通道示例1.3.3遍历1.3.3.1for遍历1.3.3.2range遍历......
  • Go基础之指针和反射讲解
    目录1指针1.1简介1.2使用指针1.3指针优化输出1.3.1优化输出复杂类型1.3.2去掉优化1.3.3基本类型1.4指针数组1.4.1指针数组优化1.5指向指针的指针1.6向函数传递指针参数2反射2.1reflect2.1.1示例2.2获取变量值ValueOf2.3修改变量值Value.Set2.3.1Elem方法2.3.2......
  • Go IO之文件处理,TCP&UDP讲解
    目录1文件处理1.1打开和关闭文件1.2读取文件1.2.1简单示例1.2.2中文乱码1.2.2.1bufio1.2.2.2ioutil1.3写入文件1.3.1Write和WriteString1.3.2fmt.Fprintln1.3.2.1写入文件1.3.2.2写入标准输出1.3.3bufio.NewWriter1.3.4ioutil.WriteFile2TCP&UDP2.1TCP2.1.1服......
  • Go 并发之WaitGroup,并发锁,Context
    目录1Go并发1.1WaitGroup1.2并发锁1.2.1互斥锁1.2.2读写互斥锁1.2.3sync.Once1.2.4sync.Map1.3Context1.3.1简介1.3.2主要功能1.3.3使用示例1.3.3.1取消信号1.3.3.2设置超时1.3.3.3传递值1Go并发1.1WaitGroupsync.WaitGroup是Go标准库提供的一种同步原语,常......
  • client-go InClusterConfig方法
    InClusterConfig方法packagemainimport( "context" "test/signals" "time" "os" core_v1"k8s.io/api/core/v1" metav1"k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes......
  • 全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之循环结构(for循环语句)(三)
    在C++程序中,累乘的思想应用很广泛,很多情况下累加、累乘和累除相互结合使用可以解决很多问题。实战训练1—求阶乘问题描述:给定正整数n,求从1到n的每一个整数的阶乘。输入格式:输入一行,包含一个正整数(1<n≤12)。输出格式:输出n行,每行有两个数,分别是i和i的阶乘,两个......
  • 2024-12-28:求出出现两次数字的 XOR 值。用go语言,给定一个数组 nums,其中的数字出现的频
    2024-12-28:求出出现两次数字的XOR值。用go语言,给定一个数组nums,其中的数字出现的频率要么是一次,要么是两次。请找出所有出现两次的数字,并计算它们的按位XOR值。如果没有数字出现两次,则返回0。1<=nums.length<=50。1<=nums[i]<=50。nums中每个数字要么出现过一......
  • 介绍一下logos这个词法分析工具,它和nom相比如何?我看lalrpop官网给出的示例就是logos配
    UUUUUUUUUUUUUUUUUUUUUULogos简介Logos是一个用于词法分析的高效Rust库,其设计目标是简单、快速且高效。它通过声明式的方式定义词法规则,并利用Rust的强类型系统生成轻量级的词法分析器。Logos的特点声明式规则:使用Rust的枚举定义每种Token类型,并通过属性宏指定......
  • C# 和 Go 的协同开发:打造高效并发与企业级应用的最佳实践
    在现代软件开发中,微服务架构和分布式系统成为主流。开发者面临着多种挑战,其中最常见的两个需求是高并发处理和复杂的企业级业务逻辑。C#和Go作为两种广泛使用的编程语言,各自有独特的优势,在应对这些挑战时能够发挥不同的作用。C#强调企业级开发的完整性和稳定性,特别适合构......