Go之路 - 6.go的指针

1. 指针基础

什么是指针

指针是存储另一个变量内存地址的变量。在Go中,指针类型使用 * 前缀表示。

go 复制代码
package main

import "fmt"

func main() {
    // 声明一个整型变量
    var x int = 10
    
    // 声明一个指向整型的指针
    var p *int
    
    // 获取x的内存地址并赋值给p
    p = &x
    
    fmt.Println("x的值:", x)        // 10
    fmt.Println("x的地址:", &x)      // 0xc0000180a8
    fmt.Println("p的值:", p)        // 0xc0000180a8
    fmt.Println("p指向的值:", *p)    // 10
    
    // 通过指针修改值
    *p = 20
    fmt.Println("修改后x的值:", x)   // 20
}

2. 指针声明和初始化

多种声明方式

go 复制代码
func pointerDeclaration() {
    // 方法1:先声明后赋值
    var a int = 100
    var p1 *int
    p1 = &a
    
    // 方法2:声明时初始化
    p2 := &a
    
    // 方法3:使用new函数
    p3 := new(int)
    *p3 = 200
    
    // 方法4:指向复合字面量
    p4 := &struct{ x, y int }{10, 20}
    
    fmt.Printf("p1: %v, *p1: %v\n", p1, *p1)
    fmt.Printf("p2: %v, *p2: %v\n", p2, *p2)
    fmt.Printf("p3: %v, *p3: %v\n", p3, *p3)
    fmt.Printf("p4: %v, *p4: %v\n", p4, *p4)
}

3. 指针操作

解引用和赋值

go 复制代码
func pointerOperations() {
    x := 42
    p := &x
    
    // 解引用读取值
    value := *p
    fmt.Println("通过指针读取的值:", value)
    
    // 通过指针修改值
    *p = 100
    fmt.Println("修改后的x:", x)
    
    // 指针的比较
    y := 42
    q := &y
    fmt.Println("p == q?", p == q)          // false,指向不同地址
    fmt.Println("*p == *q?", *p == *q)      // true,值相同
    
    // 空指针检查
    var nilPtr *int
    if nilPtr == nil {
        fmt.Println("nilPtr是空指针")
    }
}

4. 指针与函数

值传递 vs 指针传递

go 复制代码
func passByValue(x int) {
    x = x * 2
    fmt.Println("函数内值传递修改后:", x)
}

func passByPointer(x *int) {
    *x = *x * 2
    fmt.Println("函数内指针传递修改后:", *x)
}

func testPassing() {
    value := 10
    
    // 值传递 - 不影响原始值
    passByValue(value)
    fmt.Println("值传递后原始值:", value)  // 10
    
    // 指针传递 - 修改原始值
    passByPointer(&value)
    fmt.Println("指针传递后原始值:", value)  // 20
}

返回指针

go 复制代码
// 返回局部变量的指针是安全的(Go会进行逃逸分析)
func createPointer(x int) *int {
    return &x  // Go会分配在堆上
}

func returnPointerExample() {
    p := createPointer(42)
    fmt.Println("返回的指针指向的值:", *p)
    
    // 返回结构体指针
    type Person struct {
        Name string
        Age  int
    }
    
    func NewPerson(name string, age int) *Person {
        return &Person{name, age}
    }
    
    person := NewPerson("Alice", 30)
    fmt.Printf("Person: %+v\n", *person)
}

5. 指针与结构体

结构体指针的使用

go 复制代码
type Employee struct {
    ID     int
    Name   string
    Salary float64
}

func structPointers() {
    // 创建结构体实例
    emp := Employee{1, "Bob", 50000}
    
    // 获取结构体指针
    empPtr := &emp
    
    // 通过指针访问字段(两种方式等价)
    fmt.Println("ID:", (*empPtr).ID)  // 显式解引用
    fmt.Println("Name:", empPtr.Name) // 隐式解引用(语法糖)
    
    // 通过指针修改字段
    empPtr.Salary = 60000
    fmt.Printf("修改后: %+v\n", emp)
    
    // 指针作为接收者的方法
    empPtr.giveRaise(10)  // 涨薪10%
    fmt.Printf("涨薪后: %+v\n", emp)
}

// 指针接收者方法
func (e *Employee) giveRaise(percent float64) {
    e.Salary *= (1 + percent/100)
}

6. 指针与数组/切片

数组指针

go 复制代码
func arrayPointers() {
    // 数组指针
    arr := [3]int{1, 2, 3}
    arrPtr := &arr
    
    // 通过指针访问数组元素
    (*arrPtr)[0] = 100
    arrPtr[1] = 200  // 语法糖,不需要显式解引用
    
    fmt.Println("修改后的数组:", arr)
    
    // 指针数组(每个元素都是指针)
    a, b, c := 1, 2, 3
    ptrArray := [3]*int{&a, &b, &c}
    
    *ptrArray[0] = 100
    fmt.Println("a的新值:", a)
}

切片(本身已经是引用类型)

go 复制代码
func sliceAndPointers() {
    // 切片本身包含指向底层数组的指针
    slice := []int{1, 2, 3, 4, 5}
    
    // 修改切片会影响底层数组
    modifySlice(slice)
    fmt.Println("修改后的切片:", slice)
    
    // 指向切片的指针(不常用)
    slicePtr := &slice
    (*slicePtr)[0] = 100
    fmt.Println("通过指针修改切片:", slice)
}

func modifySlice(s []int) {
    s[0] = 999
}

7. 指针与映射(Map)

go 复制代码
func mapPointers() {
    // 映射本身是引用类型
    m := map[string]int{"a": 1, "b": 2}
    
    // 直接传递映射即可修改
    modifyMap(m)
    fmt.Println("修改后的映射:", m)
    
    // 很少使用指向映射的指针
    mPtr := &m
    (*mPtr)["c"] = 3
    fmt.Println("通过指针修改映射:", *mPtr)
}

func modifyMap(m map[string]int) {
    m["new"] = 100
}

8. 多级指针

go 复制代码
func multiLevelPointers() {
    var x int = 10
    var p *int = &x
    var pp **int = &p  // 指向指针的指针
    
    fmt.Println("x:", x)           // 10
    fmt.Println("*p:", *p)         // 10
    fmt.Println("**pp:", **pp)     // 10
    
    // 通过多级指针修改值
    **pp = 100
    fmt.Println("修改后的x:", x)    // 100
    
    // 三级指针
    var ppp ***int = &pp
    ***ppp = 200
    fmt.Println("再次修改后的x:", x) // 200
}

总结

  1. 指针基础 :存储内存地址,& 取地址,* 解引用
  2. 指针类型*T 表示指向类型T的指针
  3. 指针与函数:实现引用传递,允许函数修改外部变量
  4. 指针与结构体:指针接收者方法,避免大结构体复制
  5. 指针安全:Go有垃圾回收,但需注意空指针
  6. 使用场景
    • 函数需要修改参数
    • 大结构体避免复制
    • 实现链表、树等数据结构
    • 共享数据

记住:Go的指针比C/C++更安全,没有指针算术,减少了内存错误的可能性。合理使用指针可以写出高效且清晰的代码。

相关推荐
Acc1oFl4g2 小时前
详解Java反射
java·开发语言·python
Trouvaille ~2 小时前
【Java篇】存在即不变:深刻解读String类不变的艺术
java·开发语言·javase·stringbuilder·stringbuffer·string类·字符串常量池
lemon_sjdk2 小时前
java学习——枚举类
java·开发语言·学习
FreeBuf_2 小时前
Next.js 发布扫描工具:检测并修复受 React2Shell 漏洞(CVE-2025-66478)影响的应用
开发语言·javascript·ecmascript
LYFlied2 小时前
在AI时代,前端开发者如何构建全栈开发视野与核心竞争力
前端·人工智能·后端·ai·全栈
用户47949283569153 小时前
我只是给Typescript提个 typo PR,为什么还要签协议?
前端·后端·开源
Surpass余sheng军3 小时前
AI 时代下的网关技术选型
人工智能·经验分享·分布式·后端·学习·架构
JosieBook3 小时前
【Spring Boot】Spring Boot调用 WebService 接口的两种方式:动态调用 vs 静态调用 亲测有效
java·spring boot·后端
御形封灵3 小时前
基于原生table实现单元格合并、增删
开发语言·javascript·ecmascript