第十五回:Go 语言指针 (Pointer)

秋香住在哪里?
唐伯虎
唐伯虎: 9527,我想给秋香姐送情书,但我只知道她叫“秋香”(变量值),不知道她住在哪个房间(内存地址)。
每次我把信交给丫鬟(函数传参),丫鬟都抄了一份副本拿走,根本没送到真·秋香手里!
秋香
秋香: 笨蛋!你要拿到我的门牌号 (Pointer),直接按图索骥找到我的房间,才能把信送到我心里呀!

🏠 门牌号与房间 (地址与值)

在计算机的内存华府里,每一个变量都有一个专属的房间。

1. 取地址符 (&)

想知道变量住在哪里?在它面前加个 &

name := "秋香"
// addr 是 address 的缩写
// &name 意思是:获取 name 的门牌号
addr := &name 

fmt.Printf("房客名字: %s\n", name) // 秋香
fmt.Printf("房间地址: %v\n", addr) // 0xc000010200 (每次运行可能不一样)

2. 指针变量

专门用来存“门牌号”的变量,叫指针变量

// 定义一个指针变量 ptr,它指向 string 类型
// *string 表示这是一个“存放字符串地址”的盒子
var ptr *string = &name

🔑 拿钥匙开门 (解引用 *)

拿到了门牌号(指针),怎么找里面的人呢?用 * 号!这叫解引用 (Dereference)

fmt.Println("地址是:", ptr)
fmt.Println("住的人是:", *ptr) // 输出:秋香

// ⚠️ 还能隔空改名!
// *ptr = "..." 意思是:找到 ptr 指向的那个房间,把里面的人换掉
*ptr = "石榴姐"

fmt.Println(name) // 输出:石榴姐 (原来的 name 真的变了!)

📜 藏宝图复印件 (值传递 vs 引用传递)

这是 Go 语言最重要的一课!Go 语言只有值传递(Pass by Value)。

如果你传的是变量,Go 会复印一份给你;如果你传的是指针(地址),Go 也会复印一份“地址纸条”给你——但这张纸条指向的房间是同一个!

石榴姐
石榴姐: 就像你给我一张《春树秋霜图》的复印件,我怎么涂鸦都影响不了原画。
但如果你给我的是藏宝图坐标,我去那个坐标挖个坑,你的宝藏就真没了!
func modifyValue(s string) {
    s = "经过整容"
}

func modifyPointer(s *string) {
    *s = "经过整容"
}

func main() {
    face := "如花"
    
    // 1. 传值(给复印件)
    modifyValue(face)
    fmt.Println(face) // 输出:如花 (根本没变)
    
    // 2. 传地址(给坐标)
    modifyPointer(&face)
    fmt.Println(face) // 输出:经过整容 (变了!)
}

🏗️ 平地起高楼 (new 函数)

有时候我们想先占个坑,以后再住人。可以使用 new() 函数。

// new(int) 会做两件事:
// 1. 在内存分配一个 int 类型的空间
// 2. 把它初始化为 0
// 3. 返回这个空间的地址指针
ptr := new(int)

fmt.Println(ptr)  // 输出地址 (如 0xc0000...)
fmt.Println(*ptr) // 输出 0 (默认值)

*ptr = 9527       // 填入数据
fmt.Println(*ptr) // 输出 9527

👻 幽灵房间 (逃逸分析)

高级知识点: 在 C/C++ 中,函数里的局部变量在函数结束后就会销毁。但在 Go 中,如果你把局部变量的地址(指针)返回出去了,这个变量就会“逃逸”到堆(Heap)上,继续存活。
就像唐伯虎虽然离开了华府(函数结束),但他留下的真迹(指针指向的数据)依然被世人珍藏。
func createGift() *string {
    gift := "唐寅诗集" // 局部变量
    return &gift      // 返回局部变量的地址
}

func main() {
    // 在 C 语言里这就崩了,但在 Go 里是安全的!
    p := createGift()
    fmt.Println(*p) // 输出:唐寅诗集
}

☠️ 走火入魔警告 (空指针 nil)

如果一个指针没有指向任何房间,它就是 nil(空)。试图去开一个不存在的房间的门,会直接报错(Panic)!

华夫人
华夫人: 大胆!竟敢拿一把假钥匙(nil)去开空气门?拖出去斩了!(Runtime Panic)
var p *int // 声明了但没赋值,默认是 nil

// fmt.Println(*p) // 💥 报错!panic: runtime error: invalid memory address or nil pointer dereference

// ✅ 正确做法:先判断
if p != nil {
    fmt.Println(*p)
} else {
    fmt.Println("这是一个空指针,不能用!")
}

🎯 练功房(交换灵魂)

编写一个函数 swap,交换两个整数变量的值。(提示:如果不传指针,是换不掉的哦)

package main
import "fmt"

// 填空:参数类型应该是什么?
func swap(a _____, b _____) {
    temp := *a
    *a = *b
    *b = temp
}

func main() {
    x, y := 1, 2
    // 填空:怎么传参?
    swap(____, ____)
    fmt.Println(x, y) // 期望输出:2 1
}

任务: 补全代码,实现交换。

答案:

func swap(a *int, b *int) { ... }

swap(&x, &y)

只有通过指针(地址),才能真正交换两个变量肚子里的值。