Go 函数

5.1 函数声明

函数声明

基本语法

每个函数声明,都包含了一个函数名 、一个形参列表 、一个可选的返回列表 、一个函数体

func name(parameter-list) (return-list) {

        body

}

形参与返回值 形参列表,指定了一组变量的参数名和参数类型
形参是函数的局部变量,这些形参都由调用者提供的实参传递而来
返回列表,指定了函数返回值的类型
当函数返回一个未命名的返回值,或者函数没有返回值的时候,返回列表的圆括号可以省略
如果一个函数既没有返回列表,也没有任何返回值,那么这个函数的目的是调用函数所带来的附加效果
代码示例

在下面的 hypot 函数中:

func  name( x , y float64 ) float64 {

        return  math.Sqrt( x*x  +  y*y )

}

fmt.Println( hypot( 3 , 4 ) )    // " 5 "

x 和 y 是函数声明中的形参,3 和 4 是调用函数时的实参,并且函数返回一个类型为 float64 的值

形参与返回值

返回值可以像形参一样命名;

每个命名的返回值会声明为一个局部变量,并根据变量类型初始化为对应的零值

当函数存在返回列表时,必须显式地以 return 语句结束;

除非函数明确不会走完整个执行流程,比如在函数中抛出宕机异常或函数体内存在一个没有 break 退出条件的无限 for 循环

如果几个形参或者返回值的类型相同,那么类型标识符只需要写一次
代码示例

以下两个声明是完全相同的:

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 、函数 、通道,那么当函数使用形参变量时,就有可能间接地修改实参变量
有些函数的声明没有函数体,说明这个函数使用了除 Go 以外的语言实现

这样的声明定义了该函数的签名

package math

func Sin(x float64) float64  // 使用汇编语言实现

5.2 递归

递归的含义 函数可以递归调用,这意味着函数可以直接或间接地调用自己
递归,是一种实用的技术,可以处理许多带有递归特性的数据结构
下面使用递归处理 HTML 文件

下面的代码示例使用了一个非标准的包 golang.org/x/net/html ,它提供了解析 HTML 的功能;

golang.org/x/... 下的仓库(比如网络 、国际化语言处理 、移动平台 、图片处理 、加密功能以及开发者工具)都由 Go 团队负责设计和维护;这些包并不属于标准库,原因是它们还在开发当中,或者很少被 Go 程序员使用

我们需要的 golang.org/x/net/html API 如下面的代码所示;

函数 html.Parse 读入一段字节序列,解析它们,然后返回 HTML 文档树的根节点 html.Node ;HTML 有多种节点,比如文本 、注释等;

此处,我们只关心表单的 "元素节点 "

类型声明

golang.org/x/net/html

package html

type Node struct {

        Type                              NodeType

        Data                               string

        Attr                                 []Attribute

        FirstChild , NextSibling  *Node

}

type NodeType int32

const (

        ErrorNode NodeType = iota

        TextNode

        DocumentNode

        ElementNode

        CommentNode

        DoctypeNode

)

type Attribute struct {

        Key , Val string

}

func Parse( r io.Reader )  ( *Node , error )

main 函数 主函数从标准输入中读入 HTML ,使用递归的 visit 函数获取 HTML 文本的超链接,并且把所有的超链接输出

gop1.io/ch5/findlinks1

package main

import (

        "fmt"

        "os"

        "golang.org/x/net/html"

)

func main() {

        doc , err := html.Parse(os.Stdin)    // 标准输入进入 Parse 函数进行解析

        if err != nil {                                     // 判断解析函数执行是否出错

                fmt.Fprintf(os.Stderr , "findlinks1 :%v\n" , err)

                os.Exit(1)

        }

        for _ , link := range visit(nil , doc) {   // 获取 HTML 文本的超链接

                fmt.Println(link)                         // 输出超链接

        }

}

visit 函数 visit 函数遍历 HTML 树上的所有节点,从 HTML 锚元素 中得到 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 链表中

下面的程序使用递归遍历所有 HTML 文本中的结点树,并输出树的结构;当递归遇到每个元素时,会将元素标签压入栈,然后输出栈

func main()  {

        doc,err  :=  html.Parse(os.Stdin)

        if err  !=  nil  {

                fmt.Fprintf(os.Stderr,"outline:%v\n",err)

                os.Exit(1)    // 解析出错则退出程序

        }

        outline(nil,doc)

}

func outline(stack []string,n *html.Node)  {

        if  n.Type  ==  html.ElementNode  {

                stack  =  append(stack,n.Data)  // 把标签压入栈

                fmt.Println(stack)

        }

        for  c  :=  n.FirstChild;c  !=  nil;c  =  c.NextSibling  {

                outline(stack,c)

        }

    }

尽管 outline 会将元素压栈但不会出栈;

当 outline 递归调用自己时,被调用的函数会接收到栈的副本,所以 outline 无法修改调用者的栈

函数调用栈

许多变成语言使用固定长度的函数调用栈;大小在 64KB 到 2MB 之间;

递归的深度会受限于固定长度的栈大小,所以当进行深度递归调用时必须严防 “栈溢出” ;固定长度的栈甚至会造成一定的安全隐患

相比固定长的栈 ,Go 语言的实现使用了可变长度的栈,栈大小随使用而增长,可达 1GB 左右的上限;安全使用栈,不必担心溢出问题

5.3 多返回值

多返回值概念 一个函数能够返回不止一个结果
标准包内的许多函数返回两个值:一个期望得到的计算结果、一个错误值(或者一个表示函数是否调用正确的布尔值)

5.4 错误

5.5 函数变量

5.6 匿名函数

5.7 变长函数

5.8 延迟函数调用

5.9 宕机

5.10 恢复

你可能感兴趣的:(后端编程语言,golang)