深浅本质区别
- 是否真正获取到了被拷贝对象的单独掌控权,而不会产生互相影响的问题!
深拷贝(Deep Copy)
定义
- 拷贝之后互不影响,即深可以理解为完全拷贝!
- 拷贝的是数据本身,创造一个一样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
- 这样就不会产生引用问题,导致GC无法回收,也不会导致内存泄露问题!
- 如果结构中不含指针,则直接赋值就是深度拷贝;
值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。
什么时候需要深拷贝
在函数中需要更改结构体变量内容而对函数外的变量不产生影响时,需要深拷贝。
示例:
值类型就是深拷贝
/*
* @Description:
* @version:
* @Author: Steven
* @Date: 2022-10-10 23:44:53
*/
package main
import "fmt"
func main() {
var it int = 20
ita := it
fmt.Println(it, ita)
ita = 30
fmt.Println(it, ita)
it = 40
fmt.Println(it, ita)
}
浅拷贝(Shallow Copy)
定义
- 拷贝之后互相影响,即浅拷贝可以理解为只拷贝了一部分,比如引用类型,只会拷贝指向的底层数据指针,并不会拷贝底层数据!
- 即拷贝对象和被拷贝对象指向的内存地址是一样的,所以会互相影响!只有都被释放时,才会同时释放内存地址。
- 浅拷贝会引起内存泄露、GC不能回收等问题!
- 如果结构体中含有指针(包括自定义指针,以及切片,map等使用了指针的内置类型),则数据源和拷贝之间对应指针会共同指向同一块内存,这时如果想深度拷贝需要特别处理。
- 引用类型的数据,默认全部都是浅复制,Slice,Map
什么时候需要浅拷贝
很明显,仅当我们想让两个变量的修改互相被影响时,才使用浅拷贝,其他任何情况都应该使用深拷贝!
示例
- 直接将引用类型变量赋值给另一个变量,属于浅拷贝
- 将引用类型变量作为实参传给函数,也属于浅拷贝
- append,不扩容的话也属于浅拷贝
- copy拷贝的切片中的元素,如果元素是引用类型,那么此时也属于浅拷贝!
将一个引用类型变量赋值给另一个变量
/*
* @Description:
* @version:
* @Author: Steven
* @Date: 2022-10-10 23:44:53
*/
package main
import "fmt"
func main() {
var sl []int = make([]int, 3, 4)
fmt.Println("sl=", sl) //sl= [0 0 0]
sla := sl //这就属于浅拷贝
fmt.Println("sla=", sla) //sla= [0 0 0]
sla[0] = 2
sl[1] = 4
fmt.Printf("sl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl)
fmt.Printf("sla: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sla, sla, sla)
}
运行结果:
sl= [0 0 0]
sla= [0 0 0]
sl: 为该变量分配的地址为=0xc000092060;指向内存地址=0xc0000a4060;值=[2 4 0]
sla: 为该变量分配的地址为=0xc000092090;指向内存地址=0xc0000a4060;值=[2 4 0]
new也属于浅拷贝
/*
* @Description:
* @version:
* @Author: Steven
* @Date: 2022-10-10 23:44:53
*/
package main
import "fmt"
func main() {
var sl []int = make([]int, 3, 4)
fmt.Println("sl=", sl) //sl= [0 0 0]
sla := new([]int)
*sla = sl //这就属于浅拷贝
//sla = &sl和*sla = sl等价的
fmt.Println("sla=", sla) //sla= [0 0 0]
(*sla)[0] = 2
sl[1] = 4
fmt.Printf("sl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl)
fmt.Printf("sla: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sla, *sla, sla)
}
sl= [0 0 0]
sla= &[0 0 0]
sl: 为该变量分配的地址为=0xc000008078;指向内存地址=0xc00000e1e0;值=[2 4 0]
sla: 为该变量分配的地址为=0xc00000a030;指向内存地址=0xc00000e1e0;值=&[2 4 0]
Copy
元素类型为值类型(深拷贝)
/*
* @Description:
* @version:
* @Author: Steven
* @Date: 2022-10-10 23:44:53
*/
package main
import "fmt"
func main() {
var sl []int = make([]int, 3, 4)
sl[0] = 20
sl[1] = 200
sl[2] = 2000
cpSl := make([]int, len(sl))//由于元素类型为值类型,所以这里是深度拷贝
copy(cpSl, sl)
fmt.Printf("sl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl)
fmt.Printf("cpSl: 为该变量分配的地址为=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl)
}
运行结果:很明显指向的内存地址不是一个!
D:\GOMOD\hello>go run hello.go
sl: 为该变量分配的地址为=0xc000008078;指向内存地址=0xc00000e1e0;值=[20 200 2000]
cpSl: 为该变量分配的地址为=0xc000008090;指向内存地址=0xc000010210;值=[20 200 2000]
元素类型为引用类型(浅拷贝)
/*
* @Description:
* @version:
* @Author: Steven
* @Date: 2022-10-10 23:44:53
*/
package main
import "fmt"
func main() {
var sl [][]int = make([][]int, 3, 4)
sl[0] = make([]int, 1, 2)
sl[0] = []int{1}
sl[1] = make([]int, 1)
sl[1] = []int{2}
sl[2] = make([]int, 1)
sl[2] = []int{3}
cpSl := make([][]int, len(sl)) //由于元素类型为引用类型,所以这里是浅拷贝
copy(cpSl, sl)
//由于是二维切片,此时sl 通过%p获取的是系统为第二维的切片值分配的内存地址,而不是最终指向数据的地址
//&sl获取到的系统为变量sl分配的内存地址
//&sl[0]和sl通过%p获取的地址都是为了保存第二维的切片又开辟的一个内存空间的地址,即[][]int,为了保存后面的[]int数据而开辟的空间
//sl[0]通过%p获取到的地址才是一个内存空间地址,指向的数据类型为Int的数组,具体指向的是数组的第一个元素
fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
sl[0][0] = 11
fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
cpSl[1][0] = 22
fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
}
运行结果:
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[1] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a098;值=[[1] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [22] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a098;值=[[11] [22] [3]]
- 很明显最终指向数组内存空间的地址是一个
- 而我们分别修改两个变量都会彼此影响
如何解决上面引用类型浅拷贝问题呢
化简为繁,我们依次拷贝二维切片第二维的每一个元素!
/*
* @Description:
* @version:
* @Author: Steven
* @Date: 2022-10-10 23:44:53
*/
package main
import "fmt"
func main() {
var sl [][]int = make([][]int, 3, 4)
sl[0] = make([]int, 1, 2)
sl[0] = []int{1}
sl[1] = make([]int, 1)
sl[1] = []int{2}
sl[2] = make([]int, 1)
sl[2] = []int{3}
cpSl := make([][]int, len(sl)) //由于元素类型为引用类型,所以这里是浅拷贝
for k, v := range sl {//依次拷贝!
cpSl[k] = make([]int, len(v))
copy(cpSl[k], v)
}
//copy(cpSl, sl)
fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
sl[0][0] = 11
fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
cpSl[1][0] = 22
fmt.Printf("sl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &sl, sl, sl[0], sl)
fmt.Printf("cpSl: 为该变量分配的地址为=%p;第一个元素切片的地址=%p;指向内存地址=%p;值=%v\n", &cpSl, cpSl, cpSl[0], cpSl)
}
D:\GOMOD\hello>go run hello.go
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[1] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a0e0;值=[[1] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a0e0;值=[[1] [2] [3]]
sl: 为该变量分配的地址为=0xc000008078;第一个元素切片的地址=0xc000060060;指向内存地址=0xc00001a098;值=[[11] [2] [3]]
cpSl: 为该变量分配的地址为=0xc000008090;第一个元素切片的地址=0xc000062050;指向内存地址=0xc00001a0e0;值=[[1] [22] [3]]
结构体
结构体拷贝:
- 字段类型为引用类型或者指针类型,那么就会被浅拷贝
- 字段类型为值类型,那么就是深拷贝
- 接口虽然也是引用类型,但是属于深拷贝
/*
* @Description:
* @version:
* @Author: Steven
* @Date: 2022-10-10 23:44:53
*/
package main
import "fmt"
type goods struct {
name string
sl []int
ptr *int
inter interface{}
}
func main() {
var ga goods = goods{}
ga.name = "辣条"
ga.sl = make([]int, 2)
ga.sl[0] = 20
ga.ptr = new(int)
ga.inter = "aaaaa"
gb := ga
fmt.Printf("ga[v=%+v;];gb[v=%+v]\n", ga, gb)
gb.sl[1] = 30
gb.name = "汉堡"
gb.inter = "bbbbb"
fmt.Printf("ga[v=%+v;];gb[v=%+v]\n", ga, gb)
ga.sl[1] = 40
ga.name = "可乐"
ga.inter = "cccc"
fmt.Printf("ga[v=%+v;];gb[v=%+v]\n", ga, gb)
}
运行结果:
D:\GOMOD\hello>go run hello.go
ga[v={name:辣条 sl:[20 0] ptr:0xc0000a6058 inter:aaaaa};];gb[v={name:辣条 sl:[20 0] ptr:0xc0000a6058 inter:aaaaa}]
ga[v={name:辣条 sl:[20 30] ptr:0xc0000a6058 inter:aaaaa};];gb[v={name:汉堡 sl:[20 30] ptr:0xc0000a6058 inter:bbbbb}]
ga[v={name:可乐 sl:[20 40] ptr:0xc0000a6058 inter:cccc};];gb[v={name:汉堡 sl:[20 40] ptr:0xc0000a6058 inter:bbbbb}]