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)
切片的扩容策略:
- 如果申请的容量大于原来的2倍,那么直接扩容至新申请的容量。
- 如果小于1024,那么就直接翻倍
- 如果大于1024,那么就按照原容量的1.25倍去扩容
- 具体存储的值类型不同,,扩容策略也有一定的不同。
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)
}
函数的定义
基本格式
参数的格式
有参数的函数
参数类型简写
可变参数
返回值的格式
有返回值
多返回值
明明返回值
变量作用域
- 全局作用域
- 函数作用域
- 代码块作用域
高阶函数
函数也是一种类型,它可以作为参数,也可以作为返回值
函数类型
函数也是一种类型
匿名函数
没有名字的函数
// 匿名函数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)
}