本质区别
- 值类型:
内存中变量存储的是具体的值,比如:var x int
x存放的是具体的int值,但是变量在内存中的地址可以通过 &num来获取 - 引用类型:
变量直接存放的就是一个地址值, 这个地址值指向的空间存的才是值。比如var prt *int
- 差异性
相同的程序在不同的机器上执行后可能也会有不同的内存地址 - 连续内存
同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。(下面这句话不懂)
- 差异性
值类型、引用类型都包括什么?
- 值类型(6):
int、float、 bool、string、数组、 结构体 struct - 引用类型(5):
指针、切片slice、集合map、通道chan、interface
值类型和引用类型的使用特点
值类型 | 引用类型 | |
---|---|---|
存储地址 | 值类型直接存放值,内存通常在栈中分配 | 引用类型变量存储的地址(也就是通过指针访问类型里面的数据),通常真正的值在堆上分配。当没有变量号用这个地址的时候,该值会被gc回收。 |
拷贝 | 值拷贝,克隆体和本体那就是两个东西了 | 引用拷贝,克隆体和本体是一个东西 比如:切片和底层数组 |
做组成元素 (个人感受) | 值类型可以当作数组/切片/集合等引用类型 的元素 | 但是引用类型不能成为引用类型的元素,尤其是,我们总是把结构当成和切片这样的存在(虽然确实挺像) |
a与b | 值类型 | 引用类型 |
---|---|---|
a=b |
a与b不相关 | a与b共享一个底层数据 |
copy(a,b) |
copy函数只支持切片 | a与b不相关 |
实例分析
数组与切片
定义了一个数组a,它是值类型,复制给b是copy,当b发生变化后a并不会发生任何变化,程序的执行结果如下所示:
func main{
a:=[5]int{1,2,3,4,5}
b:= a
b[2]= 8
fmt.Println(a, b) //结果为:[1,2,3,4,5] [1,2,8,4,5]
func main0 {
a :=[int{1,2,3,4,5}
b:= a
b[2]= 8
fmt.Println(a, b) //结果为[1,2,8,4,5] [1,2,8,4,5]
}
结构体和集合map
package main
type Counter struct {
count int
}
func add2(s map[string]Counter) {
counter := s["count"]
counter.count++
}
func add3(s map[string]*Counter) {
counter := s["count'"]
counter.count++
}
func main() {
m2 := map[string]Counter{" count":Counter{count: 20}}
add2(m2)
//temp := m2["count"]
//temp.count+ +
println(m2["count"].count)
m3 := map[string]*Counter{"count":&Counter{20}}
add3(m3)
println(m3["count"].count)
}
:::info 理想结果为:
:::
20
21
:::info 解释
:::
- m2,m3是结构体和map的结合。m3是结构体型指针和map的结合.
- m2不会改变,主要是因为add2中使用:=重新声明了counter,由于是对象是值类型,所以也就类似于第一个实例那样, 只M2不会改变,主要是因为add2中使用:=重新声明了计数器,由于是对象是值类型,所以也就类似于第一个实例那样,只是单纯的进行了值拷贝。
- m3是的add3中虽然看起来一样。 但实际上counter是一个指针, 所以对counter进行操作会导致原来的数值也发生改变。M3是的加法3中虽然看起来一样。但实际上计数器是一个指针,所以对计数器进行操作会导致原来的数值也发生改变。