关于切片参数传递的问题

这篇具有很好参考价值的文章主要介绍了关于切片参数传递的问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:在 Golang 中函数之间传递变量时总是以值的方式传递的,无论是 int,string,bool,array 这样的内置类型(或者说原始的类型),还是 slice,channel,map 这样的引用类型,在函数间传递变量时,都是以值的方式传递,也就是说传递的都是值的副本。

在使用ioutil的ReadAll方法时查看了其内部实现如下,这让我很痛苦,不明白为什么要这样写。下面我们就来一探究竟。

func ReadAll(r Reader) ([]byte, error) {
  b := make([]byte, 0, 512)
  for {
    if len(b) == cap(b) {
      // Add more capacity (let append pick how much).
      b = append(b, 0)[:len(b)]
    }
    n, err := r.Read(b[len(b):cap(b)])
    b = b[:len(b)+n]
    if err != nil {
      if err == EOF {
        err = nil
      }
      return b, err
    }
  }
}

讨论这个问题之前先看一下标准库中切片的内部结构

type SliceHeader struct {
    Data uintptr
    Len int
    Cap int
}

由切片的结构定义可知,切片的结构由三个信息组成:

  1. 指针Data,指向底层数组中切片指定的开始位置
  2. 长度Len,即切片的长度
  3. 容量Cap,也就是最大长度,即切片开始位置到数组的最后位置的长度

最开始我想将一个文件内容读取到内存,我想到的操作是这样的

func f1() {
  f, _ := os.Open("F:\\hello.txt")
  b := make([]byte, 0, 512)

  read, err := f.Read(b)
  if err != nil {
    return
  }
  fmt.Println(read)//打印0
  fmt.Println(b)//打印[]
}

为什么会出现这种情况呢?我们点开f.Read方法看到 Read reads up to len(b) bytes from the File. 读取len(b) 长度byte的数据到b,那现在len(b)=0就一个字节都不会读取了。这时候你就会明白为什么上面标准库中ReadAll参数为什么要用b[len(b):cap(b)] (对切片的任何操作都会复制一个切片b[len(b):cap(b)] 操作对b切片结构体进行了复制,产生了新的切片并且新切片的len=cap=512,这也就解释了为什么数据能读入b[len(b):cap(b)] 了)。观察下面代码:

func f2() {
  f, _ := os.Open("F:\\hello.txt")
  b := make([]byte, 0, 512)
  //
  c := b[len(b):cap(b)]
  
  fmt.Println(len(c))//512
  fmt.Println(cap(c))//512
  
  read, err := f.Read(c)

  if err != nil {
    return
  }
  
  fmt.Println(read)//512
  fmt.Println(b)//[]
  fmt.Println(b[:cap(b)])//[...] 打印出了数据
  fmt.Println(c)//[...]打印出了和上面相同的数据
}

这就奇怪了不是说是引用传递吗,为什么现在c作为参数传进Read方法后值被改变了。这就需要看切片的内部结构了,切片本身并不承载数据。它只是一个有三个属性的结构体,传递时,就会把这个结构体的三个属性复制一份进行传递,而且复制后头指针指向相同的地址。另外还有一个重要的概念:对切片的任何操作都会复制一个切片(并不是复制切片数据,二十切片的结构体,他们指向的内存区域还是一样的),也就是复制上面说的三个属性。读取切片类型数据的另一个重要属性就是len,len是多少那就会读多少数据,虽然由b衍生出的其他结构体他们的头指针的地址是一样的,后面的数据也是一样的,但是如果你的len是0那头指针后面的数据一个byte也不属于你,也就读不出来,你有多少的len那么头指针后就有多少数据属于你。

这也就解释了为什么b始终是空的了,虽然你的头指针后面有数据被填充了,但是你的len始终是0那么数据都与你无关也是就是空了。c切片的头指针与b相同但是len和cap不同都是512。所以就能读取出头指针后512bytes的数据了。

另外还要讨论切片的扩容机制,当切片的len=cap时使用append方法会触发内置的扩容机制cap会扩大。我就有些疑问为什么是b = append(b, 0)[:len(b)] ,因为使用append函数仅仅是为了触发扩容,添加进去的0是无意义的,原来len=512现在就变成了513,再往后填充数据就会导致与原数据不一致的问题,因此要把添加的byte去除。

func f3() {
  f, _ := os.Open("F:\\hello.txt")
  b := make([]byte, 0, 512)
  for {
    if len(b) == cap(b) {
      // Add more capacity (let append pick how much).
      //b = append(b, 0)[:len(b)]
      b = append(b, 0)
    }
    n, err := f.Read(b[len(b):cap(b)])

    b = b[:len(b)+n]
    if err != nil {
      if err == io.EOF {
        err = nil
      }
      break
    }
  }
  fmt.Println(string(b))
}

可以看到使用b = append(b, 0) 会导致部分数据失真。
关于切片参数传递的问题文章来源地址https://www.toymoban.com/news/detail-463022.html

到了这里,关于关于切片参数传递的问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang 中空的切片转化成 JSON 后变为 null 的问题如何解决?

    目录 问题 原因 小结 在 Golang 中,经常需要将其他类型(例如 slice、map、struct 等类型)的数据转化为 JSON 格式。有时候转化的结果并不是预期中的,例如将一个空的切片转化为 JSON 时,会变成\\\"null\\\",而并非预期的\\\"[]\\\"。示例代码如下: 运行示例看下结果: 结果输出的值为 “

    2024年02月07日
    浏览(34)
  • 关于postman传递两个参数@RequestBody注解只能有一个!

    1.一个bean一个集合,集合前面必须加注解  postman的传参写法  ========================================================================= 2.一个bean和一个数组,数组前面是不用加注解的也可以接收的上 postman传参    

    2024年02月11日
    浏览(27)
  • 01-将函数参数化进行传递

    项目源码:https://github.com/java8/ 在我们进行开发中,经常需要面临需求的不断变更,我们可以将 行为参数化 以适应不断变更的需求。 行为参数化就是可以帮助我们处理频繁变更的需求的一种软件开发模式 我们可以将代码块作为参数传递给方法。 例如,现有一个仓库,我们想

    2024年02月15日
    浏览(35)
  • 【Python】函数进阶 ① ( 函数返回多个返回值 | 函数参数传递类型简介 | 位置参数 | 关键字参数 )

    在函数中 , 如果要 返回 多个返回值 , 可以 在 return 语句中 , 设置多个返回值 , 这些返回值之间使用 逗号 隔开 , 这些返回值的类型是 元组 tuple 类型的 ; 在下面的代码中 , 返回了 3 个返回值 , 其 本质上是返回了一个包含 3 个元素的 元组 数据容器 , 可以使用多重赋值将返回的

    2024年02月11日
    浏览(40)
  • Sql 函数传递参数 字符串拼接

    使用场景 一个计算价格的函数,多个存储过程调用,因业务需求经常要新增参数,避免修改函数时程序执行存储过程报错,将多个参数拼接为一个字符串传递

    2024年02月10日
    浏览(38)
  • python基础----05-----函数的多返回值、函数的多种参数使用形式、函数作为参数传递、lambda匿名函数

    分为以下四种。 位置参数调用函数时根据函数定义的参数位置来传递参数,传递的参数和定义的参数的顺序及个数必须一致。 函数调用时通过“键=值”形式传递参数。 作用:可以让函数更加清晰、容易使用,同时也清楚了参数的顺序需求。 注意: 函数调用时,如果有位置参

    2024年02月08日
    浏览(43)
  • Fragment为什么不用构造函数传递参数?

    Fragment 的构造方法通常不建议直接传递参数。我们先来看一下Fragment源码: 在源码中会发现,Fragment的构造函数是空的,所以他和普通类的创建对象的方式不太一样。接着我们看源码:

    2024年01月24日
    浏览(63)
  • Day6: Shell函数和参数传递

    学习目标 学习内容 1. 函数的定义和调用 2. 参数传递 3. 返回值 4. 练习任务 大树哥个人信息 学习Shell中函数的概念和用法。 理解如何在函数中定义和调用命令序列。 掌握如何传递参数给函数并获取返回值。 练习编写脚本,使用函数进行模块化编程。 今天我们将学习如何在

    2024年02月15日
    浏览(29)
  • C语言如何传递参数到函数,并从函数中返回值?

    假设我们现在要写一个函数,名字叫做\\\"吃饭\\\"。这个函数的功能是传入参数\\\"食物\\\",然后从函数中返回一个值\\\"满足程度\\\"。我们来看看如何实现吧! 我们需要写一个函数原型,声明这个函数的存在和参数的类型。嗯,函数原型就像餐厅的菜单,告诉顾客有哪些选择和怎么点菜。

    2024年02月12日
    浏览(33)
  • C 语言函数完全指南:创建、调用、参数传递、返回值解析

    函数是一段代码块,只有在被调用时才会运行。 您可以将数据(称为参数)传递给函数。 函数用于执行某些操作,它们对于重用代码很重要:定义一次代码,并多次使用。 事实证明,您已经知道什么是函数了 例如, main() 是一个函数,用于执行代码, printf() 也是一个函数,

    2024年02月03日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包