文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/

今天,我们来学习Go语言编程的第五章,函数。首先简单说一下函数的概念和作用。函数是一系列语句的集合。一般是为了完成某一特定功能而定义的。这样在需要使用该功能时,直接调用该函数即可,而不用再去写一堆代码,实现了代码的复用。另外,在需要修改该功能时,也只需修改这一个函数即可,方便了代码的维护。

5.1 函数定义

函数定义包括函数名、形参列表、返回值列表以及函数体。一般语法格式如下:

func 函数名称( [形参列表] ) [返回值列表]{

函数体

}

示例:

func Max(a int, b int) int {

if a > b {

return a

}

return b

}

func main() {

max := Max(10, 5)//调用函数,结果10

fmt.Println(max)

max = Max(-10, 5)//调用函数,结果5

fmt.Println(max)

}

Go函数中的参数没有默认参数值。形参和返回值的变量名对于函数调用者而言没有意义。

5.2 可变参数

参数数量可变的函数称为可变参数的函数。在定义可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“...”,表示该函数可接收任意数量的该类型参数。

func Sum(vals ...int) int {

total := 0

for _, v := range vals {

total += v

}

return total

}

func main() {

fmt.Println(Sum(1, 2))     //2个参数

fmt.Println(Sum(1, 2, 3))  //3个参数

slice := []int{1, 2, 3}    //把slice作为参数,需要打散

fmt.Println(Sum(slice...))

}

5.3 多返回值

与C、C++和Java等其它开发语言不同的是是Go语言的函数可以有多个返回值。这个特性能够使得我们写出比其它语言更加优雅和简洁的代码,例如File.Read()函数可以同时返回读取的字节数和错误信息。如果读取成功,返回值n为读取的字节数,err为nil,否则err为具体的错误信息。

func (file *File) Read(b []Byte)(n int,err Error)

一个简单的多返回值示例:

// 多个返回值,另外同类型的参数列表或返回值列表可以采用以下简写形式

func AddAndSub(a, b int) (add, sub int) {

return a + b, a - b

}

func main() {

v1, v2 := AddAndSub(6, 3)

fmt.Println(v1, v2)  // 9,3

}

5.4 递归函数

程序调用自身的编程技巧称为递归。递归做为一种算法编程语言中广泛应用。函数调用自身,称为递归函数。

指的是这样一个数列:0、1、1、2、3、5、8、13、21、……在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*),用文字来说:

² 前2个数是 0 和 1 。

² 第 i 个数是第 i-1 个数和第i-2 个数的和。

// 递归实现,

func Fib(n int) int {

if n < 2 {

return n

}

return Fib(n-1) + Fib(n-2)

}

func main() {

for i := 0; i < 10; i++ {

fmt.Print(Fib(i), "  ")

}

}

5.5 错误处理

对于大多数函数而言,无法确保函数一定能够成功运行,因为产生错误的原因很有可能超出程序员控制范围。例如,任何I/O操作的函数都有出现错误的可能。如果函数需要返回错误,通常将error作为多种返回值中的最后一个,虽然这并非是强制要求。

一般为如下模式:

func Foo(参数列表)(ret list, err error) {
// ...
}

调用时的处理模式:

n, err := Foo(参数)
if err != nil {
    // 错误处理
} else {
    //使用返回值ret
}

内置的error是接口类型。

常用的处理错误的5种方式:

1传递错误

2 如果错误是偶然的,重新尝试失败的操作。

3 输出错误信息,并结束程序,一般在main()函数中使用。

4 输出错误信息,并且不中断程序

5 直接忽略错误

5.6 defer

当一个函数调用前有关键字 defer 时, 那么这个函数的执行会推迟到包含这个defer 语句的函数即将返回前才执行。

func main() {

defer fmt.Println(3)

fmt.Println(1)

fmt.Println(2)

}

//打印结果:

1

2

3

defer通常用于 open/close, connect/disconnect, lock/unlock 等这些成对的操作, 来保证在任何情况下资源都被正确释放。下面是一个文件拷贝的例子:

func CopyFile(dst, src string) (w int64, err error) {

srcFile, err := os.Open(src)

if err != nil {

return

}

defer srcFile.Close()

dstFile, err := os.Create(dstName)

if err != nil {

return

}

defer dstFile.Close()

return io.Copy(dstFile, srcFile)

}

即使其中的Copy()函数抛出异常, Go仍然会保证dstFile和srcFile会被正常关闭。一个函数中可以存在多个defer语句, defer语句的调用是遵照先进后出的原则,即最后一个defer语句将最先被执行。

defer也可以跟一个用户自定义的匿名函数。

defer func() {

// 匿名函数的代码

} ()

defer 调用的函数参数的值在defer被定义时就确定了,而 defer 函数内部所使用的变量的值是在这个函数运行时才确定。

func main() {

i := 1

defer fmt.Println("延时打印:", i) //i是参数

i++

fmt.Println("常规打印:", i)

}

//打印结果:

常规打印: 2

延时打印: 1

func main() {

i := 1

defer func() {

fmt.Println("延时打印", i) //i是内部变量

}()

i++

fmt.Println("常规打印:", i)

}

//打印结果:

常规打印: 2

延时打印 2

defer 函数调用的执行时机是外层函数设置返回值之后, 并且在即将返回之前。也就是说“return 返回值”语句并不是原子的。请看下面的例子:

func double(x int) int {

return x + x

}

func triple(x int) (r int) {

defer func() {

r += x

}()

return double(x)

}

func main() {

fmt.Println(triple(3))

}

打印结果:

9

// 等价与下面代码,打印结果是9

func triple(x int) (r int) {

// 1 设置返回值

r = double(x)

// 2 执行defer语句的函数

func() {

r += x

}()

// 3 函数返回

return

}

5.7 panic、recover函数

Go语言追求简洁优雅,所以不支持传统的 try…catch…finally 这种异常处理结构。Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱,不要用异常代替错误,更不要用来控制流程。在遇到真正的异常的情况下,才使用Go中引入的Exception处理:defer, panic, recover。panic 是用来表示非常严重的不可恢复的错误的,一般会导致程序挂掉(除非recover)。Go语言对异常的处理可以这样简单描述:Go程序的执行过程中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常。

func main() {

defer func() { // 必须要先声明defer,否则不能捕获到panic异常

fmt.Println("c")

if err := recover(); err != nil {

fmt.Println(err) // 这里的err其实就是panic传入的内容

}

fmt.Println("d")

}()

foo()

}

func foo() {

fmt.Println("a")

panic("产生异常")

fmt.Println("b")

fmt.Println("f")

}

//打印结果:

a

c

产生异常

d

5.8 函数值

在Go中,函数被看作第一类值。函数值像其它值一样,拥有类型。函数类型可以使用type关键字定义,通过函数的参数和返回值区分。

函数值可以被赋值给其它变量,也可以作为其他函数的参数或返回值。对函数值的调用相当于对函数的调用。函数值属于引用类型,两个函数值之间不可比较。

package main

import "fmt"

type handle func(int) int

func main() {

//1给函数变量赋值

v := func(n int) int {

return n * n

}

//2 通过函数值调用函数

fmt.Println(v(3))

var h handle

h = v

fmt.Println(h(3))

}

5.9 匿名函数与闭包

1 匿名函数

顾名思义,匿名函数就是定义时没有函数名称的函数。

func main() {

add := func(x, y int) int { //匿名函数赋值给变量add

return x + y

}

fmt.Println(add(10, 5))     //通过变量调用函数

func(x, y int) {

fmt.Println(x - y)

}(10, 5)                    //定义时直接调用

}

2 闭包

闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块为自由变量提供绑定的计算环境(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)。

闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。

package main

import "fmt"

func Sqrt(num int) func() int { //变量num与匿名函数在同一环境中定义

return func() int {

num++

return num * num

}

}

func main() {

s := Sqrt(0) //外部函数执行完成后,num并没有被销毁,值为0;

fmt.Println(s()) //执行s函数,num的值为1,函数的返回值为1

fmt.Println(s())//执行s函数,num的值为2,函数的返回值为4

fmt.Println(s())//执行s函数,num的值为3,函数的返回值为9

}

//打印结果:1 4 9

闭包函数出现的条件:

1.被嵌套的函数引用到非本函数的外部变量,而且这外部变量不是“全局变量”

2.嵌套的函数被独立了出来(被父函数返回或赋值 变成了独立的个体),而被引用的变量所在的父函数已结束。

逃逸的函数内的局部变量一定是堆上分配存储空间的。

Go语言 5 函数的更多相关文章

  1. C语言pow函数编写

    C语言pow函数编写 #include<stdio.h> double chaoba(double f,double q); //声明自定义函数 void main(void) { dou ...

  2. C语言-自定义函数

    C语言自定义函数 --1-- 自定义函数定义 1.1 无参无返回值函数 1.2 无参有返回值函数 1.3 有参无返回值函数 1.4 有参有返回值函数 --2-- 函数的参数 2.1 形式参数介绍和使用 ...

  3. C语言printf()函数:格式化输出函数

    C语言printf()函数:格式化输出函数 头文件:#include <stdio.h> printf()函数是最常用的格式化输出函数,其原型为:     int printf( char ...

  4. C语言的函数

    "函数"在英文的翻译是"function",无论在自然科学还是计算机科学都是这个词,而"function"的本意是"功能" ...

  5. c语言main函数返回值、参数详解(返回值是必须的,0表示正常退出)

    C语言Main函数返回值 main函数的返回值,用于说明程序的退出状态.如果返回0,则代表程序正常退出:返回其它数字的含义则由系统决定.通常,返回非零代表程序异常退出. 很多人甚至市面上的一些书籍,都 ...

  6. Go语言示例-函数返回多个值

    Go语言中函数可以返回多个值,这和其它编程语言有很大的不同.对于有其它语言编程经验的人来说,最大的障碍不是学习这个特性,而是很难想到去使用这个特性. 简单如交换两个数值的例子: package mai ...

  7. 【学习笔记】【C语言】函数

    一. 什么是函数 任何一个C语言程序都是由一个或者多个程序段(小程序)构成的,每个程序段都有自己的功能,我们一般称这些程序段为“函数”.所以,你可以说C语言程序是由函数构成的. 比如你用C语言编写了一 ...

  8. 【转载】 c语言inline函数的使用

    c语言inline函数的使用 转载自:http://blog.chinaunix.net/uid-21843265-id-3056446.html 大学在教科书上学习过inline函数,定义为inli ...

  9. 【C语言】函数和自定义函数

    函数,我之前也提到过一点点内容.其实函数是很好理解的,但是写起来又十分麻烦. 一.     函数引入 我们知道,C源程序是由函数组成的.请看下面的简单函数例子 #include <stdio.h ...

  10. c语言中函数的简单介绍

    c语言中函数的介绍: 函数,简单的说就是代码的打包.存放在一个地方,当需要的时候调用. 函数分类: 1.无参无返回值函数 void func() 2.无参有返回值函数  int func() 3.有参 ...

随机推荐

  1. PHP中与类和对象有关的几个系统函数

    与类有关的系统函数: class_exists(“类名”), 判断一个类是否存在(是否定义过) interface_exists(“接口名”), 判断一个接口是否存在(是否定义过) get_class ...

  2. 【uoj#22】[UR #1]外星人 组合数学+dp

    题目描述 给你一个长度为 $n$ 的序列 $\{a_i\}$ 和一个数 $x$ ,对于任意一个 $1\sim n$ 的排列 $\{p_i\}$ ,从 $1$ 到 $n$ 依次执行 $x=x\ \tex ...

  3. 详解SQL Server数据修复命令DBCC的使用

    严重级别为 21 表示可能存在数据损坏. 可能的原因包括损坏的页链.损坏的 IAM 或该对象的 sys.objects目录视图中存在无效条目. 这些错误通常由硬件或磁盘设备驱动程序故障而引起. MS ...

  4. Python常忘的基础知识

    0.目录 1.进制 1.1 各进制的表示 1.2 各进制的转换 2.字符 2.1 转义字符 2.2 原始字符串 3.类型 3.1 基本数据类型 3.2 type() 4.变量与运算符 4.1 值类型与 ...

  5. 51nod 1292 字符串中的最大值V2(后缀自动机)

    题意: 有一个字符串T.字符串S的F函数值可以如下计算:F(S) = L * S在T中出现的次数(L为字符串S的长度).求所有T的子串S中,函数F(S)的最大值. 题解: 求T的后缀自动机,然后所有每 ...

  6. 【BZOJ3244】【NOI2013】树的计数(神仙题)

    [BZOJ3244][NOI2013]树的计数(神仙题) 题面 BZOJ 这题有点假,\(bzoj\)上如果要交的话请输出\(ans-0.001,ans,ans+0.001\) 题解 数的形态和编号没 ...

  7. 【BZOJ5251】【八省联考2018】劈配(网络流,二分答案)

    [BZOJ5251][八省联考2018]劈配(网络流,二分答案) 题面 洛谷 BZOJ Description 一年一度的综艺节目<中国新代码>又开始了. Zayid从小就梦想成为一名程序 ...

  8. 洛谷 P3157 [CQOI2011]动态逆序对 解题报告

    P3157 [CQOI2011]动态逆序对 题目描述 对于序列\(A\),它的逆序对数定义为满足\(i<j\),且\(A_i>A_j\)的数对\((i,j)\)的个数.给\(1\)到\(n ...

  9. 洛谷 P3802 小魔女帕琪 解题报告

    P3802 小魔女帕琪 题目背景 从前有一个聪明的小魔女帕琪,兴趣是狩猎吸血鬼. 帕琪能熟练使用七种属性(金.木.水.火.土.日.月)的魔法,除了能使用这么多种属性魔法外,她还能将两种以上属性组合,从 ...

  10. Active Directory中获取域管理员权限的攻击方法

    Active Directory中获取域管理员权限的攻击方法         译:by  backlion 0x00 前言 攻击者可以通过多种方式在Active Directory中获得域管理员权限, ...