Golang-函数
在编程中经常会调用相同或者类似的操作,这些相同或者类似的操作由同一段代码完成,函数的出现,可以避免重复编写这些代码。函数的作用就是把相对独立的某个功能抽象出来,使之成为一个独立的实体。
例如,开发一个支持人与人之间进行对话的社交网站,对话这个功能比较复杂,可以将它封装为一个函数,每次调用该函数就可以发起对话;大型网站都有日志功能,对所有重要操作都会记录日志,而日志处理需要由多行Go文件操作相关代码组成,将这些代码组装为函数,则每次写日志时调用此函数即可。
Go语言函数支持的特性包括:
- 参数数量不固定(可变参数)。
- 匿名函数及其闭包。
- 函数本身作为值传递。
- 函数的延迟执行。
- 把函数作为接口调用。
函数的声明以关键字func为标识,具体格式如下:
1 | func 函数名(参数列表) (返回参数列表){ |
- 函数名:函数名由字母、数字和下划线构成,但是函数名不能以数字开头;在同一个包内,函数名不可重复。
注意:可暂时简单地将一个包理解为一个文件夹。 - 参数列表:参数列表中的每个参数都由参数名称和参数类型两部分组成,参数变量为函数的局部变量。如果函数的参数数量不固定,Go语言函数还支持可变参数。
- 返回参数列表:返回参数列表中的每个参数由返回的参数名称和参数类型组成,也可简写为返回值类型列表。
- 函数体:函数体指函数的主体代码逻辑,若函数有返回参数列表,则函数体中必须有return语句返回值列表。
使用标准格式定义一个名为add的函数,其功能是进行两个整型数字的加法,并返回结果。
1 | func add(x int,y int) (sum int) { |
函数参数简写
在参数列表中,如果相邻的变量为同类型,则不必重复写出类型。
1 | func add(x,y int) (sum int) { |
函数返回值简写
如果函数的返回值都是同一类型,在返回值列表中可将返回参数省略。
1 | func returnValue() (int, int) { |
带有变量名的函数返回值
使用带有变量名的返回值时,返回默认值为类型的默认值,函数结束处直接调用return即可。
1 | func defaultValue() (a int,b string,c bool) { |
如果return后跟返回值列表也是允许的:
1 | func defaultValue() (a int,b string,c bool) { |
定义函数后,可通过对函数的调用使用函数,函数体内的代码逻辑执行完毕后,程序将继续执行被调用函数后的代码。
1 | package main |
以上程序中,我们一般将传入函数的变量i和q称为实参,将函数中的x和y称为形参。变量i和q通过值传递的方式将值赋给形参x和y。
注意:
- addSub函数中的形参x和y作用域仅限于函数体内。
- main函数中定义的变量add和sub与addSub函数中定义的局部变量sum和sub完全无关,函数体内定义的变量作用域仅限于函数体内。
- 若不想接收函数的某个返回值,可用匿名变量“_”,但是不能所有返回值都用匿名变量代替。
函数也是一种类型,我们可以将其保存在变量中。
函数变量的声明格式如下:
1 | var 变量名称 func() |
函数变量的声明和初始化:
1 | package main |
函数变量f1声明后其值初始化为nil,在将addSub函数赋值给f1后,所有对f1的调用即为对addSub函数的调用。
函数变量也可用短变量格式进行声明和初始化:
1 | package main |
Go语言支持可变参数的特性,即函数声明时可以没有固定数量的参数。
可变参数的函数格式如下:
1 | func 函数名 (固定参数列表,v ...T ) (返回参数列表) { |
- 可变参数一般放在函数参数列表的末尾,也可不存在固定参数列表。
- “v …T”代表的其实就是变量v为T类型的切片,v和T之间为三个“.”。
add函数只能对两个int类型的数进行加法计算,现在我们可以使用可变参数实现任意个int类型数的加法运算,最后返回加法结果。
1 | package main |
使用可变参数的函数体中,常常会使用for循环来对切片中的项进行操作。
Go语言中许多内置函数的参数都用了可变参数,比如最常用的fmt包中的Println函数和Printf
函数。
fmt包中的Println函数源码如下:
1 | func Println(a ...interface{}) (n int, err error) { |
Printf函数源码如下,第一个参数指定了需要打印的格式:
1 | func Printf(format string, a ...interface{}) (n int, err error) { |
可变参数本质上是一个切片,如果要在多个函数中传递可变参数,可在传递时添加“…”。
1 | package main |
如果想传递可变参数本身,可将addall函数的可变参数改为切片:
1 | package main |
匿名函数即在需要函数时定义函数,匿名函数能以变量方式传递,它常常被用于实现闭包。
匿名函数的格式如下:
1 | func (参数列表) (返回参数列表) { |
匿名函数的调用有两种方式:
- 定义并同时调用匿名函数。
- 将匿名函数赋值给变量。
可以在匿名函数后添加“()”直接传入实参:
1 | package main |
将匿名函数赋值给一个变量,之后再进行调用:
1 | package main |
闭包就是包含了自由变量的匿名函数,其中的自由变量即使已经脱离了原有的自由变量环境也不会被删除,在闭包的作用域内可继续使用这个自由变量,同一个匿名函数和不同的引用环境组成了不同的闭包。
闭包就如同有“记忆力”一般,可对作用域内的变量的引用进行修改。
闭包的“记忆力”
1 | # 例1 |
匿名函数由于在函数体内部引用了外部的自由变量num而形成了闭包。闭包每次对num变量的加1操作都是对变量num引用的修改。
1 | # 例2 |
addOne函数返回了一个闭包函数,通过定义a1和a2变量,创建了两个闭包的实例(引用环境不同导致)。
每次调用闭包实例,i的值都会在原有的基础上加1。从打印的结果可以看到,两个闭包实例的地址完全不同,两个闭包的调用结果互不影响。
Go语言中存在一种延迟执行的语句,由defer关键字标识,格式如下:
1 | defer 任意语句 |
defer后的语句不会被马上执行,在defer所属的函数即将返回时,函数体中的所有defer语句将会按出现的顺序被逆序执行,即函数体中的最后一个defer语句最先被执行。
1 | package main |
由于defer语句是在当前函数即将返回时被调用,所以defer常常被用来释放资源。
1 | package main |
由于127.0.0.1对应的远端IP的80端口处于开启状态,在本机与该地址建立连接后和函数退出前,需要对打开的文件描述符conn进行关闭。
小结
- Go语言中,函数的声明以关键字func为标识。
- Go语言支持可变参数的特性,即函数声明时可以没有固定数量的参数。
- 匿名函数即在需要函数时定义函数,匿名函数能以变量方式传递,它常常被用于实现闭包。
- 闭包就是包含了自由变量的匿名函数,其中的自由变量即使已经脱离了原有的自由变量环境也不会被删除,在闭包的作用域内可继续使用这个自由变量。
- defer语句是在当前函数即将返回时被调用,所以defer常常被用来释放资源。
两个基本概念: 值传递
和引用传递
- 值传递:将变量的一个副本传递给函数,函数中不管如何操作该变量副本,都不会改变原变量的值。
- 引用传递:将变量的内存地址传递给函数,函数中操作变量时会找到保存在该地址的变量,对其进行操作,会改变原变量的值。
Go语言函数传入参数时使用的始终是值传递,对于值传递,Go语言主要分为以下两种情况:
- 对于int、string和bool等值类型变量,传递的是原变量的副本,对副本的操作不会影响原变量。
- 对于指针、切片、map和channel(通道)引用类型变量,传递的是原变量指针的一份副本,该副本指向了原变量地址,因此对该副本的操作会影响原变量,从而达到了其他编程语言中类似于引用传递的效果。