函数有以下几个优点:可以把一系列语句打包成一个程序单元;可以把大的工作分解为小的任务,这些任务可以让不同程序员在不同时间、不同地点独立完成;一个函数可以对用户隐藏实现细节。这些优点,让函数变成了程序不可或缺的最重要的部分之一。
在此章之前,我们已经见过很多函数了,现在是时候彻底讨论函数的特性了。本章用的例子是一个网络爬虫,也是web搜索引擎中负责抓取网页的组件,他们会根据抓取到的网页中的链接继续去抓取该链接指向的页面,这个例子可以让我们学习递归函数、匿名函数、错误处理及函数的很多其它特性。
函数声明包含了函数名,可省略的参数列表、返回值列表以及函数体:
func name(parameter-list) (result-list) {
body
}
参数列表描述了参数名和参数类型,这些参数是函数范围的局部变量,值由调用者传入。返回值列表描述了函数返回值的名字和类型,如果函数只返回一个不具名变量或者干脆没有返回值,那返回值列表的括号是可以省略的。如果一个函数声明不包含返回值列表,那么函数体执行完毕后,不会返回任何值:
func hypot(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3,4)) // "5"
x和y是参数名,3和4是调用时传入的参数值,函数返回了一个float64类型的值。
返回值也可以像参数一样被命名,在这种情况下,返回值被声明成函数范围的局部变量,并会初始化为对应类型的零值。
如果函数在声明时有返回值列表,那该函数必须要执行到return语句,除非函数无法执行到结尾处:例如发生了panic或者存在没有break的无限循环。
正如hypot函数一样,如果多个参数或者返回值有相同的类型,我们可以一起集中声明,下面两个声明是等价的:
func f(i, j, k int, s, t string) { /* ... */ }
func f(i int, j int, k int, s string, t string) { /* ... */ }
下面的4个函数,每个函数都有2个int参数和一个int返回值,但是我们用4种不同的方法进行了声明。这里要注意下划线(空白操作符)的使用:
func add(x int, y int) int {return x + y}
func sub(x, y int) (z int) { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int { return 0 }
fmt.Printf("%T\n", add) // "func(int, int) int"
fmt.Printf("%T\n", sub) // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero) // "func(int, int) int"
函数类型就是函数签名,如果两个函数的参数列表和返回值列表的变量类型能一一对应,那么这两个函数就有相同的签名。参数和返回值的变量名以及是否集中声明并不会影响函数签名。
函数调用的传参必须按照参数声明的顺序。Go语言是没有默认参数值的说法,也没有任何方法通过参数名指定传入参数的值,因此除了文档外,参数和返回值的变量名对于函数调用者是没有任何意义的。
在函数体中,参数是局部变量,被初始化为调用者传的值。函数的参数和具名返回值是函数最外层的局部变量,它们的词法块就是整个函数。
参数是通过值来传递的,因此传递过去的都是原来变量的拷贝,对参数的修改不会影响到原来的变量。但是,如果参数是引用类型,例如指针、slice、map、function、channel等,那对参数的修改可能会影响原来的变量。
你可能会在标准库中看到没有函数体的函数声明,这表示该函数不是用Go实现的:
package math
func Sin(x float64) float //该函数用汇编实现
有疑问加站长微信联系(非本文作者)