值为切片的map
var sliceM1 = make(map[string][]string, 4) sliceM1["names"] = make([]string, 4) sliceM1["names"] = append(sliceM1["names"], "小王","小黄","小李") fmt.Println(sliceM1) 复制代码
练习题
- 写一个程序,统计一个字符串中每个单词出现的次数。比如:”how do you do”中how=1 do=2 you=1。
// 我的解法 var words string = "how do you do" var wordsSlice []string = strings.Split(words, " ") var wordCountMap map[string]int = make(map[string]int) for _, word := range wordsSlice{ var _, isIn = wordCountMap[word] if isIn { wordCountMap[word]++ } else { wordCountMap[word] = 1 } } var printMsg = "在"how do you do"中" for key,val := range wordCountMap { str:= key + "=" + strconv.Itoa(val) + ";" printMsg += str } fmt.Print(printMsg) 复制代码
- 观察下面代码,写出最终的打印结果。
type Map map[string][]int m := make(Map) //[] s := []int{1,2} // [1 2] len 2 cap 2 x001 s = append(s,3) [1 2 3] len 3 cap 4 x002 fmt.Printf("%+v \n", s) //[1 2 3] len3 cap4 x002 m["q1mi"] = s // [q1mi: [1 2 3] len3 cap4 x002] s = append(s[:1], s[2:]...) //[1 3] len 2 cap4 x002 fmt.Printf("%+v\n",s) //len2 cap4 x002 [1 3] fmt.Printf("%+v\n",m["q1mi"])// len3 cap4 //[1 3 3] x002 [1 2 3] [1 3] [1 3 3] 复制代码
函数
Go语言中支持函数、匿名函数和闭包。
函数定义
格式如下:
func 函数名(参数)(返回值){ 函数体 } 复制代码
举个🌰
func main(){ fmt.Println(intSum(1,2)) //3 } func intSum(x int, y int)(int) { return x + y } 复制代码
参数
类型简写
函数参数中相邻变量的类型相同,则可以省略类型,举个🌰
func intSum(x,y int) int { return x + y } 复制代码
可变参数
指函数的参数数量不固定,通过在参数名后面加...
来表示。
注意:可变参数通常要作为函数的最后一个参数,举个🌰
func intSum(x ...int) int { sum := 0 for _,v := range x { x += sum } return sum } 复制代码
也可以与固定参数搭配使用
func intSum(x int, y ...int) int { sum := x for _,v := range x { x += sum } return sum } 复制代码
本质上,函数的可变参数是通过切片来实现的。
返回值
Go语言通过return
关键字向外输出返回值,这一点与JavaScript相同,但也有不同点。
多个返回值
func calc(x, y int) (int, int) { sum := x + y sub := x - y return sub, sum } 复制代码
返回值命名
函数在定义时可以给返回值命名,并在函数体重使用这些变量,最后用return
返回
func calc(x, y int) (sub, sum int) { sum = x + y sub = x - y return } 复制代码
返回值补充
当函数返回值类型为slice
是,nil
可以看做是一个有效的slice
,没必要返回一个长度为0的切片。
func someFunc(x string) []int { if x == "" { return nil } else { res := make([]int,4) return res } } 复制代码
变量作用域
全局变量
全局变量指的是定义在函数外部的变量,程序运行期间一直都可以有效访问,与JavaScript的全局变量类似。
var commonNum int = 100 // 全局变量 commonNum func main() { fmt.Println("num:", num) //100 } func testCommonVar() { fmt.Println("num": num) //100 } 复制代码
局部变量
如果全局变量与局部变量重名,那么优先访问局部变量。
var num = 1 func main() { num := 10 fmt.Println(num) // 10 } 复制代码
语句块定义的变量(类似JavaScript中的块级作用域),比如for
, if
等,举个🌰
func testLocalVar(x, y int) { fmt.Println(x,y)// 函数的参数也只在函数内生效 if x>0 { z := 10 fmt.Println(z)// z只能在此处生效 } fmt.Println(z) //不生效 } 复制代码
函数类型与变量
定义函数类型
可以使用type
关键字定义一个函数的类型,格式如下:
type calculation func(int, int) int 复制代码
上面语句定义了一个calculation
类型,是一种函数类型,这种函数接受两个int类型的参数并返回一个int类型的返回值。
也就是说,凡是满足这个条件的函数,都是calculation
类型的函数,举个🌰
func add(x, y int) int { return x + y } func sub(x, y int) int { return x - y } 复制代码
add和sub都能赋值给calculation类型的变量。
var c calculation c = add c(3,4) //7 复制代码
函数类型变量
可以声明函数类型的变量并且为该变量赋值
var c calculation c = add fmt.Printf("type of c: %T\n", c) // main.calculation fmt.Println(c(1,2)) f := add fmt.Printf("type of f: %T\n", f) // func(int, int) int fmt.Println(c(2,3)) 复制代码
高阶函数
高阶函数分为函数作为参数
和函数作为返回值
两部分
函数作为参数
func add(x, y int) int { return x+y } func calc(x, y int, callback func(int, int) int) int { return callback(10, 20) } func main() { res := calc(10, 20, add) fmt.Println(res) //30 } 复制代码
函数作为返回值
func do(s string) (func(int, int) int, error) { switch s { case "+": return add, nil case "-": return sub, nil default: err := errors.New("无法识别的操作符") return nil, err } } 复制代码
匿名函数和闭包
匿名函数
格式如下:
func(参数)(返回值){ 函数体 } 复制代码
匿名函数因为没有函数名,所以不能直接调用,需要保存到某个变量里,或者作为立即执行函数。
add := func(x, y) int { return x + y } add(10, 20) func(x, y) { fmt.Println(x + y) }(1, 2) 复制代码
匿名函数多用于实现回调函数
或者闭包
闭包
闭包值得是一个函数与其相关的引用环境组合而成的实体。简单来说就是:闭包=函数+引用环境
,举个🌰
func add() func(int) int { var x int return func(y int) int { x += y return x } } f := add() fmt.Println(f(10))//10 fmt.Println(f(20))//30 f1 := add() fmt.Println(f1(20))//20 fmt.Println(f1(40))//60 复制代码
与JavaScript相似,函数体内的变量如果被函数体以外的环境引用,就会形成闭包,该变量不会销毁。
闭包示例2:
func makeSuffixFunc(suffix string) func(string) string { return func(fileName string) string { if !strings.HasSuffix(fileName, suffix) { return fileName + suffix } return fileName } } 复制代码
闭包示例3:
func calc(base int) (func(int) int, func(int) int) { add := func(num int) int { base += num return base } sub := func(num int) int { base -= num return base } return add, sub } add, sub := calc(10) fmt.Println(add(1)) // 11 fmt.Println(add(2)) // 9 复制代码
defer语句
defer
语句会将其后面跟孙的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说,先defer
的后执行,后defer
的先执行,举个🌰
fmt.Println("start") defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) fmt.Println(4) fmt.Println("end") // start 4 end 3 2 1 复制代码
多用于处理资源释放问题,例如:资源清理
,文件关闭
,解锁
,记录时间
等。
defer的执行时机
返回值=x --> 运行defer --> ret指令
牢记defer的三条规则
- 延迟函数的参数在defer语句出现的时候就已经确定下来了
- 后进先出的规则
- 延迟函数可能会影响主函数的具名返回值
defer案例
// 主函数拥有匿名返回值,返回变量 //一个主函数拥有一个匿名的返回值,返回使用本地或全局变量,这种情况下defer语句可以引用到返回值,但不会改变返回值。 // 返回5 func f1() int { x := 5 defer func() { x++ }() return x } // 主函数拥有具名返回值 // 主函声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果defer语句操作该返回值,可能会改变返回结果。 // x =5 // x++ // return func f2() (x int) { defer func() { x++ }() return 5 } // 主函数拥有具名返回值 // x = 5 // y = x // x++ // return func f3() (y int) { x := 5 defer func() { x++ }() return x } // 主函数拥有具名返回值,但是延迟函数的参数从defer开始就确定了,相当于拷贝一份,所以后面的x++是自执行函数x的++ func f4() (x int) { defer func(x int) { x++ }(x) return 5 } func main() { fmt.Println(f1()) // 5 fmt.Println(f2()) // 6 fmt.Println(f3()) // 5 fmt.Println(f4()) // 5 } 复制代码
defer面试题
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { x := 1 y := 2 defer calc("AA", x, calc("A", x, y)) x = 10 defer calc("BB", x, calc("B", x, y)) y = 20 } // A 1 2 3 // B 10 2 12 // BB 10 12 22 // AA 1 3 4 复制代码
内置函数
函数名 | 介绍 |
close | 用来关闭channel |
len | 用来求长度,比如string array slice map channel |
new | 用来分配内存,主要用来分配值类型,比如int, struct。返回的是指针 |
make | 用来分配内存,主要用来分配引用类型,比如chan map slice |
append | 用来追加元素到数组中、slice中 |
panic和recover | 用来做错误处理 |
panic/recover
panic用来处理错误,可以在任何地方引发,但recover
只有在defer
调用的函数中有效,举个🌰
func funcA() { fmt.Println("func A") } func funcB() { panic("func B error") } func funcC() { fmt.Println("func C") } func main() { funcA() funcB() funC() } 复制代码
这时候程序就会崩溃,因为funcB
中引发了panic
,但是,我们可以通过recover
让程序起死回生!只需要对funcB做一下修改举个🌰
func funcB() { defer func(){ err := recover() if err != nil { fmt.Println("func B") } }() panic("func B error") } 复制代码
注意
recover
必须搭配defer
使用defer
一定要再可能发panic
的语句之前定义
练习题
- 分金币
/* 你有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) } // 我的解法 func dispatchCoin() int { coinSum := coins for _,person := range users{ distribution[person] = 0 nameLower := strings.ToLower(person) nameSlice := strings.Split(nameLower, "") for _,nameWord := range nameSlice { switch nameWord { case "e": distribution[person] += 1 coinSum-=1 case "i": distribution[person] += 2 coinSum-=2 case "o": distribution[person] += 3 coinSum-=3 case "u": distribution[person] += 4 coinSum-=4 default: break } } } for key,value := range distribution{ fmt.Printf("%v分到了:%v个\n",key, value) } return coinSum }