sunwenfei
  • 关于我
  • Golang
    • Goglang基础教程【译】
      • 介绍
        • 安装
        • Hello World
      • 变量、基本类型以及常量
        • 变量
        • 基本类型
        • 常量
      • 函数和包
        • 函数
        • 包
      • 条件、循环流程控制语句
        • if else条件语句
        • switch语句
        • 循环语句
      • 数组、切片、变参函数
        • 数组(Array)
        • 切片(Slice)
        • 变参函数
      • 其他数据类型
        • 映射(Map)
        • 字符串
      • 指针、结构体和方法
        • 指针
        • 结构体
        • 方法
      • 面向对象编程
        • 结构体 vs 类
        • 组合 vs 继承
        • 接口
        • 多态
      • 并发
        • 并发介绍
        • 协程(goroutine)
        • 管道(channel)
        • 带缓存的管道(buffered channel)
        • 协程池
        • 管道选择器(select)
        • 互斥锁(Mutex)
      • Defer
      • 一等公民函数
      • 反射
      • 错误
        • 错误处理
        • 自定义错误类型
        • panic和recover
      • 文件读写
        • 读文件
        • 写文件
    • Golang面向对象编程
    • Golang函数式编程
    • Golang并发编程
    • Golang web服务编程
    • Golang数据结构与算法
  • Shell编程
    • Find命令
  • JavaScript
    • browser
    • Node.JS
    • Deno
  • TypeScript
  • HTTP
    • 【译】通过信鸽理解HTTPS交互原理
  • React
    • React16
      • Hooks
        • 使用React Hooks拉取数据
  • 移动端开发
    • 原生
    • Flutter
    • ReactNative
    • 小程序
  • 前端测试
Powered by GitBook
On this page

Was this helpful?

  1. Golang
  2. Goglang基础教程【译】
  3. 指针、结构体和方法

指针

Previous指针、结构体和方法Next结构体

Last updated 6 years ago

Was this helpful?

本节我们介绍下Golang中的指针,并了解下Golang中的指针跟其他编程语言比如C、C++之间的区别。

什么是指针

指针是一个存储了其他变量的内存地址的变量。这句话有点绕,怎么理解呢?其实很简单。指针其实也是一个变量,只要是变量就会存储某个值,指针存储的值比较特殊,是另外一个变量的内存地址。

看一下前面这张示例图,变量b存储的值为156,其在内存中的地址为0x1040a124。变量a存储的值是变量b的地址。因此这里变量a指向变量b,变量a是变量b的指针。

声明一个指针变量

一个指向T类型数据的指针变量声明格式为*T。 我们写个函数来声明一个指针变量。

package main

import "fmt"

func main() {
    b := 255
    var a *int = &b
    fmt.Printf("Type of a is %T\n", a)
    fmt.Println("Address of b is", a)
}
Type of a is *int
Address of b is 0x414020

变量b可能存在于内存中的任何地址,因此你的打印结果可能跟我的不同。

指针变量的零值

指针变量的零值为nil。

package main

import "fmt"

func main() {
    a := 25
    var b *int
    if b == nil {
        fmt.Println("b is", b)
        b = &a
        fmt.Println("b after initialization is", b)
    }
}
b is <nil>
b after initialization is 0x414020

通过new函数来创建指针

Golang提供了一个便捷的new函数用来创建指针。new函数的入参为类型T,返回值为一个指向该类型零值变量的指针。有点绕,我们举个例子看下。

package main

import "fmt"

func main() {
    size := new(int)
    fmt.Printf("Size value is %d, type is %T, address is %v\n", *size, size, size)
    *size = 85
    fmt.Println("New size value is", *size)
}
Size value is 0, type is *int, address is 0x414020
New size value is 85

指针解除引用

所谓的指针解引用指的是访问指针指向的变量的值。指针a解引用的语法为*a。

下面通过一个例子来了解下:

package main

import "fmt"

func main() {
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)
}
address of b is 0x414020
value of b is 255

下面修改下前面的程序,我们通过指针来修改变量b的值:

package main

import "fmt"

func main() {
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)
    *a++
    fmt.Println("new value of b is", b)
}
address of b is 0x414020
value of b is 255
new value of b is 256

指针作为函数入参

package main

import "fmt"

func change(p *int) {
    *p = 55
}

func main() {
    a := 58
    fmt.Println("value of a before function call is", a)
    b := &a
    change(b)
    fmt.Println("value of a after function call is", a)
}
value of a before function call is 58
value of a after function call is 55

指针作为函数返回值

Golang中允许返回指向局部变量的指针,如果一个指向局部变量的指针作为函数返回值,那么Golang编译器会在堆内存上分配该局部变量。

package main

import "fmt"

func hello() *int {
    i := 5
    return &i
}

func main() {
    d := hello()
    fmt.Println("value of d", *d)
}
value of d 5

尽量避免数组指针,多用切片

如果我们把一个指向数组的指针作为函数入参,那么可以在函数内部通过该指针来修改数组内部值,并且该修改在函数外也是可见的。

package main

import "fmt"

func modify(arrP *[3]int) {
    (*arrP)[0] = 90
}

func main() {
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

(*a)[x]可以简写为a[x],因此前面程序中的(*arrP)[0]可以简写为arrP[0]。

package main

import "fmt"

func modify(arrP *[3]int) {
    arrP[0] = 90
}

func main() {
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

尽管这种通过数组指针来在函数内部修改数组是行得通的,但是Golang中一般不这么用,Golang中比较惯用的用法一般是使用切片(slice)。

package main

import "fmt"

func modify(sls []int) {
    sls[0] = 90
}

func main() {
    a := [3]int{89, 90, 91}
    modify(a[:])
    fmt.Println(a)
}

因此,我们在Golang中,尽量避免数组指针,而应该多用切片。

Golang不支持指针运算

Golang跟C和C++不同,不支持指针运算,这其实在一定程度上降低了Golang这门编程语言的复杂度。

package main

func main() {
    b := [...]int{109, 110, 111}
    p := b
    p++
}

该程序执行会报错:

invalid operation: p++ (non-numeric type [3]int)

这里的&运算符用来获取一个变量的地址。我们将变量b的地址赋值给指针变量a,其数据类型为*int。这时指针a指向变量b。当我们打印指针变量a的值时,其实打印的是变量b的地址。该程序输出如下:

这里指针变量b的初始值为nil,然后我们给其赋值变量a的地址。执行结果如下:

这里我们通过new函数创建了一个指向一个临时的int类型数据的指针,该临时数据分配的值为对应类型的零值,比如这里即为int类型的零值0。该程序执行结果如下:

这里我们通过指针a解引用来访问其指向的变量b的值。执行结果如下:

这里我们通过*a++将指针a指向的变量加1,由于a指向的变量为b,因此b变量的值变为256。该程序输出如下:

这里我们将指向变量a的指针变量b作为入参传递给函数change,change函数内部通过指针解引用改变了变量a的值。输出结果为:

这里函数hello将局部变量i的指针作为函数返回值。在其他诸如C和C++之类的编程语言中这种写法是错误的,因为变量i是局部变量,一旦函数返回,局部变量就访问不到了。但是Golang会在局部变量初始化时检查下是否会在函数外部被引用,比如这里的局部变量i,其指针作为函数返回值返回出去,因此会在函数外部引用,这是Golang就将该局部变量分配在堆内存中。该程序执行结果为:

这里将数组a的指针作为函数modify的入参,并通过该指针来将其指向的数组的第一个元素的值改为90。输出为:[90 90 91]。

执行结果不变,输出:[90 90 91]。

这里我们将一个切片a[:]作为函数modify的入参,然后在函数内部将该切片的第一个元素修改为90。该程序输出:[90 90 91]。

Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
Go Playground在线运行
0ab351c10442374f8e6509af21c1fa7e.png