尝试理解Go语言的指针。

huangapple go评论68阅读模式
英文:

Trying to grasp Go's pointers

问题

我已经写了一个小片段,用于递归遍历目录并根据文件扩展名返回一个文件路径切片([]string)。它似乎可以工作,但我无法完全理解指针的概念以及如何正确使用它。

package main

import (
    "path/filepath"
    "io/ioutil"
    "fmt"
)

// 聚合切片应该保存结果(文件路径的切片)
// dir 是当前正在列出的目录
// exts 是应该包含在结果中的扩展名切片
func recurse(aggregator *[]string, dir string, exts *[]string) []string {
    files, _ := ioutil.ReadDir(dir)
    for _, file := range files {
        // 当前文件路径
        path := filepath.Join(dir, file.Name())

        // 如果是目录,递归分析它
        if file.IsDir() {
            *aggregator = recurse(aggregator, path, exts)

        // 否则检查文件是否具有正确的扩展名,并将其添加到聚合器中
        } else {
            for _, ext := range *exts {
                if (filepath.Ext(path) == ext) {
                    *aggregator = append(*aggregator, path)
                    break
                }
            }
        }
    }
    return *aggregator
}

func getTemplates(templatesDir string, templatesExtensions ...string) {
    templates := recurse(&[]string{}, templatesDir, &templatesExtensions)

    // 仅用于测试目的,打印文件路径
    for _, t := range templates {
        fmt.Printf("%s\n", t)
    }
}


func main() {
    getTemplates("./templates", ".tmpl", ".html")
}

主要问题是使用 *aggregator (aggregator *[]string)、&[]string{}*exts (exts *[]string)。我来自 JavaScript 的世界,在那里每个对象基本上都是一个指针,你只能显式地复制对象和数组。在这里,似乎如果我不使用指针(*aggregator 等),这些对象将在每个函数迭代中被复制。

在这种方法中,我是正确的还是有什么遗漏?

英文:

I've written a small snippet to recursively walk a directory and return a slice of files ([]string) based on extension. It seems to work, but I cannot fully get the idea behind pointers and how to properly use it.

package main

import (
    "path/filepath"
    "io/ioutil"
    "fmt"
)

// aggregator slice should hold the result (a slice of filepaths)
// dir is the current directory being listed
// exts is a slice of extensions that should be included in the result
func recurse(aggregator *[]string, dir string, exts *[]string) []string {
    files, _ := ioutil.ReadDir(dir)
    for _, file := range files {
        // current filepath
        path := filepath.Join(dir, file.Name())

        // if directory, recursively analyze it
        if file.IsDir() {
            *aggregator = recurse(aggregator, path, exts)

        // else check if file is of proper extension and add it to aggregator
        } else {
            for _, ext := range *exts {
                if (filepath.Ext(path) == ext) {
                    *aggregator = append(*aggregator, path)
                    break
                }
            }
        }
    }
    return *aggregator
}

func getTemplates(templatesDir string, templatesExtensions ...string) {
    templates := recurse(&[]string{}, templatesDir, &templatesExtensions)

    // for testing purposes just print filepaths
    for _, t := range templates {
        fmt.Printf("%s\n", t)
    }
}


func main() {
    getTemplates("./templates", ".tmpl", ".html")
}

The main question is using *aggregator (aggregator *[]string), &[]string{} and *exts (exts *[]string). I come from javascript world where every object is basically a pointer and you can only copy objects and arrays explicitly. Here, on the other hand, it seems that if I didn't use pointers (*aggregator, etc.), these objects would get copied on each function iteration.

Am I correct in this approach or am I missing something?

答案1

得分: 3

尽管这个可视化示例不是关于Go的(而是关于Java),但它非常适合用来可视化指针和值的使用(1):

正如你所看到的,指针实际上并不包含任何数据,而是指向数据所在的位置。因此,通过指针对数据进行的任何修改实际上是在数据本身上执行的。但是数据不一定驻留在我们使用指针的地方。

在不同的情况下,我们可能需要使用指针。例如,当您想要修改一个特定位置上的实际值(而不是传递这些值)或者当您的数据太大,以至于发送实际内容的成本太高时,您可以使用一个或多个指针指向这个大数据,每个具有指向该数据的指针的人(例如每个函数)都可以读取或修改它。正如我们刚才所说的,我们可以有许多指向同一数据的指针。但是这些指针的值是相同的,即源数据(对象)的地址。

(1)来源

英文:

Though this visualization is not specifically about Go (it's about Java) yet it's a perfect one to actually visualize the usage of pointers and values (1):
尝试理解Go语言的指针。

As you see a pointer does not actually contain any data, but it points to the place where data resides. So any modifications that are made on that data via a pointer, are actually getting performed on the data itself. But the data do not necessarily reside where our pointer is being used.

There are different situations when we might need pointers. For example when you want to modify the actual values in one specific place (and not pass those values around) or when your data is too big that the cost would be just too high to send the actual content around. You can use a/some pointer to this big data and everybody (every function for example) that has a pointer to that data, can read it or modify it. And as just we said, we can have as many pointers to the same data as needed. So there may be many pointers to just the same, one data. The value of these pointers but is the same; which is the address of the source data (object).

(1) Source

答案2

得分: 0

在Go语言中,切片(slices)是引用类型,与映射(maps)和指针(我很确定字符串也是引用类型,但不要引用我,可以在这里查看相关讨论:链接)。因此,这些特定类型会自动按引用传递。因此,实际变量本身会被评估为一个指针,其中值是引用地址。
所以在你的情况下,将aggregator *[]string改为aggregator []string是安全的,而且可能更好,因为只传递了一个引用,而不是复制数据。当然,除了这个改变之外,你还需要修改之前对aggregator进行解引用的所有代码,例如:

// 将这行代码改为
*aggregator = append(*aggregator, path)
// 改为这样
aggregator = append(aggregator, path)

这样做的原因可能源自基于C的语言,其中数组只是指向分配内存起始位置的指针。

**注意:**所有其他类型,包括结构体,在这种模式下都不适用(接口是另一个有趣的例外,可以阅读这篇文章了解更多信息:链接)。此外,这段代码看起来可以通过使用filepath.Walk()函数来大大简化。

关于映射的更多信息,请参考这篇博文:链接

英文:

In go slices are reference types, along with maps and pointers (I'm pretty sure strings are too don't qoute me on it though 尝试理解Go语言的指针。 see here for a discussion on this). So these particular types are automatically passed by reference. So the actual variable itself evaluates as a pointer where the value would be the reference address.
So in your case it would be safe and probably preferable to change aggregator *[]string to aggregator []string and your data will not be copied just a reference passed. Of course along with this change you will need to change all code that was previously dereferencing aggregator e.g.

// Change this line
*aggregator = append(*aggregator, path)
// To this
aggregator = append(aggregator, path)

The reasoning for doing this would likely stem from C based languages where arrays are simply pointer to the start of allocated memory.

Note: All other types including structs do not follow this pattern (interfaces are kind of another exception interesting read). Also this code looks like it could be greatly simplified with filepath.Walk().

For a bit more information although more targeted at maps see this blog post.

huangapple
  • 本文由 发表于 2017年3月4日 17:41:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/42594475.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定