函数包含连续执行的语句,可以使用代码中通过调用函数来执行他们,函数能够将一个复杂的工作切分成多个更小的模块,使多人写作变得容易。另外,函数对他的使用者隐藏了实现细节。这几方面的特性使得函数成为多数编程语言的重要特性之一。
1. 函数声明
每个函数都包含一个名字,一个形参列表,一个可选的返回列表及函数体
func name(parameter-list) (result-list) {
body
}
形参列表指定了函数返回值的类型,这些局部变量都由调用者提供的实参传递而来。返回值列表指定了函数返回值的类型。当函数返回一个未命名的返回值或没有返回值的时候,返回列表的圆括号可以省略。如果一个函数既省略返回列表页没有任何返回值,那么设计这个函数的目的是调用函数之后所带来的附加效果。在下面的hypot函数中:
func hypot(x, y float64) float64 {
return math.Sqrt(x*x +y*y)
}
fmt.Println(hypot(3,4)) //"5"
x,y是函数声明中的形参,3,4是调用函数时的实参,并且返回了一个类型为float64的值。
返回值也可以像形参一样命名。这个时候每个命名的返回值会声明为一个局部变量,并且根据变量类型初始化为相应的0值。
当函数存在返回列表时,必须显示的以return语句结束,除非函数明确不会走完整个执行流程,比如在函数中抛出宕机异常或者函数体内存在一个没有break退出条件的无限for循环。
在hypot函数中使用到一种简写,如果几个形参或者返回值类型相同,那么类型只需要写一次。以下两个声明是完全相同的。
func f(i,j,k int, s,t string)
func f(i int, j int, k int, s string, t string)
下面使用4种方式声明一个带有两个形参和一个返回值的函数,所有变量都是int类型。空白标识符用来强调这个形参在函数中未使用。
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。函数或者通道,那么函数使用形参变量时就可能会间接的修改实参变量。
2 递归
函数可以递归调用,这意味着函数可以直接或间接地调用自己。递归是一种实用技术,可以处理许多带有递归特性的数据结构。
下面的代码示例使用了一个非标准的包golang.org/x/net/html,它提供解析HTML的功能。golang.org/x/...下的仓库(比如网络,国际化语言处理,移动平台,图片处理,加密功能以及开发者工具)都由Go团队负责设计和维护。这些包并不属于标准库,原因他们还正在开发中或者程序员很少使用。
我们需要的golang.org/x/net/html API如下面代码所示。函数html.parse读入一段字节序列,解析它们。然后返回HTML文档树的根节点html.node. HTML有多种节点,比如文本,注释等。但这里我们只关心表单的元素节点<name key='value'>.
主函数从标准输入中读入HTML,使用递归的visit函数获取HTML文本的超链接,并且把所有的超链接输出。
func main() { doc, err := html.Parse(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err) os.Exit(1) } for _, link := range visit(nil,doc) { fmt.Println(link) } }
visit函数遍历HTML树上的所有节点,从HTML锚元素<a href='...'>中得到href属性的内容,将获取到的链接内容添加到字符串slice,最后返回这个slice:
//visit函数会将n节点中每个链接添加到结果中。 func visit(links []string,n *html.Node)[]string { if n.Type == html.ElementNode && n.Data == "a" { for _, a := range n.Attr { if a.Key == "href" { links = append(links,a.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { links = visit(links,c) } return links }
要对树中的任意节点n进行递归,visit递归的调用自己去访问节点n的所有自子节点并且将访问过的节点保存在firstchild链表中。我们在Go主页运行findlinks,使用管道将以前的fetch程序的输出去定向到findlinks。
allanyang-mbp:src allanyang$ go build gopl.io/ch1/fetch
allanyang-mbp:src allanyang$ go build gopl.io/ch5/findlinks1
allanyang-mbp:src allanyang$ ./fetch https://golang.org | ./findlinks1
/
/
#
/doc/
/pkg/
/project/
/help/
/blog/
#
#
//tour.golang.org/
/dl/
//blog.golang.org/
https://developers.google.com/site-policies#restrictions
https://creativecommons.org/licenses/by/3.0/
/LICENSE
/doc/tos.html
http://www.google.com/intl/en/policies/privacy/
可以注意到会获取各种不同形式的超链接,之后我们将看到如何解析这些地址,并将链接都转换为基于https://golang.org的URL绝对路径