Go语言核心编程第2章“函数”

学习目标:

Go语言核心编程 第二章“函数”

学习内容:

第二章 函数

2.1 基本概念

2.1.1 函数定义

函数定义包括以下几个部分:
1.函数声明关键字func
2.函数名(首字母大小写决定在其他包的可见性,大写可见,小写不可见)
3.参数列表
4.返回列表
5.函数体

func funcName(param-list) (result-list){
	function-body
}
函数特点:
1.可以无参数,无返回值
func function() {
	fmt.Printf("show")
}
2.多个相邻的相同类型可以简写
func add(a,b int) int{
	return a + b
}
3.不支持默认值参数
4.不支持函数重载
5.支持有名的返回值
```go
divide函数有两个有名的返回值:result和err。
这使得在函数内部可以直接对这些返回值进行赋值,而不需要使用return语句明确指定返回值。
package main
import "fmt"
func divide(a, b int) (result int, err error) {
    if b == 0 {
        err = fmt.Errorf("division by zero")
        return
    }
    result = a / b
    return
}
func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
    result, err = divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}
6.不支持命名函数嵌套,但支持嵌套匿名函数
package main
import "fmt"
func outerFunction() {
    fmt.Println("This is the outer function.")
    // 内部函数
    innerFunction := func() {
        fmt.Println("This is the inner function.")
    }
    innerFunction() // 调用内部函数
}
func main() {
    outerFunction()
}
This is the outer function.
This is the inner function.
2.1.2 多值返回

Go支持多值返回

package main
import "fmt"
func divideAndRemainder(a, b int) (int, int) {
    quotient := a / b
    remainder := a % b
    return quotient, remainder
}
func main() {
    quotient, remainder := divideAndRemainder(10, 3)
    fmt.Printf("Quotient: %d, Remainder: %d\n", quotient, remainder)
}
习惯用法:如果多值返回中有错误类型,一般将错误类型作为最后一个返回值
2.1.3 实参到形参的传递

Go函数实参到形参的传递永远是值拷贝。如果调用后发现实参指向的值发生了改变,是因为参数传递的是指针值的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,两者指向同一地址,本质上参数传递仍然是值拷贝。

package main
import "fmt"
func modifyValue(x *int) {
    *x = 100
}
func main() {
    value := 42
    fmt.Println("Before:", value)
    modifyValue(&value)
    fmt.Println("After:", value) //100
}
2.1.4 不定参数

Go支持不定参数,不定参数声明param …type

package main
import "fmt"
// 可变参数函数
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}
func main() {
    result1 := sum(1, 2, 3, 4, 5)
    fmt.Println("Sum 1:", result1)
    result2 := sum(10, 20, 30)
    fmt.Println("Sum 2:", result2)
}
不定参数的特点
1.所有不定参数的类型一定是要相同的。
2.不定参数必须是函数的最后一个参数
3.不定参数在函数体内相当于切片,对切片的操作同样符合对不定参数的操作
4.切片可作为参数传递给不定参数,切片名后要加上"...",数组不可以作为实参传递给不定参数的函数
package main
import "fmt"
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}
func main() {
    numbers := []int{1, 2, 3, 4, 5}
    result := sum(numbers...) // 传递切片作为参数,加上 "..."
    fmt.Println("Sum:", result)
}
5.形参为不定参数的函数和形参为切片的函数类型不同
```go
func sumVariadic(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}
func sumSlice(numbers []int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}
fmt.Printf("%T\n",sumVariadic) //func(...int) int
fmt.Printf("%T\n",sumSlice) //func([]int) int

2.2 函数签名和匿名函数

2.2.1 函数签名

函数签名又叫函数类型。一个函数的类型就是函数定义首行去掉函数名、参数名和{,可以使用fmt.Printf的%T格式化参数打印函数的类型。

两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型都相同)
package main
import "fmt"
func add(a, b int) int {
    return a + b
}
func subtract(a, b int) int {
    return a - b
}
func main() {
    var operation func(int, int) int
    operation = add
    fmt.Printf("Type of 'add' function: %T\n", operation)
    operation = subtract
    fmt.Printf("Type of 'subtract' function: %T\n", operation)
}
可以使用type定义函数类型,函数类型变量可以作为函数的参数或返回值
package main
import "fmt"
func add(a, b int) int {
    return a + b
}
type Functype func(int,int) int //定义一个函数类型
func dofunc(f Functype,a,b int) int{
	return f(a,b)
}
func main(){
	a := dofunc(add,1,2)
	fmt.Println(a) //3
}
函数类型变量是一种引用类型,未初始化的函数类型的变量默认值是nil
go的引用类型通常包括:slice、map、channel、function
有名的函数名可以看作函数类型的常量,可以直接使用函数名调用函数,或者直接赋值给函数类型变量,后续通过变量进行调用
func main(){
	f:=sum
	f(1,2)
}
2.2.2 匿名函数

Go提供两种函数:有名函数和匿名函数

func main() {
    // 声明并调用匿名函数
    result := func(x, y int) int {
        return x + y
    }(3, 4)
    fmt.Println("Result:", result)
    // 将匿名函数赋值给变量,然后调用
    add := func(x, y int) int {
        return x + y
    }
    result2 := add(5, 6)
    fmt.Println("Result 2:", result2)
}
------------------------------------------
匿名函数作为返回值
package main
import "fmt"
func makeCounter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}
func main() {
	firstcounter := makeCounter()
	secondcounter := makeCounter()
	fmt.Println(secondcounter()) // 输出:1
	fmt.Println(secondcounter()) // 输出:1
	fmt.Println(secondcounter()) // 输出:3
	fmt.Println(firstcounter())  // 输出:1
	fmt.Println(firstcounter())  // 输出:2
	fmt.Println(firstcounter())  // 输出:3
}
makeCounter函数返回一个匿名函数,这个匿名函数用于计数。
每次调用firstcounter或者secondcounter函数,计数器的值会递增,且计数器是在闭包内保留的。
允许创建多个计数器,每个计数器都有自己的状态,而不会相互干扰。
------------------------------------------
匿名函数作为实参
package main
import "fmt"
func applyFunction(numbers []int, f func(int) int) []int {
    result := []int{}
    for _, num := range numbers {
        result = append(result, f(num))
    }
    return result
}
func main() {
    numbers := []int{1, 2, 3, 4, 5}
    // 定义匿名函数并传递给applyFunction
    doubled := applyFunction(numbers, func(x int) int {
        return x * 2
    })
    fmt.Println("Doubled:", doubled)
    squared := applyFunction(numbers, func(x int) int {
        return x * x
    })
    fmt.Println("Squared:", squared)
}

2.3 defer

defer可以注册多个延迟调用,这些调用按照先进后出的顺序,在函数返回前被执行
常用于保证一些资源最终一定能够得到回收和释放

package main
import "fmt"
func main() {
    // 使用 defer 延迟函数的执行,确保清理工作在函数返回前完成
    defer fmt.Println("This will be executed last")
    fmt.Println("This will be executed first")
    // defer 可以被用于多个函数调用
    defer fmt.Println("This will be executed second")
    // 可以在函数内部定义多个 defer 语句,按逆序执行
    for i := 1; i <= 3; i++ {
        defer fmt.Printf("Deferred statement in loop: %d\n", i)
    }
}
This will be executed first
Deferred statement in loop: 3
Deferred statement in loop: 2
Deferred statement in loop: 1
This will be executed second
This will be executed last

defer后面必须是函数或方法的调用,不能是语句,否则会报expression in defer must be function call

defer a := 3 //报错

defer函数的实参在注册时通过值拷贝传递进去

package main
import "fmt"
func main() {
    value := 10
    defer fmt.Println("Deferred:", value)
    value = 20
    fmt.Println("Original:", value)
}
Original: 20
Deferred: 10

defer语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有被注册,不会执行

package main
import "fmt"
func main() {
	a := 1
	defer fmt.Println(a) // 打印1
	return
	defer fmt.Println(a) 
}

主动调用os.Exit(int)主动退出进程时,defer将不再被执行(即使defer已经被提前注册)

package main
import (
    "fmt"
    "os"
)
func main() {
    defer fmt.Println("Deferred")
    os.Exit(1)
    fmt.Println("This will not be printed")
}
无输出结果

defer可以在一定程度上避免资源泄露

func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close() // 确保文件在函数结束时被关闭
    // 读取文件的操作
}

defer相对于普通函数的调用需要间接的数据结构支持,相对于普通函数调用有一定的性能开销
defer会推迟资源的释放,尽量不要将defer放到循环语句中,将defer语句单独拆分成小函数是一个比较好的实际方式。

修改前
func processFiles(filePaths []string) {
    for _, filePath := range filePaths {
        file, err := os.Open(filePath)
        if err != nil {
            log.Fatal(err)
        }
        defer file.Close() // 这里的defer语句在每次循环迭代中注册
        // 处理文件的操作
    }
}
修改后
func processFiles(filePaths []string) {
    for _, filePath := range filePaths {
        file, err := os.Open(filePath)
        if err != nil {
            log.Fatal(err)
        }
        // 处理文件的操作
        file.Close() // 在每次循环迭代中释放资源
    }
}

defer位置不当可能导致panic,恢复panicdefer必须在可能引发panic的代码之前

func main() {
	someFunction()
}
func someFunction() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered from panic:", r)
		}
	}()
	panic("This will be recovered")
}

如果defer进行资源释放的操作出现在不被执行的情况,可能会导致资源泄漏。

func processFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    if someCondition {
        defer file.Close() // 如果不满足条件,这里的defer将不会执行
    }
    // 处理文件的操作
}

一般defer语句放在错误检查语句之后

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err // 错误检查语句
    }
    defer file.Close() // 清理操作在错误检查之后
    // 文件处理操作
    return nil
}

defer最好不要对有名返回值参数进行操作。(7.3分析)

2.4 闭包

2.4.1 概念

闭包=函数+引用环境
如果函数返回的闭包引用了该函数的局部变量(参数或者函数内部变量)
1.多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函数都会为局部变量分配内存

package main
import "fmt"
func makeCounter() func() int {
    count := 0
    // 返回一个闭包,该闭包可以增加并返回计数
    return func() int {
        count++
        return count
    }
}
func main() {
    counter1 := makeCounter()
    counter2 := makeCounter()
    fmt.Println(counter1()) // 输出 1
    fmt.Println(counter1()) // 输出 2
    fmt.Println(counter2()) // 输出 1(独立于counter1)
    fmt.Println(counter1()) // 输出 3
    fmt.Println(counter2()) // 输出 2
}

2.用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用

package main
var (
	a = 0
)
func fa() func(i int) int {
	return func(i int) int {
		println(&a, a)
		a = a + i
		return a
	}
}
func main() {
	f := fa()
	g := fa()
	println(f(1)) //1
	println(g(1)) //2
	println(g(1)) //3
	println(g(1)) //4
}
0x386c80 0
1
0x386c80 1
2
0x386c80 2
3
0x386c80 3
4

同一个函数返回的多个闭包共享该函数的局部变量

package main
func fa(base int) (func(int) int, func(int) int) {
	println(base, &base)
	add := func(i int) int {
		base += i
		println(base, &base)
		return base
	}
	sub := func(i int) int {
		base -= i
		println(base, &base)
		return base
	}
	return add, sub
}
func main() {
	f, g := fa(0)
	println(f(1), g(2))
}
2.4.2 闭包的价值

闭包最初的目的是为了减少全局变量
对象是附有行为的数据,而闭包是附有数据的行为

2.5 panic和recover

panic主动抛出错误,recover用来捕获panic抛出的错误

2.5.1 概念
panic(i interface{}) 可以传递任意类型的变量,调用panic(xxx)
recover() interface{} 用来捕获panic,阻止panic继续向上传递

引发panic的情况
1.程序主动调用panic函数
2.程序产生运行时错误,由运行时检测出并抛出
发生panic后,程序会从调用panic的位置或发生panic的位置立刻返回,逐级向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获,或者运行到函数最外层退出。

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

panic还可在defer中再次调用panic或者抛出panic,defer中的panic可以被后续的defer捕获

package main
import "fmt"
func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered 1:", r)
        }
    }()
    defer func() {
        panic("Second panic")
    }()
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered 2:", r)
        }
    }()
    panic("First panic")
}
Recovered 2: First panic
Recovered 1: Second panic

recover()和defer一起使用,但是recover()只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则返回nil,异常继续向外传递。

package main
import "fmt"
func main() {
	// 这里的panic不会被捕获
	panic("First panic")
	// 这里的panic不会被捕获,因为recover不在defer函数内
	recover()
	// 这里的panic不会被捕获,因为recover不是在defer后的函数体内直接调用
	defer func() {
		fmt.Println(recover())
	}()
}

可以有连续多个panic被抛出,连续多个panic的场景只能出现在延迟调用里面,否则不会出现多个panic被抛出的场景。但只有最后一次panic能被捕获。

package main
import "fmt"
func main(){
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	defer func() {
		panic("first defer panic")
	}
	defer func() {
		panic("second defer panic")
	}
	panic("main body panic")
}
first defer panic

包中init函数引发的panic只能在init函数中捕获,在main中无法捕获,原因是init函数先于main函数执行。

package main
import "fmt"
func init() {
    panic("Panic in init")
}
func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in main:", r)
        }
    }()
}

函数不能捕获内部新启动的goroutine所抛出的panic

package main
import (
    "fmt"
    "sync"
)
func main() {
    var wg sync.WaitGroup
    go func() {
        defer wg.Done()
        panic("Goroutine panic")
    }()
    wg.Add(1)
    wg.Wait()
    fmt.Println("Main function completed")
}
goroutine 6 [running]:
main.main.func1()
        D:/GoItem/Test/test.go:13 +0x65
created by main.main
        D:/GoItem/Test/test.go:11 +0x6f
Process finished with the exit code 2

可以在goroutine内部使用recover

package main
import (
	"fmt"
	"sync"
)
func main() {
	var wg sync.WaitGroup
	go func() {
		defer wg.Done()
		defer func() {
			if r := recover(); r != nil {
				fmt.Println("Recovered in goroutine:", r)
			}
		}()
		panic("Goroutine panic")
	}()
	wg.Add(1)
	wg.Wait()
	fmt.Println("Main function completed")
}
2.5.2 使用场景

什么情况下主动调用panic函数抛出panic
1.程序遇到无法正常执行下去的错误,主动调用panic结束程序运行
2.在调试程序时,通过主动调用panic实现快速退出,panic打印出的堆栈能够更快地定位错误
为了保证程序健壮性,需要主动在程序分支流程上使用recover()拦截运行时错误
Go提供两种处理错误的方式
1.借助panic和recover的抛出捕获机制
2.使用error错误类型

2.6 错误处理

2.6.1 error

Go内置错误接口类型error
任何类型只要实现Error() string方法,都可以传递error接口类型变量。
1.error通常作为函数最后一个返回值
2.一个函数返回error,则先if去处理error!=nil的异常,正常逻辑放在err判断后
3.defer放在error检查后,不然有可能panic
4.错误向上传递时,错误信息应该不断完善

type error interface {
	Error() string
}
调用errors.New()方法生成error
package main
import (
	"errors"
	"fmt"
)
func main() {
	err := errors.New("this is a error")
	if err != nil {
		fmt.Printf("%s", err)
	}
}
------------------------------------------
调用fmt.Errorf()方法生成error
package main
import (
	"fmt"
)
func main() {
	err := fmt.Errorf("this is a error")
	if err != nil {
		fmt.Printf("%s", err)
	}
}
自定义错误类型
package main
import "fmt"
type DivideError struct {
	dividea int
	divideb int
}
func (e *DivideError) Error() string {
	return fmt.Sprintf("cannot divide %d by %d", e.dividea, e.divideb)
}
func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, &DivideError{dividea: a, divideb: b}
	}
	return a / b, nil
}
func main() {
	result, err := divide(10, 0)
	if err != nil {
		if e, ok := err.(*DivideError); ok {
			fmt.Println("Divide error:", e)
			return
		}
		fmt.Println(err)
		return
	}
	fmt.Println(result)
}
2.6.2 错误和异常
2.6.2.1 错误和异常区分

广义上的错误:发生非期望的行为。
狭义上的错误:发生非期望的已知行为,这里的已知行为是预料并提前定义好的。
异常:发生非期望的未知行为,又叫未捕获异常。未知是指错误的类型不在预先定义的范围内,程序编译器和运行时都没有将其捕获,最后操作系统进行处理。

2.6.2.2 Go处理错误

Go不会出现未捕获异常
Go需要处理两部分错误
1.运行时错误,可以在运行时捕获,显式或者隐式地抛出panic
此类错误难以避免,并在不影响程序主功能地分支流程上"recover"这些panic,避免一个panic引发的程序崩溃
2.程序逻辑错误:程序执行结果不符合预期,但是不会引发运行时错误

2.6.2.3 Go中error和panic规则

1.程序发生的错误导致程序不能容错运行,此时程序主动调用panic或者由运行时抛出panic。
2.程序虽然发生错误,但是仍然可以容错运行,此时使用错误返回值的方式进行处理,或者,在可能发生运行时错误的非关键分支上使用recover捕获panic。

参考书籍:Go语言核心编程

  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值