青训营go语言基础总结二:函数| 豆包MarsCode AI 刷题

122 阅读6分钟

一、函数

1、函数的定义

在 Go 语言中,函数是组织代码的基本模块。函数可以接受参数并返回值,参数和返回值都必须明确其类型。

基本函数声明和调用

在 Go 中,一个函数的基本声明语法如下:

func functionName(parameter1 type1, parameter2 type2) returnType {
    // 函数体
    return value
}
// 定义一个简单的求和函数 
func add(x int, y int) int { 
    return x + y 
}

在上面的代码中: 在上面的代码中:

  • func 关键字后跟函数名 add
  • 函数接受两个 int 类型的参数 x 和 y
  • 函数返回一个 int 类型的值。
  • return 关键字用于返回计算结果。

go语言这样的函数声明和使用方式,使得代码的结构更加清晰。明确的参数和返回值类型声明不仅提高了代码的可读性和可维护性,也使编译器能够在编译期捕获类型错误,提升程序的健壮性。

多返回值

Go 的函数可以返回多个值,这在处理结果和错误时特别有用。例如:


import "fmt"

// 定义一个函数,返回两个值
func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

在这个例子中:

  • swap 函数接受两个字符串参数,并返回它们交换后的结果。
  • main 函数中,使用多重赋值语法来接收返回的两个值。 多返回值的特性在处理错误时尤为有用。例如,一个函数在执行中可能会遇到错误,此时除了返回正常的结果外,还可以返回一个错误值(error),函数的调用者可以检查错误值以决定下一步操作。

命名返回值

Go 语言允许在函数声明中命名返回值。这些命名返回值被视为函数体内的变量,可以直接使用,且在函数结束时隐式返回。例如:

// 命名返回值 func split(sum int) (x, y int) { 
    x = sum * 4 / 9 
    y = sum - x 
    return // 不需要指定返回的变量 
    }

可变参数函数

Go 语言支持可变参数函数,可以接受不定数量的参数。例如:

package main

import "fmt"

// 可变参数函数
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

func main() {
    fmt.Println(sum(1, 2, 3, 4, 5))  // 输出 15
}

在这个例子中:

  • sum 函数接受不定数量的 int 参数。
  • ...int 语法表示 nums 是一个 int 类型的切片。 可变参数函数很灵活,能够适应不同数量参数的需求,避免了函数重载所带来的复杂性和冗长代码,同时也提高了函数调用的便利性。

匿名函数和闭包

闭包是一个函数,能够捕获和记住它被创建时所在的环境中的变量。匿名函数即没有名字的函数,可以直接定义和调用。例如:


func main() {
    // 定义并调用匿名函数
    func(msg string) {
        fmt.Println(msg)
    }("Hello, World!")

    // 闭包
    nextInt := increment()
    fmt.Println(nextInt()) // 输出 1
    fmt.Println(nextInt()) // 输出 2
    fmt.Println(nextInt()) // 输出 3
}

func increment() func() int {
    i := 0
    return func() int {
        i += 1
        return i
    }
}

在这个例子中:

  • 第一部分展示了一个直接定义并调用的匿名函数。
  • increment 函数返回一个匿名函数,该函数引用并修改了外部的变量 i,这展示了闭包的用法。

延迟执行(defer)

defer 语句用于延迟函数的执行直到包含 defer 的函数执行完毕。常用来执行清理操作。例如关闭文件、释放资源等。例如:

func main() {
    f := createFile("/tmp/defer.txt")
    defer closeFile(f)
    writeFile(f)
}

func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}

func writeFile(f *os.File) {
    fmt.Println("writing")
    fmt.Fprintln(f, "data")
}

func closeFile(f *os.File) {
    fmt.Println("closing")
    f.Close()
}

在这个例子中:

  • defer closeFile(f) 确保 closeFile 函数会在 main 函数结束前被调用,从而确保文件被关闭。 defer 提供了一种确保资源释放的可靠机制,不论函数中是否发生错误,都会执行 defer 语句。这一点对资源管理尤为重要,避免了资源泄露问题。

方法

Go 中的方法是一个绑定到特定类型的函数。方法的定义如下

type Rect struct {
    width, height int
}

// 计算面积的方法
func (r Rect) area() int {
    return r.width * r.height
}

func main() {
    r := Rect{width: 10, height: 5}
    fmt.Println("Area:", r.area()) // 输出 50
}

方法使得数据和行为进行有机结合,从而提供了面向对象编程的能力。尽管 Go 语言没有传统的类和继承机制,但是通过结构体和方法,可以实现典型的面向对象设计中的大部分功能。

错误处理 (Error)

Go 语言提供了一种简单优雅的处理错误的机制。函数通常返回一个值和一个 error 值来表示可能的错误。

import (
    "errors"
    "fmt"
)

// 定义一个函数演示错误处理
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(4, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

Go 的错误处理方式通过显式地返回错误值,让调用者在需要时处理错误。这种方式虽然增加了代码的复杂度,但避免了隐藏错误,提高了代码的健壮性和可维护性。

恢复 (Recover)

recover 是内置函数,用于恢复 panic。这允许程序从 panic 状态中恢复正常执行。

import "fmt"

// 定义一个引发 panic 的函数
func mayPanic() {
    panic("this is a panic")
}
// 定义 main 函数包含 defer 和 recover
func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()

    mayPanic()

    fmt.Println("This line will not be executed")
}

recover函数想要捕获panic必须在defer里面捕获。

恐慌 (Panic)

panic 是一个内置函数,用于引发恐慌,停止当前函数的执行,并开始执行延迟函数。在无进一步处理时导致程序崩溃。


import "fmt"

// 定义一个导致 panic 的函数
func doPanic() {
    panic("something went wrong")
}

// main 函数演示 panic
func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in main:", r)
        }
    }()

    doPanic()

    fmt.Println("This line will not be executed")
}

panic 应该用于程序中不可恢复的错误场景,例如严重逻辑错误或无法继续执行的状态。在合理使用 panic 与 recover 的情况下,可以构建一个更加健壮的系统。我们在日常书写程序中一般不会主动的Panic。

个人思考

通过深入理解 Go 语言的函数特性,可以看到 Go 在设计上的一些独特之处。首先,Go 通过强类型系统和编译期检查,确保了代码的安全性和执行效率。其次,多返回值、命名返回值、可变参数函数、匿名函数和闭包等特性,使其在处理复杂逻辑时能够保持简洁和优雅。同时,Go 语句如 defer 提供了一种可靠的资源管理方式,避免了资源泄漏。错误处理机制通过显式返回错误值,增强了代码的健壮性和可维护性。最后,方法的设计使得 Go 既保持了简洁性又具备面向对象编程的能力。