函数基础
一 函数介绍
1.1 什么是函数
函数就是盛放一组代码块的”容器”,把实现某个特定功能一组代码块组织到一个函数内,相当于制作了一个工具,可以重复调用
1.2 为何要用函数
不同于Python,在go语言中,非声明语句只能在函数内,比如fmt.Println("啊p啊")写到函数外则会报语法错误。
syntax error: non-declaration statement outside function body
也就是说go强制我们把功能代码组织到函数内,而使用函数至少解决了以下3种问题
(1)代码的组织结构不清晰,可读性差
(2)遇到重复的功能只能重复编写实现代码,代码冗余
(3)功能需要扩展时,需要找出所有实现该功能的地方修改之,无法统一管理且维护难度极大
1.3 函数分类
#1、内置函数
为了方便我们的开发,针对一些简单的功能,go已经为我们定义好了的函数即内置函数。对于内置函数,我们可以拿来就用而无需事先定义,例如len、cap、append、make等
#2、自定义函数
很明显内置函数所能提供的功能是有限的,这就需要我们自己根据需求,事先定制好我们自己的函数来实现某种功能,以后,在遇到应用场景时,调用自定义的函数即可。
此外,我们早已知晓,go程序中必须自定义一个main函数,main函数是整个程序的入口
Go语言中还支持匿名函数和闭包,并且函数在Go语言中属于“一等公民”。
二 定义与调用
1.1 定义
语法
func 函数名(参数1 类型,参数2 类型,...)(返回参数1 类型,返回参数2 类型,...){
函数体
}
解释:
- 1、函数名:函数名要能反映其意义,命名规范同变量名,同一个包内不能重复
- 2、参数与返回值都是可选的
- 2.1 参数由参数名和类型组成,可以有多个,用逗号分隔开即可
- 2.2 返回值由参数名和类型组成,可以只写返回值的类型,也可以有多个,但在定义函数时,如果有多个返回值,则必须用逗号分隔开并且用()包裹
- 3、函数体:具体的功能实现
定义函数的三种形式
1、无参
func test() {
fmt.Println("老妹真白")
}
2、有参
// 例1
func add(x int, y int) {
fmt.Println(x + y)
}
// 例2
func add(x int,y int) int{
return x + y
}
3、空函数
// 可以声明参数但不能声明返回值,函数体为空即可
func sayHi(name string) {
}
注意:在查看源码时,我们偶尔会遇到不仅没有函数体、甚至连花括号都没有的函数声明,这表示该函数不是以Go实现的,如下
package math
func Sin(x float64) float // 采用汇编语实现
1.2 调用
调用函数语法如下
函数名(值1,值2,值3,...)
因为在go中非声明语句必须在函数内,所以函数调用这种非声明语句必须在某一函数内,具体来说,调用函数也有三种形式
1、语句形式:test()
// 定义函数
func test() {
fmt.Println("老妹真白")
}
// 调用函数
func main() {
test()
}
2、表达式形式:3*add(1,2),本质是在利用函数的返回值
// 定义函数
func add(x int, y int) int {
return x + y
}
// 调用函数
func main() {
res := 3 * add(1, 2)
fmt.Println(res)
}
3、当做参数传给另外一个函数,本质也是在利用函数的返回值
// 定义函数
func add(x int, y int) int {
return x + y
}
// 调用函数
func main() {
res := 3 * add(1, add(2,3))
fmt.Println(res)
}
强调:在调用函数时,小括号换行需在末尾加逗号
与大括号{}一样,调用函数的小括弧()也可以另起一行缩进,但必须在末尾显式加逗号,这是Go编译器的一个特性,用于防止编译器在行尾自动插入分号而导致的编译错误
package main
import "fmt"
func main() {
var x, y = 1, 2
// 错误写法
fmt.Println(x,
y
)
// 改进:
fmt.Println(x,
y, // 默认加逗号
) // 小括弧另起一行缩进,和大括弧的风格保持一致
}
三 参数
参数分为形参与实参,实参指的是调用函数时传入的值(相当于变量值),形参指的是定义函数是指定参数(相当于变量名),调用函数时,会将实参值赋值给形参
强调:
1、每一次函数调用都必须按照声明顺序为所有形参数提供实参(参数值)。
2、在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。
3.1 类型简写
形参中若相邻变量的类型相同,则可以省略类型
func add(x,y int) int{
return x + y
}
3.2 可变长参数
可变长指的就是调用函数时,传入的实参值个数不固定,我们可以在形参名or其类型前加…,该形参就可以用来接收溢出的实参值,并把它们存成一个切片,称之为可变长参数,本质上,函数的可变参数是通过切片来实现的。
例
func add(x ...int) int {
fmt.Println(x) // x是一个切片
sum := 0
for _, v := range x {
sum += v
}
return sum
}
调用
func main() {
res1 := add(1, 2, 3)
res2 := add(1, 2, 3, 4, 5)
res3 := add(1, 2, 3, 4, 5, 6)
fmt.Println(res1, res2, res3) // 6 15 21
}
注意:固定参数搭配可变参数使用时,可变参数要放在固定参数的后面
func add(x int,y ...int) int {
fmt.Println(x,y)
sum := 0
for _, v := range y {
sum += v
}
return sum
}
四 返回值
返回值是函数的运行结果,函数体一旦执行到return就会立即结束并且返回值
强调:
如果一个函数在声明时,包含返回值列表,该函数必须以 return语句结尾,除非函数明显无法运行到结尾处。例如函数在结尾时调用了panic异常或函数中存在无限循环。
4.1 多个返回值
Go语言中函数支持多返回值,函数如果需要有多个返回值,我们在定义函数的返回值时,必须用()
将所有返回值包裹起来。
调用多返回值函数时,返回给调用者的是一组值,调用者必须显式的将这些值分配给变量:
func divmod(x, y int) (int, int, int) {
res1 := x / y
res2 := x % y
res3 := x + y
return res1, res2, res3
}
func main() {
m, n, _ := divmod(1, 2)
fmt.Println(m, n)
}
4.2 命名返回值
返回值也可以像形参一样被命名,有名返回值与函数的形参会被当作函数最外层的局部变量,
func add(x int, y int) (z int) {
// 相当于在函数内最开头/最外层声明了变量
//var x int
//var y int
//var z int
}
例1
// 声明了返回值,名字为res1、res2,在函数内直接使用即可,无需声明
func divmod(x, y int) (res1,res2 int) {
fmt.Println(res1,res2)
return
}
func main() {
divmod(1,2) // 0,0
}
例2:
func divmod(x, y int) (res1,res2 int) {
res1 = x / y // 此处用的是赋值操作,而非声明res1的操作,因为已经声明过了
res2 = x % y
return // 无需跟返回值,默认返回res1与res2
}
func main() {
fmt.Println(divmod(10,3)) // 3 1
}
4.3 返回空
若函数返回值类型为slice,想要返回空切片,直接返回nil即可,没必要显示返回一个长度为0的切片,也就是说nil可以看做是一个有效的slice。
func someFunc(x string) []int {
if x == "" {
return nil // 没必要返回[]int{}
}
...
}
了解:
许多标准库中的函数返回2个值,一个是期望得到的函数处理结果,另一个是函数出错时的错误信息
func findLinks() ([]int, error) {
return 正确结果,nil
// return nil, 错误对象
}
五 练习题
- 分金币
/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (
coins = 50
users = []string{
"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
}
distribution = make(map[string]int, len(users))
)
func main() {
left := dispatchCoin()
fmt.Println("剩下:", left)
}
答案
package main
import "fmt"
var (
coins = 50
users = []string{
"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
}
distribution = make(map[string]int, len(users))
)
func dispatchCoin() int {
for _, name := range users {
for _, c := range name {
switch c {
case 'e', 'E':
distribution[name]++
coins--
case 'i', 'I':
distribution[name] += 2
coins -= 2
case 'o', 'O':
distribution[name] += 3
coins -= 3
case 'u', 'U':
distribution[name] += 4
coins -= 4
}
}
}
return coins
}
func main() {
left := dispatchCoin()
fmt.Println("剩下:", left)
fmt.Println(distribution)
}