在 Go 語言(Golang)中,*
和 &
是與指針相關的兩個重要操作符。
- 理解它們對于掌握 Go 的內存管理和函數參數傳遞機制非常關鍵。
文章目錄
- 一、`&` 操作符:取地址(Address-of)
- 示例:
- 二、`*` 操作符:解引用(Dereference)
- 示例:
- 三、指針類型聲明:`*T`
- 示例:
- 四、使用場景
- 1. 函數中修改原變量(傳引用)
- 2. 避免大結構體拷貝
- 五、new 函數創建指針
- 六、nil 指針與安全
- 七、常見誤區
- 八、總結
- 九、小練習
- 十、額外提示
- Go語言中 `*` 和 `&` 操作符詳解 - 代碼演示
- 1. 基礎概念演示
- 2. 指針的基本操作
- 3. 指針與函數 - 修改原值
- 4. 指針與結構體
- 5. 指針數組和數組指針
- 6. 多級指針
- 7. 指針的實際應用場景
- 8. 指針陷阱和注意事項
- 總結
一、&
操作符:取地址(Address-of)
&
用于獲取一個變量的內存地址。
示例:
package mainimport "fmt"func main() {x := 10fmt.Println("x 的值:", x) // 輸出: 10fmt.Println("x 的地址:", &x) // 輸出: 0xc00001a0a0 (類似這樣的地址)
}
&x
表示“變量 x 的內存地址”。- 結果是一個指針類型,例如
*int
(指向 int 的指針)。
二、*
操作符:解引用(Dereference)
*
用于訪問指針所指向的值。
示例:
package mainimport "fmt"func main() {x := 10p := &x // p 是一個 *int 類型的指針,指向 xfmt.Println(*p) // 輸出: 10,*p 表示“p 指向的值”*p = 20 // 修改 p 指向的值fmt.Println(x) // 輸出: 20,x 也被修改了
}
*p
表示“指針 p 所指向的變量的值”。- 可以通過
*p = 20
來修改原變量的值。
三、指針類型聲明:*T
在 Go 中,指針的類型是 *T
,表示“指向類型為 T 的變量的指針”。
示例:
var p *int // p 是一個指向 int 的指針
var s *string // s 是一個指向 string 的指針
未初始化的指針默認值是 nil
。
var p *int
fmt.Println(p) // 輸出: <nil>
四、使用場景
1. 函數中修改原變量(傳引用)
Go 中函數參數是值傳遞,如果想在函數中修改原變量,需要傳指針。
func increment(p *int) {*p = *p + 1
}func main() {x := 5increment(&x)fmt.Println(x) // 輸出: 6
}
- 傳入
&x
把地址傳給函數。 - 函數內用
*p
修改原值。
2. 避免大結構體拷貝
傳遞大型結構體時,使用指針可以避免復制整個結構體,提高性能。
type User struct {Name stringAge int
}func printUser(u *User) {fmt.Printf("Name: %s, Age: %d\n", u.Name, u.Age)
}func main() {user := User{Name: "Alice", Age: 30}printUser(&user) // 傳指針
}
注意:Go 允許通過指針直接訪問結構體字段(
u.Name
等價于(*u).Name
),這是語法糖。
五、new 函數創建指針
Go 提供 new(T)
函數來分配內存并返回指向該類型零值的指針。
p := new(int) // 分配一個 int 的內存,初始化為 0
*p = 10
fmt.Println(*p) // 輸出: 10
等價于:
var temp int
p := &temp
六、nil 指針與安全
未初始化或指向無效地址的指針是 nil
,解引用 nil
指針會引發 panic。
var p *int
fmt.Println(*p) // panic: runtime error: invalid memory address or nil pointer dereference
使用前應確保指針非 nil。
七、常見誤區
寫法 | 含義 |
---|---|
&x | 取變量 x 的地址 |
*p | 獲取指針 p 指向的值(解引用) |
*int | 指針類型,指向 int 的指針 |
p := &x | p 是一個 *int,指向 x |
*p = 5 | 修改 p 所指向的變量的值 |
八、總結
符號 | 名稱 | 作用 | 示例 |
---|---|---|---|
& | 取地址符 | 獲取變量的內存地址 | p := &x |
* | 解引用符 | 訪問指針所指向的值 | value := *p |
*T | 指針類型 | 聲明一個指向類型 T 的指針 | var p *int |
九、小練習
func main() {a := 5b := &a*b = *b + 10fmt.Println(a) // 輸出什么?
}
? 輸出:15
十、額外提示
- Go 沒有指針運算(不像 C/C++),不能進行
p++
這樣的操作。 - Go 的垃圾回收機制會自動管理內存,無需手動釋放指針指向的內存。
- 盡量使用值語義,僅在需要修改原值或優化性能時使用指針。
文章目錄
- 一、`&` 操作符:取地址(Address-of)
- 示例:
- 二、`*` 操作符:解引用(Dereference)
- 示例:
- 三、指針類型聲明:`*T`
- 示例:
- 四、使用場景
- 1. 函數中修改原變量(傳引用)
- 2. 避免大結構體拷貝
- 五、new 函數創建指針
- 六、nil 指針與安全
- 七、常見誤區
- 八、總結
- 九、小練習
- 十、額外提示
- Go語言中 `*` 和 `&` 操作符詳解 - 代碼演示
- 1. 基礎概念演示
- 2. 指針的基本操作
- 3. 指針與函數 - 修改原值
- 4. 指針與結構體
- 5. 指針數組和數組指針
- 6. 多級指針
- 7. 指針的實際應用場景
- 8. 指針陷阱和注意事項
- 總結
Go語言中 *
和 &
操作符詳解 - 代碼演示
1. 基礎概念演示
package mainimport ("fmt""unsafe"
)func main() {// 聲明一個整數變量x := 42fmt.Println("=== 基礎概念 ===")fmt.Printf("變量 x 的值: %d\n", x)fmt.Printf("變量 x 的地址: %p\n", &x)fmt.Printf("變量 x 的大小: %d 字節\n", unsafe.Sizeof(x))// 使用 & 操作符獲取地址ptr := &xfmt.Printf("ptr (指向 x 的指針): %p\n", ptr)fmt.Printf("ptr 的類型: %T\n", ptr)// 使用 * 操作符解引用fmt.Printf("*ptr (ptr 指向的值): %d\n", *ptr)
}
輸出:
=== 基礎概念 ===
變量 x 的值: 42
變量 x 的地址: 0xc00001a0a0
變量 x 的大小: 8 字節
ptr (指向 x 的指針): 0xc00001a0a0
ptr 的類型: *int
*ptr (ptr 指向的值): 42
2. 指針的基本操作
package mainimport "fmt"func main() {fmt.Println("=== 指針的基本操作 ===")// 聲明并初始化變量num := 100fmt.Printf("原始值 num = %d\n", num)// 獲取指針ptr := &numfmt.Printf("ptr = %p, *ptr = %d\n", ptr, *ptr)// 通過指針修改值*ptr = 200fmt.Printf("通過指針修改后: num = %d, *ptr = %d\n", num, *ptr)// 聲明空指針var nilPtr *intfmt.Printf("空指針: %v\n", nilPtr)// 創建指針的幾種方式var a int = 50var p1 *int = &a // 顯式聲明p2 := &a // 簡短聲明p3 := new(int) // 使用 new 函數*p3 = 75fmt.Printf("p1 指向的值: %d\n", *p1)fmt.Printf("p2 指向的值: %d\n", *p2)fmt.Printf("p3 指向的值: %d\n", *p3)
}
輸出:
=== 指針的基本操作 ===
原始值 num = 100
ptr = 0xc00001a0a8, *ptr = 100
通過指針修改后: num = 200, *ptr = 200
空指針: <nil>
p1 指向的值: 50
p2 指向的值: 50
p3 指向的值: 75
3. 指針與函數 - 修改原值
package mainimport "fmt"// 值傳遞 - 不會修改原值
func addByValue(x int) {x = x + 10fmt.Printf("函數內部 x = %d\n", x)
}// 指針傳遞 - 會修改原值
func addByPointer(x *int) {*x = *x + 10fmt.Printf("函數內部 *x = %d\n", *x)
}// 返回指針的函數
func createPointer() *int {value := 42return &value // 返回局部變量的地址(Go 允許這樣做)
}func main() {fmt.Println("=== 指針與函數 ===")// 值傳遞示例a := 5fmt.Printf("調用前 a = %d\n", a)addByValue(a)fmt.Printf("調用后 a = %d\n", a)fmt.Println("---")// 指針傳遞示例b := 5fmt.Printf("調用前 b = %d\n", b)addByPointer(&b)fmt.Printf("調用后 b = %d\n", b)// 使用返回指針的函數ptr := createPointer()fmt.Printf("createPointer 返回的值: %d\n", *ptr)
}
輸出:
=== 指針與函數 ===
調用前 a = 5
函數內部 x = 15
調用后 a = 5
---
調用前 b = 5
函數內部 *x = 15
調用后 b = 15
createPointer 返回的值: 42
4. 指針與結構體
package mainimport "fmt"type Person struct {Name stringAge int
}// 值接收者 - 不修改原對象
func (p Person) celebrateBirthdayByValue() Person {p.Age++fmt.Printf("函數內部: %s 的年齡變為 %d\n", p.Name, p.Age)return p
}// 指針接收者 - 修改原對象
func (p *Person) celebrateBirthdayByPointer() {p.Age++fmt.Printf("函數內部: %s 的年齡變為 %d\n", p.Name, p.Age)
}// 修改結構體字段的函數
func updatePersonName(p *Person, newName string) {p.Name = newName
}func main() {fmt.Println("=== 指針與結構體 ===")// 創建結構體person := Person{Name: "Alice", Age: 25}fmt.Printf("初始狀態: %+v\n", person)// 值接收者方法fmt.Println("\n--- 值接收者方法 ---")updatedPerson := person.celebrateBirthdayByValue()fmt.Printf("調用后 person: %+v\n", person)fmt.Printf("返回的 updatedPerson: %+v\n", updatedPerson)// 指針接收者方法fmt.Println("\n--- 指針接收者方法 ---")person.celebrateBirthdayByPointer()fmt.Printf("調用后 person: %+v\n", person)// 通過指針修改結構體fmt.Println("\n--- 通過指針修改結構體 ---")updatePersonName(&person, "Alice Smith")fmt.Printf("修改后 person: %+v\n", person)// 結構體指針的聲明和使用fmt.Println("\n--- 結構體指針 ---")var personPtr *PersonpersonPtr = &personfmt.Printf("personPtr 指向: %+v\n", *personPtr)// Go 的語法糖:可以直接通過指針訪問字段fmt.Printf("personPtr.Name = %s\n", personPtr.Name) // 等價于 (*personPtr).Namefmt.Printf("(*personPtr).Name = %s\n", (*personPtr).Name)
}
輸出:
=== 指針與結構體 ===
初始狀態: {Name:Alice Age:25}--- 值接收者方法 ---
函數內部: Alice 的年齡變為 26
調用后 person: {Name:Alice Age:25}
返回的 updatedPerson: {Name:Alice Age:26}--- 指針接收者方法 ---
函數內部: Alice 的年齡變為 26
調用后 person: {Name:Alice Smith Age:26}--- 通過指針修改結構體 ---
修改后 person: {Name:Alice Smith Age:26}--- 結構體指針 ---
personPtr 指向: {Name:Alice Smith Age:26}
personPtr.Name = Alice Smith
(*personPtr).Name = Alice Smith
5. 指針數組和數組指針
package mainimport "fmt"func main() {fmt.Println("=== 指針數組和數組指針 ===")// 普通數組arr := [3]int{10, 20, 30}fmt.Printf("原始數組: %v\n", arr)// 指針數組 - 數組的每個元素都是指針fmt.Println("\n--- 指針數組 ---")var ptrArray [3]*intfor i := range arr {ptrArray[i] = &arr[i]}fmt.Printf("指針數組: [%p, %p, %p]\n", ptrArray[0], ptrArray[1], ptrArray[2])fmt.Printf("通過指針數組訪問值: [%d, %d, %d]\n", *ptrArray[0], *ptrArray[1], *ptrArray[2])// 修改原數組,觀察指針數組的變化arr[0] = 100fmt.Printf("修改 arr[0] 后,通過指針數組訪問: [%d, %d, %d]\n", *ptrArray[0], *ptrArray[1], *ptrArray[2])// 數組指針 - 指向整個數組的指針fmt.Println("\n--- 數組指針 ---")var arrPtr *[3]int = &arrfmt.Printf("數組指針 arrPtr: %p\n", arrPtr)fmt.Printf("*arrPtr: %v\n", *arrPtr)fmt.Printf("通過數組指針訪問元素: %d, %d, %d\n", (*arrPtr)[0], (*arrPtr)[1], (*arrPtr)[2])// 修改通過數組指針(*arrPtr)[1] = 200fmt.Printf("修改后原數組: %v\n", arr)
}
輸出:
=== 指針數組和數組指針 ===
原始數組: [10 20 30]--- 指針數組 ---
指針數組: [0xc00001a080, 0xc00001a088, 0xc00001a090]
通過指針數組訪問值: [10, 20, 30]
修改 arr[0] 后,通過指針數組訪問: [100, 20, 30]--- 數組指針 ---
數組指針 arrPtr: 0xc00001a080
*arrPtr: [100 20 30]
通過數組指針訪問元素: 100, 20, 30
修改后原數組: [100 200 30]
6. 多級指針
package mainimport "fmt"func main() {fmt.Println("=== 多級指針 ===")// 一級指針a := 42ptr1 := &afmt.Printf("變量 a = %d, 地址 = %p\n", a, &a)fmt.Printf("一級指針 ptr1 = %p, *ptr1 = %d\n", ptr1, *ptr1)// 二級指針ptr2 := &ptr1fmt.Printf("二級指針 ptr2 = %p, *ptr2 = %p, **ptr2 = %d\n", ptr2, *ptr2, **ptr2)// 三級指針ptr3 := &ptr2fmt.Printf("三級指針 ptr3 = %p, ***ptr3 = %d\n", ptr3, ***ptr3)// 通過多級指針修改值fmt.Println("\n--- 通過多級指針修改值 ---")fmt.Printf("修改前: a = %d\n", a)**ptr2 = 99 // 等價于 *ptr1 = 99,等價于 a = 99fmt.Printf("通過 **ptr2 修改后: a = %d\n", a)***ptr3 = 199 // 等價于 a = 199fmt.Printf("通過 ***ptr3 修改后: a = %d\n", a)
}
輸出:
=== 多級指針 ===
變量 a = 42, 地址 = 0xc00001a0a8
一級指針 ptr1 = 0xc00001a0a8, *ptr1 = 42
二級指針 ptr2 = 0xc000006028, *ptr2 = 0xc00001a0a8, **ptr2 = 42
三級指針 ptr3 = 0xc000006038, ***ptr3 = 42--- 通過多級指針修改值 ---
修改前: a = 42
通過 **ptr2 修改后: a = 99
通過 ***ptr3 修改后: a = 199
7. 指針的實際應用場景
package mainimport "fmt"// 1. 避免大對象拷貝
type LargeStruct struct {Data [1000]intName string
}func processByValue(ls LargeStruct) {fmt.Printf("值傳遞 - 處理結構體: %s\n", ls.Name)
}func processByPointer(ls *LargeStruct) {fmt.Printf("指針傳遞 - 處理結構體: %s\n", ls.Name)
}// 2. 鏈表節點示例
type Node struct {Value intNext *Node
}func (n *Node) Append(value int) *Node {newNode := &Node{Value: value}n.Next = newNodereturn newNode
}// 3. 錯誤處理模式
func divide(a, b float64) (*float64, error) {if b == 0 {return nil, fmt.Errorf("除數不能為零")}result := a / breturn &result, nil
}func main() {fmt.Println("=== 指針的實際應用 ===")// 1. 避免大對象拷貝fmt.Println("\n--- 避免大對象拷貝 ---")large := LargeStruct{Name: "Large Data"}fmt.Println("值傳遞:")processByValue(large) // 會拷貝整個結構體fmt.Println("指針傳遞:")processByPointer(&large) // 只傳遞8字節的指針// 2. 鏈表示例fmt.Println("\n--- 鏈表示例 ---")head := &Node{Value: 1}current := head.Append(2)current.Append(3)// 遍歷鏈表for node := head; node != nil; node = node.Next {fmt.Printf("%d -> ", node.Value)}fmt.Println("nil")// 3. 錯誤處理fmt.Println("\n--- 錯誤處理 ---")if result, err := divide(10, 2); err != nil {fmt.Printf("錯誤: %v\n", err)} else {fmt.Printf("10 / 2 = %.1f\n", *result)}if result, err := divide(10, 0); err != nil {fmt.Printf("錯誤: %v\n", err)if result == nil {fmt.Println("result 是 nil 指針")}} else {fmt.Printf("10 / 0 = %.1f\n", *result)}
}
輸出:
=== 指針的實際應用 ===--- 避免大對象拷貝 ---
值傳遞:
指針傳遞 - 處理結構體: Large Data--- 鏈表示例 ---
1 -> 2 -> 3 -> nil--- 錯誤處理 ---
10 / 2 = 5.0
錯誤: 除數不能為零
result 是 nil 指針
8. 指針陷阱和注意事項
package mainimport "fmt"func main() {fmt.Println("=== 指針陷阱和注意事項 ===")// 1. nil 指針解引用fmt.Println("\n--- nil 指針解引用 ---")var nilPtr *intfmt.Printf("nilPtr = %v\n", nilPtr)// 取消注釋下面這行會引發 panic// fmt.Printf("*nilPtr = %d\n", *nilPtr) // panic: runtime error// 安全檢查if nilPtr != nil {fmt.Printf("*nilPtr = %d\n", *nilPtr)} else {fmt.Println("nilPtr 是 nil,不能解引用")}// 2. 懸空指針(Go 中較少見,但要注意生命周期)fmt.Println("\n--- 返回局部變量地址 ---")func() {local := 42ptr := &localfmt.Printf("局部變量地址: %p, 值: %d\n", ptr, *ptr)// 函數結束后,local 被銷毀,但 Go 的逃逸分析會處理這種情況}()// 3. 指針比較fmt.Println("\n--- 指針比較 ---")a := 10b := 10ptrA1 := &aptrA2 := &aptrB := &bfmt.Printf("ptrA1 == ptrA2: %t\n", ptrA1 == ptrA2) // true - 指向同一變量fmt.Printf("ptrA1 == ptrB: %t\n", ptrA1 == ptrB) // false - 指向不同變量fmt.Printf("*ptrA1 == *ptrB: %t\n", *ptrA1 == *ptrB) // true - 值相等// 4. 指針與接口fmt.Println("\n--- 指針與接口 ---")var i interface{} = &aif ptr, ok := i.(*int); ok {fmt.Printf("成功轉換為 *int: %d\n", *ptr)}
}
輸出:
=== 指針陷阱和注意事項 ===--- nil 指針解引用 ---
nilPtr = <nil>
nilPtr 是 nil,不能解引用--- 返回局部變量地址 ---
局部變量地址: 0xc00001a0a8, 值: 42--- 指針比較 ---
ptrA1 == ptrA2: true
ptrA1 == ptrB: false
*ptrA1 == *ptrB: true--- 指針與接口 ---
成功轉換為 *int: 10
總結
通過以上代碼示例,我們可以看到:
&
操作符:獲取變量的內存地址*
操作符:解引用,獲取指針指向的值- 指針類型:
*T
表示指向類型 T 的指針 - 主要用途:
- 函數間傳遞引用,修改原值
- 避免大對象拷貝,提高性能
- 構建數據結構(鏈表、樹等)
- 錯誤處理和可選值模式
記住:Go 語言中的指針是安全的,沒有指針運算,有垃圾回收機制,使用起來比 C/C++ 更安全。