【Go语言实战】4.函数

本文深入介绍了Go语言中的函数,包括函数定义、多值返回、实参到形参的值传递、不定参数。特别讨论了函数作为“第一公民”的特性,如作为类型、闭包支持、匿名函数的使用。此外,还详细阐述了defer关键字的作用和使用场景,展示了如何确保资源的正确释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Go不是一门纯函数式的编程语言,但是函数在Go中是“第一公民”,表现在:

  • 函数是一种类型,函数类型变量可以像其他类型变量一样使用,可以作为其他函数的参数或返回值,也可以直接调用执行。
  • 函数支持多值返回
  • 支持闭包
  • 函数支持可变参数。

Go是通过编译本地代码且基于“堆栈”式执行的。

1. 基本概念

1.1 函数定义

函数是Go程序源代码的基本构造单位,一个函数的定义包括如下几个部分:

  • 函数声明关键字func
  • 函数名
  • 参数列表
  • 返回列表和函数体

注意:函数名的首字母大小写决定该函数在其他包的可见性:大写时其他包可见,小写时只有相同的包才可以访问。函数的此参数和返回值需要用“()”包裹,如果只有一个返回值,而且使用的是非命名的函数,则返回参数的“()”可以省略。函数体使用“{}”包裹,并且“{"必须位于函数返回值同行的行尾。

func funcName(param-list) (result-list){
	function-body
}

函数的特性
(1)函数可以没有输入参数,也可以没有返回值(默认返回0)
(2)多个相邻的相同类型的参数可以使用简写模式
(3)支持有名的返回值,参数名就相当于函数体内最外层局部变量,命名返回值变量会被初始化为类型零值,最后return可以不带参数名直接返回。

import "fmt"
func main() {
	a := 10
	b := 20
	fmt.Println(add(a, b))
}
func add(a, b int) (sum int) {
	sum = a + b
	return
}

(4)不支持默认值参数
(5)不支持函数重载
(6)不支持函数嵌套,严格地说是不支持命名函数的嵌套定义,但支持嵌套匿名函数

import "fmt"
func main() {
	a := 10
	b := 20
	fmt.Println(add(a, b))
}
func add(a, b int) (sum int) {
	anonymous := func(x, y int) int {
		return x + y
	}
	return anonymous(a, b)
}

1.2 多值返回

Go函数支持多值返回,定义多值返回的返回参数列表时要使用“()”包裹,支持命名参数的返回。

func swap(a,b int) (int ,int){
	return b, a
}

习惯用法:如果多值返回值有错误类型,则一般将错误类型作为最后一个返回值。

1.3 实参到形参的传递

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

import "fmt"
func main() {
	a := 10
	chvalue(a)
	fmt.Println(a)

	chpointer(&a)
	fmt.Println(a)
}
func chvalue(a int) int {
	a = a + 1
	return a
}

func chpointer(a *int) {
	*a = *a + 1
	return
}

1.4 不定参数

Go函数支持不定数目的形式参数,不定参数声明使用param …type的语法格式。
函数的不定参数有如下几个特定:
(1)所有的不定参数类型必须是相同的;
(2)不定参数必须是函数的最后一个参数;
(3)不定参数名在函数体内相当于切片,对切片的操作同样适合对不定参数的操作;

func sum2(arr ...int) (sum int) {
	for _, v := range arr {
		sum += v
	}
	return
}

(4)切片可以作为参数传递给不定参数,但切片名后要加上“…”;

slice := []int{1, 2, 3, 4}
//array := [...]int{1,2,3,4} // 数组不可以作为实参传递给不定参数的函数
fmt.Println(sum2(slice...))

(5)形参为不定参数的函数和形参为切片的函数类型不相同;

func sum2(arr ...int) (sum int) {
	for _, v := range arr {
		sum += v
	}
	return
}

func sum3(arr []int) (sum int) {
	for v := range arr {
		sum += v
	}
	return
}
fmt.Printf("%T\n", sum2) //func(...int) int
fmt.Printf("%T\n", sum3) //func([]int) int

2. 函数签名和匿名函数

2.1 函数签名

  • 函数类型又叫函数签名,一个函数的类型就是函数定义首行去掉函数名、参数名和{,可以使用fmt.Printf的“%T”格式化参数打印函数的类型。
  • 两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型都相同),形参名可以不同。
  • 可以使用type定义函数类型,函数类型变量可以作为函数的参数或返回值。
package main

import "fmt"

func merge(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}

// 定义一个函数类型,输入的是两个int类型,返回值是一个int类型
type Op func(int, int) int

// 定义一个函数,第一个参数是函数类型Op
func do(f Op, a, b int) int {
	return f(a, b) // 函数类型变量可以直接用来进行函数调用
}
func main() {
	a := do(merge, 1, 2)
	fmt.Println(a)
	s := do(sub, 1, 2)
	fmt.Println(s)
}
  • 函数类型和map、slice、chan一样,实际函数类型变量和函数名都可以当作指针变量,该指针指向函数代码的开始位置。
  • 通常说函数类型变量是一种引用类型,未初始化的函数类型的变量的默认值是nil。
  • Go中函数是“第一公民”(first class)。有名函数的函数名可以看作函数类型的常量,可以直接使用函数名调用函数,也可以直接赋值给函数类型变量,后续通过该变量来调用该函数。
func combine(a, b int) int {
	return a + b
}
combine(3, 4)
f := combine
fmt.Println(f(1, 2))

2.2 匿名函数

Go语言提供两种函数:有名函数和匿名函数。匿名函数可以看作函数字面量,所有直接使用函数类型变量的地方都可以由匿名函数代替。匿名函数可以直接赋值给函数变量,可以当作实参、也可以作为返回值,还可以直接被调用。

package main

import "fmt"

// 匿名函数黑直接赋值函数变量
var sumsum = func(a, b int) int {
	return a + b
}

func doinput(f func(int, int) int, a, b int) int {
	return f(a, b)
}

//匿名函数作为返回值
func wrap(op string) func(int, int) int {
	switch op {
	case "add":
		return func(a, b int) int {
			return a + b
		}
	case "sub":
		return func(a, b int) int {
			return a - b
		}
	default:
		return nil
	}
}
func main() {
	// 匿名函数直接被调用
	defer func() {
		if err := recover(); err != nil {
			println(err)
		}
	}()
	sumsum(1, 2)
	// 匿名函数作为实参
	doinput(func(x, y int) int {
		return x + y
	}, 1, 2)
	opFunc := wrap("add")
	re := opFunc(2, 3)
	fmt.Printf("%d\n", re)
}

3. defer

  • Go函数里提供了defer关键字,可以注册多个延迟调用,这些调用以先进后出(FILO)的顺序在函数返回前被执行。
package main

func main() {
	//后进先出
	defer func() {
		println("first")
	}()
	defer func() {
		println("second")
	}()
	println("functionBody")
}
  • defer常用于保证一些资源最终一定能够得到回收和释放。
  • 主动调用os.Exit(int)退出进程时,defer将不再被执行(即使defer已经提前注册)
  • defer后面必须是函数或方法的调用,不能是语句。
  • defer函数的实参在注册时通过值拷贝传递进去。
func f() int {
	a := 0
	defer func(i int) {
		println("defer i=", i)
	}(a)

	a++
	return a
}
  • defer语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行。
  • defer的好处是可以在一定程度上避免资源泄露,特别是在有很多return语句,有多个资源需要关闭的场景中,很容易漏掉资源的关闭操作。
  • 在打开资源无报错后直接调用defer关闭资源。
  • defer语句的位置不当,有可能导致panic,一般defer语句放在错误检查语句之后
  • defer也有明显的副作用: defer会推出资源的释放,defer尽量不要放到循环语句里面;
  • defer相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗
  • defer中最好不要对有名返回值参数进行操作,否则可能会引起莫名其妙的结果。

至此,我们已经基本了解了Go语言中的函数及其用法,下一节我们将继续了解Go语言中的闭包。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值