乱起封神是那个网站开发的?,东莞市网站建设怎么样,学习网站建设要什么学历,青岛煜鹏网站建设公司引言指针在 Go 中并不复杂#xff0c;但想把它用好、用稳#xff0c;需要弄清楚几个核心概念#xff1a;Go 是按值传递、指针保存变量地址、new 与 make 的差别、以及 nil 在不同类型上的行为差异。本文把这些知识点串联起来#xff0c;边写代码边解释原理与工程实践建议。…引言指针在 Go 中并不复杂但想把它用好、用稳需要弄清楚几个核心概念Go 是按值传递、指针保存变量地址、new与make的差别、以及nil在不同类型上的行为差异。本文把这些知识点串联起来边写代码边解释原理与工程实践建议。指针的定义与基本使用在 Go 中指针类型写做*T表示指向T类型值的地址。指针可用于获取或修改其它变量的值而不进行拷贝。var p *int // p 是 *int 类型默认零值为 nil取地址得到指针用解引用访问指针指向的值用*。x : 42 p : x // p 指向 x 的地址类型 *int fmt.Println(*p) // 输出 42解引用得到 x 的值 *p 100 // 通过指针修改 x fmt.Println(x) // 输出 100注意*p是解引用表达式不是类型声明。为什么用指针示例修改结构体字段Go 的函数参数是按值传递pass-by-value。把结构体作为参数传递时会拷贝整个结构体。如果要在函数内部修改调用者的字段必须传入指针。示例按值传递不能改变调用者的字段package main import fmt type Person struct { name string } func changeName(p Person) { p.name go } func main() { p : Person{name: java} changeName(p) fmt.Println(p.name) // 仍然 java }changeName收到的是p的拷贝修改只是改了拷贝。改成传指针可以直接修改原始对象package main import fmt type Person struct { name string } func changeName(p *Person) { p.name go } func main() { p : Person{name: java} changeName(p) // 传入 p 的地址 fmt.Println(p.name) // 输出 go }为什么可以传递p*Person到changeName后函数里p.name实际编译器会把p.name视为(*p).name这是对调用者内存的直接修改没有拷贝整个结构体。指针的初始化new、与零值nil指针要使用之前必须“有东西指向”否则nil解引用会导致运行时 panic。几种常见的创建指针方式1) 使用对已声明变量取地址x : 0 p : x // p 非 nil指向 x2) 使用new(T)创建并返回*Tp : new(int) // p 是 *int指向一个被零值初始化的 int值为 0 *p 5new的作用是分配一块内存、把它置为类型零值并返回指向它的指针。new返回的是*T而非T。3) 使用复合字面量取地址常用p : Person{name: gopher} // 推荐直接得到 *Person4) 零值指针nil如果你声明var p *int此时p nil解引用*p会 panicvar p *int fmt.Println(p nil) // true // fmt.Println(*p) // panic: runtime error: invalid memory address or nil pointer dereference结论指针必须初始化指向真实内存后才能解引用。new与make的差异非常重要很容易混淆new和make。概念上new(T)分配并返回*T主要用于任何类型T返回指向T的指针内存已清零零值。make(T, args...)只用于内建引用类型slice,map,chan。返回的是初始化后的T不是*T例如make([]int, 0)返回[]int。示例对比p : new(int) // p 的类型 *int*p 0 s : make([]int, 0) // s 是 []int已初始化可用 append m : make(map[string]int) // m 是 map[string]int已初始化 c : make(chan int) // c 是 chan int已初始化为什么make需要单独存在因为切片、映射、通道都是“内建的复杂数据结构”需要初始化内部结构比如 slice 的 header、map 的哈希表、channel 的队列才能使用。new只是分配零值内存不会为这些引用类型构建内部数据结构。nil的细节指针 / slice / map / chan / interface不同类型的零值nil行为不同理解这些差异很关键。指针 (*T)默认值是nil解引用nil会 panic。切片 ([]T)零值是nil切片var s []int-s nil、len(s) 0、cap(s) 0。你可以对nil切片append这是安全的append 会自动分配底层数组。但是不能索引s[0]会 panic。var s []int s append(s, 1) // OK even if s is nil映射 (map[K]V)零值为nilmapvar m map[string]int对 nil map 做读取m[a]返回零值不会 panic对 nil map 做写入m[a] 1会 panic—— 写之前必须用make初始化var m map[string]int fmt.Println(m[x]) // 0 m[x] 1 // panic: assignment to entry in nil map通道 (chan T)零值nilchan读或写会阻塞如果没有超时/选择器向nil通道发送或接收会导致永久阻塞除非在 select 中close(nil)会 panic。接口 (interface{})接口的零值是nil。但有一个常见的陷阱带有类型信息但值为 nil 的接口 ! nil。例如一个var p *int nil把它赋给var i interface{} p此时i ! nil因为接口内部记录了动态类型*int和动态值nil。这个差异会导致if i nil检查失效常见 panic 源。var p *int nil var i interface{} p fmt.Println(i nil) // false (interface holds (*int, nil))swap示例用指针交换变量的值并逐行解析把两个整数交换如果按值传递交换仅限于函数内部拷贝用指针可以交换实际变量。package main import fmt func swap(a, b *int) { t : *a // 取 a 指针指向的值到 t *a *b // 把 b 指向的值写到 a 指向的地址 *b t // 把 t 写回 b 指向的地址 } func main() { a, b : 1, 2 swap(a, b) // 传入 a, b 的地址 fmt.Println(a, b) // 输出: 2 1 }逐行解释swap(a, b)传入的是a和b的地址类型*int函数参数a、b本身是指针传值拷贝指针即拷贝地址但地址仍然指向原变量。t : *a解引用a取a指向的值保存为临时t。*a *b把b指向的值写入a指向的位置直接改变主函数变量a的值。*b t把临时值写回b指向的位置完成交换。注意传递的是指针的复制函数内的a与b是指针拷贝但它们都指向调用者的内存所以对*a/*b的修改反映到调用者变量上。逃逸分析 指向局部变量的指针local安全性你可能会问函数内的局部变量的地址传给外部会不会不安全Go 的实现通过逃逸分析解决编译器会判断某个变量是否会逃逸到函数外比如取其地址并返回或存入堆上可访问的结构。如果变量逃逸编译器将该变量分配到堆上否则分配到栈上以提高效率。示例func f() *int { x : 100 return x // x 逃逸到堆上 }f()返回的指针是安全的x 会被分配到堆上。你可以在编译时用go build -gcflags -m查看逃逸分析结果。因此p在main中传给函数是安全的即使你把pLocal返回或保存编译器会自动把该局部变量放到堆上。指针的工程建议与常见坑建议首选值语义如果类型很小例如int、小结构体优先用值传递避免过早使用指针。对大对象使用指针以免复制开销结构体较大几百字节或包含互斥锁等不能拷贝的字段应使用指针接收者或指针参数。方法接收者一致性对于一个类型尽量保持方法接收者要么全部为值要么全部为指针混用会困惑尤其涉及接口实现。在并发场景慎用可变共享指针多 goroutine 同时访问同一内存时必须同步sync.Mutex,atomic。makemap 之后再写入不要对 nil map 写入。使用T{}或new(T)初始化指针语义清晰。T{}更常用于自定义结构体初始化。常见坑把range中的迭代变量地址存入切片/闭包会被复用经典问题nums : []int{1,2,3} ptrs : []*int{} for _, v : range nums { ptrs append(ptrs, v) // 错误v 每次相同地址 }正确写法for i : range nums { ptrs append(ptrs, nums[i]) }接口持有类型为指针但值为 nil导致if iface nil判断失败参见上文nil细节。总结Go 的指针语义简单*T指向类型T取地址*解引用。Go按值传递函数参数会被拷贝传入指针可以让函数直接修改原值或避免大对象拷贝。new(T)分配零值并返回*Tmake为slice/map/chan初始化内部数据结构并返回该类型不是指针。nil在不同类型上有不同行为nil切片能 appendnilmap 写入会 panicnilchan 在操作上会阻塞。接口的nil判断要注意“类型为 nil vs 接口为 nil”的区别。swap(a, b)演示了通过指针交换外部变量的值函数内修改*a/*b会影响调用者。了解逃逸分析可以解释local为何安全编译器会把需要逃逸的局部变量放到堆上。