一、值类型
我们刚刚学完了数组(Array) 和 结构体(Struct),也包括之前学到的基本数据类型(int,float,bool和string),它们都属于值类型。值类型的特点就是:变量直接存储值,内存通常在栈中分配,栈在函数调用完会被释放。
二、引用类型
本节要讨论的,则是引用类型。引用类型的特点是:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配,通过GC回收。
指针pointer,切片slice,字典map,通道chan,函数function类型等都是引用类型。
另外,关于引用类型和值类型有一个特别重要的不同点:
- 对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储,进而会触发panic
- 对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间
func main() {
var a *int
*a = 100
fmt.Println(*a) //panic
var b map[string]int
b["测试"] = 100 //panic
fmt.Println(b)
}
那么,我们如何分配内存呢?Go语言中有两个内建的函数,用来分配内存:new和make。
new函数很少使用。它返回一个类型的指针并初始化为类型零值。
//内置new函数
//Type表示类型,new函数只接受一个参数,这个参数是一个类型
//*Type表示类型指针,new函数返回一个指向该类型内存地址的指针。
func new(Type) *Type
//举例
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false
make函数则只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身(因为类型本身就是引用类型,就没必要再返回指针)。在使用这三种引用类型时,我们必须用make进行初始化之后才能对它们进行操作。
//内置make函数
func make(t Type, size ...IntegerType) Type
//举例
var b map[string]int
b = make(map[string]int, 10)
b["测试"] = 100
fmt.Println(b)
三、值传递和引用传递
- 值传递:值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
- 引用传递:所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。最早出现c++中
但是,其实在Go语言中不存在引用传递,只有值传递!有很多人误解说,引用类型的是引用传递,误区就是:引用类型在传递到函数内部,可以在函数内部对它的值进行修改而引起的误会。
从传递成本的角度讲,引用类型的值往往要比值类型的值低很多。
能够通过make()函数创建的都是引用类型。
package main
import "fmt"
func updateSlice(s []string) {
s[0] = "Bye"
}
func main() {
mySlice := []string{"Hi", "There", "how", "are", "you?"}
updateSlice(mySlice)
fmt.Println(mySlice)
}
上面的代码中,运行结果:
[Bye There how are you?]
为什么会是这样呢?通过下面的图解,你一定可以想明白为什么!



没错!因为发生的是值传递!所以,在函数内部发生了一次拷贝操作,但是因为拷贝得到的变量存储的内存地址和传进来的是一样的,也就指向同一个数组,所以改变“影响到了”外面的“变量”。
关于引用类型就总结这么多,接下来,开始学习第一个引用类型吧:切片!