对于指针未初始化、指针越界、指针悬挂
- 指针未初始化可能会导致程序解体或者呈现奇怪的行为,看看上面这个例子:
<!—->
package main
import "fmt"
type MyStruct struct {value string}
func (s *MyStruct) SetValue(val string) {s.value = val}
func (s *MyStruct) GetValue() string {return s.value}
func main() {
var myStruct *MyStruct
myStruct.SetValue("hello")
fmt.Println(myStruct.GetValue())
}
申明了一个指向 MyStruct 的指针 myStruct,然而没有为它分配内存,也就是说它的值为 nil。通过 myStruct 指针来设置构造体的值和获取构造体的值。因为 myStruct 指针没有初始化,它不会指向任何无效的内存地址,在 SetValue 和 GetValue 办法中会呈现谬误。
革新 main 函数即可
func main() {myStruct := &MyStruct{}
myStruct.SetValue("tantianran")
fmt.Println(myStruct.GetValue())
}
应用 \&MyStruct{} 创立了一个新的构造体对象,并将其地址调配给指针 myStruct。这样,指针就指向了一个无效的内存地址。
- 指针越界可能会导致程序解体或者呈现奇怪的行为,看上面的案例:
<!—->
package main
import "fmt"
func main() {mySlice := []int{1, 2, 3, 4, 5}
myPointer := &mySlice[2]
fmt.Println(*myPointer)
*myPointer = 6
fmt.Println(mySlice)
myPointer = &mySlice[len(mySlice)] // 超出数组范畴
*myPointer = 7
fmt.Println(mySlice)
}
创立了一个名为 mySlice 的整数切片,并且创立了一个指向切片中第三个元素的指针 myPointer。而后通过指针 myPointer 来批改切片中的第三个元素,并且打印了批改后的切片。接着试图将指针 myPointer 指向超出切片范畴的地位,即最初一个元素的下一个地位,而后再通过指针来批改该地位的值。这就是指针越界了,所以程序会解体并且抛出一个运行时谬误。
当初修复这个问题,确保指针不越界:
package main
import "fmt"
func main() {mySlice := []int{1, 2, 3, 4, 5}
myPointer := &mySlice[2]
fmt.Println(*myPointer)
*myPointer = 6
fmt.Println(mySlice)
if len(mySlice) > 0 {myPointer = &mySlice[len(mySlice)-1]
*myPointer = 7
fmt.Println(mySlice)
}
}
下面的代码中,我退出了一个判断语句,查看切片是否为空。如果切片不为空,就将指针指向最初一个元素,并且通过指针来批改该地位的值。这样指针就不会越界了,程序就能够失常运行啦!
- 指针悬挂是指一个指针指向曾经开释的内存或者未被调配的内存区域,看上面的案例:
<!—->
package main
func foo(pp *int) {
var x int = 10
pp = &x // pp 指向 x 的地址,然而在函数返回时,pp 指向的地址被开释了,变成了悬挂指针
}
func main() {
var p *int
foo(p)
*p = 10 // p 是一个悬挂指针,此处会导致运行时谬误
}
要修复指针悬挂问题,能够将 foo 函数批改为返回指向 x 的指针。这样,在 main 函数中,就能够应用这个指针来拜访 x 变量,而不会呈现悬挂指针的状况。上面是批改后的代码:
package main
import "fmt"
func foo() *int {
var x int = 10
return &x // 返回指向 x 的指针
}
func main() {
var p *int
p = foo()
fmt.Println(*p) // 批改前
*p = 100 // 此时 p 指向 x 的地址,能够平安地批改 x 的值
fmt.Println(*p) // 批改后
}
应用指针的长处案例
- 假如有一个十分大的数据结构,它须要在程序中频繁地传递和操作。如果应用值传递来操作该数据结构,每次传递都会创立该数据结构的正本,这将节约大量的工夫和内存资源。应用指针传递该数据结构,只需传递指向该数据结构的内存地址,而不是复制整个数据结构。大大减少内存应用和程序运行工夫:
<!—->
package main
import "fmt"
type LargeData struct {
// 一个十分大的数据结构
data [1024 * 1024 * 1024]int
}
func processLargeData(dt *LargeData) {
// 对大型数据结构进行解决
dt.data[0] = 1
// ...
}
func main() {
// 创立一个大型数据结构
data := LargeData{}
// 传递指向该数据结构的指针进行解决
processLargeData(&data)
fmt.Println(data.data[0]) // 输入 1
}
- 比方正在解决一个 HTTP 申请,并且须要从申请中读取和解析 JSON 数据,而后将其转换为一个 Go 构造体并进行解决。能够通过将申请体指针传递给解析函数来防止大量的内存拷贝,防止创立一个新的缓冲区将申请体数据复制到缓冲区中进行解析:
<!—->
import (
"encoding/json"
"net/http"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func handler(w http.ResponseWriter, r *http.Request) {decoder := json.NewDecoder(r.Body)
defer r.Body.Close()
var person Person
if err := decoder.Decode(&person); err != nil {http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// 解决 person 构造体
// ...
w.WriteHeader(http.StatusOK)
}
Decode() 函数的参数是 \&person,它将指向 Person 构造体的指针传递给解析函数。解析函数能够间接在申请体上工作,而不须要进行内存拷贝,从而进步了程序的性能。
- 打个比方,正在编写一个 HTTP 服务器,该服务器须要解决大量的申请并保护一个长时间运行的状态,能够应用指针来动静地调配和开释内存来存储和治理状态数据:
<!—->
package main
import (
"fmt"
"log"
"net/http"
)
type ServerState struct {Count int}
func main() {state := &ServerState{}
// 用浏览器拜访,每刷新一次就会累加
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
state.Count++
fmt.Fprintf(w, "计数: %d", state.Count)
})
log.Fatal(http.ListenAndServe(":8080", nil))
// 在服务器敞开时开释状态内存
defer func() {fmt.Println("web 服务器敞开...")
// 开释 ServerState 构造体内存
if state != nil {state = nil}
}()}
拜访成果:
下面的代码中,应用 \&ServerState{} 来动静地分配内存来存储服务器状态。在解决申请时,应用指针 state 来更新和拜访状态数据。在服务器敞开时,应用指针 state 来开释状态内存。这就不便地管理程序的内存,也防止了内存透露和资源节约的问题。所以说,应用指针来动态分配和开释内存,这在解决大量数据或长时间运行的服务时就显得尤为重要啦。
本文转载于 WX 公众号:不背锅运维(喜爱的盆友关注咱们):https://mp.weixin.qq.com/s/_EVBga-jn5Sg94p-Aq0D-w