当前位置: 首页 > 面试题库 >

Golang延迟澄清

谷梁卓
2023-03-14
问题内容

更改了该方法的结构后,当defer调用两次时会发生什么?

例如:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
for rows.Next() { 
  // do something
}
rows = Query(`SELECT FROM another`) 
defer rows.Close()
for rows.Next() {
  // do something else
}

其中rows当最后rows.Close()叫什么名字?


问题答案:

它取决于方法的接收者 变量的类型。

简短的答案:如果您使用的是database/sql包,则延迟的Rows.Close()方法将正确关闭两个Rows实例,因为它们Rows.Close()具有
指针 接收器, 并且
由于DB.Query()返回了 指针指针
也是如此rows)。请参阅下面的推理和解释。


为避免混淆,我建议使用不同的变量,这样会清楚您 想要 什么以及 将要 关闭的内容:

rows := Query(`SELECT FROM whatever`)
defer rows.Close()
// ...
rows2 := Query(`SELECT FROM whatever`)
defer rows2.Close()

我想指出一个重要的事实,它来自于deferred函数及其参数被立即评估,这在Effective
Go博客文章和Language
Spec:Deferred语句中也有说明:

每次执行“ defer”语句时,将照常评估调用的函数值和参数并 重新保存,
但不会调用实际函数。而是,在周围的函数返回之前,立即以延迟的相反顺序调用延迟的函数。

如果变量不是指针: 调用延迟的方法时,您会观察到不同的结果,具体取决于该方法是否具有指针接收器。
如果变量是指针,您将始终看到“所需”结果。

请参阅以下示例:

type X struct {
    S string
}

func (x X) Close() {
    fmt.Println("Value-Closing", x.S)
}

func (x *X) CloseP() {
    fmt.Println("Pointer-Closing", x.S)
}

func main() {
    x := X{"Value-X First"}
    defer x.Close()
    x = X{"Value-X Second"}
    defer x.Close()

    x2 := X{"Value-X2 First"}
    defer x2.CloseP()
    x2 = X{"Value-X2 Second"}
    defer x2.CloseP()

    xp := &X{"Pointer-X First"}
    defer xp.Close()
    xp = &X{"Pointer-X Second"}
    defer xp.Close()

    xp2 := &X{"Pointer-X2 First"}
    defer xp2.CloseP()
    xp2 = &X{"Pointer-X2 Second"}
    defer xp2.CloseP()
}

输出:

Pointer-Closing Pointer-X2 Second
Pointer-Closing Pointer-X2 First
Value-Closing Pointer-X Second
Value-Closing Pointer-X First
Pointer-Closing Value-X2 Second
Pointer-Closing Value-X2 Second
Value-Closing Value-X Second
Value-Closing Value-X First

在Go Playground上尝试一下。

使用指针变量,结果始终是好的(如预期的那样)。

使用非指针变量和指针接收器,我们可以看到相同的打印结果(最新的),但是如果我们有值接收器,它将打印2个不同的结果。

非指针变量的说明:

如前所述,defer执行时会评估包括接收器在内的延迟html" target="_blank">功能。如果是指针接收器,它将是 局部变量地址
。因此,当您给它分配一个新值并调用另一个值时defer,指针接收器将再次成为局部变量的 相同地址
(只是指向的值不同)。因此,稍后在执行该函数时,两个都将使用相同的地址两次,但 指向的 值将是相同的,后面将分配一个。

对于值接收器,接收器是在执行时创建的
副本defer,因此,如果将新值分配给变量并调用另一个值,则将创建defer另一个副本,该副本与先前的副本不同。



 类似资料:
  • 问题内容: 关于延期,Effective Go 声明以下内容: 延迟函数(如果函数是方法,则包括接收方)的参数在 延迟 执行时而不是在 调用 执行时进行评估。除了避免担心函数执行时变量会更改值外,这还意味着单个延迟的调用站点可以延迟多个函数的执行。这是一个愚蠢的例子。 延迟函数以LIFO顺序执行,因此该函数返回时将导致打印此代码。 这个例子使我感到困惑。如果在执行defer调用时评估了参数,则应该

  • Go 语言中没有提供其它面向对象语言的析构函数,但是 Go 语言提供了 defer 语句用于实现其它面向对象语言析构函数的功能 defer 语句常用于 释放资源、解除锁定 以及 错误处理 等 例如C语言中我们申请了一块内存空间,那么不使用时我们就必须释放这块存储空间 例如C语言中我们打开了一个文件,那么我们不使用时就要关闭这个文件 例如C语言中我们打开了一个数据库, 那么我们不使用时就要关闭这个数

  • 问题内容: 我试图在收到请求后立即发送页面响应,然后进行处理,但是我发现响应即使按代码顺序排列也没有“首先”发送。在现实生活中,我有一个页面可供上传一个Excel工作表,该工作表保存到数据库中需要花费时间(50000+行),并且希望更新用户进度。这是一个简化的示例;(取决于您有多少RAM,您可能需要添加几个零来计数才能看到结果) 问题答案: HTTP协议的原始概念是一个简单的请求- 响应服务器-客

  • 问题内容: 我正在尝试使用新的React Lazy和Suspense创建后备加载组件。这很好用,但后备时间仅显示几毫秒。有没有办法增加额外的延迟或最短时间,因此我可以在渲染下一个组件之前显示该组件的动画? 现在懒导入 等待组件: 我可以做这样的事情吗? 问题答案: 函数应该返回对象的承诺,该对象由具有默认导出功能的模块返回。不会返回承诺,也不能那样使用。尽管任意承诺可以: 如果目标是提供 最小的

  • 问题内容: 我正在阅读有关go语言的延期声明。它允许您指定函数结束后要执行的操作。例如,如果您有文件指针或资源,而不是使用每个可能的返回路径编写自由/删除,则只需指定一次defer函数。 它看起来像一个模拟可能会转到C 最终(什么是C标准延迟/终结执行 ? ,会不会有范围后卫/范围退出成语?标准化在此之前,有什么意外讲的是一个对象,它这样做析构函数进行回调?看起来局部变量的析构函数顺序是理智的,并

  • 问题内容: 这是我的代码(运行): 输出: 问题:第2部分和第3部分有什么区别?为什么第2部分输出“ 44444”而不是“ 43210”? 问题答案: “第2部分”闭包捕获变量“ i”。当闭包(稍后)中的代码执行时,变量“ i”具有在range语句的最后一次迭代中具有的值,即。‘4’。因此 输出的一部分。 “第3部分”在其闭包中未捕获任何外部变量。如规格所述: 每次执行“ defer”语句时,将照