9.指针
1. 什么是指针?
指针是一个存储变量内存地址的特殊变量。
变量在计算机底层是通过内存地址来进行存储和访问的。指针可以直接访问和操作这些内存地址,从而间接访问存储在地址上的值。
举例:
假设我们有一个变量 a
,值为 10
。变量 a
实际上是存储在计算机内存中的某个位置(地址)上。通过指针,我们可以获取到这个地址并通过它间接操作 a
的值。
a := 10
fmt.Println(&a) // 打印a的内存地址
1.1 指针的三个核心概念
指针地址 (&a):
- 指针地址是变量在内存中的位置。通过
&
符号,可以获取变量的内存地址。例如,如果定义了一个变量a := 10
,那么&a
将返回a
的内存地址。
指针取值 (*&a):
*&a
是指针取值操作,意思是根据指针地址获取存储在该地址上的值。比如,a
的值是10
,*&a
就可以取出10
这个值。
指针类型 (*int):
- 指针类型表示指向某种类型数据的指针,比如
*int
表示指向一个int
类型的指针。通过指针可以修改数据的值。
Go语言中的指针操作非常简单,使用 &
获取地址,使用 *
根据地址取值。
package main
import "fmt"
func main() {
var a = 10
fmt.Printf("%d \n", &a) // 打印a的内存地址
fmt.Printf("%d \n", *&a) // 通过指针解引用,打印a的值
fmt.Printf("%T \n", &a) // 打印a的指针类型
}
/*
824634933392
10
*int
*/
代码解释:
fmt.Printf("%d \n", &a)
: 通过&a
获取变量a
的内存地址,并使用%d
格式化输出该地址(以十进制数字形式)。fmt.Printf("%d \n", *&a)
:*&a
是对变量a
的解引用操作,实际上它相当于直接获取a
的值(即10
)。这里%d
用于格式化输出整数。fmt.Printf("%T \n", &a)
: 通过&a
获取a
的指针,使用%T
打印其类型,输出*int
,表示这是一个int
类型的指针。
2.&取变量地址
2.1 &符号取地址操作
package main
import "fmt"
func main() {
var a = 10
var b = &a
var c = *&a
fmt.Println(a) // 输出 a 的值 10
fmt.Println(b) // 输出 a 变量的内存地址,类似 0xc00001e060
fmt.Println(c) // 输出内存地址取值,也就是 a 的值 10
}
代码解释:
var a = 10
: 定义一个变量a
, 并赋值为10
。var b = &a
:b
是a
的指针,它保存了a
的内存地址。通过&a
获取a
的内存地址。var c = *&a
:c
是对a
的指针进行解引用,获取a
存储的值。*&a
实际上等价于a
,表示获取a
的值。
3.new 和 make
3.1 执行报错
在Go语言中,引用类型的变量(如切片、map、channel)在使用前不仅需要声明,还必须分配内存空间,否则无法存储和操作数据。如果没有为这些引用类型分配内存,直接使用会引发 panic
错误。
代码示例中,userinfo
是一个map类型变量,但没有分配内存空间,直接操作导致了错误。
panic: assignment to entry in nilmap
package main
import "fmt"
func main() {
var userinfo map[string]string // 声明一个map
userinfo["username"] = "张三" // 尝试在未分配内存的map中插入数据
fmt.Println(userinfo)
}
**值类型 不需要 make
分配内存 **
在Go语言中,值类型 包括基本类型(如 int
、float
、bool
等)、数组、结构体等。
为什么不需要 make
? 因为这些值类型在声明时,Go编译器已经自动为它们分配了内存,并将其初始化为零值。你可以直接使用它们 ,不会出现 nil
或 panic
的问题。
值类型在声明时会自动分配内存,并且分配的内存会初始化为零值。例如:
int
类型的零值是0
float64
类型的零值是0.0
bool
类型的零值是false
3.2 make 和 new 区别
make
主要用于创建 slice(切片)、map 和 channel 这三种内置数据结构,并为它们分配内存和初始化。返回的是分配好的引用类型,而不是指针。
new
的作用是为各种值类型(如 int
、float
、struct等)分配一片内存,并返回指向这片内存的指针。new
不会对内存进行初始化,分配的内存默认被设置为零值。
package main
import "fmt"
func main() {
a := make([]int, 3, 10) // 创建一个长度为3,容量为10的切片
a = append(a, 1) // 在切片末尾添加1
fmt.Printf("%v--%T \n", a, a) // 输出切片的值和类型
var b = new([]int) // 使用new创建一个切片的指针
//b = b.append(b, 2) // 这行会报错,因为b是指针,不能直接使用append
*b = append(*b, 3) // 解引用b,才能对其进行append操作
fmt.Printf("%v--%T", b, b) // 输出切片指针的值和类型
}
make
:分配并初始化切片,返回的是切片本身,可以直接操作。
new
:分配的是切片的指针,必须解引用后才能对其进行操作(如 append
)。
为什么需要解引用?
当你使用 new([]int)
时,b
是一个指向切片的指针。如果你想向切片添加数据(比如 append
),你需要先获取切片本身,而不是直接操作指针。这时就需要使用 *b
来解引用指针,得到切片,然后进行操作。
代码解释:
b = new([]int)
:b
是一个指向切片的指针,初始值为指向一个nil
切片。*b = append(*b, 3)
:先解引用b
得到切片[]int
,然后使用append
函数向这个切片添加元素3
。
3.3 new 函数
new
函数用于为值类型分配内存,并返回指向该内存的指针。
对于引用类型如 map
和 slice
,通过 new
分配的内存必须经过解引用后,才能进行进一步的初始化或操作。
由于 new
只分配内存并返回指针,因此你需要对引用类型(如切片、map)进行解引用,并手动初始化后才能使用。
package main
import "fmt"
func main() {
// 1. new 实例化 int
age := new(int) // 使用new分配一个int类型的内存,age是一个指向int的指针
*age = 1 // 解引用指针并赋值
fmt.Println(*age) // 输出 1
// 2. new 实例化切片
li := new([]int) // 使用new分配一个[]int类型的内存,li是一个指向切片的指针
*li = append(*li, 1) // 需要解引用指针才能对切片进行操作
fmt.Println(*li) // 输出 [1]
// 3. 实例化 map
userinfo := new(map[string]string) // userinfo 是一个指向map的指针
*userinfo = map[string]string{} // 初始化map
(*userinfo)["username"] = "张三" // 向map中添加键值对
fmt.Println(userinfo) // 输出 map[username:张三]
}
自定义类型使用 new 函数分配内存
package main
import "fmt"
func main() {
var s *Student
s = new(Student) // 分配内存,s 是一个指向 Student 结构体的指针
s.name = "zhangsan" // 设置结构体的 name 字段
fmt.Println(s) // 输出结构体的内容
}
type Student struct {
name string
age int
}
3.4 make 函数
make
主要用于创建 slice(切片)、map 和 channel,并分配相应的内存。与 new
不同,make
返回的是这些类型的实例,而不是指针。
make
会自动处理内存的分配和初始化,让这些引用类型可以直接使用。
package main
import "fmt"
func main() {
a := make([]int, 3, 10) // 创建一个长度为3,容量为10的切片
b := make(map[string]string) // 创建一个空的map
c := make(chan int, 1) // 创建一个容量为1的channel
fmt.Println(a, b, c) // 输出a、b、c的值
}
/*
[0 0 0] map[] 0xc0000180e0
*/
输出三个变量的值:
a
切片的初始值是[0 0 0]
,长度为3。b
输出为空的map
。c
输出是channel
的地址。