Skip to content

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 避免运行时恐慌
  • 理解指针和值的区别,选择合适的传递方式
  • 合理使用指针实现高效的数据结构

本站内容仅供学习和研究使用。