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. 带形参的函数```gopackage mainimport "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 mainimport "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. 一个返回值```gopackage mainimport "fmt"func f1(n ...int) int {var res intfor _, v := range n {res += v}return res}func main() {fmt.Println(f1(100, 200, 300, 400, 500))}
1.2.5. 多个返回值
当需要返回多个值的时候,可以放到一个序列中,比如数组或者切片中,也可以返回多个值!
package mainimport "fmt"func f1(n int) (int, int) {var (ret1 int = 0ret2 int = 1)for i := 1; i <= n; i++ {ret1 += iret2 *= i}return ret1, ret2}func f2(n int) [2]int {var (ret1 int = 0ret2 int = 1)for i := 1; i <= n; i++ {ret1 += iret2 *= 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.gof1: ret1:120 ret2:1307674368000f2: 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。```gopackage mainimport "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.gos0: type:string value:hello world! addr:0xc0000461f0s: 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 mainimport "fmt"func f0(s *string) {fmt.Printf("s: type:%T value:%v addr:%p\n", s, s, &s)}func main() {s0 := "hello world!"s1 := &s0fmt.Printf("s1: type:%T value:%v addr:%p\n", s1, s1, &s1)f0(s1)}
[root@heyingsheng src]# go run studygo/day03/func14/main.gos1: type:*string value:0xc0000461f0 addr:0xc000006028s: type:*string value:0xc0000461f0 addr:0xc000006038
1.3.3. 案例
package mainimport "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.goHELLO WORLD!
2. 变量的作用域
Go语言中变量的作用域分为三种,全局变量、函数内的局部变量、代码块中的局部变量。变量的检索方式与Python类似,都是从当前代码向上寻找,就近匹配!
package mainimport "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.goif 语句块内部: "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. 匿名函数赋值给变量```gopackage mainimport "fmt"func main() {// 定义匿名函数并赋值给变量add := func (n1, n2 int) int {return n1 + n2}res := add(100, 200)fmt.Println("res=",res) // 300res = add(300,400)fmt.Println("res=",res) // 700}
3.3. 全局匿名函数
package mainimport "fmt"// 定义匿名函数并赋值给变量, 几乎不用var add = func (n1, n2 int) int {return n1 + n2}func main() {fmt.Printf("%T %v\n", add, add) // func(int, int) int 0x49fb10res := add(100, 200)fmt.Println("res=",res) // 300res = add(300,400)fmt.Println("res=",res) // 700}
4. 小练习
package mainimport ("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 = 50users = [...]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")) * 4distribution[name] = retcoins -= 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剩下: 10map[string]int{"Aaron":3, "Adriano":5, "Augustus":12, "Elizabeth":4, "Emilie":6, "Giana":2, "Heidi":5, "Matthew":1, "Peter":2, "Sarah":0}
