Go基础系列:10. 函数

学到什么

  1. 如何调用函数?
  2. 如何构造函数?
  3. 函数如何返回多个值?
  4. 如何构造匿名函数?
  5. 如何传递函数?
  6. 内置函数有哪些?

介绍

函数是基本的代码块,它负责将一个复杂问题分解为不同的函数提供调用与复用。

编写函数时,无需关注顺序,因为 Go 语言是编译型的。

在 Go 语言中有三种函数类型:

  • 基本格式:有命名的函数,直接调用完事。
  • 匿名函数:没有名字的函数。
  • 结构体携带的函数:也可以称之为方法,后续结构体再展开讲解。

基本格式

func Fun1(arg1 T, arg2 T) T {
		...
		return r1
}
  • Fun1 为自定义的函数名称。
  • arg1arg2 为自定义参数名称,声明了两个参数,可以再增加。
  • T 代表 Go 语言中的任意类型,使用时替换成 int、string、slice 等等类型。
  • 小括号后紧跟函数返回值类型。
  • return 为函数返回的关键字,携带要返回的值,函数内之后的逻辑将不会执行。
  • 函数体的第一个花括号必须紧跟在函数后。

举例:

// 计算两个数之和并且返回
func AddNum(n1 int, n2 int) int {
	return n1 + n2
}

函数也可以没有返回值,这个时候就无需 return 关键字,例如: main() 入口函数、 init() 初始化函数。

当函数体内出现了 panic 函数,用于抛出异常,这时如果定义了返回类型, return 关键字就可以选择省略。

返回多个值

Go 语言函数中有个特点,可以多个值返回。在声明返回值类型时,可以不指定名称,也可以指定名称,啥意思呢,往下看。

1. 无名称

func Fun1(arg1 T, arg2 T) (T, T) {
		...
		return r1, r2
}

和“基本格式”的不同点:

  • 当需要返回至少两个值时,返回类型需要用小括号包裹,以逗号分隔。
  • 使用 return 携带多个返回值。

2. 有名称

func Fun1(arg1 T, arg2 T) (n1 T, n2 T) {
		...
		return
}
  • 返回值类型指定了名称后,在 return 返回时,可以不带值,当然也可以都带上。
  • 当有了名称,即使是 1 个返回类型,也需要用小括号包裹。

为什么有了名称 return 就不用携带值呢?

因为相当于在返回时,初始化好了返回值,例如上面的格式中 n1n2 就是初始化的两个变量,在函数运算中,只要将返回结果存入 n1n2 中,不存就按照初始化返回,当然也可以 return 携带值。

函数调用

构造好一个函数后,如何调用,格式如下:

r1, r2 := Fun1(param1, param2)

调用时传递了两个参数,返回时接受两个返回值。

如果接受多个值时,某个值我不想使用时,是不能搁置在那的,不然编译器会报错,需要使用下划线 “_” 替代,表示我不用。

r1, _ := Fun1(param1, param2)

匿名函数

匿名函数就是在构造函数时,函数没有名称,想调用时,需要把匿名函数赋值给一个变量,或者在构造时直接调用。

1. 赋值给变量

fun1 := func (arg1 T, arg2 T) T {
		...
		return r1
}

赋值后, fun1 就是一个函数类型的变量, 调用格式:fun1(param1, param2)

2. 构造时调用

func (arg1 T, arg2 T) T {
		...
		return r1
}(param1, param2)

在构造函数时,花括号后紧跟参数传递(param1, param2),不需要赋值给一个变量,直接构造后马上调用。

传递函数

在 Go 语言中,函数是“一等公民”,它和 int 、string 等等,都是一个级别,可以作为参数进行传递。

举例:

// function/deliver.go

package main

// callback 是一个函数类型参数
func Calc(callback func(n1 int, n2 int) int) int {
	x, y := 3, 4
	return callback(x, y)
}

// 计算两数之积
func Mul(n1 int, n2 int) int {
	return n1 * n2
}

func main() {
	// 第一个:传递一个匿名函数
	Calc(func(n1 int, n2 int) int {
		return n1 + n2
	})

	// 第二个:传递一个定义好的函数
	Calc(Mul)
}

分别演示了两种函数的传递方式,第一个匿名函数计算两数之和,第二个用定义好的函数计算两数之积。当然传递函数不止是通过参数,也可以是函数返回值、切片元素保存、map值保存等等。

声明函数类型

声明函数类型,意思就是可以自定义一个函数类型,给这个函数取一个别名,像例如 int 一样很方便的去声明变量或者参数类型。

type CallbackFunc func(n1 int, n2 int) int

现在自定义了一个名为 CallbackFunc 的函数类型,下来看如何使用:

func Calc(callback CallbackFunc) int {
...
}

Calc 函数有一个函数参数,这个参数的类型名称为 CallbackFunc

函数参数

1. 参数类型省略

在声明函数参数时,有时候会遇到连续声明多个相同类型,这个时候,就可以只保留一个类型名称。

// 没精简的
func Fun1(arg1 string, arg2 int, arg3 int)

// 精简后
func Fun1(arg1 string, arg2, arg3 int)

精简后, arg2 参数后省略了 int,这样就和它后面的参数类型一致。

2. 值传递与引用传递

我们先定下参数称呼,函数调用时传递的参数称为实参,构造函数时的参数称为形参。

在 Go 语言中,切片(slice)、map、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递,在函数内修改形参是会改变实参的值。

对于切片,有种情况会打破引用传递这个规律,具体可以看看 《内置结合 - 切片》 这篇文章。

对于其它剩下的类型,默认都是值传递,函数接收到的形参只是副本,函数内对形参的更改是不会影响到实参的。

如果希望更改实参的值,可以传递指针,在实参前增加“&”符号,表示取实参的地址,例如: Fun1(&param)

3. 变长参数

当构造函数时,函数的最后一个参数是 ...T 形式时,称为变长参数,它可以接受至少 0 个数据。

// 一个固定参数,一个变长参数
// nums 实际是一个切片
func Func1(str string, nums ...int) {
	...
}

调用例子如下:

// 没传递变长参数
Func1("miao")

// 给变长参数传递不同数量的值
Func1("miao", 1)
Func1("miao", 1, 2)

当把一个切片类型传递给可变参数时,在切片后跟着 ... 三个点,传递给变长参数,表示将切片元素展开。

nums := []int{1, 2, 3}
Func1("miao", nums...)

内置函数

在 Go 语言中,有一些函数无需导入任何包就可以使用,下来对这些函数简要说明一下。

总共 15 个内置函数,如下:

  1. make:为切片,map、通道类型分配内存并初始化对象。
  2. len:计算数组、切片、map、通道的长度。
  3. cap:计算数组、切片、通道的容量。
  4. delete:删除 map 中对应的键值对。
  5. append:将数据添加到切片的末尾。
  6. copy:将原切片的数据复制到新切片中。
  7. new:除切片、map、通道类型以外的类型分配内存并初始化对象,返回的类型为指针。
  8. complex:生成一个复数。
  9. real:获取复数的实部。
  10. imag:获取复数的虚部
  11. print:将信息打印到标准输出,没有换行。
  12. println:将信息打印到标准输出并换行。
  13. close:关闭通道。
  14. panic:触发程序异常。
  15. recover:捕捉 panic 的异常信息。

总结

本篇我对 Go 语言中的函数进行了系统的讲解,也列举了 15 个内置函数。对于内置函数的使用,有的在前面文章使用过,有的还没有,先做一个整体的了解,等到了用的时候再详查。

版权

本作品采用 CC BY-NC-ND 4.0 授权,转载必须注明作者和本文链接。