defer
0x00 defer概述
定义
defer,顾名思义,延迟的意思。
将defer后面的语句延迟执行,那么延迟到啥时候?等到函数即将返回结束的时候执行。
逐行分析:先执行代码,遇到了defer,先将哈哈哈这句话找一个地方存起来,执行其他两句。快要结束的时候,突然想起来还有一个defer,于是再执行。
func deferdemo() {
defer fmt.Println("哈哈哈哈哈")
fmt.Println("不不不不不")
fmt.Println("对对对对对")
}
func main() {
deferdemo()
}
使用场景
多用于函数结束前释放资源。
1、当函数执行完毕释放资源时,例如一个函数用来打开一个文件,然后写入一些内容,退出的时候一定要关闭文件。为了防止忘了,先通过defer将关闭的语句登记一下,等函数要退出时。
2、还有类似打开网络连接socket的时候
3、连接数据库的时候
多个defer的顺序
多个defer语句,按照先进后出,后进先出的顺序延迟执行。
比如上课交手机,一个一个摞起来,最后取得时候,先取最后一个放的。
func deferdemo() {
defer fmt.Println("哈哈哈哈哈")
defer fmt.Println("xxxxxxxxx")
defer fmt.Println("aaaaaaaaa")
fmt.Println("不不不不不")
fmt.Println("对对对对对")
}
func main() {
deferdemo()
}
0x01 defer执行的时机
在Go语言函数中的return不是原子操作(原子(atomic)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为“不可被中断的一个或一系列操作”),它分为给返回值赋值和RET指令两步。
第一步:返回值赋值
第二步:真正的RET返回
函数中如果存在defer,那么defer执行的时机就在第一步和第二步之间。如下图:
0x02 经典面试题分析
这个题在面试的时候出而已,根本不会在实际上用,谁闲的没事折磨自己?
请问,下方代码,4个函数中的x分别输出为多少?
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)
}
func main(){
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
fmt.Println(f4())
}
第一题
开始进入函数体,x被赋值为5;随后先忽略defer;到return的时候,切割开步骤:
第一步:return =x =5
第二步 defer:x变成6,并没有给return赋值的操作吧,return所在的内存没有任何变化。
第三步,返回return=5
func f1() int {
x := 5
defer func(){
x++
}
return x
}
第二题
首先函数体也就是花括号外面事先声明了返回值为x,即return=x;遇到defer先忽视;到return的时候切开步骤:
第一步return=x=5;
第二步,defer:x=x+1,即x=6,此时影响了return的值,它俩是真的一样的值。return=6
第三步,返回return=6
func f2()(x int){
defer func(){
x++
}()
return 5
}
第三个
与第一题同理,返回值没有变,x变了。
第四个
函数传参数进去,改的是副本,到时候讲作用域和匿名函数就懂了。
系统函数
0x00 前言
我们之前学了那么多函数,都是自定义的。但是go语言中给我们提供了一些内置的函数。我们先来学习字符串函数
0x01 字符串类系统函数
【1】统计字符串(不是字符)长度len(str)
【2】字符串遍历
利用for-range遍历,如果只用for,打印不出来汉字
第二种方式:利用r=[]rune(str)将字符串转换成切片,再将切片遍历
【3】字符串转整数+整数转字符串
需要调用strconv
包
【4】查看字符串中有多少子串
【5】不区分字符串比较
【6】区分大小写就很简单了
【7】返回子串在字符串中第一/最后一次出现的索引数
strings.Index("kslajdkasjd","asda")
strings.LastIndex("asdad","asdasd")
【8】字符串替换
strings.Replace("渗透测试渗透工程渗透师NEWX666","渗透","肾透",n) //n代表从左往右依次替换几个,-1表示全部替换
【9】按照某个字符切割:输出的是切片
fmt.Println(strings.Split("qq-gg-dd-dd","-"))
【10】将字符串的字母进行大小写转换
strings.ToLower("Go") //go
strings.ToUpper("go") //GO
【11】将字符串左右两边的空格去掉
fmt.Println(strings.TrimSpace(" ddd "))
【12】将字符串左右两边指定的字符去掉
fmt.Println(strings.Trim("~root@localhost~","~"))
【13】将字符串左边指定的字符去掉
fmt.Println(strings.TrimLeft("~root@localhost~","~"))
【14】将字符串右边指定的字符去掉
fmt.Println(strings.TrimRight("~root@localhost~","~"))
【15】判断字符串是否以指定的字符串开头
fmt.Println(strings.HasPrefix("https://github.com","https"))
0x02 日期和时间函数
【1】举个例子讲解,日期和时间的函数,必须导入time包。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf("now函数的数值为:%v, now函数输出的类型为%T", now, now)
}
time.Now()函数,返回的是一个结构体!
【2】只要年月日,时分秒怎么办
在开发文档中可以看到,结构体中含有年月日等方法,进行调用即可。
解决月输出为字符串的问题:int(now.Month())
【3】日期的格式化
第一种方式:直接通过字符串的形式输出出来。
第二种方式:将字符串输出出来的结果赋予给一个变量。fmt.Sprintf()
的值,可以一直变化,而不是像一个变量,里面的值还需要自己更改可能。
date := fmt.Sprintf("当前时间为:%d年%d月%d日 %d时%d分%d秒", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) //可以得到字符串,以便后续使用
fmt.Println(date)
第三种方式:
十分神奇!必须这么写:才会显示当前的时间。不过你只能改格式,或者删除数字,不能改数字!
这里有一个巧记方式,就是2006年12345,即2006年1月2日,下午3点4分5秒
now.Format("2006/01/02 15/04/05")
内置函数 | 介绍 |
---|---|
close | 主要用来关闭channel |
len | 用来求长度,比如string、array、slice、map、channel |
new | 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 |
make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice |
append | 用来追加元素到数组、slice中 |
panic和recover | 用来做错误处理 |
内置函数
【1】什么是内置函数/内建函数:
Golang设计者为了编程方便,提供了一些函数,这些函数不用导包就可以直接使用,我们称之为Go的内置函数/内建函数。
这些函数调用的时候,不需要导包,直接用就可以了,通过api可以看到这些内置工具是在builtin这个包中的。
【2】len函数
【3】new函数
主要是用来分配内存的。其第一个实参为类型,而非值。其返回值为指向该类型的新分配的零值的指针。
用于给int、float、bool、string、数组和结构体struct
【4】make函数
也是分配内存,只不过主要是用来分配引用类型(指针、slice切片、map、管道chan、interface等)
defer和recover机制处理错误
在Go语言最让人诟病的一点,就是认为所有的错误都是一个值,这个值需要我们来判断是否存在。做Go语言开发经常能看到if err != nil这样的语句,为什么?就是因为Go语言将每一个可能出现error的值都显示返回,调用方就要判断是否为空。据说Go2.0就要重新定义这个错误机制了。
panic/recover
Go语言中目前(Go1.12)是没有异常机制,但是使用panic/recover
模式来处理错误。 panic
可以在任何地方引发,但recover
只有在defer
调用的函数中有效。现在有一个例子,就是当程序出现panic后,是非常危险的,会导致后面的代码等打开的资源并没有释放,从而造成内存出问题等等。
func funcA() {
fmt.Println("func A")
}
func funcB() {
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}
这个例子就是,在说明程序运行期间funcB
中引发了panic
导致程序崩溃,异常退出了。
这个时候我们就可以通过recover将程序恢复回来,继续往后执行。
func funcA() {
fmt.Println("func A")
}
func funcB() {
defer func() {
err := recover()
if err != nil {
fmt.Println("程序错误!正在回收错误... ... ...")
}
}()
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}
再举一个例子
没有加defer+匿名函数+recover的代码,后面根本就不会执行,而加了的代码,就会正常执行下面的语句。
注意
1、recover()必须搭配defer使用
2、defer一定要在可能引发panic的语句之前定义,因为出现了错误的语句直接会终止程序,根本就到不了defer关键词
func test() {
s1 := 10
s2 := 0
s3 := s1 / s2
fmt.Println(s3) //10不能除以0,会报错
defer func() {
err := recover() //recover()函数会捕获到错误信息,即panic之后的,如下图
if err != nil {
fmt.Println("something wrong!")
}
}()
}
func main() {
test()
}
注意一点,就是defer尽量在函数的前面。不然函数执行的时候,不会执行defer函数,直接报错导致后方代码不运行了,也就根本不涉及到defer了。
自定义错误
上面的这个例子,看到了,就算我们捕获了错误,输出出来的错误感觉不是很容易理解,我们能不能自定义错误呢?
通过new函数,注意这个new函数不是上述新建类型的new函数,而是errors包下的new函数来操作。这个函数返回一个error类型。
package main
import (
"errors"
"fmt"
)
func main() {
err := test()
if err != nil {
fmt.Println("自定义错误:", err)
panic(err)
//return
}
fmt.Println("12345")
fmt.Println("12345")
}
func test() (err error) {
num1 := 10
num2 := 0
if num2 == 0 {
//抛出自定义错误:
err := errors.New("除数不能为0哦~~")
return err
} else { //如果除数不为0,那么正常执行就可以了
result := num1 / num2
fmt.Println(result)
}
//如果没有错误,返回nil
return nil
}
请将下面这段代码默写5遍:
package main
import (
"errors"
"fmt"
)
func test() (err error) {
num1 := 10
num2 := 0
if num2 == 0 {
//自定义
err := errors.New("不好意思,整数不能为0!!")
return err
} else { //正常执行
result := num1 / num2
fmt.Println(result)
}
return nil
}
func main() {
err := test()
if err != nil {
fmt.Println("自定义错误为:", err)
panic(err)
}
r := []string{"beijing", "shanghai", "guangdong", "shenzhen"}
for i, j := range r {
fmt.Printf("键为:%d,值为:%v", i, j)
}
}
快速查看内置函数api的方法
因为内置函数基本都在builtin
这个包文件中,所以直接使用此方法即可查看。
go doc builtin.delete