前言
大一学习C++基础时候便接触过这些概念,转Golang之后便没有再专门学习。直到前些日子的这场面试遇到一个问题------
深浅拷贝 修改拷贝的值,是否影响另一个?
浅拷贝什么时候影响,什么时候不影响?(浅拷贝只是拷贝了第一级,所以根据他内容判断,是值类型还是引用类型)(此部分没找见直接文章,待我回头研究一下源码。。)
再结合自己使用经历来看,的确许多时候,对于一个函数的传参、返回值是否需要带*
,还都是有些模糊的。因此专程学习一下相关概念,进行记录。
深浅拷贝
简介
深拷贝:创建一个一样的新对象,新分配一块内存。新旧对象的修改操作互不影响。
浅拷贝:只复制指向对象的指针,新旧对象依旧是同一块内存,修改时一起修改。
Go语言中的深浅拷贝
值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。
引用类型的数据,默认全部都是浅复制,Slice,Map。
测试代码
go
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func main() {
// 浅拷贝
p1 := Person{Name: "Alice", Age: 25}
p2 := &p1
fmt.Printf("浅拷贝 - 修改前:\np1地址:%p\np2地址:%p\n", &p1, &p2)
fmt.Printf("浅拷贝 - 修改前:\np1内容:%+v\np2内容:%+v\n", p1, p2)
p1.Name = "修改后"
fmt.Printf("浅拷贝 - 修改后:\np1地址:%p\np2地址:%p\n", &p1, &p2)
fmt.Printf("浅拷贝 - 修改后:\np1内容:%+v\np2内容:%+v\n", p1, p2)
// 深拷贝
p3 := Person{Name: "Bob", Age: 30}
p4 := p3
fmt.Printf("\n深拷贝 - 修改前:\np3地址:%p\np4地址:%p\n", &p3, &p4)
fmt.Printf("深拷贝 - 修改前:\np3内容:%+v\np4内容:%+v\n", p3, p4)
p3.Name = "修改后"
fmt.Printf("深拷贝 - 修改后:\np3地址:%p\np4地址:%p\n", &p3, &p4)
fmt.Printf("深拷贝 - 修改后:\np3内容:%+v\np4内容:%+v\n", p3, p4)
// new的情况下,看似是深拷贝其实是浅拷贝
p5 := new(Person)
p5.Name = "climber"
p5.Age = 18
p6 := p5
fmt.Printf("\n new的情况下,看似是深拷贝其实是浅拷贝 - 修改前:\np5地址:%p\np6地址:%p\n", &p5, &p6)
fmt.Printf("深拷贝 - 修改前:\np5内容:%+v\np6内容:%+v\n", p5, p6)
p3.Name = "修改后"
fmt.Printf("深拷贝 - 修改后:\np5地址:%p\np6地址:%p\n", &p5, &p6)
fmt.Printf("深拷贝 - 修改后:\np5内容:%+v\np6内容:%+v\n", p5, p6)
}
测试结果:
浅拷贝:
深拷贝:
特殊情况:使用new
特别注意------使用new
时会导致看似深拷贝其实浅拷贝
使用new
时候,这里p6 := p5
,我们发现二者同时进行了修改,但是地址却不一样。
这是因为new创建时候返回的是指针,所以我们p5存的是一个指向X区域的指针,这个指针在A区。进行p6 := p5
时候,相当于对这个A区域的指针进行了复制,复制得到一个在B区的指针(因此打印出来的地址不一样)。但是这个A指针和B指针,都是指向了X区域,所以修改时候会同时修改。
特别注意------结构体的拷贝
顺着上一层new的探索继续思考(其实主要还是那天面试官给的提示...)
所谓深拷贝,其实也只是对第一层的内容进行复制,因此可能导致,复制前后的结构体中,所存的指针依旧指向同一个地址。
见示例代码:
go
package main
import "fmt"
type Person struct {
Name string
Age int
Scores []int
}
func main() {
// 创建原始结构体
original := Person{
Name: "Alice",
Age: 30,
Scores: []int{90, 85, 80},
}
// 对原始结构体进行拷贝
copy := original
// 修改拷贝的结构体的参数
copy.Name = "Bob"
copy.Age = 25
copy.Scores[0] = 95
// 打印原始结构体和拷贝的结构体
fmt.Println("Original:", original)
fmt.Println("Copy:", copy)
}
结果如下:
我们发现,切片中的值被一起修改了,符合预期。
结构体如何实现真正的深拷贝?
通过上述测试可以看出,结构体中的地指针都是发生了浅拷贝,其余参数发生的是深拷贝。那么如何才能正确的实现整个结构体的深拷贝呢?
当结构体中,没有指针时,直接赋值就是深拷贝。
当结构体中存在指针和值时,可以通过反射等方法实现,具体见此文