Go 指標完全指南

深入理解 Go 語言中 `*` 和 `&` 符號的所有用法,掌握指標的核心概念和實戰應用。


目錄


什麼是指標?

指標(Pointer) 是一個變數,它儲存的是另一個變數的記憶體位址,而不是值本身。

為什麼需要指標?

沒有指標的問題

  • 函數參數傳遞會複製整個資料(效能損耗)
  • 無法在函數內修改外部變數
  • 大型結構體傳遞效率低

有了指標的好處

  • ✅ 高效傳遞大型資料結構
  • ✅ 可以在函數內修改外部變數
  • ✅ 實現複雜資料結構(鏈結串列、樹等)
  • ✅ 避免不必要的記憶體複製

核心概念

值(Value)vs 位址(Address)

想像記憶體像一排信箱:

┌───────────────────┐
│ 信箱編號: 0x1234   │  ← 這是「位址」(在哪裡)
│ 信箱內容: 42       │  ← 這是「值」(放什麼)
└───────────────────┘

變數 x = 42     // x 裡面裝的東西是 42
&x = 0x1234    // x 的「門牌號碼」是 0x1234

指標就像一張寫著地址的紙條:
ptr = &x       // ptr 這張紙條上寫著 "0x1234"
*ptr = ?       // 去 0x1234 這個位址看看裡面是什麼 → 42

核心困惑:同一符號不同意義

Go 的指標讓人困惑的主要原因是:* 符號在不同位置代表完全不同的意義

// 情境:我想建立一個指標,然後用它來修改值

var ptr *int      // 宣告:ptr 是「能存放地址的變數」(型別)
                  //       就像:這是一張「專門寫地址的紙」

x := 42
ptr = &x          // 把 x 的地址寫到這張紙上

*ptr = 100        // 使用:去紙上寫的地址,把裡面的值改成 100(運算)
                  //       就像:按照紙上地址找到信箱,換掉裡面的東西

簡單判斷法

看到什麼 意思 例子
* 在型別旁邊 宣告「這是指標型別」 var p *intfunc(p *int)
* 在變數前面 運算「去那個地址取值/改值」 *p = 10fmt.Println(*p)

* 符號的三種用法

用法 1:在型別宣告中 - 表示「指標型別」

💡 白話文:告訴 Go「這個變數是用來存地址的,不是存值的」

// 情境:我需要一個變數來存放「別人的地址」

var ptr *int        // ptr 是一個「能存 int 地址」的變數
var p *string       // p 是一個「能存 string 地址」的變數

// 函數參數也可以宣告為指標型別
func myFunc(p *int) {}   // 參數 p 要接收一個 int 的地址
func getData() *User {}  // 回傳一個 User 的地址

怎麼讀

  • *int 讀作「指向 int 的指標」或簡單說「int 的地址」
  • *User 讀作「指向 User 的指標」或簡單說「User 的地址」

比喻

int   = 一個裝數字的盒子
*int  = 一張紙,上面寫著「某個裝數字的盒子在哪裡」

用法 2:在變數前面 - 表示「取得指標指向的值」(解引用)

💡 白話文:「去這個地址看看裡面放什麼」或「去這個地址把東西換掉」

// 情境:我有一個地址,想知道那裡放了什麼

x := 42
ptr := &x           // ptr 現在存放 x 的地址(假設是 0x1234)

// 讀取:去地址看看裡面是什麼
value := *ptr       // *ptr = 「去 ptr 存的地址(0x1234)看看」→ 得到 42
fmt.Println(*ptr)   // 輸出:42

// 修改:去地址把東西換掉
*ptr = 100          // *ptr = 100 意思是「去 ptr 存的地址,把裡面換成 100」
fmt.Println(x)      // 輸出:100(x 被改了!因為 ptr 指向 x)

怎麼讀

  • *ptr 讀作「ptr 指向的值」或「去 ptr 那個地址拿東西」
  • *ptr = 100 讀作「把 ptr 指向的值改成 100」

比喻

ptr = 0x1234        ← 紙上寫著地址
*ptr                ← 按照紙上地址去找,看信箱裡有什麼
*ptr = 100          ← 按照紙上地址去找,把信箱裡的東西換成 100

用法 3:在結構體方法的接收者 - 表示「指標接收者」

💡 白話文:決定這個方法「能不能修改原本的資料」

type Counter struct {
    count int
}

// 指標接收者:拿到的是「原本那個 Counter 的地址」
// 所以修改會影響原本的 Counter
func (c *Counter) Increment() {
    c.count++  // ✅ 會修改原始結構體
}

// 值接收者:拿到的是「原本 Counter 的複製品」
// 所以修改只影響複製品,原本的不會變
func (c Counter) GetCount() int {
    return c.count  // 只是讀取,不修改
}

// 值接收者如果嘗試修改...
func (c Counter) TryIncrement() {
    c.count++  // ❌ 只會改到複製品,原本的 Counter 不會變!
}

實際使用

func main() {
    counter := Counter{count: 0}

    counter.Increment()              // 原始 counter.count 變成 1
    fmt.Println(counter.GetCount())  // 輸出:1

    counter.TryIncrement()           // 複製品被改了,但原始的沒變
    fmt.Println(counter.GetCount())  // 輸出:還是 1!
}

怎麼選擇

情況 用什麼 原因
需要修改原始資料 *Type(指標接收者) 才能改到原本的
只是讀取資料 Type(值接收者) 不需要修改
結構體很大(>100 bytes) *Type(指標接收者) 避免複製浪費效能
保持一致性 全部用 *Type 混用容易搞混

& 符號的用法

唯一用法:取得變數的記憶體位址

💡 白話文& 就是問「這個變數住在哪裡?」

// 情境 1:單純取得地址
x := 42
fmt.Println(x)      // 輸出:42(值)
fmt.Println(&x)     // 輸出:0xc0000140a8(地址)

// 情境 2:把地址存起來(建立指標)
ptr := &x           // ptr 現在存著 x 的地址
fmt.Println(ptr)    // 輸出:0xc0000140a8(和 &x 一樣)
fmt.Println(*ptr)   // 輸出:42(去那個地址看看)

常見使用場景

場景 1:傳給需要指標的函數

func modify(p *int) {
    *p = 100
}

x := 42
modify(&x)          // 函數要 *int,所以傳 &x(x 的地址)
fmt.Println(x)      // 輸出:100(被改了)

場景 2:建立結構體並回傳地址

type User struct {
    Name string
}

// 方法 1:先建立,再取地址
u := User{Name: "Alice"}
ptr := &u

// 方法 2:直接建立並取地址(常用寫法)
ptr := &User{Name: "Alice"}  // 一步到位

場景 3:在 slice/map 中存指標

users := []*User{
    &User{Name: "Alice"},
    &User{Name: "Bob"},
}
// 這樣 slice 裡存的是地址,不是整個 User 結構

& 不能用在什麼地方?

// ❌ 不能對常數取地址
// ptr := &42  // 編譯錯誤!

// ❌ 不能對字面值取地址
// ptr := &"hello"  // 編譯錯誤!

// ✅ 只能對變數取地址
x := 42
ptr := &x  // OK

// ✅ 結構體字面值是例外(Go 特別處理)
ptr := &User{Name: "Alice"}  // OK,Go 會自動處理

記憶方式

& = Ampersand = Address(地址)

看到 & 就想到「取地址」:
&x  → x 的地址是什麼?
&變數 → 這個變數住在哪裡?

完整對照表

位置 寫法 意義 範例
型別宣告 *Type 這是一個指向 Type 的指標型別 var p *int
函數參數 func f(p *Type) 參數 p 是指標型別 func update(u *User)
函數回傳 func f() *Type 回傳值是指標型別 func getUser() *User
方法接收者 func (r *Type) method() 接收者是指標(可修改原始資料) func (u *User) Save()
取值(解引用) *pointer 取得指標指向的值 value := *ptr
修改值 *pointer = value 修改指標指向的值 *ptr = 10
取址 &variable 取得變數的記憶體位址 ptr := &x

實戰範例

範例 1:變數宣告與操作

// 第 1 步:宣告普通變數
var x int = 42

// 第 2 步:宣告指標變數
//         ↓ 這個 * 表示「型別」
var ptr *int

// 第 3 步:取得 x 的位址,賦值給 ptr
//        ↓ 這個 & 表示「取址」
ptr = &x

// 第 4 步:透過指標取得值
//            ↓ 這個 * 表示「解引用(取值)」
fmt.Println(*ptr)  // 輸出:42

// 第 5 步:透過指標修改值
//  ↓ 這個 * 表示「解引用(修改值)」
*ptr = 100
fmt.Println(x)     // 輸出:100

範例 2:函數參數中的指標

// 函數宣告
//              ↓ 這個 * 表示「參數型別是指標」
func increment(n *int) {
    //  ↓ 這個 * 表示「取得指標指向的值」
    *n = *n + 1
}

func main() {
    x := 10
    //         ↓ 這個 & 表示「取得 x 的位址」
    increment(&x)
    fmt.Println(x)  // 輸出:11
}

對比:不使用指標的版本

// 沒有 * 表示參數是「值」,不是指標
func increment(n int) {
    n = n + 1  // 只會修改 n 的副本,不會影響原始變數
}

func main() {
    x := 10
    increment(x)    // 傳值
    fmt.Println(x)  // 輸出:10(沒有改變)
}

範例 3:函數回傳指標

type User struct {
    Name string
}

// 函數宣告
//                ↓ 這個 * 表示「回傳型別是指標」
func createUser() *User {
    u := User{Name: "Alice"}
    //    ↓ 這個 & 表示「回傳 u 的位址」
    return &u
}

func main() {
    //        ↓ user 的型別是 *User(指標)
    user := createUser()
    //             ↓ 可以直接用 . 存取(Go 自動解引用)
    fmt.Println(user.Name)

    // 也可以明確解引用
    //               ↓ 這個 * 表示「取得指標指向的值」
    fmt.Println((*user).Name)
}

範例 4:結構體方法的接收者

type Counter struct {
    count int
}

// 指標接收者(會修改原始資料)
//      ↓ 這個 * 表示「接收者是指標型別」
func (c *Counter) Increment() {
    c.count++  // Go 自動處理,等同於 (*c).count++
}

// 值接收者(不會修改原始資料)
//      ↓ 沒有 * 表示「接收者是值型別(複製)」
func (c Counter) GetCount() int {
    return c.count
}

func main() {
    counter := Counter{count: 0}

    counter.Increment()  // 修改原始 counter
    fmt.Println(counter.GetCount())  // 輸出:1
}

關鍵概念圖解

概念 1:變數在記憶體中的樣子

每個變數都有兩個屬性:「住在哪」和「裡面放什麼」

想像記憶體是一排有編號的置物櫃:

        記憶體
    ┌──────────────┐
    │ 櫃子 0x1230   │  (空)
    ├──────────────┤
    │ 櫃子 0x1234   │  ← 變數 x 住這裡
    │   內容: 42    │     裡面放著 42
    ├──────────────┤
    │ 櫃子 0x1238   │  (空)
    └──────────────┘

程式碼對照:
x := 42          // 在某個櫃子裡放入 42,這個櫃子叫做 x
fmt.Println(x)   // 輸出 42(櫃子裡的東西)
fmt.Println(&x)  // 輸出 0x1234(櫃子的編號)

概念 2:指標是什麼?

指標就是「一個專門存放櫃子編號的變數」

x := 42
ptr := &x

        記憶體
    ┌──────────────┐
    │ 櫃子 0x1234   │  ← 變數 x
    │   內容: 42    │
    ├──────────────┤
    │ 櫃子 0x5678   │  ← 變數 ptr(指標)
    │ 內容: 0x1234  │     裡面放的是 x 的櫃子編號!
    └──────────────┘

所以:
ptr   = 0x1234   // ptr 裡面存的是一個地址
*ptr  = 42       // 去 ptr 存的那個地址(0x1234)看看 → 42

概念 3:透過指標修改值

*ptr = 100 意思是「去 ptr 記錄的地址,把那裡的東西換成 100」

執行 *ptr = 100 之後:

        記憶體
    ┌──────────────┐
    │ 櫃子 0x1234   │  ← 變數 x
    │  內容: 100    │     被改成 100 了!
    ├──────────────┤
    │ 櫃子 0x5678   │  ← 變數 ptr
    │ 內容: 0x1234  │     還是指向 x
    └──────────────┘

結果:
fmt.Println(x)    // 輸出 100(x 被改了)
fmt.Println(*ptr) // 輸出 100(透過 ptr 看,也是 100)

概念 4:函數參數傳遞

理解「傳值」vs「傳指標」的差別

【傳值】func increment(n int)

呼叫 increment(x) 時:

    ┌──────────────┐          ┌──────────────┐
    │ 櫃子 0x1234   │          │ 櫃子 0x9999  │
    │  x = 42      │ ──複製──> │  n = 42      │
    └──────────────┘          └──────────────┘
    main 的 x                   函數內的 n(副本)

n++ 只會改 n,不會改 x!

【傳指標】func increment(p *int)

呼叫 increment(&x) 時:

    ┌──────────────┐     ┌──────────────┐
    │ 櫃子 0x1234   │<─── │ 櫃子 0x9999  │
    │  x = 42      │     │ p = 0x1234   │
    └──────────────┘     └──────────────┘
    main 的 x              函數內的 p(存著 x 的地址)

*p = *p + 1 會去 0x1234 把東西改掉,所以 x 會變!

圖解總結

┌─────────────────────────────────────────┐
│                                         │
│   變數名稱     記憶體位置      值          │
│   ────────    ──────────    ────        │
│      x    →    0x1234    →   42         │
│                  ↑                      │
│                  │                      │
│      ptr   →    0x5678    →  0x1234     │
│              (ptr 存的值是 x 的地址)     │
│                                         │
│   &x   = 0x1234  (x 的地址)             │
│   *ptr = 42      (ptr 指向的值)         │
│                                         │
└─────────────────────────────────────────┘

最容易混淆的情況

混淆 1:同一行程式碼出現兩種 *

這是最常讓人困惑的情況!

func modify(ptr *int) {
//              ↑ 型別宣告:ptr 是指標型別
    *ptr = 100
//  ↑ 運算:去 ptr 指向的地方改值
}

拆解理解

// 看這行:func modify(ptr *int)
// 問:*int 是什麼?
// 答:*int 是一個「型別」,表示「int 的指標」
//     所以 ptr 這個參數要接收一個 int 的地址

// 看這行:*ptr = 100
// 問:*ptr 是什麼?
// 答:*ptr 是一個「運算」,表示「去 ptr 存的地址看」
//     *ptr = 100 就是「去那個地址,把值改成 100」

快速判斷法

情況 * 的意思 範例
* 後面接型別名稱(int, string, User...) 型別宣告 *int, *User
* 後面接變數名稱 解引用運算 *ptr, *p

混淆 2:什麼時候用 &,什麼時候用 *

很多人搞不清楚何時該用哪個符號

func main() {
    x := 42

    // 情境 1:我要把 x 的「地址」給別人
    ptr := &x           // ✅ 用 &(取地址)
    modify(&x)          // ✅ 用 &(傳地址給函數)

    // 情境 2:我有一個「地址」,要看/改裡面的值
    value := *ptr       // ✅ 用 *(取值)
    *ptr = 100          // ✅ 用 *(改值)
}

記憶口訣

想要「地址」→ 用 &(從值得到地址)
想要「值」  → 用 *(從地址得到值)

& 是「取地址」  (value → address)
* 是「取內容」  (address → value)

混淆 3:為什麼結構體可以用 . 不用 *

Go 會自動幫你處理,這是語法糖

type User struct {
    Name string
}

func main() {
    u := &User{Name: "Alice"}  // u 是 *User(指標)

    // 理論上應該這樣寫:
    fmt.Println((*u).Name)     // 先解引用,再取欄位

    // 但 Go 允許你這樣寫(自動解引用):
    fmt.Println(u.Name)        // Go 自動處理,更簡潔
}

這也是為什麼方法接收者可以直接用 .

func (u *User) UpdateName(name string) {
    // 理論上:(*u).Name = name
    // 實際上:Go 讓你直接寫
    u.Name = name  // Go 自動處理
}

混淆 4:函數呼叫時的 & 和函數定義的 *

定義用 *,呼叫用 &,剛好相反!

// 函數定義:參數是 *int(指標型別)
func modify(p *int) {
    *p = 100
}

func main() {
    x := 42

    // 函數呼叫:傳入 &x(x 的地址)
    modify(&x)

    // 為什麼不能寫 modify(*x)?
    // 因為 *x 會試圖「解引用」x,但 x 不是指標!
    // modify(*x)  // ❌ 編譯錯誤
}

對照表

位置 寫法 意思
函數定義 func f(p *int) 「我要接收一個地址」
函數呼叫 f(&x) 「給你 x 的地址」

混淆 5:值接收者 vs 指標接收者

這影響「能不能修改原始資料」

type Counter struct {
    count int
}

// 值接收者:拿到的是「副本」
func (c Counter) IncrementWrong() {
    c.count++  // 只改了副本,原本的不會變!
}

// 指標接收者:拿到的是「地址」
func (c *Counter) IncrementRight() {
    c.count++  // 會改原本的
}

func main() {
    counter := Counter{count: 0}

    counter.IncrementWrong()
    fmt.Println(counter.count)  // 輸出:0(沒變)

    counter.IncrementRight()
    fmt.Println(counter.count)  // 輸出:1(變了)
}

怎麼選

要修改原始資料?           → 用 *Type(指標接收者)
只是讀取、不修改?         → Type 或 *Type 都可以
結構體很大(效能考量)?    → 用 *Type(避免複製)
團隊習慣或一致性?         → 通常全部用 *Type

最佳實踐

1. 何時使用指標接收者

type LargeStruct struct {
    Data [1000]int
}

// ❌ 不好的做法:大型結構體使用值接收者
func (s LargeStruct) Process() {
    // 每次呼叫都複製 1000 個 int
}

// ✅ 好的做法:大型結構體使用指標接收者
func (s *LargeStruct) Process() {
    // 只傳遞指標,不複製資料
}

何時使用指標接收者

  • 需要修改接收者的狀態
  • 接收者是大型結構體(避免複製)
  • 保持一致性(如果有一個方法用指標,其他方法也應該用)

2. 不要對基本型別過度使用指標

// ❌ 不好的做法:簡單型別使用指標增加複雜度
func add(a *int, b *int) *int {
    result := *a + *b
    return &result
}

// ✅ 好的做法:簡單型別直接傳值
func add(a int, b int) int {
    return a + b
}

3. 避免回傳指向局部變數的指標(在其他語言)

// ✅ Go 中是安全的(編譯器會自動處理)
func createUser() *User {
    u := User{Name: "Alice"}
    return &u  // Go 會將 u 移到 heap,安全
}

// 但在 C/C++ 中這是危險的!
// Go 的垃圾回收機制確保這是安全的

4. 使用 nil 檢查避免 panic

// ❌ 不好的做法:不檢查 nil
func process(user *User) {
    fmt.Println(user.Name)  // 如果 user 是 nil 會 panic
}

// ✅ 好的做法:檢查 nil
func process(user *User) {
    if user == nil {
        fmt.Println("User is nil")
        return
    }
    fmt.Println(user.Name)
}

5. 指標與切片、map 的關係

// ✅ 切片和 map 本身就是引用型別,不需要指標
func modifySlice(s []int) {
    s[0] = 100  // 會修改原始切片的元素
}

func modifyMap(m map[string]int) {
    m["key"] = 100  // 會修改原始 map
}

// ❌ 不需要:切片/map 的指標(通常不必要)
func modifySlice(s *[]int) {
    (*s)[0] = 100
}

6. 方法接收者一致性

type User struct {
    Name string
    Age  int
}

// ✅ 好的做法:所有方法使用相同的接收者類型
func (u *User) SetName(name string) { u.Name = name }
func (u *User) SetAge(age int)      { u.Age = age }
func (u *User) GetName() string     { return u.Name }

// ❌ 不好的做法:混用值接收者和指標接收者
func (u *User) SetName(name string) { u.Name = name }
func (u User) SetAge(age int)       { u.Age = age }  // 不一致

7. 使用指標減少記憶體分配

type Config struct {
    Settings map[string]string
    Options  []string
}

// ✅ 好的做法:傳遞指標避免複製
func processConfig(cfg *Config) {
    // 處理配置
}

// ❌ 不好的做法:複製整個結構體
func processConfig(cfg Config) {
    // 每次呼叫都複製 map 和 slice
}

8. 記憶口訣

值 ──&──> 位址    (&x: 從值得到位址)
位址 ──*──> 值    (*ptr: 從位址得到值)

決策樹

需要修改原始資料?
├─ 是 → 使用指標 (*Type)
└─ 否 → 是大型結構體?
    ├─ 是 → 使用指標(效能考量)
    └─ 否 → 使用值(簡單安全)

常見錯誤

錯誤 1:忘記宣告是指標型別

問題:想修改原始值,但函數參數不是指標

// ❌ 錯誤:參數是 int,不是 *int
func modify(n int) {
    n = 100  // 只會修改副本,原始變數不會變
}

func main() {
    x := 42
    modify(x)
    fmt.Println(x)  // 輸出:42(沒變!)
}

// ✅ 正確:參數改成 *int
func modify(n *int) {
    *n = 100  // 修改原始值
}

func main() {
    x := 42
    modify(&x)      // 記得傳地址
    fmt.Println(x)  // 輸出:100(變了!)
}

錯誤 2:忘記傳遞位址

問題:函數要指標,但傳了值

func modify(n *int) {
    *n = 100
}

func main() {
    x := 10

    // ❌ 錯誤:型別不符
    // modify(x)  // 編譯錯誤:cannot use x (type int) as type *int

    // ✅ 正確:傳入位址
    modify(&x)
    fmt.Println(x)  // 輸出:100
}

記住:函數定義用 *,呼叫時用 &


錯誤 3:對非指標變數使用 *

問題:變數不是指標,卻試圖解引用

x := 42

// ❌ 錯誤:x 是 int,不是指標,不能解引用
// fmt.Println(*x)  // 編譯錯誤:invalid indirect of x

// ✅ 正確:先取地址,得到指標,再解引用
ptr := &x          // ptr 是 *int
fmt.Println(*ptr)  // 輸出:42

判斷方式

  • 變數是 intstring 等 → 不能用 *
  • 變數是 *int*string 等 → 可以用 *

錯誤 4:對 nil 指標解引用

問題:指標沒有指向任何東西,就試圖存取

var ptr *int  // ptr 是 nil(沒有指向任何東西)

// ❌ 錯誤:ptr 是 nil,會 panic
// *ptr = 100  // runtime error: invalid memory address

// ✅ 正確:先讓指標指向某個變數
x := 0
ptr = &x       // 現在 ptr 指向 x
*ptr = 100     // OK,修改 x 的值
fmt.Println(x) // 輸出:100

防範方式

func process(ptr *int) {
    // 先檢查 nil
    if ptr == nil {
        fmt.Println("錯誤:指標是 nil")
        return
    }
    *ptr = 100  // 安全
}

錯誤 5:誤解 new() 和 &

問題:搞混 new()& 的用法

// new(T) 會:
// 1. 分配記憶體給型別 T
// 2. 把值初始化為零值
// 3. 回傳該記憶體的地址(*T)

ptr := new(int)    // ptr 是 *int,指向一個值為 0 的 int
fmt.Println(*ptr)  // 輸出:0

// &x 會:
// 取得現有變數 x 的地址

x := 42
ptr2 := &x         // ptr2 是 *int,指向 x
fmt.Println(*ptr2) // 輸出:42

什麼時候用什麼

情況 用什麼
已有變數,想取地址 &x
想分配新記憶體,取得指標 new(T)&T{}

練習題

練習 1:判斷每個符號的意義

func process(data *[]int) *int {
    first := (*data)[0]
    return &first
}
答案
符號 意義
*[]int(參數) 型別宣告:參數是「指向 int 切片的指標」
*int(回傳) 型別宣告:回傳「指向 int 的指標」
(*data) 解引用運算:取得 data 指向的切片
&first 取址運算:取得 first 變數的地址

練習 2:預測輸出

package main

import "fmt"

func main() {
    x := 10
    y := 20

    px := &x
    py := &y

    *px = *py
    *py = 30

    fmt.Println(x, y)  // 輸出 ?
}
答案
20 30

步驟解析

初始狀態:x = 10, y = 20, px 指向 x, py 指向 y

*px = *py
→ 把「py 指向的值」賦給「px 指向的位置」
→ 把 20 賦給 x
→ 現在 x = 20, y = 20

*py = 30
→ 把 30 賦給「py 指向的位置」
→ 把 30 賦給 y
→ 現在 x = 20, y = 30

練習 3:找出錯誤

以下程式碼有什麼問題?

func double(n *int) {
    n = n * 2
}

func main() {
    x := 5
    double(&x)
    fmt.Println(x)
}
答案

錯誤n = n * 2 這行有問題

  • n*int(指標)
  • n * 2 試圖對指標做乘法,但指標不能這樣運算
  • 編譯會報錯:invalid operation: n * 2 (mismatched types *int and int)

正確寫法

func double(n *int) {
    *n = *n * 2  // 解引用後再運算
}

練習 4:預測輸出

type Person struct {
    Name string
}

func changeName(p Person) {
    p.Name = "Bob"
}

func changeNamePtr(p *Person) {
    p.Name = "Charlie"
}

func main() {
    person := Person{Name: "Alice"}

    changeName(person)
    fmt.Println(person.Name)  // 輸出 ?

    changeNamePtr(&person)
    fmt.Println(person.Name)  // 輸出 ?
}
答案
Alice
Charlie

解析

  • changeName(person):傳值,p 是副本,修改副本不影響原始 person
  • changeNamePtr(&person):傳指標,p 指向原始 person,修改會影響原始資料

練習 5:填空題

補完以下程式碼,使得 swap 函數能交換兩個整數的值:

func swap(a ___, b ___) {
    temp := ___
    ___ = ___
    ___ = temp
}

func main() {
    x, y := 1, 2
    swap(___, ___)
    fmt.Println(x, y)  // 應該輸出:2 1
}
答案
func swap(a *int, b *int) {
    temp := *a
    *a = *b
    *b = temp
}

func main() {
    x, y := 1, 2
    swap(&x, &y)
    fmt.Println(x, y)  // 輸出:2 1
}

解析

  • 參數要用指標 *int,才能修改原始變數
  • 呼叫時傳地址 &x, &y
  • 函數內用 *a, *b 存取實際的值

總結

核心要點

指標 = 位址 + 解引用能力

關鍵符號:
  & = 取址運算子(值 → 位址)
  * = 解引用運算子(位址 → 值)或型別宣告

快速決策表

情境 使用指標? 理由
需要修改原始資料 ✅ 是 使用 *Type
大型結構體(>100 bytes) ✅ 是 避免複製
小型資料(int, bool) ❌ 否 值傳遞更簡單
切片、map、chan ❌ 否 已經是引用型別
方法需要修改接收者 ✅ 是 使用指標接收者

符號總結

符號 位置 意義 範例
* 型別宣告 指標型別 var p *int
* 變數前面 解引用 *p = 10
& 變數前面 取址 p = &x

記憶口訣

三個關鍵問題

  1. 何時用 & → 需要傳遞位址時
  2. 何時用 * 解引用? → 需要透過指標讀寫值時
  3. 何時用 * 型別? → 宣告變數/參數/回傳值是指標時

最重要的概念

  • & 把值變成位址
  • * 把位址變回值(當作運算子時)
  • * 宣告指標型別(當作型別時)

建立日期:2025-10-20 最後更新:2025-12-12

🔗相關文章