Go语言的slice的copy方法探究

118 阅读3分钟

背景

Go语言开发过程中遇到需要拷贝一个slice的操作,go提供 builtin 库提供了一个copy方法

恰好在写代码时看到一段copy使用的代码 会输出怎样的结果?

src := []string{"ip", "tcp", "http"}
dst := make([]string, 0, len(src))
copy(dst, src)
fmt.Printf("src=%v \n dst %v\n", src, dst)

分析原因

image.png

  1. copy函数拷贝的数量是 len(src) 和 len(dst) 中的最小值。
  2. 拷贝操作从src中取最小个元素从头开始覆盖dst中的元素,dst剩余的元素保留
  3. 特别:入参支持string 拷贝到 bytes 类型的数组

深浅拷贝

本质是浅拷贝,验证得出结论

  1. slice元素为指针类型的,浅拷贝
  2. slice元素为非指针类型的,深拷贝

验证代码

package main

import (
   "fmt"

   "github.com/aws/aws-sdk-go-v2/aws"
)

type Parent struct {
   Num  *int    `json:"num"`
   Name *string `json:"name"`
}


func main() {

   src := []string{"a", "b", "c"}
   dst := make([]string, 0, len(src))
   copy(dst, src)
   fmt.Printf("src=%v \n dst %v\n", src, dst)

   //拷贝的使用
   TestSliceCopy()

   //元素是指针类型: 浅拷贝
   TestSliceCopyTypePointElement()

   //元素非指针类型: 元素改变时
   TestSliceCopyTypeNoPointElement()
}

func TestSliceCopy() {
   size := 3
   //初始化并打印
   src := initSlice(size, "src")
   printSlice(src, "src")

   //执行拷贝,初始化元素数量相等,这是一般的拷贝使用方法
   dst1 := make([]*Parent, len(src))
   copy(dst1, src)
   printSlice(dst1, "dst1")

   //误区:初始化元素数量为0,拷贝不到任何元素, 原因是:【copy函数数量是 len(src) 和 len(dst) 中的最小值】
   dst2 := make([]*Parent, 0, len(src))
   copy(dst2, src)
   //slice3没有拷贝到任何元素:原因:由于len(dst2)==0,所以不会被拷贝
   printSlice(dst2, "dst2-0个元素")
   dst2 = make([]*Parent, 1, len(src))
   copy(dst2, src)
   printSlice(dst2, "dst2-1个元素")

   //【copy函数数量是 len(src) 和 len(dst) 中的最小值】
   dst3 := make([]*Parent, size+1)
   copy(dst3, src)
   //dst3,第4个为nil 原因是,被拷贝的
   printSlice(dst3, "dst3")

   // 初始化5个元素,再拷贝从有3个元素的slice1过来,结果:前3个数据被覆盖
   dst5 := initSlice(5, "dst5")
   printSlice(dst5, "dst5-初始化")
   copy(dst5, src)
   printSlice(dst5, "dst5-拷贝后")

   /**
    * 结论1:copy函数拷贝的数量是 len(src) 和 len(dst) 中的最小值copyNum,
    * 拷贝操作从src中取copyNum个元素从头开始覆盖dst中的元素,dst剩余的元素保留
    */

}

func printSlice(slice []*Parent, name string) {
   println(fmt.Sprintf("------%s------len=%d", name, len(slice)))
   for i := range slice {
      if slice[i] == nil {
         println(fmt.Sprintf("index-%d addr=%p is nil", i, slice[i]))
         continue
      }
      println(fmt.Sprintf("index-%d: num=%v,name=%v,addr=%p,parent=%#v", i, aws.ToInt(slice[i].Num), aws.ToString(slice[i].Name), slice[i], slice[i]))
   }
   println(fmt.Sprintf("------%s------", name))
}

func printNotPointSlice(slice []Parent, name string) {
   println(fmt.Sprintf("------%s------len=%d", name, len(slice)))
   for i := range slice {
      println(fmt.Sprintf("index-%d: num=%v,name=%v,addr=%p,parent=%#v", i, aws.ToInt(slice[i].Num), aws.ToString(slice[i].Name), &slice[i], slice[i]))
   }
   println(fmt.Sprintf("------%s------", name))
}

func TestSliceCopyTypePointElement() {
   size := 3
   src := initSlice(size, "src")
   printSlice(src, "src")

   // 拷贝到slice2
   dst := make([]*Parent, len(src))
   printSlice(dst, "dst-拷贝前")
   copy(dst, src)
   printSlice(dst, "dst-拷贝后")
   // 拷贝后地址

   // 更新slice2的name
   for i := range dst {
      newName := aws.ToString(dst[i].Name) + "_Updated"
      dst[i].Name = &newName
   }
   printSlice(dst, "dst-更新名称")
   printSlice(src, "src-名称变化")

}

func TestSliceCopyTypeNoPointElement() {
   size := 3
   src := initNotPointSlice(size, "src")
   printNotPointSlice(src, "src")

   // 拷贝到slice2
   dst := make([]Parent, len(src))
   printNotPointSlice(dst, "dst-拷贝前")
   copy(dst, src)
   printNotPointSlice(dst, "dst-拷贝后")

   // 更新slice2的name
   for i := range dst {
      newName := aws.ToString(dst[i].Name) + "_Updated"
      dst[i].Name = &newName
   }
   printNotPointSlice(dst, "dst-更新名称")
   printNotPointSlice(src, "src-名称变化")

}

func initSlice(size int, namePrefix string) []*Parent {
   slice := make([]*Parent, 0, size)
   for idx := 0; idx < size; idx++ {
      var num = idx + 1
      slice = append(slice, &Parent{
         Name: aws.String(fmt.Sprintf("%s_%d", namePrefix, num)),
         Num:  &num,
      })
   }
   return slice
}

func initNotPointSlice(size int, namePrefix string) []Parent {
   slice := make([]Parent, 0, size)
   for idx := 0; idx < size; idx++ {
      var num = idx + 1
      slice = append(slice, Parent{
         Name: aws.String(fmt.Sprintf("%s_%d", namePrefix, num)),
         Num:  &num,
      })
   }
   return slice
}