提问人:Jason Murphy 提问时间:4/15/2022 更新时间:4/16/2022 访问量:2343
不理解切片和指针 [重复]
Not understanding slices and pointers [duplicate]
问:
当前项目让我采用一个结构体(带有注释标签)并将数据写成一个平面文件。此文件是列式文件,因此数据的定位非常重要。这些位置和长度在我的 struct 标签中设置在字段级别。
我遇到的问题是,我将指向我的 []字节结果切片的指针传递给我的函数,但无论我做什么,原始切片都没有容纳数据。这是一个简短的示例代码,演示了我正在做什么。
package main
import (
"fmt"
"strconv"
)
func writeInt(value int, fieldData *[]byte, col, length int) {
v := fmt.Sprintf("%+0" + strconv.Itoa(length) +"d", value)
copyData(fieldData, v, col, length)
}
func writeString(value string, fieldData *[]byte, col, length int) {
v := fmt.Sprintf("%-" + strconv.Itoa(length) + "s", value)
copyData(fieldData, v, col, length)
}
func copyData(fieldData *[]byte, v string, col, length int) {
data := *fieldData
if len(data) < col + length {
temp := make([]byte, col + length - 1)
copy(temp, data)
data = temp
}
copy(data[col - 1:length], v)
fieldData = &data
}
func main() {
var results []byte
writeInt(13, &results, 1, 3)
writeString("TEST", &results, 4, 10)
fmt.Print(results)
}
预期结果(字符串)应为:
'013TEST ' - zero pad in front of int and space pad behind string
但我正在得到 []
我是完全看错了,还是我只是不明白什么?
答:
事先注意:不要使用指向切片的指针(切片已经是指向后备数组的小标头)。您可以在没有指针的情况下修改元素,如果您需要修改标头(例如,将元素附加到其中),请返回新切片,就像内置一样。append()
此外,您尝试执行的操作更容易通过字节实现。缓冲区
类型。它实现了,你可以直接写入其中(甚至使用 ),并以 a 或 a 的形式获取其内容。io.Writer
fmt.Fprint()
[]byte
string
每个参数都是传递值的副本。修改参数只会修改此副本。
如果这样做:
fieldData = &data
即使是一个指针,您也只是在修改副本。您必须修改指向的值:fieldData
*fieldData = data
打印结果:
fmt.Println(results)
fmt.Printf("%q\n", string(results))
输出(在 Go Playground 上尝试):
[43 49 51 84 69 83 84 32 32 32 0 0 0]
"+13TEST \x00\x00\x00"
请参阅 icza 的回答,尤其是“事先注意”部分,了解您的具体情况。有关指针的一般性讨论,请继续阅读;请注意,其中只有一部分是特定于 Go 本身的。
指针的作用是为您提供间接级别。某些语言(包括 Go)使用“按值传递”机制,无论您是将常量值还是变量传递给某个函数:
f(3)
艺术
f(x)
该函数接收值,而不是变量的名称或类似的东西。(其他语言则不同,在某些情况下具有“按名称传递”、“1通过引用传递”或“值-结果”语义。参见 为什么这么多语言 [使用 pass] by value?当没有变量时,接收值(而不是变量的名称或类似的东西)这一事实很有帮助,例如在本例中,或者对于:f
f
f(3)
f(x + y)
我们必须先进行求和,因此不涉及单个变量。
现在,特别是在 Go 中,函数可以而且经常有多个返回值:
func g(a int) (bool, int, error) { ... }
因此,如果我们希望能够更新某些内容,我们可以编写:
ok, x, err = g(x)
它收集了所有三个值,包括更新的 ,就在我们想要的地方。但这确实暴露了更新的细节,和/或可能不方便。如果我们想给某个函数权限来更改存储在某个变量中的某些值,我们可以定义我们的函数来获取指向该变量的指针:x
x
func g2(a *int) (bool, error) { ... }
现在我们可以写.ok, x, err = g(x)
ok, err = g2(&x)
对于这种特殊情况,这根本没有太大的改进。但是假设现在不是简单的 ,而是一个具有一堆复杂状态的结构,例如,将从一系列文件中读取输入的东西,并自动切换到下一个文件:int
x
x := multiFileReader(...)
现在,如果我们希望多文件读取器的某些部分能够访问任何结构所表示的各种字段,我们可以使自己成为一个指针变量,指向 .然后:x
x
struct
str, err := readNextString(x)
传递一个指针(因为是一个指针),它允许更新中的某些字段。(如果是一个方法,例如,一个 ,则相同的一般逻辑也适用,但在这种情况下,我们开始使用 ,这会增加一堆额外的皱纹。我在这里忽略了那些专注于指针方面的内容。x
readNextString
x
x
io.Reader
interface
添加间接性会增加复杂性
当我们做这种事情时——传递一个指向原始变量的指针值,其中原始变量本身包含一些初始值或中间值,我们随时更新它——接收此指针值的函数现在有一个类型为指向 T 的变量,用于某种类型 T。这个额外的变量是一个变量。这意味着我们可以为其分配一个新值。如果我们这样做,我们将失去原始值:
func g2(a *int) (bool, error) {
... section 1: do stuff with `*a` ...
var another int
a = &another
... section 2: do more stuff with `*a` ...
return ok, err
}
在第 1 节中,指向并因此引用调用方传递给的任何变量。进行更改将显示在那里。但在第 2 节中,指向 ,并且最初为零(因为为零)。对此处进行更改不会显示在调用方中。a
*a
g2
*a
a
another
*a
another
*a
如果您愿意,您可以避免直接使用:*a
func g2(a *int) (bool, error) {
b := *a
p := &b
var ok bool
// presumably there's a `var err error` or equivalent too
for attempts := 0; !ok && attempts < 5; attempts++ {
... do things ...
... if things are working well, set ok = true and set p = a ...
... update *p ...
}
return ok, err
}
有时这是你想要的那种事情:如果事情进展不顺利,我们小心地不要通过覆盖来覆盖,但如果事情进展顺利,我们通过在“更新*p”部分中指向来覆盖。但更难推理的是:在你诉诸这种事情之前,确保你得到了一个非常明显的好处。*a
b
*a
p
a
当然,如果我们有一个指向某个变量的指针存储在变量中,我们也可以获取指向该变量的指针:.这给了我们另一个机会来增加另一个层次的间接性,这给了我们另一个机会,依此类推。它是一路向下,指针一路向上,除了在最后,我们必须有某种最终答案,比如.i := 3; a := &i; pa := &a
ppa := &pa
i
1按名称传递特别棘手,而且不是很常见;请参阅什么是“按名称传递”以及它是如何工作的?但这确实导致了尼克劳斯·沃斯(Niklaus Wirth)曾经讲过的关于自己的一个大笑话,有些人会说“尼克·虱子·维尔特”(Nick-louse Veert),因此会直呼其名,而另一些人会说“尼克尔的价值”(Nickle's Worth),因此会用价值来称呼他。😀 (我想我是二手的——我不认为我在那里的时候他从来没有来过U。
上一个:切片到数组指针的转换
评论
fieldData = &data
与你想要的相反:它用指向局部变量的指针覆盖局部变量。你想要的更有可能是,用 中的数据覆盖 所指向的内存。fieldData
data
*fieldData = data
fieldData
data