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

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

闭包

使用嵌入式函数时,作用域非常重要。函数中使用且未声明的任何变量都是对上层范围的引用。众所周知的使用 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

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


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

  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
  1. 在本地范围内创建变量的副本
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 都有自己的副本,所以没有问题。


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


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

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))
  }
}

在这种情况下, ok 不会被遮蔽,它只是被覆盖。这就是为什么 ok 不能改变类型。但是,在范围内这样做会隐藏变量并允许使用不同的类型:

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
}

在这种情况下,在 if 中隐藏 err 可以保证以前的错误不会受到影响,而如果使用相同的代码,我们在 if 中使用 = 而不是 :=,它不会隐藏变量而是覆盖错误的值。

相关文章
|
21天前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
34 7
|
21天前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
21天前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
94 71
|
20天前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
101 67
|
1天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
26 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
21天前
|
存储 Go
go语言中映射
go语言中映射
33 11
|
13天前
|
Go 数据安全/隐私保护 UED
优化Go语言中的网络连接:设置代理超时参数
优化Go语言中的网络连接:设置代理超时参数
|
Go 索引
Go实战(二)-变量、语句、函数、指针、关键字(下)
Go实战(二)-变量、语句、函数、指针、关键字
114 0
Go实战(二)-变量、语句、函数、指针、关键字(下)
|
Go
Go实战(二)-变量、语句、函数、指针、关键字(中)
Go实战(二)-变量、语句、函数、指针、关键字
102 0
Go实战(二)-变量、语句、函数、指针、关键字(中)
|
Go
Go实战(二)-变量、语句、函数、指针、关键字(上)
Go实战(二)-变量、语句、函数、指针、关键字
115 0
Go实战(二)-变量、语句、函数、指针、关键字(上)