lang

LangBook

7.函数

函数能减少重复的代码。它就像机器,接收输入,经过内部的处理后,返回结果。

1. 函数的定义

使用func关键字声明一个函数。创建一个求和函数sum

func sum(a int, b int) int {
	return a + b
}

当多个参数类型一样时,可以省略前面参数的类型:

func sum(a, b int) int { 
	return a + b
}

函数可以有多个返回值,我们来写一个求商和余数的函数:

func divideAndMod(dividend, divisor int) (int, int) { 
	return dividend / divisor, dividend % divisor
}

dividend是除数, divisor是被除数

该函数有两个类型相同的返回值,函数调用者并不知道返回的值分别对应哪个。这时候,命名返回值就派上用场了。

func divideAndMod(dividend, divisor int) (quotient, remainder int) {
	quotient = dividend / divisor // '=' 赋值操作
	remainder = dividend % divisor
	return quotient, remainder
}

quotient是商, remainder是余数。

命名返回值已经是初始化好的变量,初始值是对应类型的零值。注意quotient = dividend / divisor是赋值操作,而不是声明新变量。

quotientremainder 已经被赋值了,可以省略return语句后面的变量,直接返回。

func divideAndMod(dividend, divisor int) (quotient, remainder int) {
	quotient = dividend / divisor // '=' 赋值操作
	remainder = dividend % divisor
    return
}

但不建议这样做,这样会降低可读性, 推荐使用前一种方式。

函数该怎么取名?

把自己代入函数调用者的视角,你希望看到函数名时就能大概知道这个函数的作用。取名时要遵循这个原则,同时兼顾简洁。

1.1 可变参数

上面的sum函数只能求两个数的和。如何求一组数的和呢?

func sum(nums ...int) int { 
	result := 0
	// nums 是[]int类型
	for _, v := range nums {
		result += v
	}
	return result
}

在参数的类型前加...sum就可以传人任意数量的int的变量。nums就是int类型的切片,通过切片来实现可变参数。

main.go
package main
 
import "fmt"
 
func sum(nums ...int) int {
	result := 0
	// nums 是[]int类型
	for _, v := range nums {
		result += v
	}
	return result
}
 
func main() {
	fmt.Println(sum(1, 2, 3, 4))
}

输出

10

可变参数必须排在最后

例如,这样写是错误的

func sum(nums ...int, a int) int { 
	result := 0
	// nums 是[]int类型
	for _, v := range nums {
		result += v
	}
	return result
}

报错:

can only use ... with final parameter in list

考考你,为什么可变参数必须排在最后?

2. 函数是一等公民

函数是一等公民,意味着函数和其他数据类型(如整型、字符串等)一样。你可以把函数赋值给变量、作为参数传递给其他函数,或者作为函数的返回值。

2.1 把函数赋值给变量

你可以像创建变量一样来创建一个函数:

// 创建一个匿名函数并把它赋值给sum变量。
var sum = func(a, b int) int { 
	return a + b
}

等价于

func sum(a, b int) int {
	return a + b
}

sum是一个变量,它的类型是func(a int, b int) intfunc(a int, b int) int又叫函数签名。只要是接收两个int类型,返回一个int类型的函数都可以赋值给sum变量:

main.go
package main
import "fmt"
 
func sum2(a, b int) int { // 创建函数sum2
	return a + b + 2
}
 
func main() {
	sum := func(a, b int) int {
		return a + b 
	}
	fmt.Println(sum(1, 1))
 
	sum = sum2 // 赋值,sum, sum2类型相同
	fmt.Println(sum(1, 1))
}

输出

2
4

匿名函数

没有名字的函数就叫匿名函数,例如定义函数时直接运行:

main.go
package main
 
import "fmt"
 
func main() {
	func(a, b int) {
		fmt.Println(a + b)
	}(1, 2) // 匿名函数,直接运行
}

输出

3

函数是引用类型,它的零值是nil:

main.go
package main
 
import "fmt"
 
// 给类型起一个别名
type myFunc func(int, int) int 
 
func main() {
	var sum myFunc // 函数类型的零值是nil
	fmt.Println(sum == nil)
}

输出

true

2.2 函数作为参数

main.go
package main
import "fmt"
// 定义函数类型
type operation func(int, int) int
 
func applyOperation(a, b int, op operation) int { // op是一个函数类型的参数
	return op(a, b)
}
 
func main() {
	add := func(x, y int) int { // 创建一个加法函数
		return x + y
	}
	
	multiply := func(x, y int) int { // 创建一个乘法函数
		return x * y
	}
	// 输入不同的函数,返回不同的值
	fmt.Println(applyOperation(3, 4, add))      // 输出:7
	fmt.Println(applyOperation(3, 4, multiply)) // 输出:12
}

输出

7
12

函数就是值,可以像其他类型一样传递给其他函数。

2.3 函数作为返回值

main.go
package main
import "fmt"
 
// 定义counter函数类型
type counter func() int
 
// makeCounter函数返回counter函数类型
func makeCounter() counter {
	n := 0
	return func() int {
		n++ // 引用外部变量n,形成闭包
		return n
	}
}
 
func main() {
	c := makeCounter() // c 是一个函数
	fmt.Println(c())   // 1
	fmt.Println(c())   // 2
}

输出

1
2

每调用一次c(), n就递增1。

什么是闭包函数

闭包就是函数里引用了外部的变量,例如c是一个闭包函数,引用了外部变量n

n是在makeCounter()函数里创建的,是c的外部变量。

2.4 函数占多少内存

main.go
package main
 
import (
	"fmt"
	"unsafe"
)
 
// 新建一个sum函数
func sum(a, b int) int {
	return a + b
}
 
func main() {
	// 使用unsafe包里的Sizeof函数打印sum变量占多少内存
	fmt.Println(unsafe.Sizeof(sum))
}

输出

8

你可以发现,函数类型的变量永远占8个字节的内存(64位系统)。很好理解,函数是引用类型,它本质上就是个指针,指向实际的函数。

On this page