Go 语言入门很简单:Go 中的作用域和变量隐藏

Posted 宇宙之一粟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 语言入门很简单:Go 中的作用域和变量隐藏相关的知识,希望对你有一定的参考价值。

变量隐藏在 Go 中可能会令人困惑,让我们尝试弄清楚。

package main

import (
"fmt"
"io/ioutil"
"log"
)

func main()

f, err := ioutil.TempFile("", "")
if err != nil
log.Fatal(err)

defer f.Close()

if _, err := f.Write([]byte("Hello World\\n")); err != nil
log.Fatal(err)
else
fmt.Println("All success")

请注意,我们首先从 ​​TempFile​​ 函数创建了两个变量:​​f​​ 和 ​​err​​。然后我们调用 ​​Write​​ 丢弃写入的字节数。我们让函数在 ​​if​​ 语句中调用它。让我们编译,它工作正常。

$ go run main.go
All success

现在,与 ​​if​​ 之外的 ​​Write​​ 调用相同的代码:

package main

import (
"fmt"
"io/ioutil"
"log"
)

func main()

f, err := ioutil.TempFile("", "")
if err != nil
log.Fatal(err)

defer f.Close()

// if _, err := f.Write([]byte("Hello World\\n")); err != nil
// log.Fatal(err)
// else
// fmt.Println("All success")
//

_, err := f.Write([]byte("Hello World\\n"))
if err != nil
log.Fatal(err)
else
fmt.Println("All success")

运行该代码:

$ go run main.go
# command-line-arguments
./main.go:23:9: no new variables on left side of :=

所以发生了什么事?

请注意,我们使用 ​​:=​​ 调用 ​​Write​​,这意味着我们创建了一个新变量 ​​err​​。在第二个例子中,很明显,​​err​​ 已经存在,所以我们不能重新声明它。

但是为什么它第一次起作用呢?因为在 Go 中,变量是其作用域的本地变量。在第一个示例中,我们实际上在 ​​if​​ 范围内隐藏了 ​​err​​。

例如:

package main

func main()
var err error
_ = err
var err error
_ = err

这显然会失败,但是,如果我们限定第二个 ​​err​​,它会起作用!

package main

func main()
var err error
_ = err

var err error
_ = err

包隐藏

考虑以下代码:

package main

import "fmt"

func Debugf(fmt string, args ...interface)
fmt.Printf(fmt, args...)

起初,它看起来不错。我们从 ​​fmt​​ 包中调用 ​​Printf​​ 并将 ​​fmt​​ 变量传递给它。

函数声明中的 ​​fmt​​ 字符串实际上隐藏了包,现在“只是”一个变量。编译器会抱怨:我们需要使用不同的变量名来保存对 ​​fmt​​ 包的访问。

全局变量

需要考虑的是,一个函数已经是一个“子作用域”,它是全局作用域内的一个作用域。这意味着您在函数中声明的任何变量都可以在全局范围内隐藏某些内容。

正如我们之前看到的,变量可以映射包,全局变量和函数的概念是相同的。

类型强制

就像我们可以用变量或函数来映射一个包一样,我们也可以用任何类型的新变量来映射一个变量。阴影变量不需要来自同一类型。这个例子编译得很好:

package main

func main()
var a string
_ = a

var a int
_ = a

闭包

使用嵌入式函数时,作用域非常重要。函数中使用且未声明的任何变量都是对上层范围的引用。众所周知的使用 ​​goroutine​​ 的例子:

package main

import (
"fmt"
"time"
)

func main()
for _, elem := range []bytea, b, c
go func()
fmt.Printf("%c\\n", elem)
()

time.Sleep(1e9) // Sleeping to give time to the goroutines to be executed.

运行该代码:

$ go run main.go
c
c
c

这不是我们真正想要的。这是因为范围改变了 ​​goroutine​​ 中引用的 ​​elem​​,因此在短列表中,它将始终显示最后一个元素。

为了避免这种情况,有两种解决方案:

  1. 将变量传递给函数
package main

import (
"fmt"
"time"
)

func main()
for _, elem := range []bytea, b, c
go func(char byte)
fmt.Printf("%c\\n", char)
(elem)

time.Sleep(1e9)

运行结果:

$ go run main.go
a
c
b
  1. 在本地范围内创建变量的副本
package main

import (
"fmt"
"time"
)

func main()
for _, elem := range []bytea, b, c
char := elem
go func()
fmt.Printf("%c\\n", char)
()

time.Sleep(1e9)

运行该代码,可以得到我们想要的结果:

Go

当我们将变量传递给函数时,我们实际上将变量的副本发送给以字符形式接收它的函数。因为每个 ​​goroutine​​ 都有自己的副本,所以没有问题。

当我们复制变量时,我们创建一个新变量并将 ​​elem​​ 的值分配给它。我们在每次迭代中都这样做,这意味着对于每个步骤,我们都会创建一个新变量,​​goroutine​​ 会引用该变量。每个 ​​goroutine​​ 都有一个对不同变量的引用,并且它也可以正常工作。

现在,我们知道我们可以隐藏变量,为什么还要更改名称呢?我们可以简单地使用相同的名称,因为它会影响上层范围:

package main

import (
"fmt"
"time"
)

func main()
for _, elem := range []bytea, b, c
go func(elem byte)
fmt.Printf("%c\\n", Go 语言入门很简单:sort 中的sortInts 方法

Go 语言入门很简单 -- 7. Go Slices #私藏项目实操分享#

Go 语言入门很简单:Go 实现凯撒密码

Go 语言入门很简单 -- 8. Go Maps #私藏项目实操分享#

Go 语言入门很简单:正则表达式

Go 语言入门很简单:什么是上下文