Go 基础之函数与defer

Go 4

运算符

算术运算符

+-*\

逻辑运算符

&&,||,!

位运算符

>>,<<,|,^,&

赋值运算符

=,+=...

++,--是独立的语句,不属于赋值运算符

比较运算符

数组

var arg [30]int

数组包含元素的类型和元素的个数。元素的个数(数组的长度)属于数据类型的一部分。

数组是值类型。

初始化的三种方式:

func main() {
	var name string
	name = "hina"
	fmt.Println(name)
	var ages [30]int // 声明了一个变量ages,它是[30]int类型,还未初始化
	fmt.Println(ages)

	ages = [30]int{1, 3, 4, 5} // 方式一
	fmt.Println(ages)

	var ages2 = [...]int{1, 2, 3, 4} // 方式二
	fmt.Println(ages2)

	var age3 = [...]int{1: 100, 99: 200} // 方式三
	fmt.Println(age3)

	// 二维数组
	// var a1 [3][2]int // [[1 2] [3 4] [5 6]]
	var a1 = [...][2]int{
		{1, 2},
		{3, 4},
		{5, 6},
	}
	fmt.Println(a1)
	// 多维数组只有最外层可以使用

	// 数组是值类型
	x := [...]int{1, 2, 3}
	y := x     // 把x的值拷贝了一份给了y
	y[1] = 200 // 修改的是拷贝y,并不影响x

	fmt.Println(x)
	f1(x)
	fmt.Println(x) // ?[1,2,3]
}

func f1(a [3]int) {
	// Go语言中的函数传递的都是拷贝的值(副本)
	a[1] = 100 // 此处修改的是副本的值
}

切片

切片的定义

切片的本质:是对底层数组的封装和引用。(指针(第一个数据在底层的内存地址)、长度、容量)

	var s1 []int        // 没有分配内存 == nil,需要初始化
	s1 = []int{1, 2, 3} // 初始化方式一
	fmt.Println(s1)

	// make初始化,分配内存
	s2 := make([]bool, 2, 4) // 初始化方式二
	fmt.Println(s2)

	// 切片的本质是对底层数组的封装和引用。(指针(第一个数据在底层的内存地址)、长度、容量)

	s1 := []int{1, 2, 3} // [1 2 3]
	s2 := s1
	fmt.Println(s2) // [1 2 3]
	s1[1] = 200
	fmt.Println(s1)
	fmt.Println(s2)

切片的扩容策略:

  1. 如果申请的容量大于原来的2倍,那么直接扩容至新申请的容量。
  2. 如果小于1024,那么就直接翻倍
  3. 如果大于1024,那么就按照原容量的1.25倍去扩容
  4. 具体存储的值类型不同,,扩容策略也有一定的不同。

append函数

	var s1 []int
	// s1 = make([]int, 1, 2)
	// s1[0] = 10
	// fmt.Println(s1)
	s1 = append(s1, 1) // 会自动初始化,并扩容
	fmt.Println(s1)

copy

	s1 := []int{1, 2, 3} // [1 2 3]
	s2 := s1
	// var s3 []int  // nil
	var s3 = make([]int, 3, 3)
	copy(s3, s1)
	fmt.Println(s2) // [1 2 3]
	s1[1] = 200
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(s3)

指针

只需要记住两个符号:

&,*

func main() {
	// Go中的指针只能读不能修改指针变量的内存地址
	addr := "SH"
	addrp := &addr

	fmt.Println(addrp) // addr的内存地址
	fmt.Printf("%T\n", addrp)
	fmt.Println(*addrp) // 根据内存地址找值

}

map

map存储的是键值对的数据,也是需要申请内存的。

func main() {

	var m1 map[string]int
	m1 = make(map[string]int, 10)
	m1["hina"] = 111
	fmt.Println(m1)
	// fmt.Println(m1["ji"]) // 如果key不存在取不到返回该类型的默认值
	// 默认用这种方法
	score, ok := m1["ji"]
	if !ok {
		fmt.Println("查无此人")
	} else {
		fmt.Println(score)
	}

	delete(m1, "xxx") // 删除的key不存在则do nothing
	delete(m1, "hina")
	fmt.Println(m1)
	fmt.Println(m1 == nil) // 已经开辟了内存地址
}

homework

func main() {
	// 1.判断字符串中汉字的数量
	// 如何判断一个字符是汉字
	count := 0
	s1 := "HINA雨送黄昏花易落"
	// 1.依次拿到字符串中的字符
	for _, v := range s1 {
		// 2.判断当前这个字符是不是汉字
		if unicode.Is(unicode.Han, v) {

			// 3.把汉字出现的次数累加
			count++
		}
	}
	fmt.Println(count)

	// 2.how do you do 单词出现的次数
	s2 := "how do you do"
	dict := make(map[string]int, 10)
	// 2.1 把字符串按照空格切割得到切片
	s3 := strings.Split(s2, " ")
	fmt.Println(s3)
	// 2.2遍历切片存储到一个map
	for _, v := range s3 {
		//法一:
		// c, ok := dict[v]
		// if !ok {
		// 	dict[v] = 1
		// } else {
		// 	dict[v] = c + 1
		// }

		// 法二:
		// if _, ok := dict[v]; !ok {
		// 	dict[v] = 1
		// } else {
		// 	dict[v]++
		// }

		// 法三:
		dict[v]++

	}
	// 2.3累加出现的次数
	fmt.Println(dict)
}

判断回文

func main() {
	// 回文判断:字符串从左往右读和从右往左读是一样的,那么就是回文。
	// 上海自来水来自海上
	s1 := "abc上海自来水来自海上cba"

	// 方法一将字符串放到切片中进行遍历
	// s2 := strings.Split(s1, "")
	// fmt.Println(s2)
	// for i := range s2 {
	// 	if s2[i] != s2[len(s2)-1-i] {
	// 		fmt.Println("不是回文")
	// 		return
	// 	}
	// }
	// fmt.Println("是回文")

	// 方式二:构建函数使用字符串反转
	// res := reserveStr(s1)
	// if res == s1 {
	// 	fmt.Println("是回文")
	// } else {
	// 	fmt.Println("不是回文")
	// }

	// 方式三:把字符串中字符拿出来放到[]rune类型
	r := []rune(s1)
	fmt.Println(r)
	for i := 0; i < len(r)/2; i++ {
		if r[i] != r[len(r)-i-1] {
			fmt.Println("不是回文")
			return
		}
	}
	fmt.Println("是回文")

}

// 字符串反转
func reserveStr(s string) string {
	runes := []rune(s)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}

函数

package main

import "fmt"

// 函数

func f1() {
	fmt.Println("hina")
}

func f2(name string) {
	fmt.Println(name, "dasuki")
}

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

func main() {
	f2("hina")
	fmt.Println(f3(100, 200))
	fmt.Println(f4("hina"))
	fmt.Println(f5(3, 3))
	fmt.Println(f6(2, 5))
}

// 可变参数
func f4(name string, y ...int) int {
	fmt.Println(y) // y是一个int类型的切片
	return 1
}

func f5(x, y int) (sum int) {
	sum = x + y // 如果使用声明过的变量则用=,未声明的用 :=
	return
}

// Go中支持多个返回值

func f6(x, y int) (a, b, c int) {
	a = x + y
	b = x - y
	c = x * y
	return
}

defer以及return含义

package main

import "fmt"

// defer: 会将后面的语句延迟到函数即将返回的时候再执行并且是逆序的

func deferDemo() {
	fmt.Println("start")
	defer fmt.Println("hina")
	defer fmt.Println("lem")
	fmt.Println("end")
}

// Go语言中函数的return不是原子性操作,在底层是分为两步来执行
// 第一步:返回值赋值
// 第二步:真正的RET返回
// 函数中如果存在defer,那么defer执行的时间是在第一步和第二部之间

func f1() int {
	x := 5
	defer func() {
		x++ // 修改的是x不是返回值
	}()
	return x // 此时ret=x=5,后面再对x进行更改不影响返回值
}

func f2() (x int) { // 返回值指向x对应的值
	defer func() {
		x++
	}()
	return 5 // 先赋值x=5,在执行x++,最后将x返回
}

func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x // y=5,defer中未对y操作,最终返回5
}
func f4() (x int) {
	defer func(x int) {
		x++
	}(x) // 函数传参改的是副本,不是原来内存对应的值
	return 5 // x=5
}

func f6() (x int) {
	defer func(x *int) {
		(*x)++ // 修改的是x对应内存地址的值,所有x变为6
	}(&x)
	return 5 // 1.ret=x=5  2.defer x=6 3.return x
}

func main() {
	fmt.Println(f1()) // 5
	fmt.Println(f2()) // 6
	fmt.Println(f3()) // 5
	fmt.Println(f4()) // 5
    fmt.Println(f6()) // 6
	// 结合内存地址来看,若返回值时切片则defer中修改x可能会修改到返回值
}

作用域

func main() {	f1()	// if或for中定义的语句块作用域	if i := 10; i < 19 {		fmt.Println("hina")	}}

函数类型和变量

package main

import "fmt"

// 函数类型

func f1() {
	fmt.Println("hina")
}

func f2() int {
	return 2
}

// 函数也可以作为参数的类型
func f3(x func() int) string {
	ret := x()
	fmt.Println(ret)
	return "xxx"
}

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

// 函数还可以作为返回值
func f5(x func() int) func(int, int) int {

	return f4
}

func main() {
	a := f1
	fmt.Printf("%T\n", a)

	b := f2
	fmt.Printf("%T\n", b)

	fmt.Println(f3(f2))

	c := f5(f2)
	fmt.Printf("%T\n", c)
}

函数的定义

基本格式
参数的格式

有参数的函数

参数类型简写

可变参数

返回值的格式

有返回值

多返回值

明明返回值

变量作用域
  1. 全局作用域
  2. 函数作用域
  3. 代码块作用域
高阶函数

函数也是一种类型,它可以作为参数,也可以作为返回值

函数类型

函数也是一种类型

匿名函数

没有名字的函数

// 匿名函数var f1 = func(x, y int) {	fmt.Println(x + y)}func main() {	// 函数内部没有办法声明带名字的函数	// 匿名函数	f1 := func(x, y int) {		fmt.Println(x + y)	}	f1(10, 20)	// 如果只是调用一次的函数,还可以简写成立即执行函数	func(x, y int) {		fmt.Println(x, y)		fmt.Println("hina rui")	}(100, 200)}

闭包

package main

import "fmt"

// 什么是闭包?闭包=函数+外部变量的引用
// 闭包是一个函数,这个函数包含了它外部作用域的变量

// 底层的原理
// 1.函数可以作为返回值
// 2.函数的内部查找变量的顺序,现在自己内部找,找不到就往外层找

func adder(x int) func(int) int {
	return func(y int) int {
		x += y
		return x
	}
}

func f1(f func()) {
	fmt.Println("from f1")
	f()
}

func f2(x, y int) {
	fmt.Println("from f2")
	fmt.Println(x + y)
}

// 要求f1(f2)
func f31(x, y int) func() {

	ret := func() {
		f2(x, y)
	}
	return ret
}

func f32(f func(int, int), x, y int) func() {

	ret := func() {
		f(x, y)
	}
	return ret
}

func main() {
	f1(f31(100, 200))
	f1(f32(f2, 100, 200)) // 把原来需要传递两个int类型的参数包装成一个不需要传参的函数
}

示例2

package main

import (
	"fmt"
	"strings"
)

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

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

示例3

package main

import (
	"fmt"
)

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  修改的是同一个base
	fmt.Println(f1(5), f2(6)) //13 7 修改的是同一个base
}

defer面试题

package main

import "fmt"

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)) // 函数的参数不会等待defer去执行,会先传参,所有后面即使修改了函数内的参数也不会影响
	// 所以先执行calc("A", x, y),打印出"A" 1 2 3
	// 此时变为 defer calc("AA", 1, 3) 等待函数执行结束逆序执行 打印出"AA" 1 3 4

	x = 10
	defer calc("BB", x, calc("B", x, y))
	// 先执行calc("B", x, y) = calc("B", 10, 2) 打印出"B" 10 2 12
	// 此时变为 defer calc("BB", 10, 12) 等待函数执行结束逆序执行 打印出"BB" 10 12 22

	y = 20

	/*最终返回结果
	1."A" 1 2 3
	2."B" 10 2 12
	3."BB" 10 12 22
	4."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

package main

import "fmt"

// panic和recover

func fa() {
	fmt.Println("a")
}

func fb() {
	// 刚刚打开数据库连接
	defer func() {
		err := recover() // 必须搭配defer使用,defer一定要在可能引发panic的语句之前定义
		fmt.Println(err)
		fmt.Println("释放数据库连接")
	}()
	panic("出现严重错误!!") // 程序崩溃退出 类似于raise error
	fmt.Println("b")
}

func fc() {
	fmt.Println("c")
}

func main() {
	fa()
	fb()
	fc()
}

字符串格式化及计算机交互

package main

import "fmt"

func main() {
	fmt.Printf("\n")
	// Printf("格式化字符串",值)
	// %T :查看类型
	// %d :十进制数
	// %b :二进制数
	// %o :八进制数
	// %x :十六进制数
	// %c :字符
	// %s :字符串
	// %p :指针
	// %v :值
	// %f :浮点数
	// %t :布尔值

	// var m1 = make(map[string]int, 2)
	// m1["hina"] = 111
	// fmt.Printf("%v\n", m1)
	// fmt.Printf("%#v\n", m1)

	// 获取用户输入
	// var s string
	// fmt.Scan(&s)
	// fmt.Println(s)

	var (
		name  string
		age   int
		class string
	)
	// fmt.Scanf("%s %d %s\n", &name, &age, &class)
	// fmt.Println(name, age, class)

	fmt.Scanln(&name, &age, &class)
	fmt.Println(name, age, class)
}

作业:分金币

package main

import "fmt"

/*
你有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", "阳菜hina",
	}
	distribution = make(map[string]int, len(users))
)

func getcoin(name string) int {
	runes := []rune(name)
	num := 0
	for _, v := range runes {
		switch v {
		case 'e', 'E':
			num += 1
		case 'i', 'I':
			num += 2
		case 'o', 'O':
			num += 3
		case 'u', 'U':
			num += 4
		}
	}
	return num
}

func dispatchCoin() int {
	for _, v := range users {
		fmt.Println(v)
		num := getcoin(v)
		coins -= num
		distribution[v] = num
	}
	return coins
}

func main() {
	// dispatchCoin()
	left := dispatchCoin()
	fmt.Println("剩下:", left)
	fmt.Println(distribution)
}

posted @ 2021-05-10 10:49  橘丶阳菜  阅读(81)  评论(0编辑  收藏  举报