August Rush

一个还在努力成长的小火汁!

游龙当归海,海不迎我自来也。

We create our own demons.

You can reach me at augustrush0923@gmail.com
Go语言基础-指针
发布:2021年02月18日 | 作者:augustrush | 阅读量: 376

什么是指针

指针是一种数据类型

// 声明一个 字符串类型 的变量(默认初始化值为空字符串)
var s1 string

// 声明一个 字符串的指针类型 的变量(默认初始化值为nil)
var s2 *string

用于表示数据的内存地址

// 声明一个 字符串类型 的变量, 值为august
var name stirng = "august"


// 声明一个 字符串的指针类型 的变量,值为 name 对应的内存地址
var pointer *string = &name

当我们定义一个变量name

var name stirng = "August Rush"

此时,name是变量名,它只是编程语言中方便程序员编写和理解代码的一个标签。

当我们访问这个标签时,计算机会返回给我们它指向的内存地址里存储的值:August Rush

出于某些需要,我们会将这个内存地址赋值给另一个变量名,而这个变量,我们称之为指针变量

换句话说,指针变量的值是指针,也就是内存地址。

根据变量指向的值,是否是内存地址,我们把变量分为两种:

  • 普通变量:存数据值本身
  • 指针变量:存值的内存地址

Go语言中的指针不能进行偏移和运算,因此Go语言中的指针操作非常简单,我们只需要记住两个符号:&(取地址)和*(根据地址取值)。


指针的创建

指针的创建有三种方式

第一种方式

先定义对应的变量,再通过变量取得内存地址,创建指针

// 定义一个普通变量
num := 13

// 定义一个指针变量
numPtr := &num

第二种方式

先创建指针,分配好内存后,再给指针指向的内存地址写入对应的值。

// 创建指针
str := new(string)  // 此时初始化一个指针变量,并指向nil

// 给指针赋值
*str = "world peace"

第三种方式

先声明一个指针变量,再从其他变量取得内存地址赋值给它

num := 13

var numPtr *int // 声明一个指针

numPtr = &num   // 初始化

image-20210119135842688

相当于创建了一个地址的引用,以后根据这个引用再去获取它里面的

关于指针的两个符号:

  • &: 从一个普通变量中取得内存地址

  • *: 当*在赋值操作符(=)的右边,是从一个指针变量中取得变量值,当*在赋值操作符(=)的左边,是指该指针指向的变量

v1 := "august"
v2 := &v1

fmt.Println(v1, v2, *v2)

v1 = "alex"
fmt.Println(v1, v2, *v2)

// v2 = "eric" 报错
*v2 = "eric"
fmt.Println(v1, v2, *v2)

要想打印指针指向的内存地址,方法有两种:

// 第一种
fmt.Printf("%p", ptr)

// 第二种
fmt.Println(ptr)


指针的类型

Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int*int64*string等。

package main

import "fmt"

func main() {
    astr := "hello"
    aint := 1
    abool := false
    arune := 'a'
    afloat := 1.2

    fmt.Printf("astr 指针类型是:%T\n", &astr)
    fmt.Printf("aint 指针类型是:%T\n", &aint)
    fmt.Printf("abool 指针类型是:%T\n", &abool)
    fmt.Printf("arune 指针类型是:%T\n", &arune)
    fmt.Printf("afloat 指针类型是:%T\n", &afloat)
}

/*
输出结果:
        astr 指针类型是:*string
        aint 指针类型是:*int
        abool 指针类型是:*bool
        arune 指针类型是:*int32
        afloat 指针类型是:*float64
*/


指针的默认值

当指针声明后,没有进行初始化,其默认值是nil。

func main() {
    a:=25
    var b * int
    if b == nil {
        fmt.Println(b)
        b = &a
        fmt.Println(b)
    }
}

/*
输出结果:
        <nil>
        0xc0000100a0
*/


指针与切片

切片与指针一样,都是引用类型。

如果我们想通过一个函数改变一个数组的值,有两种方法

  1. 将这个数组的切片做为参数传给函数
  2. 将这个数组的指针做为参数传给函数

尽管两者都可以实现我们的目的,但是按照Go语言的使用习惯,建议使用第一种方法,写出来的代码会更加简洁,易读。

使用切片

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

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

使用指针

func modify(arr *[3]int) {
    (*arr)[0] = 96
}

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


new和make

Go语言中newmake是内建的两个函数,主要用来分配内存

在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。

new

new是一个内置的函数

func new(Type) *Type

其中:

  • Type表示类型,new函数只接受一个参数,这个参数是一个类型
  • *Type表示类型指针,new函数返回一个指向该类型内存地址的指针

new函数得到的是一个类型的指针,并且该指针对应的值为该类型的默认值。

func main() {
    a := new(int)
    b := new(bool)
    fmt.Printf("%T\n", a)
    fmt.Printf("%T\n", b)
    fmt.Println(*a)
    fmt.Println(*b)
}

/*
输出结果:
        *int
        *bool
        0
        false
*/

var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

make

make也是用于内存分配的,区别于new,它只用于slice、map以及channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回它们的指针了。

func make(t Type, size ...IntegerType) Type

make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。

var b map[string]int 只是声明变量b是一个map类型的变量,需要使用make函数进行初始化操作之后,才能对其进行键值对赋值

func main() {
    var b map[string]int
    b = make(map[string]int, 10)
    b["age"]=18
    fmt.Println(b)
}

new也make的区别

  1. 两者都是用来做内存分配的
  2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身
  3. 而new用于类型的内存分配,并且内存对应的值为类型默认值,返回的是指向类型的指针


指针的应用场景

场景一

当希望值随着修改,变量引用的值也随之修改时。便可用指针来完成

name := "august"
copyName := name

fmt.Println(name, &name)
fmt.Println(copyName, &copyName)

name = "alex"

fmt.Println(name, &name)
fmt.Println(copyName, &copyName)

/*
输出结果:
        august 0xc00008e1e0
        august 0xc00008e1f0
        alex 0xc00008e1e0
        august 0xc00008e1f0
*/
name := "august"
copyName := &name

fmt.Println(name, &name)
fmt.Println(*copyName, copyName, &copyName)

name = "alex"

fmt.Println(name, &name)
fmt.Println(*copyName, copyName, &copyName)

/*
输出结果:
        august 0xc000010200
        august 0xc000010200 0xc00000e028
        alex 0xc000010200
        alex 0xc000010200 0xc00000e028
*/

场景二

当需要传参数给函数时,其本质是先把数据拷贝一份再赋值给函数体的对应参数

package main

import "fmt"

func main() {
    name := "august"
    modify(name)
    fmt.Println(name)
}

func modify(passName string) {
    passName = "alex"
}

// 输出结果: august
package main

import "fmt"

func main() {
    name := "august"
    modify(&name)
    fmt.Println(name)
}

func modify(passName *string) {
    *passName = "alex"
}

// 输出结果: alex

场景三

var username string

fmt.Scanf("%s", &username)

fmt.Println(username)


指针的指针

name := "武沛齐"

// 声明一个指针类型变量p1,内部存储name的内存地址
var p1 *string = &name

// 声明一个指针的指针类型变量p2,内部存储指针p1的内存地址
var p2 **string = &p1

// 声明一个指针的指针的指针类型变量p3,内部存储指针p2的内存地址
var p3 ***string = &p2

fmt.Println(name, &name)
fmt.Println(p1, &p1, *p1)
fmt.Println(p2, &p2, *p2)
fmt.Println(p3, &p3, *p3)

/*
输出结果:
        武沛齐 0xc000010200
        0xc000010200 0xc00000e028 武沛齐
        0xc00000e028 0xc00000e030 0xc000010200
        0xc00000e030 0xc00000e038 0xc00000e028
*/



  • 标签云

  • 支付宝扫码支持一下

  • 微信扫码支持一下



基于Nginx+Supervisord+uWSGI+Django1.11.1+Python3.6.5构建

版权所有 © 2020-2021 August Rush

京ICP备20007446号-1 & 豫公网安备 41100202000460号

网站地图 & RSS | Feed