1. 函数的定义
Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。与Python不同,Go语言的函数定义格式变种比较多,且对形参和返回值要求严格,需要指定其类型,因此产生了很多不同形式的变种!所有语言中函数的都是三要素构成,分别是 函数名称、参数、返回值!
1.1. 函数的通用格式
func funcName(args)(res) {
funcBody
}
- func: 函数声明的关键词,类似于Python中def
- funcName: 函数名称,由字母、数字、下划线组成,不以数字开头,通常采用驼峰结构。在同一个包中函数名称不能重复。main函数为程序的入口函数。
- args: 形参,可选项。支持一个或者多个参数,也支持不定长参数,但是不支持默认参数。参数需要指定数据类型
- res: 返回值,可选项。支持一个或者多个返回值,需要指定返回值类型。
- funcBody: 函数体,即函数内部代码块。
1.2. 函数的各种形式
1.2.1. 不带参数和返回值
```go package main
import “fmt”
func f1() { fmt.Println(100 + 200) }
func main() { f1() }
<a name="Kpz3r"></a>
#### 1.2.1. 带形参的函数
```go
package main
import "fmt"
func f1(a int, b int, c string) { // 位置实参类型与形参不一致会报错
fmt.Println(c, "=", a+b)
}
func main() {
f1(100, 200, "100+200")
}
1.2.2. 形参简写
package main
import "fmt"
func f1(a, b int, c string) { // 参数a 省略类型表示与后一个参数类型一致
fmt.Println(c, "=", a+b)
}
func main() {
f1(100, 200, "100+200")
}
1.2.3. 不定长传参
- 不定长传参的变量需要作为最后的形参
- 不定长参数接收参数后,转换为切片,不能与前一个形参采用简写形式
- 不定长传参可以接收0个实参 ```go package main
import “fmt”
func f1(a int, b …int) { // 不能用 a, b …int, 因为b为切片 fmt.Printf(“a:%#v\tb:%#v\n”, a, b) // var res int for _, v := range b { res += v } fmt.Println(res) }
func main() { f1(100, 200, 300, 400, 500) f1(1000) }
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\02-define_func>go run main.go a:100 b:[]int{200, 300, 400, 500} 1400 a:1000 b:[]int(nil) 0
<a name="ntzmy"></a>
#### 1.2.4. 一个返回值
```go
package main
import "fmt"
func f1(n ...int) int {
var res int
for _, v := range n {
res += v
}
return res
}
func main() {
fmt.Println(f1(100, 200, 300, 400, 500))
}
1.2.5. 多个返回值
当需要返回多个值的时候,可以放到一个序列中,比如数组或者切片中,也可以返回多个值!
package main
import "fmt"
func f1(n int) (int, int) {
var (
ret1 int = 0
ret2 int = 1
)
for i := 1; i <= n; i++ {
ret1 += i
ret2 *= i
}
return ret1, ret2
}
func f2(n int) [2]int {
var (
ret1 int = 0
ret2 int = 1
)
for i := 1; i <= n; i++ {
ret1 += i
ret2 *= i
}
return [2]int{ret1, ret2}
}
func main() {
ret1, ret2 := f1(15)
ret3 := f2(15)
fmt.Printf("f1: ret1:%d\tret2:%d\n", ret1, ret2)
fmt.Printf("f2: res:%#v\n", ret3)
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\02-define_func>go run main.go
f1: ret1:120 ret2:1307674368000
f2: res:[2]int{120, 1307674368000}
1.2.6. 命名返回值
当在指定返回值的变量名称时,即完成了对该变量声明,如对数字来说,初始值为0,这个可能会导致计算结果异常!
- 命名返回值会自动完成变量的声明
- 必须要使用return语句,否则语法错误。可以仅仅使用一个return
- 命名返回值可以采用简写
- 不支持命名返回值和不命名返回值混用 ```go package main
import “fmt”
func f1(n int) (ret1 int, ret2 int) { ret2 = 1 // ret2 初始值为0,会导致计算异常 for i := 1; i <= n; i++ { ret1 += i ret2 *= i } return // 等同于 return ret1,ret2 }
func f2(n int) (ret1, ret2 int) { ret2 = 1 // ret2 初始值为0,会导致计算异常 for i := 1; i <= n; i++ { ret1 += i ret2 *= i } return ret1, ret2 }
func main() { ret1, ret2 := f1(15) ret3, ret4 := f2(15) fmt.Printf(“f1: ret1:%d\tret2:%d\n”, ret1, ret2) fmt.Printf(“f2: ret3:%d\tret4:%d\n”, ret3, ret4) }
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\02-define_func>go run main.go f1: ret1:120 ret2:1307674368000 f2: ret3:120 ret4:1307674368000
<a name="XVUgm"></a>
### 1.3. 参数类型
在Go中,无论传递的参数是值类型还是引用类型([参考](https://www.yuque.com/duduniao/go/icbfqw#rn0H4)),都是拷贝一份参数的值到函数内部,但是在使用中,引用类型和值类型的变量是有区别的,引用类型只是拷贝一个内存地址,资源开销小,较为常用。
<a name="pmzfM"></a>
#### 1.3.1. 值类型的参数
函数内部接收到的参数 s 和 传递的s0 是两个值相同,但是内存地址不同的变量。因此,修改s并不会影响s0。
```go
package main
import "fmt"
func f0(s string) {
fmt.Printf("s: type:%T value:%v addr:%p\n", s, s, &s)
}
func main() {
s0 := "hello world!"
fmt.Printf("s0: type:%T value:%v addr:%p\n", s0, s0, &s0)
f0(s0)
}
[root@heyingsheng src]# go run studygo/day03/func14/main.go
s0: type:string value:hello world! addr:0xc0000461f0
s: type:string value:hello world! addr:0xc000046220
1.3.2. 引用类型参数
下面案例中,s1为引用类型,它的内存地址为 0xc000006028
,存储的是s0的地址 0xc0000461f0
。函数f0中,s的内存地址为 0xc000006038
,存储的也是s0的地址 0xc0000461f0
。
通过这个案例可以发现,引用类型作为参数时,函数参数接收的也是实参的副本,只不过s1和函数内s指向的都是s0,因此可以通过s去修改s0的值罢了。
package main
import "fmt"
func f0(s *string) {
fmt.Printf("s: type:%T value:%v addr:%p\n", s, s, &s)
}
func main() {
s0 := "hello world!"
s1 := &s0
fmt.Printf("s1: type:%T value:%v addr:%p\n", s1, s1, &s1)
f0(s1)
}
[root@heyingsheng src]# go run studygo/day03/func14/main.go
s1: type:*string value:0xc0000461f0 addr:0xc000006028
s: type:*string value:0xc0000461f0 addr:0xc000006038
1.3.3. 案例
package main
import "fmt"
func f0(s *string) {
*s = "HELLO WORLD!"
}
func main() {
s0 := "hello world!"
f0(&s0) // 修改s0的值
fmt.Println(s0)
}
[root@heyingsheng src]# go run studygo/day03/func14/main.go
HELLO WORLD!
2. 变量的作用域
Go语言中变量的作用域分为三种,全局变量、函数内的局部变量、代码块中的局部变量。变量的检索方式与Python类似,都是从当前代码向上寻找,就近匹配!
package main
import "fmt"
var (
a string = "global a"
b string = "global b"
c string = "global c"
)
func main() {
a := "func a"
b := "func b"
if a := "if a"; true {
fmt.Printf("if 语句块内部: %#v\t%#v\t%#v\n", a, b, c)
}
fmt.Printf("main 函数内部: %#v\t%#v\t%#v\n", a, b, c)
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\03-vars>go run main.go
if 语句块内部: "if a" "func b" "global c"
main 函数内部: "func a" "func b" "global c"
3. 匿名函数
匿名函数就是在定义的时候没有指定名字的函数,在Go语言中,函内部是无法使用func关键词定义新的命名函数,如果需要定义则必须使用匿名函数的方式。匿名函数的使用方式有两种:
import “fmt”
func main() { // 定义匿名函数并直接使用 res := func (n1, n2 int) int { return n1 + n2 }(100,200)
fmt.Println("res=",res) // 300
}
<a name="o9pf1"></a>
### 3.2. 匿名函数赋值给变量
```go
package main
import "fmt"
func main() {
// 定义匿名函数并赋值给变量
add := func (n1, n2 int) int {
return n1 + n2
}
res := add(100, 200)
fmt.Println("res=",res) // 300
res = add(300,400)
fmt.Println("res=",res) // 700
}
3.3. 全局匿名函数
package main
import "fmt"
// 定义匿名函数并赋值给变量, 几乎不用
var add = func (n1, n2 int) int {
return n1 + n2
}
func main() {
fmt.Printf("%T %v\n", add, add) // func(int, int) int 0x49fb10
res := add(100, 200)
fmt.Println("res=",res) // 300
res = add(300,400)
fmt.Println("res=",res) // 700
}
4. 小练习
package main
import (
"fmt"
"strings"
)
/*
你有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枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
*/
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{
ret := strings.Count(name, "e") + strings.Count(name, "E") +
(strings.Count(name, "i")+ strings.Count(name, "I")) * 2 +
(strings.Count(name, "o")+ strings.Count(name, "O")) * 3 +
(strings.Count(name, "u")+ strings.Count(name, "U")) * 4
distribution[name] = ret
coins -= ret
}
return coins
}
//func dispatchCoin() int {
// for _, name := range users{
// distribution[name] = 0
// for _, char := range name { // 循环遍历
// switch char {
// case 'e','E':
// distribution[name] += 1
// coins -= 1
// 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.Printf("%#v\n", distribution)
}
[root@heyingsheng day03]# go run 10-homework/main.go
剩下: 10
map[string]int{"Aaron":3, "Adriano":5, "Augustus":12, "Elizabeth":4, "Emilie":6, "Giana":2, "Heidi":5, "Matthew":1, "Peter":2, "Sarah":0}