[TOC]
值类型:变量内存空间中存放的是值
引用类型:变量内存空间中存放的是地址,地址再指向具体的值
二者的区别就在两点上:
1、赋值操作前,是否需要手动申请内存空间
2、原值赋值给新值,改原值,新值是否受影响,反过来一个道理
- 1、值类型:
- 1、基本数据类型都是值类型:整型、浮点、复数、布尔、字符串
- 2、复合数据类型之数组(array)
- 3、复合数据类型之结构体(struct)
- 2、引用类型:
- 2.1 复合数据类型之切片(slice)
- 2.2 复合数据类型之字典(map)
- 2.3 其他类型之通道(chan)
- 2.4 其他类型之指针(pointer)
- ps:函数也属于引用类型
1、赋值操作前,无需手动申请内存,因为在声明时编译器会依据它们的零值为它们申请好内存空间
| |
| var a int |
| fmt.Printf("%p\n", &a) |
| |
| b := 3 |
| a = 3 |
| |
| |
| var m [2]int |
| fmt.Printf("%p\n",&m) |
| |
| var n [2]int = [2]int{11, 22} |
| m = n |
| fmt.Println(m) |
2、 原值拷贝为一个新值,改原值,新值不受影响
| |
| var a =10 |
| fmt.Println(&a) |
| |
| b := a |
| fmt.Println(&b) |
| |
| b = 101 |
| fmt.Println(a) |
| fmt.Println(b) |
| |
| fmt.Println(&a) |
| fmt.Println(&b) |
| |
| 例2: |
| var c = [3]int{1, 2, 3} |
| fmt.Printf("%p\n", &c) |
| |
| d := c |
| fmt.Printf("%p\n", &d) |
| |
| d[1] = 100 |
| fmt.Println(c) |
| fmt.Println(d) |
| |
| fmt.Printf("%p\n", &c) |
| fmt.Printf("%p\n", &d) |
1、赋值操作前,需要手动申请好内存,如果不,那么初始值为nil,nil代表空、没有申请内存,无法被直接赋值
| var a []int |
| |
| fmt.Printf("%p\n", &a) |
| fmt.Printf("%p\n", a) |
| fmt.Printf("%v\n", a) |
| |
| fmt.Println(a == nil) |
| a[0] = 666 |
| |
| |
| var b []int |
| var v = []int{11, 22, 33} |
| b = v |
| fmt.Println(b) |
引用类型存在的意义在于引用了一个已经存在的内存空间,所以对于引用类型我们必须为其申请好内存才可以使用,有两种方式:
| |
| var a []int = make([]int, 3, 3) |
| fmt.Printf("%p\n", &a) |
| fmt.Printf("%p\n", a) |
| fmt.Printf("%v\n", a) |
| |
| fmt.Println(a == nil) |
| a[0] = 666 |
| |
| |
| var b map[string]int = make(map[string]int) |
| |
| fmt.Println(b == nil) |
| b["aaa"] = 666 |
| |
| fmt.Println(b["aaa"]) |
- (2)一步到位式,即赋一个完整的值,我们之前用的都是这种方式,该方式等同于make完毕之后再塞值,如下所示
| |
| var names []string = make([]string,3,3) |
| |
| names[0] = "egon" |
| names[1] = "bigegon" |
| names[2] = "smallegon" |
| |
| |
| var names []string = []string{"egon", "bigegon", "smallegon"} |
2、原值拷贝为一个新值,改原值,新值受影响
| 详解: |
| 引用类型的变量赋值操作,如 j = i ,当然也是在内存中将 i 的值拷贝给了j,但是因为引用类型的变量直接存放的就是一个内存地址(这个地址指向的空间存的才是值),即i与j都是同一个地址。所以通过i或j修改对应内存地址空间中的值,另外一个也会修改。 |
| |
| 例1 |
| var a = []int{1,2,3,4,5} |
| b := a |
| |
| |
| fmt.Printf("%p\n",&a) |
| fmt.Printf("%p\n",&b) |
| |
| |
| fmt.Printf("%p\n",a) |
| fmt.Printf("%p\n",b) |
| |
| |
| b[1] = 666 |
| fmt.Println(a) |
ps:a,b底层数组是一样的,即操作的都是同一个底层数组,但是上层切片不同,所以内存地址不一样。
copy操作示例
| var a = []int{1, 2, 3, 4, 5} |
| c := make([]int, 5, 5) |
| copy(c, a) |
| |
| |
| fmt.Printf("%p\n", &a) |
| fmt.Printf("%p\n", &c) |
| |
| |
| fmt.Printf("%p\n", a) |
| fmt.Printf("%p\n", c) |
| |
| |
| c[1] = 20 |
| fmt.Println(a) |
指针操作示例
| var a = []int{1, 2, 3, 4, 5} |
| d := &a |
| |
| |
| fmt.Printf("%p\n", &a) |
| fmt.Printf("%p\n", &d) |
| |
| |
| fmt.Printf("%p\n", d) |
| |
| |
| (*d)[1] = 666 |
| fmt.Println(a) |
| |
| a[1] = 777 |
| fmt.Println(*d) |
关于函数
函数的参数传递机制是将实参的值拷贝一份给形参.只不过这个实参的值有可能是地址, 有可能是数据.
所以, 函数传参的传递其实本质全都是值传递,只不过该值有可能是数据(通常被简称为”值传递“),也有可能是地址(通常被简称为”引用传递“).