【问题标题】:The Reader interface change valueReader 界面更改值
【发布时间】:2015-07-08 07:46:18
【问题描述】:

我有一个关于阅读器界面的问题,定义如下:

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

我有以下使用阅读器界面的代码:

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

}

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

我对上面的代码非常困惑的是,为什么b1Read 方法之后突然包含值。

在 Golang 中,当我将值传递给方法时,它将作为值而不是作为引用传递。为了澄清,我在说什么,我做了一个示例应用程序:

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 不包含任何值,并且我在 golang 中所期望的,参数将作为值传递给函数或方法。

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

【问题讨论】:

    标签: go slice reader


    【解决方案1】:

    Go 中的切片头本身包含一个指向底层数组的指针。

    可以阅读官方博文:https://blog.golang.org/slices

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

    【讨论】:

      【解决方案2】:

      一切都按值传递(通过创建正在传递的值的副本)。

      但是由于 Go 中的切片只是底层数组的连续段的 描述符,因此描述符将被复制,该描述符将引用相同的底层数组,因此如果您修改 内容 的切片,相同的底层数组被修改。

      如果你在函数中修改了切片值本身,那在调用的地方是不会体现出来的,因为切片值只是一个拷贝,拷贝会被修改(不是原来的切片描述符值)。

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

      相关博客文章:

      Go Slices: usage and internals

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

      【讨论】:

      • 切片完全不是引用类型。它是一个值类型,包含对底层数组的引用。
      • @Anonymous By reference 我的意思和 pointer 不一样。但经过编辑使其明确。谢谢。
      • @Anonymous Reference 类型与 Pointer 类型不同。请查看博客文章Go maps in action“地图类型是引用类型,如指针或切片
      • 我认为那篇文章中将切片描述为引用类型是错误的。
      【解决方案3】:

      这与在C 中传递指针的行为完全相同:

      #include <stdio.h>
      #include <stdlib.h>
      
      // 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<10; i++) {
              p[i] = i+1;
          }
      }
      
      // p is passed by value, so changing p itself has no effect out
      // of the function's scope
      void passAsValue(int*p) {
         int* c = (int*)malloc(3*sizeof(int));
      
         c[0] = 15; // 'F' 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<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 // <- not 15, the modification from
      //                             //    within passAsValue is not persistent
      

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-03
        • 1970-01-01
        • 2021-06-30
        • 2016-05-08
        相关资源
        最近更新 更多