Go指针未初始化、越界、悬挂问题以及优点

本文涉及的产品
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
简介: Go指针未初始化、越界、悬挂问题以及优点

关于指针未初始化、指针越界、指针悬挂

  1. 指针未初始化可能会导致程序崩溃或者出现奇怪的行为,看看下面这个例子:
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。这样,指针就指向了一个有效的内存地址。

  1. 指针越界可能会导致程序崩溃或者出现奇怪的行为,看下面的案例:
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)
 }
}

上面的代码中,我加入了一个判断语句,检查切片是否为空。如果切片不为空,就将指针指向最后一个元素,并且通过指针来修改该位置的值。这样指针就不会越界了,程序就可以正常运行啦!

  1. 指针悬挂是指一个指针指向已经释放的内存或者未被分配的内存区域,看下面的案例:
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) // 修改后
}

使用指针的优点案例

  1. 假设有一个非常大的数据结构,它需要在程序中频繁地传递和操作。如果使用值传递来操作该数据结构,每次传递都会创建该数据结构的副本,这将浪费大量的时间和内存资源。使用指针传递该数据结构,只需传递指向该数据结构的内存地址,而不是复制整个数据结构。大大减少内存使用和程序运行时间:
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
}
  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结构体的指针传递给解析函数。解析函数可以直接在请求体上工作,而不需要进行内存拷贝,从而提高了程序的性能。

  1. 打个比方,正在编写一个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来释放状态内存。这就方便地管理程序的内存,也避免了内存泄漏和资源浪费的问题。所以说,使用指针来动态分配和释放内存,这在处理大量数据或长时间运行的服务时就显得尤为重要啦。


相关文章
|
1月前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
80 22
零值在go语言和初始化数据
|
12天前
|
存储 安全 Go
Go 中的指针:了解内存引用
Go 中的指针:了解内存引用
|
13天前
|
存储 缓存 Java
涨姿势啦!Go语言中正则表达式初始化的最佳实践
在Go语言中,正则表达式是处理字符串的强大工具,但其编译过程可能消耗较多性能。本文探讨了正则表达式编译的性能影响因素,包括解析、状态机构建及优化等步骤,并通过示例展示了编译的时间成本。为了优化性能,推荐使用预编译策略,如在包级别初始化正则表达式对象或通过`init`函数进行错误处理。此外,简化正则表达式和分段处理也是有效手段。根据初始化的复杂程度和错误处理需求,开发者可以选择最适合的方法,以提升程序效率与可维护性。
29 0
涨姿势啦!Go语言中正则表达式初始化的最佳实践
|
1月前
|
自然语言处理 算法 Go
理解在go程序的初始化顺序和MVS
【7月更文挑战第9天】本文介绍Go程序初始化顺序:按导入顺序执行`init()`,先变量定义、常量、再执行`init()`,最后`main()`. 运行时使用`GOCOVERDIR`保存覆盖率数据。
78 1
理解在go程序的初始化顺序和MVS
|
22天前
|
存储 Go
go切片和指针切片
go切片和指针切片
14 2
|
6天前
|
人工智能 Go
go切片参数传递用值还是指针
go切片参数传递用值还是指针
11 0
|
6天前
|
存储 人工智能 Java
深入理解 go reflect - 要不要传指针
深入理解 go reflect - 要不要传指针
5 0
|
1月前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
54 8
|
22天前
|
Go
GO 指针数据类型的使用
GO 指针数据类型的使用
9 0
|
2月前
|
JSON Go 数据格式
Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】(4)
Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】