目錄
什麼是指標?
指標(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 *int、func(p *int) |
* 在變數前面 |
運算「去那個地址取值/改值」 | *p = 10、fmt.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
判斷方式:
- 變數是
int、string等 → 不能用* - 變數是
*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 是副本,修改副本不影響原始 personchangeNamePtr(&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 |
記憶口訣
三個關鍵問題:
- 何時用
&? → 需要傳遞位址時 - 何時用
*解引用? → 需要透過指標讀寫值時 - 何時用
*型別? → 宣告變數/參數/回傳值是指標時
最重要的概念:
&把值變成位址*把位址變回值(當作運算子時)*宣告指標型別(當作型別時)
建立日期:2025-10-20 最後更新:2025-12-12