Go 语言指针
指针是存储另一个变量内存地址的变量。Go 语言支持指针,但比 C/C++ 更安全,不支持指针运算。理解指针对于高效的 Go 编程至关重要。
📍 指针基础
指针声明和基本操作
go
package main
import "fmt"
func main() {
// 1. 声明变量
var num int = 42
var str string = "Hello"
// 2. 声明指针变量
var numPtr *int // 指向 int 的指针
var strPtr *string // 指向 string 的指针
// 3. 获取变量地址(使用 & 操作符)
numPtr = &num
strPtr = &str
// 4. 打印地址和值
fmt.Printf("num 的值: %d\n", num)
fmt.Printf("num 的地址: %p\n", &num)
fmt.Printf("numPtr 的值(地址): %p\n", numPtr)
fmt.Printf("numPtr 指向的值: %d\n", *numPtr) // 解引用
fmt.Printf("\nstr 的值: %s\n", str)
fmt.Printf("str 的地址: %p\n", &str)
fmt.Printf("strPtr 的值(地址): %p\n", strPtr)
fmt.Printf("strPtr 指向的值: %s\n", *strPtr)
// 5. 零值指针
var nilPtr *int
fmt.Printf("\n零值指针: %p\n", nilPtr) // <nil>
// 6. 检查指针是否为 nil
if nilPtr == nil {
fmt.Println("指针为 nil")
}
}指针的内存布局
go
import "unsafe"
func main() {
// 变量和指针的内存布局
var x int64 = 100
var ptr *int64 = &x
fmt.Printf("变量 x:\n")
fmt.Printf(" 值: %d\n", x)
fmt.Printf(" 地址: %p\n", &x)
fmt.Printf(" 大小: %d 字节\n", unsafe.Sizeof(x))
fmt.Printf("\n指针 ptr:\n")
fmt.Printf(" 值(指向的地址): %p\n", ptr)
fmt.Printf(" 自身地址: %p\n", &ptr)
fmt.Printf(" 指向的值: %d\n", *ptr)
fmt.Printf(" 大小: %d 字节\n", unsafe.Sizeof(ptr))
// 修改指针指向的值
*ptr = 200
fmt.Printf("\n修改后:\n")
fmt.Printf(" x 的值: %d\n", x) // 200
fmt.Printf(" *ptr 的值: %d\n", *ptr) // 200
}🔄 指针操作
通过指针修改值
go
func main() {
// 基本类型指针
num := 10
fmt.Printf("修改前: num = %d\n", num)
modifyValue(&num)
fmt.Printf("修改后: num = %d\n", num)
// 字符串指针
message := "Hello"
fmt.Printf("\n修改前: message = %s\n", message)
modifyString(&message)
fmt.Printf("修改后: message = %s\n", message)
// 切片指针
numbers := []int{1, 2, 3}
fmt.Printf("\n修改前: numbers = %v\n", numbers)
modifySlice(&numbers)
fmt.Printf("修改后: numbers = %v\n", numbers)
}
func modifyValue(ptr *int) {
*ptr = *ptr * 2
}
func modifyString(ptr *string) {
*ptr = *ptr + ", World!"
}
func modifySlice(ptr *[]int) {
*ptr = append(*ptr, 4, 5, 6)
}指针传递 vs 值传递
go
import "fmt"
// 结构体定义
type Person struct {
Name string
Age int
}
func main() {
person := Person{Name: "Alice", Age: 25}
fmt.Printf("原始: %+v\n", person)
// 值传递:不会修改原始结构体
modifyPersonByValue(person)
fmt.Printf("值传递后: %+v\n", person)
// 指针传递:会修改原始结构体
modifyPersonByPointer(&person)
fmt.Printf("指针传递后: %+v\n", person)
// 性能对比
comparePerformance()
}
func modifyPersonByValue(p Person) {
p.Name = "Bob"
p.Age = 30
fmt.Printf("函数内(值传递): %+v\n", p)
}
func modifyPersonByPointer(p *Person) {
p.Name = "Charlie" // 等价于 (*p).Name = "Charlie"
p.Age = 35
fmt.Printf("函数内(指针传递): %+v\n", *p)
}
func comparePerformance() {
// 大结构体性能对比
type LargeStruct struct {
Data [1000]int
Info string
}
large := LargeStruct{Info: "Large data"}
fmt.Printf("\n性能对比:\n")
fmt.Printf("大结构体大小: %d 字节\n", unsafe.Sizeof(large))
fmt.Printf("指针大小: %d 字节\n", unsafe.Sizeof(&large))
// 值传递会复制整个结构体
processByValue(large)
// 指针传递只传递地址
processByPointer(&large)
}
func processByValue(ls LargeStruct) {
// 接收复制的结构体
ls.Info = "Processed by value"
}
func processByPointer(ls *LargeStruct) {
// 接收指针,直接操作原结构体
ls.Info = "Processed by pointer"
}🏗️ 指针与数据结构
数组指针 vs 指针数组
go
func main() {
// 1. 数组指针:指向数组的指针
arr := [5]int{1, 2, 3, 4, 5}
var arrPtr *[5]int = &arr
fmt.Printf("原数组: %v\n", arr)
fmt.Printf("通过数组指针访问: %v\n", *arrPtr)
// 修改数组元素
arrPtr[0] = 100 // 等价于 (*arrPtr)[0] = 100
fmt.Printf("修改后数组: %v\n", arr)
// 2. 指针数组:存储指针的数组
var ptrArr [3]*int
a, b, c := 10, 20, 30
ptrArr[0] = &a
ptrArr[1] = &b
ptrArr[2] = &c
fmt.Printf("\n指针数组中的值:\n")
for i, ptr := range ptrArr {
if ptr != nil {
fmt.Printf("ptrArr[%d] = %d (地址: %p)\n", i, *ptr, ptr)
}
}
// 通过指针数组修改原变量
*ptrArr[0] = 100
fmt.Printf("修改后 a = %d\n", a)
}切片和指针
go
func main() {
// 切片本身就包含指针
slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1 // 浅拷贝
fmt.Printf("slice1: %v\n", slice1)
fmt.Printf("slice2: %v\n", slice2)
// 修改 slice2 会影响 slice1
slice2[0] = 100
fmt.Printf("修改 slice2 后:\n")
fmt.Printf("slice1: %v\n", slice1)
fmt.Printf("slice2: %v\n", slice2)
// 切片的指针
var slicePtr *[]int = &slice1
(*slicePtr)[1] = 200
fmt.Printf("通过切片指针修改后: %v\n", slice1)
// 函数中操作切片
processSlice(slice1)
fmt.Printf("函数处理后: %v\n", slice1)
processSlicePointer(&slice1)
fmt.Printf("指针函数处理后: %v\n", slice1)
}
func processSlice(s []int) {
// 可以修改元素,但不能修改长度/容量
if len(s) > 0 {
s[0] = 999
}
}
func processSlicePointer(s *[]int) {
// 可以修改长度/容量
*s = append(*s, 6, 7, 8)
}映射和指针
go
func main() {
// 映射的值为指针
ages := map[string]*int{
"Alice": new(int),
"Bob": new(int),
}
// 设置值
*ages["Alice"] = 25
*ages["Bob"] = 30
fmt.Println("年龄映射:")
for name, agePtr := range ages {
if agePtr != nil {
fmt.Printf("%s: %d岁\n", name, *agePtr)
}
}
// 修改值
*ages["Alice"] = 26
fmt.Printf("Alice 一年后: %d岁\n", *ages["Alice"])
// 指向映射的指针
mapPtr := &ages
(*mapPtr)["Charlie"] = new(int)
*(*mapPtr)["Charlie"] = 28
fmt.Printf("通过映射指针添加 Charlie: %d岁\n", *ages["Charlie"])
}🔧 指针的高级应用
new 和 make 函数
go
func main() {
// 1. new 函数:分配内存并返回指针
ptr1 := new(int)
*ptr1 = 42
fmt.Printf("new 创建的指针: %p, 值: %d\n", ptr1, *ptr1)
ptr2 := new(string)
*ptr2 = "Hello"
fmt.Printf("new 创建的字符串指针: %p, 值: %s\n", ptr2, *ptr2)
// 2. make 函数:用于创建切片、映射、通道
slice := make([]int, 5, 10)
fmt.Printf("make 创建的切片: %v, 长度: %d, 容量: %d\n", slice, len(slice), cap(slice))
m := make(map[string]int)
m["key"] = 100
fmt.Printf("make 创建的映射: %v\n", m)
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Printf("make 创建的通道: %v\n", ch)
// 3. new vs make 的区别
// new 返回指针,make 返回类型本身
var p1 *[]int = new([]int) // p1 指向 nil 切片
var s1 []int = make([]int, 0) // s1 是空切片
fmt.Printf("new 切片: %v, 是否为 nil: %t\n", *p1, *p1 == nil)
fmt.Printf("make 切片: %v, 是否为 nil: %t\n", s1, s1 == nil)
}指针链和多级指针
go
func main() {
// 多级指针
num := 42
ptr1 := &num // 指向 int 的指针
ptr2 := &ptr1 // 指向指针的指针
ptr3 := &ptr2 // 指向指针的指针的指针
fmt.Printf("num = %d\n", num)
fmt.Printf("*ptr1 = %d\n", *ptr1)
fmt.Printf("**ptr2 = %d\n", **ptr2)
fmt.Printf("***ptr3 = %d\n", ***ptr3)
// 通过多级指针修改值
***ptr3 = 100
fmt.Printf("修改后 num = %d\n", num)
// 指针链表示例
type Node struct {
Data int
Next *Node
}
// 创建链表节点
node1 := &Node{Data: 1}
node2 := &Node{Data: 2}
node3 := &Node{Data: 3}
// 连接节点
node1.Next = node2
node2.Next = node3
// 遍历链表
fmt.Println("\n链表遍历:")
current := node1
for current != nil {
fmt.Printf("节点数据: %d\n", current.Data)
current = current.Next
}
}函数指针
go
func main() {
// 函数指针(函数作为值)
var mathOp func(int, int) int
mathOp = add
fmt.Printf("加法: %d\n", mathOp(5, 3))
mathOp = multiply
fmt.Printf("乘法: %d\n", mathOp(5, 3))
// 函数指针数组
operations := []func(int, int) int{add, subtract, multiply, divide}
operationNames := []string{"加法", "减法", "乘法", "除法"}
a, b := 10, 3
for i, op := range operations {
result := op(a, b)
fmt.Printf("%s: %d\n", operationNames[i], result)
}
// 回调函数
processNumbers([]int{1, 2, 3, 4, 5}, func(n int) int {
return n * n
})
}
func add(a, b int) int { return a + b }
func subtract(a, b int) int { return a - b }
func multiply(a, b int) int { return a * b }
func divide(a, b int) int { return a / b }
func processNumbers(numbers []int, processor func(int) int) {
fmt.Println("处理数字:")
for _, num := range numbers {
result := processor(num)
fmt.Printf("%d -> %d\n", num, result)
}
}🎯 实际应用示例
链表数据结构
go
// 单向链表实现
type LinkedList struct {
head *ListNode
size int
}
type ListNode struct {
data int
next *ListNode
}
func NewLinkedList() *LinkedList {
return &LinkedList{}
}
func (ll *LinkedList) Add(data int) {
newNode := &ListNode{data: data}
if ll.head == nil {
ll.head = newNode
} else {
current := ll.head
for current.next != nil {
current = current.next
}
current.next = newNode
}
ll.size++
}
func (ll *LinkedList) Remove(data int) bool {
if ll.head == nil {
return false
}
// 如果要删除的是头节点
if ll.head.data == data {
ll.head = ll.head.next
ll.size--
return true
}
current := ll.head
for current.next != nil {
if current.next.data == data {
current.next = current.next.next
ll.size--
return true
}
current = current.next
}
return false
}
func (ll *LinkedList) Find(data int) *ListNode {
current := ll.head
for current != nil {
if current.data == data {
return current
}
current = current.next
}
return nil
}
func (ll *LinkedList) Display() {
if ll.head == nil {
fmt.Println("链表为空")
return
}
current := ll.head
fmt.Print("链表: ")
for current != nil {
fmt.Printf("%d", current.data)
if current.next != nil {
fmt.Print(" -> ")
}
current = current.next
}
fmt.Printf(" (大小: %d)\n", ll.size)
}
func main() {
// 测试链表
list := NewLinkedList()
// 添加元素
list.Add(1)
list.Add(2)
list.Add(3)
list.Add(4)
list.Display()
// 查找元素
node := list.Find(3)
if node != nil {
fmt.Printf("找到元素: %d\n", node.data)
}
// 删除元素
if list.Remove(2) {
fmt.Println("删除元素 2 成功")
list.Display()
}
// 删除头节点
if list.Remove(1) {
fmt.Println("删除头节点 1 成功")
list.Display()
}
}二叉树数据结构
go
// 二叉搜索树实现
type TreeNode struct {
data int
left *TreeNode
right *TreeNode
}
type BinarySearchTree struct {
root *TreeNode
}
func NewBST() *BinarySearchTree {
return &BinarySearchTree{}
}
func (bst *BinarySearchTree) Insert(data int) {
bst.root = insertNode(bst.root, data)
}
func insertNode(node *TreeNode, data int) *TreeNode {
if node == nil {
return &TreeNode{data: data}
}
if data < node.data {
node.left = insertNode(node.left, data)
} else if data > node.data {
node.right = insertNode(node.right, data)
}
return node
}
func (bst *BinarySearchTree) Search(data int) bool {
return searchNode(bst.root, data)
}
func searchNode(node *TreeNode, data int) bool {
if node == nil {
return false
}
if data == node.data {
return true
} else if data < node.data {
return searchNode(node.left, data)
} else {
return searchNode(node.right, data)
}
}
func (bst *BinarySearchTree) InorderTraversal() {
fmt.Print("中序遍历: ")
inorder(bst.root)
fmt.Println()
}
func inorder(node *TreeNode) {
if node != nil {
inorder(node.left)
fmt.Printf("%d ", node.data)
inorder(node.right)
}
}
func main() {
// 测试二叉搜索树
bst := NewBST()
// 插入节点
values := []int{50, 30, 20, 40, 70, 60, 80}
for _, val := range values {
bst.Insert(val)
fmt.Printf("插入 %d\n", val)
}
// 中序遍历(应该是有序的)
bst.InorderTraversal()
// 搜索节点
searchValues := []int{40, 25, 80, 90}
for _, val := range searchValues {
found := bst.Search(val)
fmt.Printf("搜索 %d: %t\n", val, found)
}
}🎓 小结
本章我们全面学习了 Go 语言的指针:
- ✅ 指针基础:声明、地址获取、解引用操作
- ✅ 指针操作:通过指针修改值、指针传递
- ✅ 数据结构:数组指针、切片指针、映射指针
- ✅ 高级应用:new/make、多级指针、函数指针
- ✅ 实际应用:链表、二叉树等数据结构实现
指针是 Go 语言的重要特性,正确使用指针可以提高程序性能并实现复杂的数据结构。
接下来,我们将学习 Go 语言结构体,了解 Go 的自定义数据类型。
指针使用建议
- 大结构体作为函数参数时使用指针避免复制开销
- 始终检查指针是否为 nil 避免运行时恐慌
- 理解指针和值的区别,选择合适的传递方式
- 合理使用指针实现高效的数据结构