在 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++ 更安全。