函数是组织好的、可重复使用的、用于执行指定任务的代码块。

函数

Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。

函数定义

Go语言中定义函数使用func关键字,具体格式如下:

  1. func 函数名(参数)(返回值){
  2. 函数体
  3. }

函数名
由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
参数
参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
返回值
返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
函数体
实现指定功能的代码块。

注意
函数使用func开头,左大括号不能另起一行;小写字母开头的函数指在本包内可见,大写字母开头的函数才能被其他包调用。

我们先来定义一个求两个数之和的函数:

  1. func intSum(x int, y int) int {
  2. return x + y
  3. }

函数的参数和返回值都是可选的,例如我们可以实现一个既不需要参数也没有返回值的函数:

  1. func sayHello() {
  2. fmt.Println("Hello 杨幂")
  3. }

函数表现形式

  1. //一般函数
  2. func func_name(a int) {
  3. println(a)
  4. }
  5. //多参数,无返回值
  6. func func_name(a, b int, c string) {
  7. println(a, b, c)
  8. }
  9. //单个返回值
  10. func func_name(a, b int) int { //同类型,可以省略 a, b int
  11. return a + b
  12. }
  13. //多个返回值
  14. func func_name(a, b int) (c int, err error) { //返回值还可以是 (int, error)
  15. return a+b, nil
  16. }
  17. func SumAndProduct(A, B int) (int, int) {
  18. return A+B, A*B
  19. }


函数的参数

类型简写

函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:

  1. func intSum(x, y int) int {
  2. return x + y
  3. }

上面的代码中,intSum函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。

可变参数

可变参数是指函数的参数数量不固定,Go语言中的可变参数通过在参数名后加…来标识
…type类型只能作为函数的参数类型存在,并且是最后一个参数本质上是一个数组切片,即[]type

举个例子:

  1. func intSum2(x ...int) int {
  2. fmt.Println(x) //x是一个切片
  3. sum := 0
  4. for _, v := range x {
  5. sum = sum + v
  6. }
  7. return sum
  8. }
  9. 调用上面的函数:
  10. ret1 := intSum2()
  11. ret2 := intSum2(10)
  12. ret3 := intSum2(10, 20)
  13. ret4 := intSum2(10, 20, 30)
  14. fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60

本质上,变参本质上就是一个slice,固定参数搭配可变参数使用时,可变参数要放在固定参数的后面,且必须是最后一个形参。
示例代码如下:

  1. func intSum3(x int, y ...int) int {
  2. fmt.Println(x, y)
  3. sum := x
  4. for _, v := range y {
  5. sum = sum + v
  6. }
  7. return sum
  8. }
  9. 调用上述函数:
  10. ret5 := intSum3(100)
  11. ret6 := intSum3(100, 10)
  12. ret7 := intSum3(100, 10, 20)
  13. ret8 := intSum3(100, 10, 20, 30)
  14. fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160

将slice传递给变参函数时,注意用…展开,否则会被当做单个参数处理。

任意类型的不定参数

  1. func Printf(format string, args ...interface{}) {
  2. }

参数传递

值传递

值传递作为函数参数,在被调用函数中引用值传递参数时候,需要重新为值传递参数开辟新的用于存放值传递参数的内存空间,传递的是真实的另一个相同内容的值变量,该变量用临时生成的内存空间保存。

指针传递

指针, Go保留指针,用”.”而非”->”操作指针目标对象成员操作符
指针传递作为函数参数,在被调用函数中引用指针传递参数时候,不需要重新为指针传递参数开辟新的用于存放指针传递参数的内存空间,传递的是原先值变量的内存地址的寻找方式,该寻找方式也用临时生成的内存空间保存。

  1. func add1(a int) int {
  2. a = a + 1
  3. return a
  4. }
  5. x := 3
  6. x1 := add1(x)
  7. x //3
  8. x1 //4
  9. 传值,x1的值没有改变
  10. func add2(a *int) int {
  11. *a = *a + 1
  12. return *a
  13. }
  14. x := 3
  15. x1 := add2(&x)
  16. x // 4
  17. x1 // 4

传指针多个函数能操作同一个对象;传指针比较轻量级(8byte),只是传内存地址,我们可以用指针来传递体积大的结构体。

值传递与指针传递区别

用于保存值传递的临时内存空间的大小是不定的,要根据之前的变量值的大小来定。而用于保存指针的临时内存空间的大小是确定的,因为指针是变量的内存地址寻找方式,为指针临时分配的内存的保存空间只需要将这个变量的内存地址寻找方式 记录下来即可。

变量的内存地址寻找方式都是大致相同的,与该指针指向的原变量的所占内存的大小无关。所以为指针临时分配的内存的保存空间的大小一般都是相近的。

注意
& 取变量地址
* 通过指针间接访问目标函数
若函数需要改变 slice长度,仍需要取地址传指针。
Go语言中,string,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传指针。

函数的调用

定义了函数之后,我们可以通过函数名()的方式调用函数。 例如我们调用上面定义的两个函数,代码如下:

  1. func main() {
  2. sayHello()
  3. ret := intSum(10, 20)
  4. fmt.Println(ret)
  5. }

注意,调用有返回值的函数时,可以不接收其返回值。