Go语言程序设计(十一)函数

一、前言

        函数( Function)是能完成程序预定义功能的代码逻辑单位,一个Go程序至少由一个或多个函数组成。对于可执行的Go程序必须有main()函数,而且只能有一个。
        一般来说,Go程序在编译时,函数在程序中所处的位置并不影响编译结果。但为了代码的可读性,一.般是从main()函数开始,按函数间的逻辑结构编写代码。编写函数的目的主要是为了将冗长的代码划分成较小的功能模块,另外-一个目的是为了能反复调用程序的某些功能,提高程序代码的复用性。
        Go函数不支持嵌套,不支持重载,也不支持默认参数。但Go函数支持变参、多返回值,还可以命名返回值参数。另外,Go语言还支持匿名函数和闭包。在C语言中,如果要在函数定义之前使用它,首先要声明一个函数原型,而在Go语言中无须这么做,Go函数不需要声明函数原型就可以直接使用。

二、函数声明

        在Go语言中,函数在声明之后就可以使用,无须声明函数原型。Go函数一般由关键字func、函数名、参数列表、返回值、函数体和返回语句组成。当然,有些函数没有参数或返回值。

函数声明的基本格式:

func functionName (参数列表) 返回值 {
    functionBody
        ...
    return语句
}

  在声明函数时要注意:

  • (1)函数名的命名规则和变量名相同,遵循标识符命名规则。
  • (2)函数可以有参数或者没有参数,主调用函数通常使用参数向被调用函数传递数据。
  • (3)函数体内的所有语句使用一对“{}”括起来,左大括号“{”必须和“func”放在同一行,右大括号“}”必须单独占一行。
  • (4)函数会在执行完最后一条语句,或执行return语句后结束,Go语言函数支持多返回值。另外,在终止一个无限循环或goroutine时通常也使用return语句。

三、函数调用

1、调用标准函数

        Go提供了大量的包和实用函数供用户使用,这些函数被称为标准丽数。常用的标准包有fmt、math、os、time、bytes等,标准包的信息可以在Go安装目录的pkg下查看,或使用godoc查看。在调用标准函数时首先要导人该函数所在的包,比如调用Println()函数要导入fmt包。

2、调用自定义函数

        通常一个可执行的Go程序,首先要构建一个main包,在main包中必须声明一个main函数,然后再声明一些其他自定义函数让main函数调用,在这种情况下,除非是必需的标准包,否则不用导人任何其他包,直接调用自定义函数就行了。

3、调用外部包中函数

        有时在一个Go项目中,除了main包外还可能创建了其他包,如果被调丽数是由其他包提供的,此时需导人这个包才能调用相关函数。

4、调用内置函数

        除了前面几种函数的调用,Go语言还提供了一些非常有用的内置函数( Built-inFunction),这些函数在调用时无须导入任何包就可以直接使用。内置函数一般都能对不同的数据类型进行操作,比如len()函数能获取数组、字符串、切片的长度。有些内置函数直接作用于系统底层,比如像panic()函数,通常用于系统错误处理。Go 语言的内置函数虽然不多,但都非常有用,。

四、参数传递

        在Go语言中,函数参数可以是值类型或引用类型,值类型作为函数参数进行传递时,是一个参数值的拷贝,引用类型作为函数参数传递时,是一个地址拷贝。

说明:

  • (1)在声明函数时指定的形参,在未进行函数调用时并不占用存储单元。在进行函数调用时,形参才被分配存储单元,在调用结束后存储单元被释放。
  • (2)实参可以是常量、变量或者表达式,不管是哪一种形式都必须有确定的值,在调用时将实参的值赋给形参。
  • (3)在声明函数时,必须指定形参的类型,而且实参与形参的类型必须一致。

1、常规传递
        当使用普通变量作为函数参数时,在传递参数时只是对变量值的拷贝,即将实参的值复制给变参。当函数对变参进行处理时,并不影响原来实参的值。

2、指针传递
        函数的参数不仅可以使用普通变量,还可以使用指针变量。当使用指针变量作为函数的参数时,在进行参数传递时将是一个地址拷贝,即将实参的内存地址复制给变参,这时对变参的修改同时也将会影响到实参的值。

3、数组元素作为函数参数
        前面已经介绍了可以使用普通变量作为函数的参数,显然,数组元素也可以作为函数的参数,因为数组就是同一种变量的集合。当使用数组元素作为函数参数时,其使用方法和普通变量相同,即是一个“值拷贝”。

4、数组名作为函数参数
        除了数组元素可以作为函数参数外,数组名也可以作为函数的参数。和其他语言不同的是,Go语言在将数组名作为函数参数时,参数传递即是对数组的复制。在形参中对数组元素的修改,都不会影响实参数组元素原来的值。

5、Slice作为函数参数
        当希望通过形参对底层数组进行修改时,可以使用Slice作为函数参数。在使用Slice作为函数参数时,进行参数传递将是一个地址拷贝,即将底层数组的内存地址复制给参数Slice。这时,对Slice元素的操作即是对底层数组元素的操作。

6、函数作为参数
        在Go语言中,函数也作为一种数据类型,所以函数也可以作为函数的参数来使用。

例如:

        该例中函数sum作为函数f6()的形参,而变量f是一个函数类型,作为f6()调用时的实参,程序运行结果为:7。

func main( ) {
    var a,b int=3,4
    f:= sum
    f6(a,b,f)
}

func f6(a, b int, sum func(int, int) int) {
    fmt. Println(sum(a, b))
}

func sum(a,b int) int {
    return a + b
}

五、返回值

        函数被调用时,允许有返回值,甚至是多个返回值。Go语言还允许定义返回值变量,这样在使用return语句进行返回时语句更加简洁。

多返回值
        Go语言支持函数可以有多个返回值,这和C语言有明显不同,多返回值使得Go程序设计起来更加灵活,而且功能强大。如果函数定义了多个返回值,除了说明各个返回值的类型以外,还需使用一对括号将它们括起来。

返回值的忽略
        在处理多返回值时,对于不想要的值,可以使用空白标识符(Blankidentifier)“_”进行忽略。

命名返回值参数
        Go语言还支持命名返回值参数,如果使用命名返回值参数,则return语句可以为空。否则,return语句必须按照顺序返回多个结果。
例如:

func main() {
    sum,sub := f3(3,6)
    fmt.Println(sum, sub)
}
func f3(a,b int) (sum, sub int) {
    sum = a+b
    sub = a-b
    return
}

        该例中函数f3()有两个返回值参数,一个是sum,一个是sub。命名了返回值参数,函数f3()使用一条空return语句,就可以向主调函数返回sum、sub这两个结果。如果未命名返回值参数,函数f3()的返回语句写法是: return a+b,a-b。

六、变参函数

Go语言支持不定长变参,但是要注意不定长变参只能作为函数的最后一个参数,不能放在其他参数的前面。

变参函数的声明格式如下:

func functionName (variableArgumentName ...dataType) 返回值 {
        functionBody
        .....
}

说明:

  • (1)变参类型的定义格式是“...类型”,而且变参必须是函数的最后一个参数。如果函数还有其他参数,则必须放在变参的前面定义,例如func fl(a int,s string, args ...int){}。
  • (2)不定长变参其实质就是一个切片,可以使用range进行遍历。

任意类型的变参

        前面的例子中变参中的元素都必须是同一种类型,比如同为整型、浮点型、字符串等,但在实际应用中,用户希望向变参函数传递不同类型的参数,比如向fmt.Printf()函数那样。Go语言规定,如果希望传递任意类型的变参,变参类型应指定为空接口interface{}。
例如:

func f1(args ... interface{}) {
        .....
}

        在Go语言中,空接口interface{}可以指向任何数据对象,所以可以使用interface{}定义任意类型变参。同时,interface{}也是类型安全的。

七、匿名函数

        匿名函数(Anonymous function)是指不需要定义函数名的一种函数实现方式。匿名函数最早出现在LISP语言中,目前PHPJavaScript都支持这种函数形式。
        在Go语言中,函数可以像变量一-样被传递或使用,这与C语言的函数回调比较类似。不同的是,Go语言支持随时在代码里定义匿名函数。匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:

func (参数列表) 返回值 {
    functionBody
        ...
    return语句
}

例如:

func(a,b int) int {
    return a + b
}

        上例声明了一个匿名函数,该匿名函数有两个整型参数,返回值也为整型。匿名函数在
调用时,可以直接赋值给一个变量,或者直接执行。示例如下:

//匿名函数的声明与调用
package main
import(
    "fmt"
)

func main() {
    //声明并直接将匿名函数赋值给变量f
    f:= func(a,b int) int {
            return a+ b
        }
    //对函数类型变量f进行调用
    sum : = f(2,3)
    fmt.Println(sum)
    //声明并直接执行匿名函数
    sum = func(a,b int) int {
        return a + b
    }(5, 7)
    fmt.Println(sum)
}

八、闭包

        闭包(Closure),就是内部函数通过某种方式使其可见范围超出了其定义的范围,这就产生了一个在其定义范围内的闭包。在Go语言中,闭包可以作为函数对象或者匿名函数,也就是说Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。

例如:

func main() {
    f:= closures(10)
    fmt.Println(f(1))
    fmt.Println(f(2))
}

func closures(x int) func(int) int {
    return func(y int) int {
        return x+ y
    }
}

这段代码有三个特点:

  • (1)匿名函数嵌套在函数closures内部。
  • (2)函数closures返回匿名函数。
  • (3)匿名函数引用了自身外部的变量x。

        函数closures内的匿名函数就是上面所说的内部函数,这样在执行完f:=closures(10)后,变量f实际上是指向了匿名函数,所以执行f(1)后输出11,执行f(2)后输出12。这段代码其实就创建了一个闭包,因为函数closures的变量f引用了函数closures内的匿名函数。也就是说:当函数closures的内部函数(匿名函数)被函数closures外的一个变量引用的时候,就创建了一个闭包。

通过对函数闭包的原理分析和实例测试,函数闭包主要有以下几个作用:

  • (1)函数闭包可以保护函数内的变量安全。比如函数closures的参数x只有内部函数才能访问,而无法通过其他途径访问到。
  • (2)函数闭包可以在内存中维持一个变量。比如上例中函数closures的参数x在定义变量f时被初始化为10,然后就一直保持为这个值,后面无论调用多少次f,x在内存中一直存在。
     

九、函数的递归调用

递归调用必须满足以下两个条件:

  • (1)递归函数在每一次调用自己时,必须是(在某种意义上)更接近于最终结果。
  • (2)必须有一个终止递归调用的条件控制。

所以,函数的递归调用通常使用if语句来控制,只有当某一条件成立时才继续执行递归调用,否则就不再继续。
 

十、defer语句

        在Go语言中,可以使用关键字defer向函数注册退出调用,即当主调函数退出时,defer后的函数才会被调用。defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码。

例如:

func main(){
    defer fmt.Println( "The first.")
    fmt.Println("The second. ")
}

上例首先输出“The second.”,然后才输出“The first.'

1、defer语句实现函数逆序调用
        如果程序中有多个defer语句,则按照“先进后出(FILO)”的次序执行,即最后一个defer语句将最先被执行。

2、defer语句支持匿名函数调用
        在Go语言中,defer语句还支持匿名函数调用,如果函数有返回值,被延迟执行的匿名函数还会读取函数的返回值,并对返回值赋值。

3、defer语句用于清理工作
        在Go程序中,当程序返回或发生异常时,defer语句通常用来做一些函数调用后的清理工作,释放资源变量。

十一、异常恢复机制

        Go没有Java中那种try-catch-finally结构化异常处理机制,而是使用panic()函数代替throw/raise引发错误,然后在defer语句中调用recover()函数捕获错误,这就是Go语言的异常恢复机制——panic -and-recover机制。
        panic是一个内置函数,可以中断原有的控制流程,进入一个异常流程中。当函数调用panic时,函数的执行将被中断,并且函数中的延迟函数会正常执行,然后函数返回到调用它的地方。在调用的地方,函数的行为就像调用了panic。 这一过程继续向上,直到程序崩溃时的所有goroutine返回。异常可以直接调用panic产生,也可以由运行时错误产生,例如访问越界的数组。
        Recover也是一个内置的函数,它可以让进入异常流程中的goroutine 恢复过来。Recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil并且没有其他任何效果。如果当前的goroutine陷人异常,调用recover可以捕获到panic的输人值,并且恢复正常的执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值