本文摘自 《Go 语言 从入门到进阶实战》一书,作者 徐波
1. 普通函数声明方式
Go语言的函数声明以func标识,后面紧接着 函数名
、 参数列表
、返回参数列表
、函数体
,具体形式如下:
func 函数名(参数列表) (返回参数列表) {
函数体
}
- 参数列表:一个参数由参数变量和参数类型组成,例如
func foo (a int, b string)
其中,参数列表中的变量作为函数的局部变量而存在 - 返回参数列表:可以是返回值类型列表,也可以是类似参数列表中变量名和类型的组合。函数在声明有返回值时,必须在函数中使用return语句提供返回值列表。
- 函数体: 能够被重复调用的代码片段。
2. 参数类型的简写
在参数列表中,如有多个参数变量,则以,
分隔开;如果相邻变量是同类型,则可以将类型省略,例如:
func add(a, b int) int {
return a + b
}
3. 函数的返回值
Go语言支持多返回值,多返回值能方便地获得函数执行后的多个返回参数,Go语言经常使用多返回值中的最后一个返回参数返回函数执行中可能发生的错误
,示例如下:
conn, err := connectToNetwork()
这段代码中,connectToNetwork返回两个参数,conn表示连接对象,err返回错误。
1.同一类型返回值
如果返回类型是同一种类型,则用括号将多个返回值类型括起来,用逗号分隔每个返回值的类型。
使用return语句 返回时,值列表顺序需要与函数声明的返回值类型一致。示例代码如下:
package main
import "fmt"
func typedTwoValues() (int, int) {
return 1, 2
}
func main() {
a, b := typedTwoValues()
fmt.Println(a, b)
}
纯类型的返回值对于代码可读性不是很友好,特别是在同类型的返回值出现时,无法区分每个返回参数的意义
2. 带有变量名的返回值
Go语言支持对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型。命名的返回值变量默认值为类型的默认值,即数值为0,字符串为空字符串,布尔类型为false,指针为nil等。
下面代码中的函数拥有两个整型返回值,函数声名时将返回值命名为a和b,因此可以在函数体中直接对函数返回值赋值。在命名的返回值方式的函数体中,在函数结束前需要使用return语句进行返回,代码如下:
package main
import "fmt"
func typedTwoValues() (a, b int) {
a = 1
b = 2
return
}
func main() {
fmt.Println(typedTwoValues())
}
//当函数使用命名返回值时,可以在return中不填写返回值列表,如果填写也是可行的,示例如下:
package main
import "fmt"
func typedTwoValues() (a, b int) {
a = 1
return a, 3
}
func main() {
fmt.Println(typedTwoValues())
}
4. 调用函数
函数在定义后,可以通过调用的方式,让当前代码跳转到被调用的函数中进行执行,调用前的函数局部变量都会保存起来不会丢失;被调用的函数结束后,恢复到被调用函数的下一行继续执行代码,之前的局部变量也能继续访问。
函数的局部变量只能在函数体中使用,函数调用结束后,这些局部变量都会被释放且失效。
Go语言的函数调用格式如下:
返回值变量列表 = 函数名(参数列表)
- 函数名: 需要调用的函数名
- 参数列表: 参数变量以逗号隔开,尾部无需以分号结尾
- 返回值变量列表:多个返回值使用逗号隔开
例如,加法函数调用样式如下
result := add(1, 2)
5. 示例:将“秒”解析为时间单位
在本例中,使用一个数值表示时间中的“秒”值,然后使用resolveTime()函数将传入的秒数转换为天、 小时、 和分钟等时间单位
package main
import "fmt"
const (
//定义每分钟的秒数
SecondsPerMinute = 60
//定义每小时的藐视
SecondsPerHour = SecondsPerMinute * 60
//定义每天的秒数
SecondsPerDay = SecondsPerHour * 24
)
func resolveTime(seconds int) (day, hour, mintue int) {
day = seconds / SecondsPerDay
hour = seconds / SecondsPerHour
mintue = seconds / SecondsPerMinute
return
}
func main() {
//将返回值作为参数打印
fmt.Println(resolveTime(20000))
//只获取小时和分钟
_, hour, mintue := resolveTime(20000)
fmt.Println(hour, mintue)
//只获取天
day, _, _ := resolveTime(300000)
fmt.Println(day)
}
//定义每小时的秒数,SecondsPerHour 常量值会在编译期间计算出结果。
6. 示例:函数中的参数传递效果测试
Go 语言中传入和返回参数在调用和返回时都使用值传递,这里需要注意的是指针、切uy片和map等引用型对象指向的内容在参数传递中不会发生复制,而是将指针进行复制,类似于创建一次引用,Go语言参数值传递示例如下:
1.测试数据类型
2.值传递的测试函数
在本节定义的passByValue()函数用于值传递测试,该函数的参数和返回值都是Data类型,在调用中,Data 的内存会被复制后传入函数,当函数返回时,又会将返回值复制一次,赋给函数返回值的接收变量。代码示例如下:
3. 测试流程
测试流程会准备一个Data格式的数据结构并填充所有成员,这些成员类型包括切片,结构体成员以及指针。通过调用测试函数,传入 Data 结构数据,并获得返回值,对比输入和输出后的Data 的结构数值变化,特别是指针变化情况以及输入和输出整块数据是否被复制,代码如下:
以上示例整体代码如下:
package main
import "fmt"
//用于测试值传递的结构体
type Data struct {
complax []int //测试切片在参数传递中的效果
instance InnerData //实例分配的InnerData
ptr *InnerData //将ptr声名为InnerData 的指针类型
}
//代表各种结构体字段
type InnerData struct {
a int
}
func passByValue(inFunc Data) Data {
//输出参数的成员情况
fmt.Printf("inFunc value: %+v\n", inFunc)
//打印inFunc的指针
fmt.Printf("inFunc ptr: %p\n", &inFunc)
return inFunc
}
func main() {
//测试流程
//准备传入函数的结构体
in := Data {
complax: []int{1, 2, 3},
instance: InnerData{
5,
},
ptr: &InnerData{1},
}
//输入结构的成员情况
fmt.Printf("in value:%+v\n",in)
//输入结构的指针地址
fmt.Printf("in ptr: %p\n", &in)
//传入结构体,返回同类型的结构体
out := passByValue(in)
//输出结构的成员情况
fmt.Printf("out value:%+v\n",out)
//输出结构的指针地址
fmt.Printf("out ptr: %p\n", &out)
}
返回结果如下
in value:{complax:[1 2 3] instance:{a:5} ptr:0xc00006c008}
in ptr: 0xc000066180
inFunc value: {complax:[1 2 3] instance:{a:5} ptr:0xc00006c008}
inFunc ptr: 0xc000066210
out value:{complax:[1 2 3] instance:{a:5} ptr:0xc00006c008}
out ptr: 0xc0000661e0