【Go语言快速上手】第一部分:函数与错误处理

文章目录

  • 一、函数的基本定义
    • [1.1 多返回值](#1.1 多返回值)
    • [1.2 命名返回值](#1.2 命名返回值)
  • 二、函数的参数
    • [2.1 值传递与 指针传递](#2.1 值传递与 指针传递)
    • [2.2 可变参数](#2.2 可变参数)
  • 三、函数类型与高阶函数
    • [3.1 函数类型](#3.1 函数类型)
    • [3.2 匿名函数lambda 与 闭包](#3.2 匿名函数lambda 与 闭包)
  • 四、方法(成员函数)
    • [4.1 方法的定义](#4.1 方法的定义)
    • [4.2 指针接收者](#4.2 指针接收者)
  • 五、错误处理
    • [5.1 错误类型](#5.1 错误类型)
    • [5.2 错误返回值](#5.2 错误返回值)
    • [5.3 `if` 语句检查错误](#5.3 if 语句检查错误)
    • [5.4 自定义错误类型](#5.4 自定义错误类型)
    • [5.5 `fmt.Errorf` 创建错误](#5.5 fmt.Errorf 创建错误)
    • [5.6 多重错误检查](#5.6 多重错误检查)
    • [5.7 `panic` 和 `recover` 机制](#5.7 panicrecover 机制)
    • [5.8 `defer` 用于资源清理](#5.8 defer 用于资源清理)

一、函数的基本定义

在Go语言中,通过func定义函数,基本的函数定义如下:

go 复制代码
func add(a int, b int) int {
	return a + b
}
  • func:关键字,用于定义函数
  • add:函数名
  • (a int, b int):参数列表,参数类型在参数名之后
  • int:返回值类型。
  • return a + b:函数体,返回两个整数的和。

1.1 多返回值

Go语言函数支持多返回值,一般在处理错误,或者返回多个结果时使用:

go 复制代码
func swap(x, y string) (string, string) {
    return y, x
}

1.2 命名返回值

Go语言允许为返回值命名,命名后函数体中可以直接使用这些变量,最后直接return即可

go 复制代码
func divide(a, b float64) (result float64, err error) {
	if b == 0 {
		err = errors.New("division by zero")
		return
	}
}

二、函数的参数

2.1 值传递与 指针传递

Go中参数传递默认是值传递,即函数内部对变量的修改不会影响外部变量,如果想使函数内部的修改同时影响外部变量,可以传指针(Go没有类似C++那样直接传引用的方法)

go 复制代码
func modifyValue(x int) {
	x = 100;
}

func modifyPointer(x *int) {
	*x = 100
}

2.2 可变参数

Go中,通过 ... 表示变长参数(可变参数),允许在函数中传递一个不确定数量的参数。

go 复制代码
func sum(nums ...int) int {
	total := 0
	for _, num := range nums {
		total += num
	}
	return total
}
  • nums ...int 表示 sum 函数可以接受 任意数量 的 int 类型参数。可以传递零个、一个或多个 int 参数。
  • 在函数体内,nums 是一个 切片 ([]int),它包含了传递给 sum 函数的所有参数。
  • ... 后面跟的是参数类型,表示这个参数的类型是一个切片类型,可以接收任意数量的元素。

三、函数类型与高阶函数

Go语言中,函数本质也是一种类型,可以同其他类型一样被传递与使用。

3.1 函数类型

我们可以通过type关键字将一个函数定义为一种类型,从而被传递/使用。

go 复制代码
// 定于函数类型
type operation func(int, int) int

func apply(op operation, a, b int) int {
	return op(a, b)
}

对于上面的代码,operation 是一个 类型别名,代表的是一个函数类型,函数 apply 接受一个 operation 类型的 op 对象,可以直接在函数体内调用op。

3.2 匿名函数lambda 与 闭包

Go中也有lambda函数(匿名函数),如下:

go 复制代码
add := func(a, b int) int {
	return a + b
}
  • 这行代码定义了一个 匿名函数,即没有函数名的函数。
  • func(a, b int) int 表示该函数接收 两个 int 类型的参数 ,并返回一个 int 类型的结果。
  • 函数体内部的 return a + b 表示返回 ab 的和。
  • add := 将这个匿名函数赋值给了变量 add,因此 add 现在是一个函数类型的变量,且类型为 func(int, int) int

匿名函数的作用

  • 匿名函数是一种没有函数名的函数,通常用于临时的、一次性的操作,或者在需要将函数作为参数传递时使用。
  • 匿名函数通常用于实现闭包

闭包

闭包是指一个函数捕获了其外部作用域的变量。

go 复制代码
func adder() func(a, b int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}	
}

func main() {
	a := adder()
	fmt.Println(a(1)) // 输出 1
    fmt.Println(a(2)) // 输出 3
}
  • sum 变量是 闭包 中的一个状态,匿名函数每次调用时都能"记住"之前的 sum 值。
  • 即使 adder 函数已经返回,匿名函数仍然可以访问并修改 sum,因为它持有对外部 sum 变量的引用

四、方法(成员函数)

Go语言中没有类的概念,但可以为结构体定义方法。

4.1 方法的定义

go 复制代码
type Rectangle struct {
	width, height float64
}

func (r Rectangle) area() float64 {
	return r.width * r.height
}

对于上面的代码:

  • func (r Rectangle) area() float64 这行代码定义了一个 方法 ,该方法与 Rectangle 类型关联。
    • (r Rectangle) 是方法的 接收者 ,表示 area 方法是为 Rectangle 类型定义的。这里的 r 是一个 Rectangle 类型的值,它代表一个矩形对象。
    • area() 是方法的名称,表示该方法会计算矩形的面积。
    • float64 是方法的返回值类型,表示计算出的面积是一个 float64 类型的值。

4.2 指针接收者

同理我们可以将接收者改为指针方式:

go 复制代码
func (r* Rectangle) scale(factor float64) {
	r.width *= factor
	r.height *= factor
}
  • func (r *Rectangle) scale(factor float64):这是一个方法定义。
    • (r *Rectangle)接收者 (receiver),意味着 scale 是为 Rectangle 类型的指针(*Rectangle)定义的方法。使用指针接收者而不是值接收者,可以让该方法直接修改原始对象(结构体)本身的字段。
    • factor float64scale 方法的参数,表示缩放因子,用于调整矩形的大小。

五、错误处理

Go 语言的错误处理与其他语言有些不同,Go 提倡显式的错误处理,避免隐式的异常机制。Go 没有传统的 try-catch 语句,而是通过函数返回值来传递错误,程序员需要手动检查和处理这些错误。

5.1 错误类型

在 Go 中,错误通常是 error 类型的一个值。error 是一个内建的接口类型,定义如下:

go 复制代码
type error interface {
    Error() string
}

error 接口只有一个方法 Error() string,它返回一个描述错误的字符串。许多标准库和自定义函数都会返回这个类型的值,以指示操作是否成功。

5.2 错误返回值

在 Go 中,函数通常会返回两个值:一个是主要结果值,另一个是错误值。一般要求我们显式地检查错误值。比如:

go 复制代码
func someFunction() (int, error) {
    return 0, fmt.Errorf("something went wrong")
}

在调用时,返回的错误必须检查:

go 复制代码
result, err := someFunction()
if err != nil {
    fmt.Println("Error:", err)
} else {
    fmt.Println("Result:", result)
}

5.3 if 语句检查错误

根据 Go 的错误处理风格,一般我们显式地检查每一个函数调用的返回值中的 error

go 复制代码
func example() {
    file, err := os.Open("nonexistent_file.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return // 处理错误后,可能会退出函数或做其他错误处理
    }
    defer file.Close()

    // 进一步的操作
}

在这里,如果 os.Open 返回一个错误(例如文件不存在),我们就会打印错误并退出函数。if err != nil 是 Go 错误处理的常见模式。

5.4 自定义错误类型

Go 支持自定义错误类型,通过实现 Error() 方法,可以创建自己的错误类型。这样可以携带更多的错误信息,比如错误代码、详细描述等。

go 复制代码
type MyError struct {
    Code    int
    Message string
}

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

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

func main() {
    err := doSomething()
    if err != nil {
        fmt.Println("Error:", err)
    }
}

对于上面的代码,MyError 结构体包含了一个错误码和错误消息,我们通过 Error() 方法实现了 error 接口。

5.5 fmt.Errorf 创建错误

Go 提供了 fmt.Errorf 函数来创建错误并格式化错误信息:

go 复制代码
err := fmt.Errorf("something went wrong: %v", someVar)

可以用于动态生成带有变量信息的错误消息。

5.6 多重错误检查

有时,我们需要同时检查多个函数调用的错误:

go 复制代码
file, err := os.Open("file.txt")
if err != nil {
    fmt.Println("Error opening file:", err)
    return
}
defer file.Close()

content, err := ioutil.ReadAll(file)
if err != nil {
    fmt.Println("Error reading file:", err)
    return
}

每个函数调用后都要检查错误,并根据需要处理它。

5.7 panicrecover 机制

虽然 Go 没有 try-catch 语法,但 Go 提供了 panicrecover 来实现类似异常处理的机制。panic 用于在程序出现无法恢复的错误时终止执行,而 recover 则用于捕获和处理 panic

go 复制代码
func riskyFunction() {
    panic("Something went wrong!")
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    
    riskyFunction() // 此调用将导致 panic 被 recover 捕获
}

5.8 defer 用于资源清理

在 Go 中,defer 语句用于在函数退出时执行某些操作,通常用于资源清理,如关闭文件、解锁资源等。即使函数中发生了错误,defer 语句仍会被执行。

go 复制代码
func readFile() (string, error) {
    file, err := os.Open("file.txt")
    if err != nil {
        return "", err
    }
    defer file.Close() // 确保文件在函数退出时关闭

    content, err := ioutil.ReadAll(file)
    if err != nil {
        return "", err
    }
    return string(content), nil
}

在这个例子中,即使读取文件时出现错误,defer file.Close() 仍会确保文件被正确关闭。

相关推荐
用户298698530143 分钟前
.NET 文档自动化:Spire.Doc 设置奇偶页页眉/页脚的最佳实践
后端·c#·.net
序安InToo34 分钟前
第6课|注释与代码风格
后端·操作系统·嵌入式
xyy12335 分钟前
C#: Newtonsoft.Json 到 System.Text.Json 迁移避坑指南
后端
洋洋技术笔记37 分钟前
Spring Boot Web MVC配置详解
spring boot·后端
JxWang0538 分钟前
VS Code 配置 Markdown 环境
后端
qianpeng89740 分钟前
水声匹配场定位原理及实验
算法
navms41 分钟前
搞懂线程池,先把 Worker 机制啃明白
后端
JxWang0541 分钟前
离线数仓的优化及重构
后端
Nyarlathotep011342 分钟前
gin01:初探gin的启动
后端·go
JxWang0542 分钟前
安卓手机配置通用多屏协同及自动化脚本
后端