1. 指针基础
什么是指针
指针是存储另一个变量内存地址的变量。在Go中,指针类型使用 * 前缀表示。
go
package main
import "fmt"
func main() {
// 声明一个整型变量
var x int = 10
// 声明一个指向整型的指针
var p *int
// 获取x的内存地址并赋值给p
p = &x
fmt.Println("x的值:", x) // 10
fmt.Println("x的地址:", &x) // 0xc0000180a8
fmt.Println("p的值:", p) // 0xc0000180a8
fmt.Println("p指向的值:", *p) // 10
// 通过指针修改值
*p = 20
fmt.Println("修改后x的值:", x) // 20
}
2. 指针声明和初始化
多种声明方式
go
func pointerDeclaration() {
// 方法1:先声明后赋值
var a int = 100
var p1 *int
p1 = &a
// 方法2:声明时初始化
p2 := &a
// 方法3:使用new函数
p3 := new(int)
*p3 = 200
// 方法4:指向复合字面量
p4 := &struct{ x, y int }{10, 20}
fmt.Printf("p1: %v, *p1: %v\n", p1, *p1)
fmt.Printf("p2: %v, *p2: %v\n", p2, *p2)
fmt.Printf("p3: %v, *p3: %v\n", p3, *p3)
fmt.Printf("p4: %v, *p4: %v\n", p4, *p4)
}
3. 指针操作
解引用和赋值
go
func pointerOperations() {
x := 42
p := &x
// 解引用读取值
value := *p
fmt.Println("通过指针读取的值:", value)
// 通过指针修改值
*p = 100
fmt.Println("修改后的x:", x)
// 指针的比较
y := 42
q := &y
fmt.Println("p == q?", p == q) // false,指向不同地址
fmt.Println("*p == *q?", *p == *q) // true,值相同
// 空指针检查
var nilPtr *int
if nilPtr == nil {
fmt.Println("nilPtr是空指针")
}
}
4. 指针与函数
值传递 vs 指针传递
go
func passByValue(x int) {
x = x * 2
fmt.Println("函数内值传递修改后:", x)
}
func passByPointer(x *int) {
*x = *x * 2
fmt.Println("函数内指针传递修改后:", *x)
}
func testPassing() {
value := 10
// 值传递 - 不影响原始值
passByValue(value)
fmt.Println("值传递后原始值:", value) // 10
// 指针传递 - 修改原始值
passByPointer(&value)
fmt.Println("指针传递后原始值:", value) // 20
}
返回指针
go
// 返回局部变量的指针是安全的(Go会进行逃逸分析)
func createPointer(x int) *int {
return &x // Go会分配在堆上
}
func returnPointerExample() {
p := createPointer(42)
fmt.Println("返回的指针指向的值:", *p)
// 返回结构体指针
type Person struct {
Name string
Age int
}
func NewPerson(name string, age int) *Person {
return &Person{name, age}
}
person := NewPerson("Alice", 30)
fmt.Printf("Person: %+v\n", *person)
}
5. 指针与结构体
结构体指针的使用
go
type Employee struct {
ID int
Name string
Salary float64
}
func structPointers() {
// 创建结构体实例
emp := Employee{1, "Bob", 50000}
// 获取结构体指针
empPtr := &emp
// 通过指针访问字段(两种方式等价)
fmt.Println("ID:", (*empPtr).ID) // 显式解引用
fmt.Println("Name:", empPtr.Name) // 隐式解引用(语法糖)
// 通过指针修改字段
empPtr.Salary = 60000
fmt.Printf("修改后: %+v\n", emp)
// 指针作为接收者的方法
empPtr.giveRaise(10) // 涨薪10%
fmt.Printf("涨薪后: %+v\n", emp)
}
// 指针接收者方法
func (e *Employee) giveRaise(percent float64) {
e.Salary *= (1 + percent/100)
}
6. 指针与数组/切片
数组指针
go
func arrayPointers() {
// 数组指针
arr := [3]int{1, 2, 3}
arrPtr := &arr
// 通过指针访问数组元素
(*arrPtr)[0] = 100
arrPtr[1] = 200 // 语法糖,不需要显式解引用
fmt.Println("修改后的数组:", arr)
// 指针数组(每个元素都是指针)
a, b, c := 1, 2, 3
ptrArray := [3]*int{&a, &b, &c}
*ptrArray[0] = 100
fmt.Println("a的新值:", a)
}
切片(本身已经是引用类型)
go
func sliceAndPointers() {
// 切片本身包含指向底层数组的指针
slice := []int{1, 2, 3, 4, 5}
// 修改切片会影响底层数组
modifySlice(slice)
fmt.Println("修改后的切片:", slice)
// 指向切片的指针(不常用)
slicePtr := &slice
(*slicePtr)[0] = 100
fmt.Println("通过指针修改切片:", slice)
}
func modifySlice(s []int) {
s[0] = 999
}
7. 指针与映射(Map)
go
func mapPointers() {
// 映射本身是引用类型
m := map[string]int{"a": 1, "b": 2}
// 直接传递映射即可修改
modifyMap(m)
fmt.Println("修改后的映射:", m)
// 很少使用指向映射的指针
mPtr := &m
(*mPtr)["c"] = 3
fmt.Println("通过指针修改映射:", *mPtr)
}
func modifyMap(m map[string]int) {
m["new"] = 100
}
8. 多级指针
go
func multiLevelPointers() {
var x int = 10
var p *int = &x
var pp **int = &p // 指向指针的指针
fmt.Println("x:", x) // 10
fmt.Println("*p:", *p) // 10
fmt.Println("**pp:", **pp) // 10
// 通过多级指针修改值
**pp = 100
fmt.Println("修改后的x:", x) // 100
// 三级指针
var ppp ***int = &pp
***ppp = 200
fmt.Println("再次修改后的x:", x) // 200
}
总结
- 指针基础 :存储内存地址,
&取地址,*解引用 - 指针类型 :
*T表示指向类型T的指针 - 指针与函数:实现引用传递,允许函数修改外部变量
- 指针与结构体:指针接收者方法,避免大结构体复制
- 指针安全:Go有垃圾回收,但需注意空指针
- 使用场景 :
- 函数需要修改参数
- 大结构体避免复制
- 实现链表、树等数据结构
- 共享数据
记住:Go的指针比C/C++更安全,没有指针算术,减少了内存错误的可能性。合理使用指针可以写出高效且清晰的代码。