go 学习笔记之咬文嚼字带你弄清楚 defer 延迟函数
温故知新不忘延迟基础
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
延迟函数的运行时机一般有三种情况:
- 周围函数遇到返回时
func funcWithMultipleDeferAndReturn() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
return
fmt.Println(4)
}
运行结果:
3 2 1
.「雪之梦技术驿站」:
defer fmt.Println(1)
和defer fmt.Println(2)
两个语句由于前面存在defer
关键字,因此均被延迟到正常语句return
前.当多个defer
语句均被延迟时,倒序执行延迟语句,这种特点非常类似于数据结构的栈(先入后出).所以依次输出fmt.Println(3)
,defer fmt.Println(2)
,defer fmt.Println(1)
.
- 周围函数函数体结尾处
func funcWithMultipleDeferAndEnd() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}
运行结果:
3 2 1
.「雪之梦技术驿站」: 比
funcWithMultipleDeferAndReturn
示例简单一些,虽然包围函数funcWithMultipleDeferAndEnd
并没有显示声明return
语句,但是当函数运行结束前依然不会忘记执行延迟语句.所以fmt.Println(3)
执行完后,程序并没有立即结束而是紧接着执行延迟语句defer fmt.Println(2)
和defer fmt.Println(1)
.
- 当前协程惊慌失措中
func funcWithMultipleDeferAndPanic() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
panic("panic")
fmt.Println(4)
}
运行结果:
3 2 1
.「雪之梦技术驿站」: 和
funcWithMultipleDeferAndReturn
示例有点类似,只不过由原来的return
语句换成了panic("panic")
. 我们知道延迟语句defer fmt.Println(1)
和defer fmt.Println(2)
肯定会被延迟执行,所以并不会先输出1,2
而是先执行了fmt.Println(3)
,下一步就遇到了panic("panic")
,此时顾不上惊慌失措,先让已存在的defer
语句先执行再说!
同时,defer
是倒序执行的,因而先输出defer fmt.Println(2)
再输出defer fmt.Println(1)
,最后完成使命,光荣挂掉,至于fmt.Println(4)
就无法执行了!
关于这一句话的详细解读,请参考 go 学习笔记之解读什么是defer延迟函数,示例源码见 snowdreams1006/learn-go/tree/master/error
如果你真的试图去理解 defer
的执行时机,最好看一下汇编代码的具体实现,推荐一下大佬的 defer关键字
关于 defer
关键字相关解释,摘录如下:
当函数包含
defer
语句,则汇编代码:
c
call runtime.deferreturn,
add xx SP
return
goroutine
的控制结构中,有一张表记录defer
,调用runtime.deferproc
时会将需要defer
的表达式记录在表中,而在调用runtime.deferreturn
的时候,则会依次从defer
表中出栈并执行。
但是,从语义上理解会更加简单,问一下自己为什么需要 defer
关键字,到底解决了什么问题?
一旦理解了 defer
关键字的实现意图,那么自然而然就能大概猜出有关执行顺序,所以何必深究实现细节呢?
简而言之,defer
关键字是确保程序一定会执行的代码逻辑,不管程序是正常 return
还是意外 panic
,包围函数一旦存在 defer
关键字就要保证延迟函数一定执行!
当存在多个 defer
关键字时,意味着有多个紧急任务需要处理,时间紧迫,当然是事故发生点最近的优先执行,离return
或 panic
越远的越晚执行.
所以以防万一和就近原则是理解 defer
执行时机的最佳途径: 万一哪天发生火灾,第一反应自然是就近救人啊!
支持什么又不支持哪些
The expression must be a function or method call; it cannot be parenthesized. Calls of built-in functions are restricted as for expression statements.
- 支持函数调用
func funcCallWithDefer() {
fmt.Println("funcInvokeWithDefer function is called")
}
func TestFuncCallWithDefer(t *testing.T) {
// 「雪之梦技术驿站」: defer 语句可以是函数调用.
fmt.Println(" 「雪之梦技术驿站」: defer 语句可以是函数调用.")
defer funcCallWithDefer()
fmt.Println("TestFuncInvokeWithDefer function call has ended")
}
- 支持方法调用
type Lang struct {
name string
website string
}
func (l *Lang) ToString() {
fmt.Printf("Lang:[name = %s,website = %s] \n", l.name, l.website)
}
func TestMethodCallWithDefer(t *testing.T) {
// 「雪之梦技术驿站」: defer 语句也可以是方法调用.
fmt.Println(" 「雪之梦技术驿站」: defer 语句也可以是方法调用.")
var l = new(Lang)
l.name = "Go"
l.website = "https://snowdreams1006.github.io/go/"
defer l.ToString()
fmt.Println("TestMethodCallWithDefer method call has ended")
}
- 不可以被括号包裹
- 内建函数和表达式一样受限
函数名 | 说明 | 说明 |
---|---|---|
close | 关闭channel | 仅用于channel通讯 |
delete | 从map中删除实例 | map操作 |
len | 返回字符串,slice和数组的长度 | 可用于不同的类型 |
cap | 返回容量 | 可用于不同的类型 |
new | 内存分配 | 用于各种类型 |
make | 内存分配 | 仅用于chan/slice/map |
copy | 复制slice | slice操作 |
append | 追加slice | slice操作 |
panic | 报告运行时问题 | 异常处理机制 |
recover | 处理运行时问题 | 异常处理机制 |
内建打印函数 | 主要用于不引入fmt的时候的调试,实际使用时建议使用标准库fmt | |
println | 内建打印函数 | 主要用于不引入fmt的时候的调试,实际使用时建议使用标准库fmt |
complex | 构造复数类型 | 复数操作 |
real | 抽出复数的实部 | 复数操作 |
imag | 抽出复数的虚部 | 复数操作 |
func TestBuiltinFuncCallWithDefer(t *testing.T) {
// 「雪之梦技术驿站」: defer 语句不可以被括号包裹.
fmt.Println(" 「雪之梦技术驿站」: defer 语句不可以被括号包裹.")
arr := new([10]int)
arr[4] = 5
arr[7] = 8
// defer discards result of len(arr)
defer len(arr)
defer println("Calls of built-in functions are restricted as for expression statements.")
fmt.Println("TestBuiltinFuncCallWithDefer function call has ended")
}
咬文嚼字深入理解延迟
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the "defer" statement is executed.
打蛇打七寸
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.
每次延迟语句执行时,函数值和调用参数会像以往一样被评估和保存,但是实际函数并不会被调用.
func trace(funcName string) func(){
start := time.Now()
fmt.Printf("function %s enter at %s \n",funcName,start)
return func(){
fmt.Printf("function %s exit at %s(elapsed %s)",funcName,time.Now(),time.Since(start))
}
}
func foo(){
fmt.Printf("foo begin at %s \n",time.Now())
defer trace("foo")()
time.Sleep(5*time.Second)
fmt.Printf("foo end at %s \n",time.Now())
}
func TestFoo(t *testing.T) {
foo()
}
trace
函数实现了函数计时功能,而 foo
函数则是包围函数用于演示 defer
关键字的逻辑,TestFoo
是测试函数,输出测试结果.
测试结果如下:
=== RUN TestFoo
foo begin at 2019-11-18 23:12:38.519097 +0800 CST m=+0.000735902
function foo enter at 2019-11-18 23:12:38.519287 +0800 CST m=+0.000926011
foo end at 2019-11-18 23:12:43.524445 +0800 CST m=+5.005934027
function foo exit at 2019-11-18 23:12:43.524549 +0800 CST m=+5.006038281(elapsed > 5.005112612s)--- PASS: TestFoo (5.01s)
PASSProcess finished with exit code 0
如果此时试图去解释上述运行结果,很遗憾铩羽而归!
记得官方文档中关于 defer
描述的第一句话就阐明了延迟函数的执行时机,原文如下:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
但是如果按照这句话来解释此次示例的运行结果,显然是解释不通的!
func foo(){
fmt.Printf("foo begin at %s \n",time.Now())
defer trace("foo")()
time.Sleep(5*time.Second)
fmt.Printf("foo end at %s \n",time.Now())
}
func TestFoo(t *testing.T) {
foo()
}
如果 defer trace("foo")()
延迟函数真的被延迟到函数体结束之前,那么上述 foo()
函数应该等价于这种形式:
func fooWithoutDefer(){
fmt.Printf("foo begin at %s \n",time.Now())
time.Sleep(5*time.Second)
fmt.Printf("foo end at %s \n",time.Now())
trace("foo")()
}
func TestFooWithoutDefer(t *testing.T) {
fooWithoutDefer()
}
但是对于 fooWithoutDefer
函数的执行结果直接实力打脸:
=== RUN TestFooWithoutDefer
foo begin at 2019-11-19 11:44:20.066554 +0800 CST m=+0.001290523
foo end at 2019-11-19 11:44:25.068724 +0800 CST m=+5.003312582
function foo enter at 2019-11-19 11:44:25.068796 +0800 CST m=+5.003384341
function foo exit at 2019-11-19 11:44:25.068847 +0800 CST m=+5.003435185(elapsed 51.196µs)--- PASS: TestFooWithoutDefer (5.00s)
PASSProcess finished with exit code 0
由此可见,延迟函数其实并不简单,想要弄清楚 defer
关键字还要继续读下去才有可能!
这一点也是我最大的疑惑,潜意识告诉我: 只要无法真正理解 Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.
这句话的含义,那么永远不可能彻底弄清 defer
关键字!
通过直接调换 defer
语句的出现位置并没有解释测试结果,反而告诉我们 defer
语句可不是简简单单的延迟执行.
任何函数都会或多或少依赖相应的执行环境,defer
延迟函数也不例外,在本示例中 defer trace("foo")()
延迟函数的 trace("foo")
函数的返回值是函数,然后 trace("foo")()
相当于立即执行返回函数,因而问题可能出现在 trace("foo")
函数中,那么不妨继续看看吧!
func foo(){
fmt.Printf("foo begin at %s \n",time.Now())
defer trace("foo")()
time.Sleep(5*time.Second)
fmt.Printf("foo end at %s \n",time.Now())
}
func trace(funcName string) func(){
start := time.Now()
fmt.Printf("function %s enter at %s \n",funcName,start)
return func(){
fmt.Printf("function %s exit at %s(elapsed %s)",funcName,time.Now(),time.Since(start))
}
}
1. foo begin at 2019-11-19 14:06:42.385982 +0800 CST m=+0.000943615
暗示着已经开始进入 foo()
函数内部,接下来的 function foo enter at 2019-11-19 14:06:42.38623 +0800 CST m=+0.001191025
意味着函数并没有执行 time.Sleep(5*time.Second)
而是直接进入了 defer trace("foo")()
语句内部,可见函数依旧是顺序执行,但是 trace(funcName string) func()
函数内部会返回函数,此时函数返回值并没有执行,因为此时并不存在打印输出的日志.
所以 trace(funcName string) func()
函数应该是已经执行了,接下来返回上一层回到主函数 foo()
就遇到了 time.Sleep(5*time.Second)
休息 5s
语句,所以在执行 fmt.Printf("foo end at %s \n",time.Now())
语句时输出的时间和最近的上一句差了大概 5s
.
foo end at 2019-11-19 14:06:47.391581 +0800 CST m=+5.006394415
输出后也就意味着 foo()
函数运行到包围函数的结束处,此时按照延迟语句的第一句,我们知道是时候执行真正的延迟逻辑了.
所以下一句就是 trace("foo")()
的函数返回值的调用,输出了 function foo exit at 2019-11-19 14:06:47.391706 +0800 CST m=+5.006518615(elapsed 5.005327927s)--- PASS: TestFoo (5.01s)
至此,延迟函数执行完毕,单元测试函数也输出了 PASS
.
=== RUN TestFoo
foo begin at 2019-11-19 14:06:42.385982 +0800 CST m=+0.000943615
function foo enter at 2019-11-19 14:06:42.38623 +0800 CST m=+0.001191025
foo end at 2019-11-19 14:06:47.391581 +0800 CST m=+5.006394415
function foo exit at 2019-11-19 14:06:47.391706 +0800 CST m=+5.006518615(elapsed 5.005327927s)--- PASS: TestFoo (5.01s)
PASS
通过上述分析,可以这么理解,延迟函数也是需要执行环境的,而执行环境就是依赖于定义 defer
语句时的相关环境,这也就是延迟函数的准备阶段或者说入栈.
当遇到包围函数体返回时或到达包围函数体结尾处或发生错误时,包围函数就会调用已存在的延迟函数,这部分就是延迟函数的执行阶段或者说出栈.
- 无论是否存在延迟函数,均顺序执行函数逻辑
- 准备阶段的入栈操作会正常运行但不会调用函数
- 执行阶段的出栈操作在合适时机时会调用函数
同样地,仍然以消防队员作为 Go
的调度器,平民百姓作为无 defer
保护的对比参考,而有 defer
保护的特殊人群作为延迟函数.
有一天,普通百姓和特殊人士都在商场逛街,突发火灾,附近消防员迅速赶紧救人,任务只要一个:那就是按照就近原则快速救出全部特殊人群,因为这些特殊人群都是有头有脸的人物,每个人都有自己的脾气个性.
明星 A : 我进商场前拿着限量版的 LV
包包,这个我也要拿出去!
富二代 B : 我进商场前答应小女友要给他买个礼物,这个是寄存柜地址,别忘了把礼物也带回来!
暴发户 C : 我在商场有个保险柜,存放了大量金条,一定要给我带出去!
消防员很无奈,心里咒骂了一句: 这都生死攸关了,还管什么身外之物啊!
可是,埋怨归埋怨,对于这些特殊人群的照顾,那是一丁点也不敢怠慢,只能照办,终于全部救出了!
A 表示声明
defer
语句时已经传递了参数,等到执行defer
时调用的就是刚才的参数值,而Go
语言中参数的传递只能是值传递,所以虽然看起来还是那个包,其实已经变了,这里并不是特别准确!
B 表示声明defer
语句时传递的参数不是具体值而是引用,当执行defer
逻辑时会按图索骥,因此虽然给的是一张寄存柜的密码纸,最后拿出来的却是存在柜子里的礼物.
C 表示声明defer
时什么都没有传递,没有任何入参但是执行defer
语句中遇到了访问包围函数的需求,这时候延迟函数会扩大搜索范围向上寻找直到找到商场的金库为止.
- 零依赖而无顾虑
func deferWithoutParams() {
// 2 1
defer fmt.Println(1)
fmt.Println(2)
}
「雪之梦技术驿站」: 入栈时没有任何依赖,出栈时也不会有任何顾虑,非常简单直观输出了
2 1
.
- 随身携带的牵挂
func deferWithValueParams() {
x := 10
defer func(n int) {
// 10
fmt.Println(n)
}(x)
x++
}
「雪之梦技术驿站」: 入栈时存在值参数
func(n int)(10)
,出栈时需要输出参数的值,而fmt.Println(n)
涉及到的n
刚好保存在入栈环境中,所以等到deferWithValueParams
运行到函数结束后输出的结果就是已缓存的副本10
.
如果此时匿名函数调用的不是 n
而是 x
,而变量 x
并不存在于入栈环境中,此时就会继续扩大范围搜到 deferWithValueParams
函数是否存在变量 x
的声明,本示例中找到的 x=11
.
func deferWithOuterParams() {
x := 10
defer func(n int) {
// 11
fmt.Println(x)
}(x)
x++
}
- 心有牵挂放不下
func deferWithReferParams() {
x := 10
defer func(n *int) {
// 11
fmt.Println(*n)
}(&x)
x++
}
「雪之梦技术驿站」: 入栈时保存的不再是值而是地址,因此出栈时会按图索骥,找到该地址对应的值,也就是
11
.
相信以上案例应该帮助读者理解 defer
语句的一些注意事项了吧?
延迟函数准备阶段的入栈会收集函数运行所需的环境依赖,比如说入参的值,收集结束后即使外界再改变该值也不会影响延迟函数,因为延迟函数用的是缓存副本啊!
出栈会倒序
Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.
相反的,延迟函数会在包围函数返回之前按照被延迟顺序逆序调用.
func TestFuncWithMultipleDefer(t *testing.T) {
// 「雪之梦技术驿站」: 猜测 defer 底层实现数据结构可能是栈,先进后出.
t.Log(" 「雪之梦技术驿站」: 猜测 defer 底层实现数据结构可能是栈,先进后出.")
// 3 2 1
defer t.Log(1)
defer t.Log(2)
t.Log(3)
}
「雪之梦技术驿站」: 运行阶段的出栈操作会倒序执行多个
defer
延迟函数,所以输出了3 2 1
.
及时雨插入
That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller.
当包围函数通过明确的 return
返回语句返回时,defer
延迟函数会在 result parameters
结果参数被赋值之后且在函数 return
返回之前执行.
按照这句话可以将下面这种代码进行拆解:
defer yyy
return xxx
其中 return xxx
相当于拆开了两步并且最终返回前及时插入了 defer
语句的执行逻辑,如下:
1. result parameters = xxx
2. 调用 defer 函数
3. return
同样地,我们举例说明:
func deferWithExplicitReturn() (result int) {
defer func() {
// 2. before : result = 10
fmt.Printf("before : result = %v\n", result)
result++
// 3. after : result = 11
fmt.Printf("after : result = %v\n", result)
}()
result = 10
// 1. return : result = 10
fmt.Printf("return : result = %v\n", result)
return result
}
关于 defer
延迟函数的执行顺序和输出结果已经不再是难点了,现在主要关注下 deferWithExplicitReturn()
函数运行结束后的返回值到底是 10
还是 11
.
func TestDeferWithExplicitReturn(t *testing.T) {
// TestDeferWithExplicitReturn result = 11
fmt.Printf("TestDeferWithExplicitReturn result = %d\n",deferWithExplicitReturn())
}
「雪之梦技术驿站」: 测试结果输出了
11
,很显然这里是因为延迟函数内部执行了result++
操作最终影响了外部函数的返回值.
如果对上述示例进行改造,下面的代码就清晰看出了为什么会影响返回值了.
func deferWithExplicitReturnByExplain() (result int) {
result = 10
// 1. return : result = 10
fmt.Printf("return : result = %v\n", result)
func() {
// 2. before : result = 10
fmt.Printf("before : result = %v\n", result)
result++
// 3. after : result = 11
fmt.Printf("after : result = %v\n", result)
}()
return
}
「雪之梦技术驿站」: 延迟函数会在
return
返回前有机会对返回值进行更改,这里演示了及时雨插入的逻辑,输出结果不变还是11
.
下面提供一些例题,请自行思考
func surroundingFuncEvaluatedNotInvoked(init int) int {
fmt.Printf("1.init=%d\n",init)
defer func() {
fmt.Printf("2.init=%d\n",init)
init ++
fmt.Printf("3.init=%d\n",init)
}()
fmt.Printf("4.init=%d\n",init)
return init
}
func noDeferFuncOrderWhenReturn() (result int) {
func() {
// 1. before : result = 0
fmt.Printf("before : result = %v\n", result)
result++
// 2. after : result = 1
fmt.Printf("after : result = %v\n", result)
}()
// 3. return : result = 1
fmt.Printf("return : result = %v\n", result)
return 0
}
func deferFuncWithAnonymousReturnValue() int {
var retVal int
defer func() {
retVal++
}()
return 0
}
func deferFuncWithNamedReturnValue() (retVal int) {
defer func() {
retVal++
}()
return 0
}
「雪之梦技术驿站」: 如果一眼看不出答案,不妨复制到编辑器直接运行,然后在思考为什么.
调用时报错
If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the "defer" statement is executed.
如果延迟函数值为 nil
,则函数调用时发生错误异常 panic
而不是 defer
语句执行时报错.
func deferWithNil() func() {
return nil
}
func TestDeferWithNil(t *testing.T) {
fmt.Println("begin exec deferWithNil()()")
defer deferWithNil()()
fmt.Println("end exec deferWithNil()()")
}
公布答案以及总结全文
在上篇文章中留下了两个小问题,相信看到这篇文章的人都能独立完成并自行解释了吧?
下面给出问题以及答案!
func deferFuncWithAnonymousReturnValue() int {
var retVal int
defer func() {
retVal++
}()
return 0
}
func deferFuncWithNamedReturnValue() (retVal int) {
defer func() {
retVal++
}()
return 0
}
func TestDeferFuncWhenReturn(t *testing.T) {
// 0
t.Log(deferFuncWithAnonymousReturnValue())
// 1
t.Log(deferFuncWithNamedReturnValue())
}
「雪之梦技术驿站」:
deferFuncWithAnonymousReturnValue()
函数无明确的返回值参数,而deferFuncWithNamedReturnValue()
函数已经声明了(retVal int)
返回值,因为延迟函数并不会影响未命名的函数.
通过本文,我们知道了延迟函数的执行时机以及一些细节,关键是理解 Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.
这句话,绝对是重中之重!
简而言之,延迟函数在声明时会收集相关参数赋值拷贝一份入栈,时机合适时再从入栈环境中寻找相关环境参数,如果找不到就扩大范围寻找外层函数是否包含所需变量,执行过程也就是延迟函数的出栈.
有一个消防员专门负责保卫商场的安全,每天商场进进出出很多人流,总有一些重要人物也会来到商场购物,突然有一天,发生了火灾,正在大家惊慌失措中...
这个消防员到底干了什么才能保证重要人物安全的同时也能让他们不遭受财产损失?
请补充你的答案,感谢你的阅读与关注,下一节再见~
阅读延伸以及参考文档
- Defer_statements
- Built-in_functions
- Go语言规格说明书 之 内建函数(Built-in functions)
- go语言快速入门:内建函数(6)
- 你知道defer的坑吗?
- golang语言defer特性详解.md
- Golang之轻松化解defer的温柔陷阱
如果本文对你有所帮助,请动动小手点一下推荐,否则还请留言指正,如有需要,请关注个人公众号「 雪之梦技术驿站 」
go 学习笔记之咬文嚼字带你弄清楚 defer 延迟函数的更多相关文章
- [原创]java WEB学习笔记42:带标签体的自定义标签,带父标签的自定义标签,el中自定义函数,自定义标签的小结
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- UNP学习笔记(第十四章 高级I/O函数)
本章讨论我们笼统地归为“高级I/O”的各个函数和技术 套接字超时 有3种方法在涉及套接字的I/O操作上设置超时 1.调用alarm,它在指定超时时期满时产生SIGALRM信号 2.在select中阻塞 ...
- golang学习笔记(一):包,变量,函数
欢迎访问我的博客和github! go 语言学习笔记第一弹,来自 gotour ,以后要常写笔记,把自己学习笔记记录下来,就算只是笔记也要多写. 好记性不如烂笔头,也要多锻炼自己的写作能力. 说实话, ...
- Bootstrap学习笔记上(带源码)
阅读目录 排版 表单 网格系统 菜单.按钮 做好笔记方便日后查阅o(╯□╰)o bootstrap简介: ☑ 简单灵活可用于架构流行的用户界面和交互接口的html.css.javascript工具集 ...
- GO语言学习笔记1-输入带空格的字符串
最近开始学习GO语言,并做了一些编程练习.有道题要输入带空格的字符串,这在C/C++中很容易实现,但GO中好像并不那么容易.学过C/C++的可能都知道,在C中可以使用gets()函数,在C++可以使用 ...
- 周末学习笔记——day02(带参装饰器,wraps修改文档注释,三元表达式,列表字典推导式,迭代器,生成器,枚举对象,递归)
一,复习 ''' 1.函数的参数:实参与形参 形参:定义函数()中出现的参数 实参:调用函数()中出现的参数 形参拿到实参的值,如果整体赋值(自己改变存放值的地址),实参不会改变,(可变类型)如果修改 ...
- zabbix4.2学习笔记--用自带的mysql监控模块
这里演示监控zabbix本身用到的mysql 第一步:建立mysql监控用户 在生产环境中,出于安全考虑,建议监控客户端数据库时,单独配置一个查询权限用户做查询操作即可 # 撤掉安装时给予的分配单个数 ...
- iOS: 学习笔记, 添加一个带界面约束的控制器
1. 创建一个空iOS应用程序(Empty Application). 2. 添加加控制器类. 修改控制器类的viewDidLoad - (void)viewDidLoad { [super view ...
- Scala学习笔记2 (带着问题学习, 逐渐扩展。理解吃透scala.)
问题: 把 文本字符串"[1, 2, 3, 4, 5]" 转换成一个数组. 答案: val x = "[1, 2, 3, 4, 5]" val y =x sli ...
随机推荐
- Vtable内存布局分析
vtale 内存布局分析 虚函数表指针与虚函数表布局 考虑如下的 class: class A { public: int a; virtual void f1() {} virtual void f ...
- Oracle基于布尔的盲注总结
0x01 decode 函数布尔盲注 decode(字段或字段的运算,值1,值2,值3) 这个函数运行的结果是,当字段或字段的运算的值等于值1时,该函数返回值2,否则返回3 当然值1,值2,值3也可以 ...
- PMP涉及的几个工作系统
PMP涉及的几个工作系统 工作系统作为事业环境因素,提高或限制项目管理的灵活性,并可能对项目结果产生积极或消极影响,包括项目管理系统.项目管理信息系统PMIS.配置管理系统.变更控制系统.合同变更 ...
- go-linux环境搭建
下载 go1..linux-amd64.tar.gz 解压: tar zxvf go1..linux-amd64.tar.gz -C /usr/local 配置环境变量:vim /root/.bas ...
- PowerShell渗透--Empire(三)
会话注入 我们可以使用usemodule management/psinject模块来进程注入,获取权限 设置下Listeners和ProcID这2个参数,这里的ProcID就是之前的CMD的pid, ...
- Maven -- 使用Myeclipse创建Maven项目
使用Myeclipse创建Maven项目有如下几种方式: 1.创建Maven Java项目 1.1 选择新建Maven项目 1.2.选择创建简单项目 1.3.填写项目信息 1.4.创建成功后项目目录结 ...
- Codeforces--Books Exchange (hard version)
题目链接http://codeforces.com/contest/1249/problem/B2 .并查集思想,将数分成多个集合,每个集合的大小就是一轮的所需天数. Map[i]存储数据. flag ...
- Redis事务深入解析和使用
作为关系型数据库中一项非常重要的基础功能--事务,在 Redis 中是如何处理并使用的? 1.前言 事务指的是提供一种将多个命令打包,一次性按顺序地执行的机制,并且保证服务器只有在执行完事务中的所有命 ...
- MyBatis 示例-联合查询
简介 MyBatis 提供了两种联合查询的方式,一种是嵌套查询,一种是嵌套结果.先说结论:在项目中不建议使用嵌套查询,会出现性能问题,可以使用嵌套结果. 测试类:com.yjw.demo.JointQ ...
- 基础安全术语科普(三)——RAT
什么是RAT? RAT 即 Remote Access Tools (远程管理工具或远程访问工具)的缩写.通俗点说就是木马病毒. RAT 分为两部分——客户端 与 服务端. RAT的工作原理? 服务端 ...