Go基础|第4章:function

  • 原创
  • Madman
  • /
  • /
  • 0
  • 5094 次阅读

Golang.jpg

Synopsis: Go 语言中,函数是一等公民(First Class Functions),这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以像字符串和数值那样作为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等

1. 函数声明

函数声明包括 func 关键字、函数名、形式参数列表(可省略)、返回值列表(可省略)以及函数体

package main

import "fmt"

// 函数可以接收多个参数
func add(x int, y int) int {
  return x + y
}

// 如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型
func add2(x, y int) int {
  return x + y
}

// 函数可以没有参数、没有返回值
func main() {
  fmt.Println(add(1, 2))
  fmt.Println(add2(1, 2))
}

/* Output:
3
3
*/

更多函数声明示例:

// 明确指定每个参数的类型
func add1(x int, y int) int { return x + y }

// 参数类型相同时只需写出最后一个的类型即可
func add2(x, y int) (z int) { return x + y }

// 如果函数返回一个无名变量或者没有返回值,返回值列表的括号可以省略
func add3(x, y int) int { return x + y }

// 下划线(blank identifier)可以强调某个参数未被使用
func first(x int, _ int) int { return x }

// syntax error: mixed named and unnamed function parameters
// func zero(x int, int) int { return 0 }

// 匿名参数在前、命名参数在后
func zero(int, x int) int { return 0 }

2. 多返回值

一个函数可以返回多个值

package main

import "fmt"

func swap(x, y string) (string, string) {
  return y, x
}

func main() {
  a, b := swap("hello", "world")
  fmt.Println(a, b)
}

/* Output:
world hello
*/

3. 命名返回值

返回值也可以像形式参数一样被命名

package main

import "fmt"

func split(sum int) (x, y int) {
  // 命名返回值相当于定义在函数顶部的变量,并初始化为该类型的零值
  // (x, y int) 等价于 var x, y int
  x = sum * 4 / 9
  y = sum - x
  // 没有参数的 return 语句按照返回值列表的次序,返回所有的返回值
  // 等价于 return x, y
  return
}

func main() {
  fmt.Println(split(17))
}

/* Output:
7 10
*/

注意: 上例中没有参数的 return 语句也叫 bare return,建议只用于上面这样的短函数中,在长函数中会影响代码的可读性

准确的变量名可以传达函数返回值的含义,尤其在返回值的类型都相同时:

func Size(rect image.Rectangle) (width, height int)
func Split(path string) (dir, file string)
func HourMinSec(t time.Time) (hour, minute, second int)

4. 函数调用

每次调用函数时,都必须按照声明顺序为所有参数提供实参(参数值),相当于所有形参都是 位置参数,调用时只能按顺序传入而不能通过参数名指定形参来改变顺序(而且每个参数都必须传值,Go 语言的函数没有 默认参数),比如:

package main

import "fmt"

func echo(name string, age int) {
  fmt.Printf("%v's age is %v", name, age)
}

func main() {
  echo("Madman", 18)  // Madman's age is 18

  // syntax error: unexpected =, expecting comma or )
  // echo(age=18, name="Madman")
  // echo(name="Madman", age=18)
}

既然调用时不能通过参数名指定形参,因此形参和返回值的变量名对于 函数调用者 而言没有意义

值传递:

调用函数时,函数的形参作为局部变量,被初始化为调用者提供的值。实参通过值的方式传递,因此函数的形参是实参的拷贝(值传递)。对形参进行修改不会影响实参,但是如果实参是引用类型或它的内部包含引用类型(比如 pointer、slice、map、function、channel 等),那么修改形参时可能会由于间接引用导致实参所对应的值被修改

5. 函数类型

函数类型被称为函数的标识符,如果两个函数的 形式参数列表返回值列表 中的变量个数和类型都一样时,那么这两个函数被认为有相同的类型和标识符。形参和返回值的 变量名 不影响函数标识符,以下三个具有相同的函数类型:

func(a int, b int) (c int)
func(x, y int) int
func(int, int) int

我们可以使用 type 定义函数类型:

package main

import (
    "fmt"
)

type add func(int, int) int

func main() {
    var a add = func(a int, b int) int {
        return a + b
    }
    s := a(5, 6)
    fmt.Println("Sum:", s) // Sum: 11
}

6. 函数值

函数也是值,可以像其它值一样传递。函数像其他值一样拥有类型,可以被赋值给其他变量,用作其它函数的参数或返回值。对 函数值(function value) 的调用类似函数调用:

package main

import "fmt"

func square(n int) int     { return n * n }
func negative(n int) int   { return -n }
func product(m, n int) int { return m * n }

func main() {
  f := square
  fmt.Println(f(3)) // 9

  f = negative
  fmt.Println(f(3))     // -3
  fmt.Printf("%T\n", f) // func(int) int

  // 函数类型不同时不能互相赋值
  // 报错 cannot use product (type func(int, int) int) as type func(int) int in assignment
  f = product 

  // 函数类型的零值是 nil
  var g func(int) int
  // 调用值为 nil 的函数值会引起 panic 错误
  // panic: runtime error: invalid memory address or nil pointer dereference
  g(3)
}

函数值可以与 nil 比较:

var g func(int) int
if g != nil {
  g(3)
}

函数值与函数值之间是不可比较的,也不能用函数值作为 map 的 key:

f := square
g := square
if f == g {  // 报错 invalid operation: f == g (func can only be compared to nil)
  fmt.Println("f equal g")
}

可以将函数值传递给其它函数,比如标准库中的 strings.Map:

func Map(mapping func(rune) rune, s string) string {...}
package main

import (
  "fmt"
  "strings"
)

func add(r rune) rune { return r + 1 }

func main() {
  fmt.Println(strings.Map(add, "ABC"))
}

/* Output:
BCD
*/

7. 递归函数

函数可以递归,这意味着函数可以直接或间接的调用自身。递归实现 斐波那契数列(fibonacci)

package main

import "fmt"

func fibonacci(n int) int {
  if n == 0 || n == 1 {
    return n
  }
  return fibonacci(n-1) + fibonacci(n-2)
}

func main() {
  for i := 0; i < 10; i++ {
    fmt.Print(fibonacci(i), " ")
  }
}

/* Output:
0 1 1 2 3 5 8 13 21 34
*/

8. 匿名函数

拥有函数名的函数只能在包级语法块中被声明:

func main() {
  // syntax error: unexpected add, expecting (
  func add(r rune) rune { return r + 1 }

  fmt.Println(strings.Map(add, "ABC"))
}

通过 函数字面量(function literal),我们可以绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于 func 关键字后没有函数名。函数字面量是一种表达式,它的值被称为 匿名函数(anonymous function)

函数字面量允许我们在使用函数时再定义它,通过这种技巧,我们可以改写之前对 strings.Map 的调用:

package main

import (
  "fmt"
  "strings"
)

func main() {
  fmt.Println(strings.Map(func(r rune) rune { return r + 1 }, "ABC"))
}

9. 闭包

闭包(closure) 是一个函数值,它引用了其函数体之外的变量(这就是函数值属于引用类型、函数值不可比较的原因 )。该函数可以访问并赋值其引用的变量值,换句话说,该函数与这些变量 绑定 在一起:

package main

import "fmt"

func adder() func(int) int {
  sum := 0
  return func(x int) int {
    sum += x
    return sum
  }
}

func main() {
  pos, neg := adder(), adder()
  for i := 0; i < 3; i++ {
    fmt.Println(
      pos(i),
      neg(-2*i),
    )
  }
}

/* Output:
0 0
1 -2
3 -6
*/

闭包实现 斐波那契数列(fibonacci)

package main

import "fmt"

func fibonacci() func() int {
  a, b := 1, 0
  return func() int {
    a, b = b, a+b
    return a
  }
}

func main() {
  f := fibonacci()
  for i := 0; i < 10; i++ {
    fmt.Print(f(), " ")
  }
}

/* Output:
0 1 1 2 3 5 8 13 21 34
*/

10. 可变参数函数(variadic function)

参数数量可变的函数称为 可变参数函数,如果函数的 最后一个参数 类型为 ...T,表示可以接收任何数量的 T 类型值,此时这个参数在函数内部表示为 []T

package main

import "fmt"

func sum(nums ...int) int {
  res := 0
  // nums 的实际类型为 []int
  for _, n := range nums {
    res += n
  }
  return res
}

func main() {
  fmt.Println(sum())        // 0
  fmt.Println(sum(5))       // 5
  fmt.Println(sum(1, 2, 3)) // 6
}

在上面的代码中,调用 sum 函数时会隐式地创建一个数组,并将实参复制到数组中,再把数组的一个切片作为参数传给被调函数。如果原始参数已经是切片类型,我们该如何传递给 sum ? 只需在最后一个参数的后面加上省略符即可:

primes := []int{2, 3, 5, 7}
fmt.Println(sum(primes...)) // 直接将 slice 的全部元素传递给函数的可变参数

典型的可变参数函数 fmt.Printlnfmt.Fprintln

func Println(a ...interface{}) (n int, err error) {
  return Fprintln(os.Stdout, a...)  // 将 slice 类型的变量 a 解包传入 Fprintln 函数
}

内建函数 append 也是可变参数函数:

func append(slice []Type, elems ...Type) []Type
未经允许不得转载: LIFE & SHARE - 王颜公子 » Go基础|第4章:function

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

0 条评论

暂时还没有评论.