读者接口更改值

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

The Reader interface change value

问题

我对阅读器接口有一个问题,定义如下:

type Reader interface {
    Read(p []byte) (n int, err error)
}

我有以下使用阅读器接口的代码:

package main

import (
    "fmt"
    "os"
)

// 读取文件时需要检查大多数调用是否出错。
// 下面的辅助函数将简化我们下面的错误检查。
func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {

    // 通常,您会希望更多地控制如何读取文件的哪些部分。
    // 对于这些任务,首先通过`Open`打开一个文件以获取`os.File`值。
    f, err := os.Open("configuration.ini")
    check(err)

    // 从文件开头读取一些字节。
    // 允许最多读取5个字节,但也要注意实际读取了多少个字节。
    b1 := make([]byte, 10)
    n1, err := f.Read(b1)
    check(err)
    fmt.Printf("%d bytes: %s\n", n1, string(b1))

    f.Close()

}

如您所见,上面的代码中,b1被定义为字节切片,并作为值参数传递给Read方法。在Read方法之后,b1包含来自文件的前10个字母。

对于上面的代码,让我感到困惑的是,为什么在Read方法之后,b1突然包含了值。

在Go语言中,当我将一个值传递给方法时,它将作为值传递,而不是作为引用传递。为了澄清我所说的,我制作了一个示例应用程序:

package main


import (
    "fmt"
)

func passAsValue(p []byte) {
    c := []byte("Foo")
    p = c
}

func main() {

    b := make([]byte, 10)
    passAsValue(b)
    fmt.Println(string(b))
}

passAsValue函数之后,b不包含任何值,这是我在Go语言中预期的,参数将作为值传递给函数或方法。

那么,为什么第一个代码片段可以更改传递参数的内容呢?如果Read方法期望[]byte切片的指针,那么我会同意,但在这种情况下不是这样。

英文:

I have a question about the reader interface, the definition looks like:

type Reader interface {
    Read(p []byte) (n int, err error)
}

I have following code that use the reader interface:

package main

import (
	"fmt"
	"os"
)

// Reading files requires checking most calls for errors.
// This helper will streamline our error checks below.
func check(e error) {
	if e != nil {
		panic(e)
	}
}

func main() {

	// You'll often want more control over how and what
	// parts of a file are read. For these tasks, start
	// by `Open`ing a file to obtain an `os.File` value.
	f, err := os.Open("configuration.ini")
	check(err)

	// Read some bytes from the beginning of the file.
	// Allow up to 5 to be read but also note how many
	// actually were read.
	b1 := make([]byte, 10)
	n1, err := f.Read(b1)
	check(err)
	fmt.Printf("%d bytes: %s\n", n1, string(b1))

	f.Close()

}

As you can see the code above, b1 is defined as byte slice and it passed to the Read method as value argument. After the Read method, the b1 contains the first 10 letters from file.

What for me very confusing about the code above is, why does b1 contains suddenly values after the Read method.

In Golang, when I pass a value to the method, it will be passed as value and not as reference. To clarify, what I talking about, I made a sample application:

package main


import (
	"fmt"
)

func passAsValue(p []byte) {
	c := []byte("Foo")
	p = c
}

func main() {

	b := make([]byte, 10)
	passAsValue(b)
	fmt.Println(string(b))
}

After passAsValue function, b does not contain any values and that what I expected in golang, arguments will be pass as value to the function or method.

Why then, the first code snippet can change the content of the passed argument? If the Read method expects a pointer of []byte slice, then I would be agreed, but on this case not.

答案1

得分: 3

在Go语言中,所有的传递都是按值传递的(通过创建传递值的副本)。

但是,由于Go语言中的切片只是底层数组的连续片段的描述符,所以描述符会被复制,而引用的仍然是同一个底层数组。因此,如果你修改切片的内容,底层数组也会被修改。

如果你在函数中修改切片值本身,那么在调用的地方不会反映出来,因为切片值只是一个副本,副本会被修改,而不是原始的切片描述符值。

如果你传递一个指针,指针的值也是按值传递的(指针的值会被复制),但是在这种情况下,如果你修改指针指向的值,那么在调用的地方也会发生相同的修改(指针的副本和原始指针指向同一个对象/值)。

相关博文:

Go切片:用法和内部原理

数组、切片(和字符串):'append'的机制

英文:

Everything is passed by value (by creating a copy of the value being passed).

But since slices in Go are just descriptors for a contiguous segment of an underlying array, the descriptor will be copied which will refer to the same underlying array, so if you modify the contents of the slice, the same underlying array is modified.

If you modify the slice value itself in the function, that is not reflected at the calling place, because the slice value is just a copy and the copy will be modified (not the original slice descriptor value).

If you pass a pointer, the value of the pointer is also passed by value (the pointer value will be copied), but in this case if you modify the pointed value, that will be the same as at the calling place (the copy of the pointer and the original pointer points to the same object/value).

Related blog articles:

Go Slices: usage and internals

Arrays, slices (and strings): The mechanics of 'append'

答案2

得分: 1

在Go语言中,切片头部本身包含了指向底层数组的指针。

你可以从官方博客文章中了解更多信息:https://blog.golang.org/slices

即使切片头部是按值传递的,但头部包含了指向数组元素的指针,因此原始切片头部和传递给函数的头部副本都描述了同一个数组。因此,当函数返回时,通过原始切片变量可以看到修改后的元素。

英文:

The slice header in Go contains in itself a pointer to the underlaying array.

You can read from the official blog post: https://blog.golang.org/slices

>Even though the slice header is passed by value, the header includes a pointer to elements of an array, so both the original slice header and the copy of the header passed to the function describe the same array. Therefore, when the function returns, the modified elements can be seen through the original slice variable.

答案3

得分: 0

这段代码的行为与在C语言中传递指针的行为完全相同:

#include <stdio.h>
#include <stdlib.h>

// p是按值传递的;然而,这个函数不修改p本身,而是修改p指向的值。
void read(int* p) {
    int i;
    for (i = 0; i < 10; i++) {
        p[i] = i + 1;
    }
}

// p是按值传递的,所以在函数作用域之外改变p本身没有效果
void passAsValue(int* p) {
    int* c = (int*)malloc(3 * sizeof(int));

    c[0] = 15; // 'F'的十六进制是15...
    c[1] = 0;
    c[2] = 0;

    p = c;
}

int main() {
    int* p = (int*)malloc(10 * sizeof(int));
    int i;
    for (i = 0; i < 10; i++) {
        p[i] = 0;
    }

    printf("             init : p[0] = %d\n", p[0]);

    read(p);
    printf("       after read : p[0] = %d\n", p[0]);

    passAsValue(p);
    printf("after passAsValue : p[0] = %d\n", p[0]);

    return 0;
}

输出结果:

//             init : p[0] = 0
//       after read : p[0] = 1
//after passAsValue : p[0] = 1 // <- 不是15,passAsValue内部的修改不是持久的

(记录一下:这个C程序泄漏了int* c数组)

Go语言的切片包含的信息比指针多:它是一个小的结构体,包含指针、长度和分配数组的最大容量(参见其他答案中提到的链接:https://blog.golang.org/slices)。但从代码的角度来看,它的行为与C指针完全相同。

英文:

It is the exact same behaviour as passing a pointer in C :

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

// p is passed by value ; however, this function does not modify p,
// it modifies the values pointed by p.
void read(int* p) {
    int i;
    for( i=0; i&lt;10; i++) {
        p[i] = i+1;
    }
}

// p is passed by value, so changing p itself has no effect out
// of the function&#39;s scope
void passAsValue(int*p) {
   int* c = (int*)malloc(3*sizeof(int));

   c[0] = 15; // &#39;F&#39; in hex is 15 ...
   c[1] = 0;
   c[2] = 0;

   p = c;
}

int main() {
    int* p = (int*)malloc(10*sizeof(int));
    int i;
    for( i=0; i&lt;10; i++) {
        p[i] = 0;
    }

    printf(&quot;             init : p[0] = %d\n&quot;, p[0]);

    read(p);
    printf(&quot;       after read : p[0] = %d\n&quot;, p[0]);

    passAsValue(p);
    printf(&quot;after passAsValue : p[0] = %d\n&quot;, p[0]);

    return 0;
}

output :

//             init : p[0] = 0
//       after read : p[0] = 1
//after passAsValue : p[0] = 1 // &lt;- not 15, the modification from
//                             //    within passAsValue is not persistent

(for the record : this C program leaks the int* c array)

A Go slice contains more info than just the pointer : it is a small struct, which contains the pointer, the length, and the max capacity of the allocated array (see the link mentioned in other answers : https://blog.golang.org/slices ).
But from the code's perspective, it behaves exactly like the C pointer.

huangapple
  • 本文由 发表于 2015年4月29日 15:13:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/29936700.html
匿名

发表评论

匿名网友

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

确定