目录
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.Is
和 errors.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
或抛出panic
。defer
里面的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()
打印堆栈跟踪。