主页

在Go语言的旅程中,&和*这对符号就像是一对形影不离的搭档。它们看似简单,却是理解Go内存管理、性能优化的关键所在。今天,让我们彻底揭开它们的神秘面纱。
符号概览:各司其职的兄弟
&(取地址符):获取变量的内存地址

*(解引用符):通过指针访问或修改目标值

package main

import "fmt"

func main() {
    x := 42
    
    // & 获取地址
    ptr := &x
    fmt.Printf("x的值: %dn", x)           // 42
    fmt.Printf("x的地址: %pn", &x)         // 0xc000018030
    fmt.Printf("ptr的值: %pn", ptr)        // 0xc000018030
    
    // * 解引用
    fmt.Printf("通过ptr访问x: %dn", *ptr) // 42
    
    // 通过指针修改值
    *ptr = 100
    fmt.Printf("修改后x的值: %dn", x)     // 100
}

基础篇:理解内存模型
让我们通过一个生动的比喻来理解:

package main

import "fmt"

func main() {
    // 情景:房子与地址
    house := "漂亮的别墅"  // 实际的数据(房子)
    address := &house    // 地址卡片(指针)
    
    fmt.Printf("房子本身: %sn", house)               // 直接访问房子
    fmt.Printf("地址卡片上写的: %pn", address)        // 查看地址内容
    fmt.Printf("按地址找到的房子: %sn", *address)     // 根据地址找到房子
    
    // 通过地址重新装修房子
    *address = "豪华的庄园"
    fmt.Printf("装修后的房子: %sn", house)           // 房子真的变了!
}

实战篇:函数中的指针传递
场景1:修改函数外部变量

package main

import "fmt"

// 值传递 - 无法影响外部世界
func swapByValue(a, b int) {
    a, b = b, a
    fmt.Printf("函数内交换: a=%d, b=%dn", a, b)
}

// 指针传递 - 真正改变外部变量
func swapByPointer(a, b *int) {
    a, b = b, a
    fmt.Printf("函数内交换: a=%d, b=%dn", a, b)
}

func main() {
    x, y := 10, 20
    
    fmt.Printf("交换前: x=%d, y=%dn", x, y)
    swapByValue(x, y)
    fmt.Printf("值传递交换后: x=%d, y=%dn", x, y) // 无变化!
    
    swapByPointer(&x, &y)
    fmt.Printf("指针传递交换后: x=%d, y=%dn", x, y) // 真正交换!
}

场景2:避免大结构体复制

package main

import (
    "fmt"
    "time"
)

type BigData struct {
    Content [10000]int
    Tag     string
}

// 值传递 - 复制整个大结构体,性能杀手!
func processValue(data BigData) string {
    start := time.Now()
    // 模拟处理...
    elapsed := time.Since(start)
    return fmt.Sprintf("值传递处理时间: %v", elapsed)
}

// 指针传递 - 只传递地址,高效!
func processPointer(data *BigData) string {
    start := time.Now()
    // 模拟处理...
    elapsed := time.Since(start)
    return fmt.Sprintf("指针传递处理时间: %v", elapsed)
}

func main() {
    bigData := BigData{Tag: "大型数据集"}
    
    fmt.Println(processValue(bigData))   // 慢,复制开销大
    fmt.Println(processPointer(&bigData)) // 快,只传指针
}

进阶篇:结构体与方法
指针接收者 vs 值接收者

package main

import "fmt"

type BankAccount struct {
    balance float64
    owner   string
}

// 值接收者方法 - 操作副本,不影响原对象
func (b BankAccount) DisplayBalance() {
    fmt.Printf("账户 %s 余额: $%.2fn", b.owner, b.balance)
}

// 指针接收者方法 - 可以修改原对象状态
func (b *BankAccount) Deposit(amount float64) {
    b.balance += amount
    fmt.Printf("存入 $%.2f, 新余额: $%.2fn", amount, b.balance)
}

func (b *BankAccount) Withdraw(amount float64) bool {
    if b.balance >= amount {
        b.balance -= amount
        fmt.Printf("取出 $%.2f, 新余额: $%.2fn", amount, b.balance)
        return true
    }
    fmt.Println("余额不足!")
    return false
}

func main() {
    account := BankAccount{balance: 1000, owner: "Alice"}
    
    // Go自动转换:account.Deposit() 等价于 (&account).Deposit()
    account.DisplayBalance()
    account.Deposit(500)
    account.Withdraw(200)
    account.DisplayBalance()
}

复合字面量的指针语法糖

package main

import "fmt"

type Config struct {
    Host    string
    Port    int
    Timeout int
}

func main() {
    // 传统方式:先创建,再取地址
    config1 := Config{Host: "localhost", Port: 8080}
    configPtr1 := &config1
    
    // Go的语法糖:直接创建指针
    configPtr2 := &Config{
        Host:    "example.com", 
        Port:    443,
        Timeout: 30,
    }
    
    // 访问时都需要解引用
    fmt.Printf("配置1: %+vn", *configPtr1)
    fmt.Printf("配置2: %+vn", *configPtr2)
    
    // 数组和切片同样适用
    intSlicePtr := &[]int{1, 2, 3, 4, 5}
    fmt.Printf("切片值: %vn", *intSlicePtr)
}

总结:何时使用&和*
场景 推荐方式 理由
A、修改函数外部变量 使用指针 允许函数改变外部状态
B、大结构体传递 使用指针 避免复制开销
C、方法需要修改接收者 指针接收者 可以修改结构体字段
D、小型结构体、基本类型 值传递 复制开销小,更安全
E、不需要修改的配置数据 值传递或常量 保证不可变性
F、实现接口方法 根据需求选择 一致性原则

黄金法则
1、默认使用值传递,除非有明确的理由使用指针

2、指针不是性能银弹,对小对象可能适得其反

3、清晰胜于聪明,代码可读性很重要

4、善用Go的自动转换,让编译器帮你处理简单的指针操作

掌握&和*的艺术,意味着你真正理解了Go语言的内存管理和性能特性。希望这篇博客能帮助你在Go的指针世界中游刃有余!

版权属于:三分快乐,七纷幸福
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
0
查看目录

目录

来自 《深入理解Go语言中的&和*:指针的艺术与科学》
评论

三分快乐,七纷幸福
117 文章数
8 评论量
11 分类数
120 页面数
已在风雨中度过 3年208天21小时10分