【问题标题】:Do slices or arrays act as a global scope?切片或数组是否充当全局范围?
【发布时间】:2020-06-29 19:06:10
【问题描述】:

我还是编程新手。原谅我缺乏计算机科学知识。不确定这个问题是否特定于 Golang 或一般的计算机科学......

我一直认为函数不会改变它们自己作用域之外的变量/数据,除非你使用 return 语句回到另一个作用域,或者除非它们在作用域的层次结构中更高。有人可能会争辩说,此示例中的函数 f1f2 是从较低范围调用的。但是,这仍然不能解释为什么变量 numnums 得到不同的结果。

package main

import "fmt"

func f1(a int) {
    a = 50 // this will not work, as it shouldn't
}

func f2(a ...int) {
    a[0] = 50 // this will work without return statement
    a[1] = 50 // this will work without return statement
}

func main() {

    num := 2
    nums := []int{2, 2}

    f1(num)
    f2(nums...)

    fmt.Printf("function f1 doesn't affect the variable num and stays: %v\n", num)
    fmt.Printf("function f2 affects the variable nums and results in: %v", nums)

问题:

  1. 为什么f2 不需要return 语句来修改num 之类的num 会在f1内吗?
  2. Golang 函数被称为传递值(而不是引用), 这不应该强制函数返回副本吗?
  3. 这可以在其他语言中发生吗? (我想我可能有 在其他语言中看到过)。

【问题讨论】:

标签: go computer-science


【解决方案1】:

这是正确的行为,因为a ...int 等于一个切片,例如:a []int

func f2(a []int) {
    a[0] = 50
    a[1] = 50
}
func main() {
    b := []int{2, 2}
    f2(b)
    fmt.Println(b) // [50 50]
}

切片是原始数据的视图,这里是“b”。

“为什么 f2 不像 f1 中的 num 那样需要 return 语句来修改 num?”

f2 中,您使用的是切片,它有一个指向原始数组的指针,因此f2 可以更改外部数组。

“据说Golang函数传递值(而不是引用),那不应该强制返回副本吗?(如果问题是相关的......)”

f2 中,切片本身是按值传递的,这意味着指针以及原始数组的长度和容量。

“这会发生在其他语言中吗?(我想我可能已经在其他语言中看到过)”

回答太宽泛了,有很多语言,一般来说,如果你有一个指向外部世界数组的指针,是的。


编辑:

package main

import "fmt"

func sum(a ...int) int {
    s := 0
    for _, v := range a {
        s += v
    }
    return s
}
func f2(a []int) {
    c := make([]int, len(a))
    copy(c, a)
    c[0] = 50
    fmt.Println(sum(c...)) // 52
}
func main() {
    b := []int{2, 2}
    fmt.Println(sum(1, 2, 3, 4)) // 10
    fmt.Println(sum(b...))       // 4

    f2(b)
    fmt.Println(b) // [2 2]
}

注意事项:
上面的sum() 函数是一个纯函数,因为它没有副作用。
上面新的f2 函数是一个纯函数,因为它没有副作用:它将a 复制到c 然后调用sum。

【讨论】:

  • 感谢您的回答。我知道通过引用传递只是意味着它使任何周围的代码影响局部变量。但是,我认为没有 return 语句的函数不会发回任何转换来影响该本地代码。 return 语句可用于指示函数是否覆盖引用。不知道为什么它是这样设计的。我对指针了解不多,所以我仍在消化其他答案。但是,您将其简单地说。谢谢。
  • 准确地说,这个f2 函数不是pure function,因为它有副作用。而切片的哲学就是这样使用它,切片,我认为是 Go 中最重要的数据结构,所以你需要这样理解它,这是设计使然。我希望这会有所帮助。
  • 非常感谢!我刚刚发现有些函数不能是纯的。出于某种原因,我的大脑只在纯函数式编程中思考,所以我不知道。在我将此标记为答案之前,请提出最后一个问题......这也可能对其他人有所帮助。你是说这个函数不纯是因为我的编码方式,还是因为指针使它如此?
  • 换句话说,有没有办法在我编写函数的方式中防止这种情况(不接触切片)?
  • 是的,这是你的选择,让函数变得纯粹:你可以复制原始切片,查看编辑。
【解决方案2】:

在 go 中,函数参数是按值传递的。这意味着,如果您传递一个 int(如在 f1 中),编译器将传递 f1 的值,本质上是复制它。如果函数采用*int 并且您传递&num,则编译器传递&num 的值,它是指向num 的指针。当函数改变*num时,函数外变量的值会改变。如果函数改变numnum的指针值会改变,指向不同的变量。

相比之下,Java 将所有原始值作为值传递,所有对象通过引用传递。也就是说,如果您传递int,则该函数无法修改调用者可见的该 int 的值。如果你想传递一个函数可以修改的 int,你可以把它放在一个类中,然后在 Java 中传递该类的一个实例。

切片(如f2)包含指向底层数组的指针。当您使用切片调用函数时,切片头(包含指向底层数组的指针)会被复制,因此当函数更改切片元素时,底层数组元素也会发生变化。

范围问题有些不同。函数的作用域是它可以看到的所有变量。这些是全局变量(如果来自不同的包,则导出全局变量)、函数参数,如果函数声明为嵌套在另一个函数中,那么此时该函数中的所有变量都是可见的。

【讨论】:

  • 我问的不是切片参考,而是函数的工作原理。我认为这可能是最好的答案,因为第二段最好(到目前为止)解释了为什么该函数会影响切片,即使它没有返回任何内容。它与函数如何访问内存有关。这和一等公民也有关系吗?
  • 一个函数不需要返回一些东西来影响内存中的位置,否则它是不可见的。如果您将指针传递给函数,则该函数可以为该指针分配值以更改指针指向的任何位置。它可能指向函数否则无法访问的变量。
  • 好的....我明白了,因为在您的第一段中,您解释了只有当函数“接受”*int 并“通过”&num 时才会发生功能。我认为这意味着函数参数需要有 *int。但似乎传递给它的 num 变量的指针足以在不改变函数语法的情况下产生这种效果。这是正确的吗?
  • 没有。函数 f 获取 *int 参数,调用 f 的函数传递 &num。您不能将指针传递给未声明为获取指针的函数。即:func f(i *int),而你调用f为:f(&num),那么f可以改变num的值。在您最初的问题中:切片包含一个指针,因此该函数可以更改切片内容。
【解决方案3】:

1 & 2) 在查看切片在 Go 中的工作方式时,这两个问题都可以得到回答。上面有a blog article

一般来说,Go 中所有的变量都是按值传递的。您可以使用指针(例如 *int 代表 f1)通过引用(或更正确地,指针的地址)传递。 但是,从技术上讲,切片也是按值传递的。

当我们查看here 时,我们可以了解它们是如何工作的:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

LenCap 是整数,但 Data 是指针。

当这个结构被复制时(当通过值传递时),LenCapData 的副本将被复制。由于Data 是一个指针,因此对它所指向的值所做的任何修改都将在您的函数返回后可见。

你也可以read this

【讨论】:

  • 感谢您的回答。我试图理解的是为什么函数在没有 return 语句的情况下返回一个值。您通过 struct 示例对它进行了一些解释,让我明白这不是问题的功能。但是,我仍然不明白为什么要使用这种方式设计的指针以及如何/为什么会以这种方式使用。似乎有风险,因为它会创建反模式。有什么优势?
  • @Alex,该函数不返回任何内容。解释指针的历史(因为 Go 中的指针在任何方面都不是特别的)对于 SO 答案来说有点过于宽泛。
  • @JimB 感谢您的回复。我知道该函数不返回任何内容,并且我了解切片是如何引用的。我要问的是为什么该函数没有阻止对切片的引用,因为它没有返回语句。它在做什么?你自己也说了,它不应该返回任何东西。我认为这与函数如何访问内存有关。该函数的行为似乎与其他所有非函数一样。
  • @Alex,对不起,我不明白你的意思。你说你明白这个函数没有返回任何东西,然后你继续问“它在做什么返回一些东西?”。没有返回值,句号。我认为您需要对指针的实际含义以及使用它们的原因进行一些阅读。虽然这里不再是可接受的问题类型,但也许这篇旧帖子可以提供帮助:stackoverflow.com/questions/5727/…
  • 是的,你说得对,我对指针的了解不够。在我看来,指针使函数完全不纯,并将“函数式编程”完全抛出窗口之外。我会对此进行更多研究。谢谢!
猜你喜欢
  • 1970-01-01
  • 2018-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-15
  • 2019-10-10
  • 1970-01-01
相关资源
最近更新 更多