Go基础|第4章:function
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. 函数类型
函数类型被称为函数的标识符,如果两个函数的 形式参数列表
和 返回值列表
中的变量个数和类型都一样时,那么这两个函数被认为有相同的类型和标识符。形参和返回值的 变量名
不影响函数标识符,以下三个具有相同的函数类型:
我们可以使用 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 比较:
函数值与函数值之间是不可比较的,也不能用函数值作为 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
:
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 ? 只需在最后一个参数的后面加上省略符即可:
典型的可变参数函数 fmt.Println
和 fmt.Fprintln
:
func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) // 将 slice 类型的变量 a 解包传入 Fprintln 函数 }
内建函数 append
也是可变参数函数:
0 条评论
评论者的用户名
评论时间暂时还没有评论.