当前位置: 首页 > news >正文

【Go】P13 Go 语言核心概念:指针 (Pointer) 详解

目录

  • 前言
  • 变量与内存地址
  • 什么是指针?
  • 指针的核心操作
    • 取地址 (&)
    • 解引用 (*)
    • 通过指针修改值
  • 指针的零值:nil
  • 内存分配:new 与 make 的区别
    • make:用于引用类型的初始化
    • new:用于值类型的内存分配
    • new 与 make 的核心区别总结
  • 为什么 Go 语言需要指针?
    • 在函数间共享和修改数据
    • 提高性能
  • 总结

在这里插入图片描述

前言

在 Go 语言的学习中,指针(Pointer)是一个绕不开的核心概念。它既是理解 Go 语言内存管理、函数传参以及性能优化的关键,也是许多初学者容易混淆的难点。

本文将从最基础的变量和内存地址讲起,带你一步步深入理解 Go 语言中指针的定义、使用、以及 newmake 这两个与内存分配密切相关的内置函数。


变量与内存地址

在深入指针之前,我们必须先巩固一个基础知识:变量(Variable)

在 Go 语言中,变量是程序用来存储数据的基本单元。变量的本质,其实是给某块用于存储数据的内存空间起的一个好记的“别名”

比如,我们定义一个变量 a := 10,程序在运行时会执行以下操作:

  1. 在内存中寻找一块空闲的空间。
  2. 将数据 10 存储到这块空间中。
  3. 让变量名 a 与这块内存空间(的地址)建立映射关系

之后,我们就可以通过 a 这个变量名来访问或修改内存中存储的 10 这个值。

在计算机底层,a 变量对应的是一个实实在在的内存地址。Go 语言使用 & (取地址) 操作符来获取一个变量的内存地址。

代码示例 1:查看变量的内存地址

package mainimport "fmt"func main() {a := 10fmt.Printf("变量 a 的值: %d\n", a)// 使用 &a 获取变量 a 在内存中的地址// %p 是一个占位符,专门用于格式化输出指针和内存地址fmt.Printf("变量 a 的内存地址: %p\n", &a)
}

示例1输出样例:

变量 a 的值: 10
变量 a 的内存地址: 0xc00001a0a8

什么是指针?

理解了内存地址,指针就非常容易理解了。

指针(Pointer)也是一个变量,但它是一种特殊的变量。它存储的数据不是一个普通的值(如 10"hello"),而是另一个变量的内存地址。我们可以说,一个指针“指向”了另一个变量

代码示例 2:定义和使用指针

package mainimport "fmt"func main() {a := 10 // 这是一个普通的 int 变量// 1. 声明一个指针变量 p// var p *int 表示 p 是一个指针,它专门用来存储 int 类型变量的地址var p *int// 2. 将变量 a 的地址(&a)赋值给指针 pp = &afmt.Printf("变量 a 的值: %d\n", a)fmt.Printf("变量 a 的内存地址: %p\n", &a)fmt.Println("--------------------")fmt.Printf("指针 p 存储的值 (即 a 的地址): %p\n", p)fmt.Printf("指针 p 的类型: %T\n", p) // 类型为 *intfmt.Printf("指针 p 自己的内存地址: %p\n", &p)
}

示例2输出样例:

变量 a 的值: 10
变量 a 的内存地址: 0xc00001a0a8
--------------------
指针 p 存储的值 (即 a 的地址): 0xc00001a0a8
指针 p 的类型: *int
指针 p 自己的内存地址: 0xc000006028

分析:

  • p 的值(0xc00001a0a8)等于 a 的地址(&a)。这证实了指针存储的就是地址。
  • p 的类型是 *int(读作 “int pointer” 或 “指向 int 的指针”)。
  • p 既然也是一个变量,它自己当然也有一个内存地址(&p),即 0xc000006028

Go 语言中的值类型(intfloatboolstringarraystruct)都有对应的指针类型,如:*int*int64*string*MyStruct 等。


指针的核心操作

指针有两个核心操作符:& (取地址) 和 * (解引用)。

取地址 (&)

我们已经在上面用过了。& 放在一个变量前,用于获取该变量的内存地址

p := &a

p 得到了 a 的地址,或者说,将 a 的地址值赋给指针类型变量 p

解引用 (*)

“解引用”(Dereferencing)也常被俗称为“取值”。* 放在一个指针变量前,用于获取该指针所指向的内存地址中存储的值

&* 是一对互逆的操作。

代码示例 3:指针的解引用

package mainimport "fmt"func main() {a := 100p := &a // p 指向 a// 使用 *p 来获取 p 指向的地址(即 a 的地址)上存储的值val := *pfmt.Printf("变量 a 的值: %d\n", a)fmt.Printf("通过指针 p 解引用获取的值: %d\n", val)fmt.Printf("val 和 a 是否相等: %v\n", val == a)
}

示例3输出:

变量 a 的值: 100
通过指针 p 解引用获取的值: 100
val 和 a 是否相等: true

通过指针修改值

这才是指针最强大的用途之一。既然指针 p 知道变量 a 的“住址”,它不仅能读取 a 的值,还能修改 a 的值

我们同样使用 * 操作符,但这次是将它放在赋值操作的左侧。

代码示例 4:通过指针修改变量的值

package mainimport "fmt"func main() {a := 100p := &afmt.Printf("修改前,a 的值: %d\n", a)// *p 代表 a 变量本身// 下面这行代码等价于 a = 200*p = 200fmt.Printf("通过指针 p 修改后,a 的值: %d\n", a)
}

示例4输出:

修改前,a 的值: 100
通过指针 p 修改后,a 的值: 200

分析: 我们没有直接操作 a,而是通过操作 *p 改变了 a 的值。这在函数传参时尤其重要,因为它允许我们在函数内部修改函数外部的变量。


指针的零值:nil

一个指针变量被声明后,如果没有被赋予任何变量的地址,它的默认值是 nilnil 是 Go 语言中指针、切片、映射、通道、函数和接口类型的“零值”。

一个 nil 指针不指向任何内存地址。对 nil 指针进行解引用(*p)操作会引发一个运行时恐慌(panic),因为你试图访问一个不存在的内存地址。

代码示例 5:nil 指针与恐慌

package mainimport "fmt"func main() {var p *int // p 被声明,但未初始化,其值为 nilfmt.Printf("p 的值: %v\n", p) // 输出: <nil>if p == nil {fmt.Println("p 是一个 nil 指针")}// 对 nil 指针解引用会引发 panic// 下面这行代码如果取消注释,程序将崩溃// fmt.Println(*p) // panic: runtime error: invalid memory address or nil pointer dereference
}

高价值提示: 在使用指针之前,尤其是那些可能来自函数返回值或复杂逻辑的指针,最好先检查它是否为 nil


内存分配:new 与 make 的区别

在 Go 语言中,我们经常需要手动管理内存分配,尤其是对于引用类型和指针。newmake 是 Go 提供的两个用于内存分配的内置函数,但它们服务的目的截然不同。

make:用于引用类型的初始化

首先,我们来看你提供的参考示例中提到的问题。

错误示例(未分配内存的 map):

package mainimport "fmt"func main() {var userinfo map[string]string // 只是声明了 map,但它是 niluserinfo["username"] = "张三" // 恐慌!panic: assignment to entry in nil mapfmt.Println(userinfo)
}

上述代码中,userinfo 只是一个 nil 映射,它没有指向任何底层的哈希表数据结构。你不能向一个 nil 映射中添加键值对。

make 的职责 是为切片(slice)、映射(map)和通道(channel) 这三种“引用类型分配内存并初始化它们。make 返回的是初始化后的类型实例,而不是指针。

正确示例(使用 make 初始化 map):

package mainimport "fmt"func main() {// 使用 make 创建一个 map,分配了底层内存var userinfo = make(map[string]string) userinfo["username"] = "张三" // 现在可以安全地赋值了fmt.Println(userinfo)
}

new:用于值类型的内存分配

new 的职责 是为任意类型(包括 int、struct 等值类型)分配内存空间,并返回一个指向该内存空间的指针(*T)

这块新分配的内存会被初始化为该类型的零值(zero value)

  • new(int) 会分配一块内存,存入 0,并返回一个 *int 类型的指针。
  • new(string) 会分配一块内存,存入 ""(空字符串),并返回一个 *string 类型的指针。
  • new(bool) 会分配一块内存,存入 false,并返回一个 *bool 类型的指针。

代码示例 6:使用 new 函数

package mainimport "fmt"func main() {// new(int) 分配了一个 int 的内存空间(值为 0)// a 是一个 *int 类型的指针,它指向这块内存var a = new(int)fmt.Printf("a 的值 (内存地址): %v\n", a)fmt.Printf("a 的类型: %T\n", a)fmt.Printf("a 指针变量对应的值 (零值): %v\n", *a)// 我们可以通过解引用来修改这个值*a = 100fmt.Printf("修改后 a 指针变量对应的值: %v\n", *a)
}

示例6结果样例:

a 的值 (内存地址): 0xc00001a0b0
a 的类型: *int
a 指针变量对应的值 (零值): 0
修改后 a 指针变量对应的值: 100

new 与 make 的核心区别总结

这是一个常见的面试题,我们可以用一个表格来清晰地总结:

特性new(T)make(T, ...)
作用分配内存,并初始化为零值用于初始化引用类型的数据结构
使用类型任意类型 T仅限:切片(slice)、映射(map) 、通道(channel)
返回值*T(指向类型 T 的指针)T(初始化后的类型实例本身,不是指针)

简单来说:

  • 想得到一个指向零值的指针,用 new
  • 想得到一个初始化后(非 nil)的切片、映射或通道,用 make

为什么 Go 语言需要指针?

最后,我们来谈谈指针的价值。为什么不(像某些语言一样)隐藏指针呢?

在函数间共享和修改数据

Go 语言中所有的函数参数传递都是值传递(Pass by Value)。这意味着当你把一个变量 a 传给一个函数时,函数内部得到的是 a 的一个副本。在函数内修改这个副本,不会影响到函数外部的 a

如果你希望函数能够修改外部的原始变量,你就必须传递该变量的指针

代码示例 7:值传递 vs 指针传递

package mainimport "fmt"// 接受 int 值(副本)
func modifyByValue(val int) {val = 100 // 只修改了副本
}// 接受 *int 指针
func modifyByPointer(ptr *int) {*ptr = 100 // 通过解引用,修改了指针指向的原始值
}func main() {// 值传递a := 10modifyByValue(a)fmt.Printf("值传递后,a 的值: %d\n", a) // a 仍然是 10// 指针传递b := 10modifyByPointer(&b) // 传递 b 的地址fmt.Printf("指针传递后,b 的值: %d\n", b) // b 变成了 100
}

示例7输出

值传递后,a 的值: 10
指针传递后,b 的值: 100

提高性能

值传递意味着数据拷贝。如果传递的是一个非常大的结构体(Struct),拷贝它会带来显著的性能开销。

而传递一个指向该结构体的指针,无论结构体有多大,都只是拷贝一个内存地址(在 64 位系统上通常是 8 字节),这非常高效。


总结

指针是 Go 语言中一把强大而锋利的“手术刀”。它让我们能够直接与内存地址打交道,实现高效的数据共享和修改。

  • & (取地址): 获取变量的内存地址
  • * (解引用): 获取指针指向的值,或修改指针指向的值。
  • 指针的零值是 nil,对 nil 指针解引用会导致 panic
  • new 用于分配任意类型的内存,返回一个指向零值的指针 (*T)。
  • make 仅用于初始化切片、映射和通道,返回它们实例 (T)。

希望这篇博文能帮你彻底搞懂 Go 语言的指针!


2025.10.27 G33高铁 前往杭州途中

http://www.dtcms.com/a/536013.html

相关文章:

  • oss中的文件替换后chrome依旧下载到缓存文件概述
  • Go Web 编程快速入门 08 - JSON API:编码、解码与内容协商
  • Golang交叉编译到Android上运行
  • 学网站开发去哪学最好的公文写作网站
  • F035 vue+neo4j中医南药药膳知识图谱可视化系统 | vue+flask
  • 图形数据库Neo4J简介
  • QR算法:矩阵特征值计算的基石
  • 宁波网站建设公司代理珠海集团网站建设报价
  • 「用Python来学微积分」17. 导数与导函数
  • RAID技术:RAID 0/1/5/10 原理、配置与故障恢复
  • 7.1-性能与测试工具
  • linux磁盘使用流程
  • KVM虚拟化部署全流程指南
  • 【用homebrew配置nginx+配置前端项目与后端联调】Macbook M1(附一些homebrew操作)
  • 建立个人博客网站wordpress免费发布信息大全
  • 做设计转钱网站公司网站开发模板
  • 网站建设目标是什么意思win7用本地文件做网站模板
  • VR党建骑行|VR红色骑行漫游|虚拟骑行设备
  • 人脸识别1-Windows下基于MSVC编译opencv-4.5.5
  • 上海网站建设的公司站长之家查询
  • 游戏盾和高防IP的差异与选择
  • 内管理模式和外管理模式的网络比较
  • 【android bluetooth 协议分析 11】【AVDTP详解 4】【A2dp Sink 状态机通俗讲解】
  • Python、Java与Go:AI大模型时代的语言抉择
  • 【Go】P14 Go语言核心利器:全面解析结构体 (Struct)
  • 华为OD机试双机位A卷 - 最佳植树距离 (C++ Python JAVA JS GO)
  • Go学习资料整理
  • 旅游网站规划建设郑州网站建设网络公司
  • k8s滚动升级
  • 舆情网站入口wordpress文章添加seo标题代码