目录

前言

变量隐藏在 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")
	}
}
TempFileferrWriteif
$ go run main.go
All success
ifWrite
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 :=

所以发生了什么事?

:=Writeerrerr
iferr

例如:

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...)
}
fmtPrintffmt
fmtfmt

全局变量

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

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

类型强制

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

package main

func main() {
	var a string
	_ = a
	{
		var a int
		_ = a
	}
}

闭包

goroutine
package main

import (
	"fmt"
	"time"
)

func main() {
	for _, elem := range []byte{'a', '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
goroutineelem

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

1.将变量传递给函数

package main

import (
	"fmt"
	"time"
)

func main() {
	for _, elem := range []byte{'a', 'b', 'c'} {
		go func(char byte) {
			fmt.Printf("%c\n", char)
		}(elem)
	}
	time.Sleep(1e9)
}

运行结果:

$ go run main.go
a
c
b

2.在本地范围内创建变量的副本

package main

import (
	"fmt"
	"time"
)

func main() {
	for _, elem := range []byte{'a', 'b', 'c'} {
		char := elem
		go func() {
			fmt.Printf("%c\n", char)
		}()
	}
	time.Sleep(1e9)
}

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

goroutine
elemgoroutinegoroutine

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

package main

import (
	"fmt"
	"time"
)

func main() {
	for _, elem := range []byte{'a', 'b', 'c'} {
		go func(elem byte) {
			fmt.Printf("%c\n", elem)
		}(elem)
	}
	time.Sleep(1e9)
}
package main

import (
	"fmt"
	"time"
)

func main() {
	for _, elem := range []byte{'a', 'b', 'c'} {
		elem := elem
		go func() {
			fmt.Printf("%c\n", elem)
		}()
	}
	time.Sleep(1e9)
}

当我们将变量传递给函数时,会发生同样的事情,我们将变量的副本传递给函数,该函数以名称 elem 和正确的值获取它。

在这个范围内,由于变量被遮蔽,我们无法从上层范围影响元素,所做的任何更改都将仅在此范围内应用。

elem

:= 的情况

当 := 与多个返回函数(或类型断言、通道接收和映射访问)一起使用时,我们可以在 2 个语句中得到 3 个变量:

package main

func main() {
	var iface interface{}

	str, ok := iface.(string)
	if ok {
		println(str)
	}
	buf, ok := iface.([]byte)
	if ok {
		println(string(buf))
	}
}
okok
package main

func main() {
	var m = map[string]interface{}{}

	elem, ok := m["test"]
	if ok {
		str, ok := elem.(string)
		if ok {
			println(str)
		}
	}
}

总结

隐藏可能非常有用,但需要牢记以避免意外行为。它当然是基于案例的,它通常有助于提高可读性和安全性,但也可以减少它。

goroutines
package main

import (
	"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 {
		err = nil
	}
	// err is still the one form TempFile
}
iferrif=:=
您可能感兴趣的文章: