Go学习笔记 1.1 指针刨析

指针

指针是指向另一个地址的值(pointer)在Go语言中可以被拆分为两个核心概念:
类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
切片,由指向起始元素的原始指针、元素数量和容量组成。

指针类型和值

在Go中,指针类型的字面形式为*T,其中T为一个任意类型。类型T称为指针类型 T 的基类型(base type)如果一个指针类型的基类型为T,则我们可以称此指针类型为一个T指针类型。如果一个指针类型的底层类型是T,则它的基类型为T。如果两个无名指针类型的基类型为同一类型,则这两个无名指针类型亦为同一类型。

*int  // 一个基类型为int的无名指针类型。
**int // 一个多级无名指针类型,它的基类型为*int。

type Ptr *int // Ptr是一个具名指针类型,它的基类型为int。
type PP *Ptr  // PP是一个具名多级指针类型,它的基类型为Ptr。

指针类型的零值的字面量使用预声明的nil来表示。一个nil指针(常称为空指针)中不存储任何地址。如果一个指针类型的基类型为T,则此指针类型的值只能存储类型为T的值的地址。

指针使用

&取出地址,*根据地址取出地址指向的值。

  1. 定义指针变量
var ptr *int  //指针声明
  1. 为指针变量赋值
var a int=1
ptr = &a
  1. 获取指针内容
fmt.Println(*ptr)
  1. 零值与空指针(nil)
    变量声明而没有赋值,默认为零值,不同类型零值不同,例如字符串零值为空字符串;
    指针声明而没有赋值,默认为nil,即该指针没有任何指向。当指针没有指向的时候,不能对(*point)进行操作包括读取,否则会报空指针异常。
func main() {
    var a *int
    fmt.Println("%#v",a) //(*int)(nil)
    *a = 1  //保错 panic: runtime error: invalid memory address or nil pointer dereference -->
    //解决办法 初始化一个内存,并把该内存地址赋予指针变量
    *a = &b 或 *a = new(int)
    *a = 1
}
  1. 指针指向结构体
  2. 指针指向数组
  3. 内部指针
  4. 隐式指针
  5. 指针和接口

常见问题

  1. 函数参数和返回值使用变量和使用指针
    函数返回变量的值会对整个值进行拷贝而使用引用只拷贝内存理论上传指针比传值快,但是实际指针传递要比值传递慢的多,造成原因是stack(栈)和heap(堆)的内存管理有关系,变量分配在stack上还是heap上,对性能是会有影响的。
/*
    stack上分配内存效率比heap更高,而且stack上分配的内存不用做GC,超出了作用域,就自动回收内存。
    放在heap上的内存,需要由GC来做内存回收,而且容易产生内存碎片。
    编译器在编译期决定变量分配在stack还是heap上,需要做逃逸分析(escape analysis),逃逸分析在编译阶段就完成了。
*/
type foo struct {
    a string
    b int
}

/*
    变量result定义的时候会在这个goroutine的stack上分配result的内存空间。
    当函数返回时,getFooValue的调用方如果有接收返回值,那result的值会被拷贝给对应的接收变量。
    stack上变量result的内存空间会被释放(标记为不可用,不能再被访问,除非这块空间再次被分配给其它变量)
    但是如果foo占用的内存空间比较小,约0.3KB,goroutine的stack空间足够存储,如果foo占用的空间过大,在stack里存储不了,就会分配内存到heap上。
*/
func getValueFoo () foo {
    var r foo
    return r
}

/*
    函数getFooPointer因为返回的是一个指针,如果变量result分配在stack上,那函数返回后,result的内存空间会被释放,就会导致接受函数返回值的变量无法访问原本result的内存空间,成为一个悬浮指针(dangling pointer)。
    所以这种情况会发生内存逃逸,result会分配在heap上,而不是stack上。
*/
func getFooPointer() *foo {
    var result foo
    return &result
}

func f(p *foo) {
    fmt.Println(p)
}

func main()  {
    p := &foo{}
    f(p)
}

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。