Go基础3

Source

目录

一.map

1.1 声明map

1.2 map的遍历

1.2.1 只遍历key

1.2.2 只遍历value

1.2.3 按照指定的顺序遍历map

1.3  delete()删除键值对

1.4 元素为map类型的切片

1.5 值为切片类型的map

1.6 练习

二.函数

2.1 函数的定义

2.2 函数的基本使用

2.3 defer语句

2.3.1 defer的执行时机

2.3.2 defer面试题

2.4 函数中变量的作用域

2.4.1 全局变量

2.4.2 局部变量

2.5 函数作为参数和返回值

2.6 匿名函数和闭包

2.6.1 匿名函数

2.6.2 闭包

2.7 内置函数

2.7.1 panic和recover

2.8 练习

2.9 递归

三. 自定义类型和类型别名

3.1 自定义类型

3.2 类型别名


一.map

1.1 声明map

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

Go语言中 map的定义语法如下:

map[KeyType]ValueType
  • KeyType:表示键的类型。

  • ValueType:表示键对应的值的类型。

map类型的变量默认初始值为nil,需要使用make()函数来分配内存。

make(map[KeyType]ValueType, cap)

cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。

package main

import "fmt"

func main() {
	m1 := make(map[string]int, 10)
	m1["hpl"], m1["laowang"] = 18, 35
	fmt.Println(m1)

	fmt.Println(m1["hpl"]) // 取出键对应的值

	// 如果键不存在,将返回0
	// fmt.Println(m1["xxx"]) // 0(不推荐这种写法)

	value, ok := m1["xxx"] // 如果xxx键存在,value就是键对应的值,ok就睡返回true;如果xxx键不存在,value就返回0,ok就会返回false
	fmt.Println(value, ok)
	if !ok {
		fmt.Println("查无此key")
	} else {
		fmt.Println(value)
	}

}

map[hpl:18 laowang:35]
18
0 false
查无此key

map也支持在声明的时候填充元素

package main

import "fmt"

func main() {
	// map也支持声明的时候填充元素
	user := map[string]string{
		"name1": "hpl",
		"name2": "laowang",
		"name3": "laoxu",
		"name4": "pangzi",
	}
	fmt.Println(user)
}

map[name1:hpl name2:laowang name3:laoxu name4:pangzi]

1.2 map的遍历

Go语言中使用for range遍历map比较方便。

package main

import "fmt"

func main() {
	// map也支持声明的时候填充元素
	user := map[string]string{
		"name1": "hpl",
		"name2": "laowang",
		"name3": "laoxu",
		"name4": "pangzi",
	}
	fmt.Println(user)

	for key, value := range user {
		fmt.Println(key, value)
	}
}

map[name1:hpl name2:laowang name3:laoxu name4:pangzi]
name1 hpl
name2 laowang
name3 laoxu
name4 pangzi

1.2.1 只遍历key

package main

import "fmt"

func main() {
	// map也支持声明的时候填充元素
	user := map[string]string{
		"name1": "hpl",
		"name2": "laowang",
		"name3": "laoxu",
		"name4": "pangzi",
	}
	fmt.Println(user)

	// 如果只想遍历key
	for key := range user {
		fmt.Println(key)
	}
}

map[name1:hpl name2:laowang name3:laoxu name4:pangzi]
name1
name2
name3
name4

1.2.2 只遍历value

package main

import "fmt"

func main() {
	// map也支持声明的时候填充元素
	user := map[string]string{
		"name1": "hpl",
		"name2": "laowang",
		"name3": "laoxu",
		"name4": "pangzi",
	}
	fmt.Println(user)

	// 只想遍历value
	for _, value := range user {
		fmt.Println(value)
	}
}

map[name1:hpl name2:laowang name3:laoxu name4:pangzi]
hpl
laowang
laoxu
pangzi

1.2.3 按照指定的顺序遍历map

package main

import (
	"fmt"
	"sort"
)

func main() {
	map1 := map[int]string{
		2505180213: "小狗13",
		2505180215: "小狗15",
		2505180210: "小狗10",
		2505180214: "小狗14",
		2505180212: "小狗12",
		2505180211: "小狗11",
	}

	// 取出map1中所有的key
	s1 := make([]int, 0, 100)
	for key := range map1 {
		s1 = append(s1, key)
	}
	fmt.Println(s1)

	// 对s1进行排序
	sort.Ints(s1)
	fmt.Println(s1)

	// 按照排序后的key遍历map1
	for _, value := range s1 {
		fmt.Println(value, map1[value])
	}
}

[2505180214 2505180212 2505180211 2505180213 2505180215 2505180210]
[2505180210 2505180211 2505180212 2505180213 2505180214 2505180215]
2505180210 小狗10
2505180211 小狗11
2505180212 小狗12
2505180213 小狗13
2505180214 小狗14
2505180215 小狗15

注意: 遍历map时的元素顺序与添加键值对的顺序无关。

1.3  delete()删除键值对

使用delete()内建函数从map中删除一组键值对

delete(map, key)
  • map:表示要删除键值对的map

  • key:表示要删除的键值对的键

package main

import "fmt"

func main() {
	// map也支持声明的时候填充元素
	user := map[string]string{
		"name1": "hpl",
		"name2": "laowang",
		"name3": "laoxu",
		"name4": "pangzi",
	}
	fmt.Println(user)

	// 删除
	delete(user, "name4") // 删除指定的键值对
	fmt.Println(user)

	delete(user, "xxx") // 删除不存在的键值对,什么都不做,也不会报错
	fmt.Println(user)
}

map[name1:hpl name2:laowang name3:laoxu name4:pangzi]
map[name1:hpl name2:laowang name3:laoxu]
map[name1:hpl name2:laowang name3:laoxu]

1.4 元素为map类型的切片

package main

import "fmt"

func main() {
	mapslice := make([]map[string]string, 10)
	fmt.Println(mapslice) // [map[] map[] map[] map[] map[] map[] map[] map[] map[] map[]]

	mapslice[0] = make(map[string]string, 10)
	mapslice[0]["hpl"] = "靓仔"
	mapslice[0]["laowang"] = "haha"
	mapslice[0]["pangzi"] = "xixi"
	fmt.Println(mapslice)
}

[map[] map[] map[] map[] map[] map[] map[] map[] map[] map[]]
[map[hpl:靓仔 laowang:haha pangzi:xixi] map[] map[] map[] map[] map[] map[] map[] map[] map[]]

1.5 值为切片类型的map

package main

import (
	"fmt"
)

func main() {
	sliceMap := make(map[string][]string, 10)
	fmt.Println(sliceMap) // map[]

	key := "hpl"
	value, ok := sliceMap[key]
	if !ok {
		value = make([]string, 0, 2)
	}
	value = append(value, "开心", "难过")
	sliceMap[key] = value
	fmt.Println(sliceMap)

}

map[]
map[hpl:[开心 难过]]

1.6 练习

1.6.1 写一个程序,统计字符串中每个单词出现的次数。比如:”how do you do”中how:1 do:2 you:1。

package main

import (
	"fmt"
	"strings"
)

func main() {
	str1 := "how do you do"
	str2 := strings.Split(str1, " ")
	m1 := make(map[string]int, 10)
	for _, value := range str2 {
		if _, ok := m1[value]; !ok {
			m1[value] = 1
		} else {
			m1[value]++
		}
	}
	fmt.Println(m1) // map[do:2 how:1 you:1]
}

map[do:2 how:1 you:1]

1.6.2 观察下面代码,写出最终的打印结果。

func main() {
	type Map map[string][]int
	m := make(Map)
	s := []int{1, 2}
	s = append(s, 3)
	fmt.Printf("%+v\n", s)
	m["xxx"] = s
	s = append(s[:1], s[2:]...)
	fmt.Printf("%+v\n", s)
	fmt.Printf("%+v\n", m["xxx"])
}

[1 2 3]
[1 3]
[1 3 3]

1.6.3 回文判断

就是从左往右读和从右往左读都是一样的就是回文,例:上海自来水来自海上。

package main

import "fmt"

func main() {
	// 回文判断(就是从左往右读和从右往左读都是一样的就是回文)
	// 例:上海自来水来自海上
	s1 := "上海自来水来自海上"
	runeType := []rune(s1)
	count := len(runeType)
	for i := 0; i < count/2; i++ {
		if runeType[i] != runeType[count-1-i] {
			fmt.Println("s1不是回文字符串")
			return
		}
	}
	fmt.Println("s1是回文字符串")
}
     
s1是回文字符串

二.函数

函数是组织好的、可重复使用的、用于执行指定任务的代码块。Go语言中支持函数、匿名函数和闭包。

2.1 函数的定义

Go语言中定义函数使用func关键字,具体格式如下:

func 函数名(参数)(返回值){
    函数体
}
  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名。

  • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。

  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。

  • 函数体:实现指定功能的代码块。

注意:函数的参数和返回值都是可选的。

2.2 函数的基本使用

package main

import "fmt"

// 函数的基本使用
func f1() {
	fmt.Println("这是函数f1")
}

func f2() int {
	res := 6 + 7
	return res
}

// 多个返回值
func f3() (int, int) {
	return 2, 3
}

// 函数的定义
func sum1(x int, y int) (ret int) {
	ret = x + y
	return
}

// 函数的定义变种
func sum2(x int, y int) int {
	ret := x + y
	return ret
}

// 变种
func sum3(x, y int) (ret int) {
	ret = x + y
	return
}

// 变种
func f4(x, y, z int, e, r, t string) int {
	res := x - y
	return res
}

// 可变长参数
// 可变长的参数必须写在参数的最后
func f5(x string, y ...int) { // y就是可以传递你多个值,但是值都是int类型
	fmt.Println(x)
	// y是一个切片,...后面是什么类型的,就是什么类型的切片
	fmt.Println(y)
}

func main() {
	fmt.Println("这是函数f1")

	fmt.Println(f2())

	_, res3 := f3()
	fmt.Println(res3)

	res1 := sum1(3, 4)
	fmt.Println(res1)

	res2 := sum2(5, 6)
	fmt.Println(res2)

	res4 := sum3(34, 56)
	fmt.Println(res4)

	f5("我喜欢你")
	f5("我喜欢你", 1, 2, 3, 4)

}

这是函数f1
13
3
7
11
90
我喜欢你
[]
我喜欢你
[1 2 3 4]

注意:在一个命名函数中不能再声明命名函数

2.3 defer语句

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

package main

import "fmt"

func f1() {
	fmt.Println("111111111")
	defer fmt.Println("222222222") // defer会被延时执行,延时到函数即将返回时再被执行
	defer fmt.Println("333333333")
	fmt.Println("444444444")
}

func main() {
	f1()
}

111111111
444444444
333333333
222222222

由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

2.3.1 defer的执行时机

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和return指令两步。而defer语句执行的时机就在返回值赋值操作后,return指令执行前。

package main

import "fmt"

func f1() int {
	x := 5
	defer func() {
		x++
	}()
	return x
}

func f2() (x int) {
	defer func() {
		x++
	}()
	return 5
}

func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x
}

func f4() (x int) {
	defer func(x int) {
		x++
	}(x)
	return 5
}

func main() {
	fmt.Println(f1())
	fmt.Println(f2())
	fmt.Println(f3())
	fmt.Println(f4())
}

5
6
5
5

2.3.2 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

2.4 函数中变量的作用域

2.4.1 全局变量

全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。

package main

import "fmt"

//定义全局变量num
var num int64 = 10

func testGlobalVar() {
	fmt.Printf("num=%d\n", num) //函数中可以访问全局变量num
}
func main() {
	testGlobalVar() //num=10
}

2.4.2 局部变量

局部变量又分为两种: 函数内定义的变量无法在该函数外使用,例如下面的示例代码main函数中无法使用test函数中定义的变量x:

func test() {
	//定义一个函数局部变量x,仅在该函数内生效
	var x int64 = 100
	fmt.Printf("x=%d\n", x)
}

func main() {
	test()
	fmt.Println(x) // 此时无法使用变量x
}

如果局部变量和全局变量重名,优先访问局部变量。

package main

import "fmt"

//定义全局变量num
var num int64 = 10

func testNum() {
	num := 100
	fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量
}
func main() {
	testNum() // num=100
}

通常我们会在if条件判断、for循环、switch语句上使用这种定义变量的方式。

func testLocalVar2(x, y int) {
	fmt.Println(x, y) //函数的参数也是只在本函数中生效
	if x > 0 {
		z := 100 //变量z只在if语句块生效
		fmt.Println(z)
	}
	//fmt.Println(z)//此处无法使用变量z
}

还有之前学过的for循环语句中定义的变量,也是只在for语句块中生效:

func test() {
	for i := 0; i < 10; i++ {
		fmt.Println(i) //变量i只在当前for语句块中生效
	}
	//fmt.Println(i) //此处无法使用变量i
}

2.5 函数作为参数和返回值

函数中函数可以作为参数,也可以作为返回值。

package main

import (
	"fmt"
	"reflect"
)

// 函数类型
func f1() {
	fmt.Println("陕西省汉中市")
}

func f2() int {
	return 23
}

func f3(x func() int) int {  // 函数作为参数
	return 66
}

func f4(x func() int) func() int {  // 函数作为参数和返回值
	return f2
}

func ff(x, y int) int {
	return 44
}

func f5(x func(int) int) func(int, int) int {  // 函数作为参数和返回值
	return ff
}

func main() {
	a := f1
	fmt.Println(reflect.TypeOf(a)) // func()

	b := f2
	fmt.Println(reflect.TypeOf(b)) // func() int

	c := f3
	fmt.Println(reflect.TypeOf(c)) // func(func() int) int

	d := f4
	fmt.Println(reflect.TypeOf(d)) // func(func() int) func() int

	e := f5
	fmt.Println(reflect.TypeOf(e)) // func(func(int) int) func(int, int) int

}

2.6 匿名函数和闭包

2.6.1 匿名函数

func(参数)(返回值){
    函数体
}

匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数:

func main() {
	// 将匿名函数保存到变量
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(10, 20) // 通过变量调用匿名函数

	//自执行函数:匿名函数定义完加()直接执行
	func(x, y int) {
		fmt.Println(x + y)
	}(10, 20)
}

匿名函数多用于实现回调函数和闭包。

2.6.2 闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder()
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.Println(f(30)) //60

	f1 := adder()
	fmt.Println(f1(40)) //40
	fmt.Println(f1(50)) //90
}

变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也一直有效。 闭包进阶示例1:

func adder2(x int) func(int) int {
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder2(10)
	fmt.Println(f(10)) //20
	fmt.Println(f(20)) //40
	fmt.Println(f(30)) //70

	f1 := adder2(20)
	fmt.Println(f1(40)) //60
	fmt.Println(f1(50)) //110
}

闭包进阶示例2:

func makeSuffixFunc(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

func main() {
	jpgFunc := makeSuffixFunc(".jpg")
	txtFunc := makeSuffixFunc(".txt")
	fmt.Println(jpgFunc("test")) //test.jpg
	fmt.Println(txtFunc("test")) //test.txt
}

闭包进阶示例3:

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		base += i
		return base
	}

	sub := func(i int) int {
		base -= i
		return base
	}
	return add, sub
}

func main() {
	f1, f2 := calc(10)
	fmt.Println(f1(1), f2(2)) //11 9
	fmt.Println(f1(3), f2(4)) //12 8
	fmt.Println(f1(5), f2(6)) //13 7
}

2.7 内置函数

内置函数 介绍
close 主要用来关闭channel
len 用来求长度,比如string、array、slice、map、channel
new 用来分配内存,主要用来分配值类型,比如int、struct、string。返回的是指针
make 用来分配内存,主要用来分配引用类型,比如channel、map、slice
append 用来追加元素到数组、slice中
panic和recover 用来做错误处理

2.7.1 panic和recover

Go语言中目前(Go1.18.2)是没有异常机制,但是使用panic/recover模式来处理错误。 panic可以在任何地方引发,但recover只有在defer调用的函数中有效。

package main

import "fmt"

func f1() {
	fmt.Println("这是f1函数")
}

func f2() {
	panic("f2函数中出现了一个严重的错误")
	fmt.Println("这是f2函数")
}

func f3() {
	fmt.Println("这是f3函数")
}

func main() {
	f1()
	f2()
	f3()
}

这是f1函数
panic: f2函数中出现了一个严重的错误

goroutine 1 [running]:
main.f2(...)
        D:/projects/GoProjects/day5/错误处理.go:10
main.main()
        D:/projects/GoProjects/day5/错误处理.go:20 +0x66
exit status 2

程序运行期间f2中引发了panic导致程序崩溃,异常退出了。这个时候我们就可以通过recover将程序恢复回来,继续往后执行。

package main

import "fmt"

func f1() {
	fmt.Println("这是f1函数")
}

func f2() {
	defer func() {
		err := recover()
		// 如果程序出现了panic错误,可以通过recover恢复过来
		if err != nil {
			fmt.Println("recover in f2")
		}
	}()
	panic("f2函数中出现了一个严重的错误")
	fmt.Println("这是f2函数")
}

func f3() {
	fmt.Println("这是f3函数")
}

func main() {
	f1()
	f2()
	f3()
}

这是f1函数
recover in f2
这是f3函数

注意:

  1. recover()必须搭配defer使用。

  2. defer一定要在可能引发panic的语句之前定义。

2.8 练习

1. 分金币

/*
你有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"
	"strings"
)

var (
	coins = 50
	users = []string{
		"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
	}
	distribution = make(map[string]int, len(users))
)

// 计算给定字符串中某个字符出现的个数,返回应得的金币数量
func byteNum(str1 string) (num int) {
	str1 = strings.ToLower(str1) // 将字符串中所有的字符小写
	for _, v := range str1 {
		switch v {
		case 'e':
			num++
		case 'i':
			num += 2
		case 'o':
			num += 3
		case 'u':
			num += 4
		}
	}
	return
}

// 计算每个人应分得数量以及总的花费数量
func dispatchCoin() (map[string]int, int) {
	var count int
	for _, value := range users {
		num := byteNum(value)
		count += num
		distribution[value] = num
	}
	return distribution, count
}

func main() {
	left, count := dispatchCoin()
	fmt.Println("每个人应分得:", left)
	fmt.Println("剩下:", coins-count)
}

每个人应分得: map[Aaron:3 Adriano:5 Augustus:12 Elizabeth:4 Emilie:6 Giana:2 Heidi:5 Matthew:1 Peter:2 Sarah:0]
剩下: 10

2.9 递归

递归就是自己调用自己

1.计算阶乘

package main

import "fmt"

// 计算阶乘(方法1)
func factorial(num int) (ret int) {
	ret = 1
	for i := 1; i <= num; i++ {
		ret *= i
	}
	return
}

// 计算阶乘(方法2)
func factorial2(num int) int {
	if num < 1 {
		return 1
	}
	return num * factorial2(num-1)
}

func main() {
	// 计算N的阶乘
	fmt.Println(factorial(4))
	fmt.Println(factorial2(5))

}

2.有N个台阶,一次可以走一步,也可以走两步,一共有多少种走法?

package main

import "fmt"

func fMethods(num int) int {
	if num < 1 {
		return 0
	} else if num == 1 { // 如果只有一个台阶就只有一种走法
		return 1
	} else if num == 2 { // 如果只有两个台阶就只有两种走法
		return 2
	}
	return fMethods(num-1) + fMethods(num-2)
}

func main() {
	// 上台阶问题
	// 有N个台阶,一次可以走一步,也可以走两步,一共有多少种走法?
	fmt.Println(fMethods(3))
}

三. 自定义类型和类型别名

3.1 自定义类型

package main

import (
	"fmt"
	"reflect"
)

// type后面跟的是类型
type myInt int

func main() {
	var num myInt
	num = 50
	fmt.Println(num, reflect.TypeOf(num))

}

50 main.myInt

注:type后面跟的是类型。

3.2 类型别名

package main

import (
	"fmt"
	"reflect"
)

// type后面跟的是类型
type myInt int     // 自定义类型
type yourInt = int // 类型别名

func main() {
	var num myInt
	num = 50
	fmt.Println(num, reflect.TypeOf(num))

	var num2 yourInt
	num2 = 34
	fmt.Println(num2, reflect.TypeOf(num2))

}

50 main.myInt
34 int