# day03 函数func、指针pointer

# 内容回顾

复合数据类型 -- 想到 --> 基础数据类型(字符串、整型、浮点型、布尔型)

学习路线

什么是(定义) -> 使用 --> 注意事项

# 数组

数组 [3]int、[5]string、[...]bool

var array1 [3]int  // [0 0 0]
var array2 = [3]int{1, 2, 3}

默认值 --> 对应类型的零值

  • [3]string --> ""(fmt.Println()[]) --> fmt.Printf("%#v", v) [""]
  • [100]int32

长度固定的

不怎么常用

数据的遍历

  • 索引(下标)

  • for range

# 切片

切片:[]int、[]string、[]bool

  • 切片是一个Go语言基于数组封装的类型,它底层是一个数组。
  • 大力出奇迹教育公司的例子
var slice1 []int  // nil
var slice []int{1, 2, 3}
  • 切片表达式(得到一个切片的方法)

    • 简短切片表达式 s[low:high]

    • 完整切片表达式 s[low:high:max]

  • make初始化

  • 切片的空值是nil

  • 扩容策略

  • 遍历切片

  • copy

    • 由切片x得到切片y(直接赋值y = x、切片再切片y = x[:]),不想修改原来的切片
    • copy(dst, src) // 返回拷贝成功的元素数量
    • 复制之前一定要把dst切片,准备好空间(len)make([]int, 10)
  • 不支持负数索引, 取最后一个元素要写:s[len(s)-1]

  • append函数

    • 向切片中追加元素
    • 必须接收值 --> 可能会触发切片的扩容 --> 保证切片正常需要再次赋值 slice1 = append(slice1, 1)
var slice3 []int
slice3 = make([]int, 0, 1000000)
slice3 = append(slice3, 1)
slice3 = append(slice3, 1, 1, 1)
  • 切片中删除元素, 目前没有内置方法,可以自己用append实现

# map

键值对类型的数据,(类似于其他语言中的hash map 、dict等等)

key-value

map: map[string]int

  • 增删改查
var map1 map[string]int  // map初始化之后才能用
map1 = make(map1, 8)
map1["jade"] = 300  // 存值
v := map1["ddd"]  // 取值
v,ok := map1["ddd"] // 取值
_,ok := map1["ddd"] // 判断key存不存在于map中
delete(map1, "ddd")
  • 遍历

# 课后作业

func ex1() {
	// 写一个程序,统计一个字符串中每个单词出现的次数。
	// 比如:”how do you do”中how=1 do=2 you=1。
	s := "how do you do"

	// 1.用map存数据,key是单词,value是单词出现的次数
	// 2.将字符串分成一个一个的单词
	// 3.把上一步得到的单词挨个存放到map里
	// 4.遍历map打印结果

	// 迎刃而解
	var m map[string]int
	m = make(map[string]int)
	s1 := strings.Split(s, " ") // 切片
	for _, v := range s1 {
		// m[v] = 1  // m["do"] = 1
		num := m[v]
		m[v] = num + 1

		// m[v]++

		// if ok{
		// 	m[v] = num+1
		// }else{
		// 	m[v] = 0+1
		// }
	}

	for k, v := range m {
		fmt.Println(k, v)
	}
}
func ex2() {
	// 对数组var a = [...]int{3, 7, 8, 9, 1}进行排序
	var a = [...]int{3, 7, 8, 9, 1}
	s := a[:]
	fmt.Println("before sort:", s)
	// 按什么规则对s排序
	sort.Slice(s, func(i, j int) bool {
		return s[i] < s[j]
	})
	fmt.Println("after sort:", s)
}
func ex3() {
	m := make(map[string][]int) // 声明并初始化了一个map变量m
	s := []int{1, 2}            // 声明一个切片变量s
	s = append(s, 3)            // 向s追加一个元素
	fmt.Printf("%+v\n", s)      // [1 2 3]

	m["q1mi"] = s
	s3 := s

	// 用append从切片删除索引为1的元素 append(s[:idx], s[idx+1]...)
	s = append(s[:1], s[2:]...) // ...表示拆开切片 s = append(s[:1], 3)  [1 3]  s[1] = 3
	// s[:1]     [1]
	// s[2:]...  3
	s[1] = 100  // 修改了底层数组

	fmt.Printf("s:%+v\n", s) // [1 3]

	fmt.Printf(`m["q1mi"]:%+v`+"\n", m["q1mi"]) // [1 3 3]
	fmt.Printf("s3:%+v\n", s3)                  // [1 3 3]

	fmt.Println(len(s), len(m["q1mi"]))
}

image-20220116111715732

# 函数的定义

func bar(x int, y string)(string, bool){
}

# 函数的参数

  • 参数的类型简写 func bar(x, y int)
  • 不定长参数 func bar(x ...int)
  • 没有默认参数

# 函数返回值

  • 多个返回值 func calc(x, y int) (int, int)
  • 命名返回值 func calc(x, y int) (sum, sub int)
  • 如当返回值类型为slice,可以返回nil,当作有效的slice类型

# 函数进阶

# 变量的作用域

  1. 全局变量 没有global直接,函数内部可直接修改
  2. 局部变量
    • 函数内定义的变量
    • 语句块定义的变量
    if v, ok := m["linda"]; ok{
    	fmt.Println(v)
    }
    

只要以type关键字开头的都是定义类型。

type calculation func(int, int) int

func add(x, y int) int {
	return x + y
}

func sub(a, b int) int {
	return a - b
}

func f15() {
	var x calculation
	fmt.Printf("%T\n", x) // main.calculation
	fmt.Println(x == nil) // true
	x = add               // 把add赋值给x
	res := x(10, 20)      // ???
	fmt.Println(res)

	add(10, 20)
}

# 函数签名

  • 函数签名 --> 函数定义(声明)的格式,与参数名称和返回值名称无关
  • 函数签名一样 --> 函数的参数、返回值的类型和个数、顺序都要一样
func fi(name string, age int) {}

func fj(age int, name string) {}

type MyFF func(string, int)

func f16() {
	var mf MyFF
	mf = fi
	// mf = fj  // 函数签名不一致
	mf("ddd", 1)
}

# 高阶函数

高阶函数分为函数作为参数和函数作为返回值两部分

// 函数作为参数
func f17(x, y int, op calculation) int {
	res := op(x, y)
	return res
}

// 命名返回值
// 1.函数内部声明了一个变量res
// 2.返回值是res
func f18(x, y int, s string) (res func(int, int) int) {
	switch s {
	case "+":
		return add
	case "-":
		return sub
	}
	return  // 默认就把res返回
}

func f19(x, y int, s string) func(int, int) int {
	var res func(int, int) int // res = nil
	switch s {
	case "+":
		return add
	case "-":
		return sub
	}
	return res
}

# 匿名函数

// 把匿名函数赋值给变量
func f33() func() {
	f1 := func() {
		fmt.Println("美好的周末就要结束啦~")
	}
	// var f2 func() = func() {
	// 	fmt.Println("美好的周末就要结束啦~")
	// }
	return f1
}

// 匿名函数立即执行
func f34() {
	func() {
		fmt.Println("美好的周末就要结束啦~")
	}()
}

# 闭包

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

// 闭包

func adder() func() int {
	var x int

	// 函数内部使用了它外部函数的变量x
	f := func() int {
		x++
		return x
	}

	// 把匿名函数当成返回值返回了
	return f
}

func adder2() func(int) int {
	var x int
	f := func(y int) int {
		x += y // x = x+y
		return x
	}
	return f
}

// 定义一个累加器
// x的值会被返回的函数一直占用着
func adder3(x int) func(int) int {
	f := func(y int) int {
		x += y // x = x+y
		return x
	}
	return f
}

func makeSuffixFunc(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}
  • 如 f = adder() 得到闭包f其中x一直跟随者f的生命周期,可以联系Python实例化一个对象,对象是有状态

# defer

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

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}
// 先输出 start end,再输出 3 2 1
  • 什么场景会用到defer
    • 释放资源
    • 关闭文件
    • 释放连接
  • defer的执行顺序
    • 先注册的后执行
  • defer的执行时机
    • 返回值赋值之后,底层RET指令之前
  • defer语句不能接收返回值 defer x := sub(10, 2)
  • defer执行时机 return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步

image-20220116184209206

func f2() (x int) {
	defer func() {
		x++
	}()
	return 5
}
// 第一步给返回值赋值,返回值定义了变量 x,所以 x = 5
// 第二步执行 defer语句 func(){x++}() 是一个闭包会影响x的值,所以 x++  --> x = x + 1
// 第三步RET指令 x = 6 返回
  • 注意:defer注册要延迟执行的函数时该函数所有的参数都需要确定其值

# 内置函数

image-20220116184209206

# panic与recover

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		err := recover()
		//如果程序出出现了panic错误,可以通过recover恢复过来
		if err != nil {
			fmt.Println("recover in B")
		}
	}()
	panic("panic in B")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}
  • recover()必须搭配defer使用
  • defer一定要在可能引发panic的语句之前定义

# 指针

ptr := &v // v的类型为T

  • v:代表被取地址的变量,类型为T
  • ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型,*代表指针
func main() {
	a := 10
	b := &a // 取变量a的地址,将指针保存到b中
	fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
	fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
	fmt.Println(&b)                    // 0xc00000e018

	c := *b // 指针取值(根据指针去内存取值)
	fmt.Printf("type of c:%T\n", c)
	fmt.Printf("value of c:%v\n", c)
}

image-20220116184209206

就两个操作:

  1. 取变量x的内存地址: &x 得到的是指针
  2. 有了指针变量p,*p 根据内存地址去找值

总结:取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址&操作,可以获得这个变量的指针变量
  • 指针变量的值是指针地址
  • 对指针变量进行取值*操作,可以获得指针变量指向的原变量的值
// 指针传值示例
func modify1(x int) {
	x = 100
}

func modify2(x *int) {
	*x = 100
}

func main() {
	a := 10
	modify1(a)
	fmt.Println(a) // 10
	modify2(&a)
	fmt.Println(a) // 100
}

# 内置函数 new与make

// 引发 panic
func main() {
	var a *int
	*a = 100
	fmt.Println(*a)

	var b map[string]int
	b["分数"] = 100
	fmt.Println(b)
}
  • Go语言中对于引用类型的变量,在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储

# 内置函数 new

// 源码 a := new(int) 
func new(Type) *Type
  • new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值
func main() {
	a := new(int)
	b := new(bool)
	fmt.Printf("%T\n", a) // *int
	fmt.Printf("%T\n", b) // *bool
	fmt.Println(*a)       // 0
	fmt.Println(*b)       // false
}

# 内置函数 make

// make 源码
func make(t Type, size ...IntegerType) Type

make也是用于内存分配的,区别于new,它只用于slice、map以及channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针

# new与make小结

  • 二者都是用来做内存分配的;
  • make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
  • 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针

# 课后作业

/*
你有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)
}
上次更新: 9/10/2022, 5:57:39 PM