跳到主要内容

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
*/

代码解释:

  1. fmt.Printf("%d \n", &a): 通过 &a 获取变量 a 的内存地址,并使用 %d 格式化输出该地址(以十进制数字形式)。
  2. fmt.Printf("%d \n", *&a): *&a 是对变量 a 的解引用操作,实际上它相当于直接获取 a 的值(即 10)。这里 %d 用于格式化输出整数。
  3. fmt.Printf("%T \n", &a): 通过 &a 获取 a 的指针,使用 %T 打印其类型,输出 *int,表示这是一个 int 类型的指针。

ee1af18c06dd

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
}

代码解释:

  1. var a = 10: 定义一个变量 a,并赋值为 10
  2. var b = &a: ba 的指针,它保存了 a 的内存地址。通过 &a 获取 a 的内存地址。
  3. var c = *&a: c 是对 a 的指针进行解引用,获取 a 存储的值。*&a 实际上等价于 a,表示获取 a 的值。

f0cb737955ab

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语言中,值类型 包括基本类型(如 intfloatbool 等)、数组、结构体等。

为什么不需要 make 因为这些值类型在声明时,Go编译器已经自动为它们分配了内存,并将其初始化为零值。你可以直接使用它们,不会出现 nilpanic 的问题。

值类型在声明时会自动分配内存,并且分配的内存会初始化为零值。例如:

int 类型的零值是 0

float64 类型的零值是 0.0

bool 类型的零值是 false

3.2 make 和 new 区别

make 主要用于创建 slice(切片)、mapchannel 这三种内置数据结构,并为它们分配内存和初始化。返回的是分配好的引用类型,而不是指针。

new 的作用是为各种值类型(如 intfloat、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 函数用于为值类型分配内存,并返回指向该内存的指针。

对于引用类型如 mapslice,通过 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(切片)、mapchannel,并分配相应的内存。与 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 的地址。