Golang语言高级数据类型之指针篇

简介: 这篇文章详细讲解了Golang语言中的指针概念、指针地址和类型、定义指针变量、指针的细节操作、指针传值,以及内置函数new和make的用法和它们之间的区别。

                                              作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.指针

1.指针概述

任何程序数据载入内存后,在内存都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。

Go语言中的指针不能进行偏移和运算,因此我们说Go语言的指针是只读的。

Go语言中的指针操作非常简单,我们只需要记住两个符号:
    - &:
        用于取地址。
    - *:
        根据地址取值。

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

要搞明白Go语言中的指针需要先知道三个概念:
    - 指针地址: 
        &a
    - 指针取值: 
        *&a
    - 指针类型: 
        比如: "*int"

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
    - 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
    - 指针变量的值是指针地址。
    - 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

2.指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。

Go语言中使用"&"字符放在变量前面对变量进行"取地址"操作。 

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


取变量指针的语法如下:
    ptr := &v    // v的类型为T

其中:
    - v:
        代表被取地址的变量,类型为T。
    - ptr:
        用于接收地址的变量,ptr的类型就为"*T",称做T的"指针类型"。"*"代表指针。

3.定义指针变量

package main

import (
    "fmt"
)

func main() {
    var (
        a int = 10

        // 定义指针变量,*int可以理解为指向int类型的指针。指针本质上指向的是一个内存地址
        b *int = &a // 取变量a的内存地址,将指针保存到b指针中
    )
    fmt.Printf("a类型: [%T], a的数据: [%v], a的地址: [%v]\n", a, a, &a)
    fmt.Printf("b类型:[%T], b的数据: [%v], b的地址: [%v]\n", b, b, &b)

    // 指针取值(根据指针去内存地址取值)
    c := *b
    fmt.Printf("c类型:[%T],c的数据: [%v], c的地址: [%v]\n", c, c, &c)
}

4.指针细节

4.1 基础数据类型又称为值类型

基础数据类型又称为值类型,都有对应的指针类型,形式为"*数据类型"。

比如int的对应指针就是"*int",float64对应的指针类型就是"*float64",以此类推。

4.2 可以通过"取指针值"改变指向值

package main

import (
    "fmt"
)

func main() {
    var (
        a int  = 100
        b *int = &a
    )

    fmt.Printf("a = [%T], a =[%v]\n", a, a)

    // 可以通过"取指针值"改变指向值
    *b = 200 // 把b指针的值取出并修改
    fmt.Printf("a = [%T], a =[%v]\n", a, a)
}

4.3 指针变量接收的一定是地址值

package main

import (
    "fmt"
)

func main() {
    var (
        a int  = 100
        // 指针变量接收的一定是地址值。
        // b *int = a  // 编译报错: "cannot use a (variable of type int) as *int value in variable declaration"
        b *int = &a
    )

    fmt.Printf("b = %v\n", b)

}

4.4 指针的地址类型和接受类型必须匹配

package main

import (
    "fmt"
)

func main() {
    var (
        a int  = 100
        // 指针的地址不可以不匹配,说白了,就是指针地址类型要匹配。
        // b *float64 = &a  // 编译报错: cannot use &a (value of type *int) as *float64 value in variable declaration
        b *int = &a
    )

    fmt.Printf("b = %v\n", b)

}

5.指针传值

5.1 案例一

package main

import (
    "fmt"
)

func modify1(x int) {
    x = 200
}

func modify2(x *int) {
    *x = 300
}

func main() {
    a := 100
    fmt.Printf("in main: %d\n", a)

    // 传递的是值
    modify1(a)
    fmt.Printf("after modify1: %d\n", a)

    // 传递的是指针变量
    modify2(&a)
    fmt.Printf("after modify2: %d\n", a)
}

5.2 案例二(注意,数组也是值类型哟~)

package main

import (
    "fmt"
)

func modifyArray1(x [3]int) {
    x[0] = 200
}

func modifyArray2(x *[3]int) {
    x[0] = 300
}

func main() {

    a := [3]int{1, 2, 3}
    fmt.Printf("in main:  %v\n", a)

    modifyArray1(a)
    fmt.Printf("after modifyArray1: %v\n", a)

    modifyArray2(&a)
    fmt.Printf("after modifyArray2: %v\n", a)
}

二.new和make

1.new

1.1 new概述

new是一个内置的函数,它的函数签名如下:
    func new(Type) *Type

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

new函数主要用来分配值类型(int,float,bool,string,数组和struct结构体)的初始内存。

new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。

指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。

1.2 new案例

package main

import (
    "fmt"
)

func main() {
    // a是一个int类型的指针,但并没有内存地址,但无法使用,写法错误!
    // var a *int
    // 正确的写法是,使用new得到一个int类型的指针,此时指针是有内存地址的。
    var a = new(int)

    fmt.Printf("内存地址: %v, 数据: %v, 类型: %T\n", a, *a, a)
    // 取出a的内存地址,并修改其值为100
    *a = 100

    // 对a指针类型进行初始化
    fmt.Printf("内存地址: %v, 数据: %v, 类型: %T\n", a, *a, a)

    // 声明一个"[3]int"数组类型指针并分配内存地址
    var b = new([3]int)
    fmt.Printf("内存地址: %v, 数据: %v, 类型: %T\n", b, *b, b)

    // 下面这种两种写法都是正确的,但Go编译器使用了语法糖,让我们可以有更简洁的写法。
    // (*b)[0] = 200
    b[0] = 200

    fmt.Printf("内存地址: %v, 数据: %v, 类型: %T\n", b, *b, b)
}

2.make

2.1 make概述

make也是用于内存分配的,区别于new,它只用于slice切片、map以及管道chan,接口interface等的内存分配。

make返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。

make函数的函数签名如下:
    func make(t Type, size ...IntegerType) Type

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

2.2 make案例

package main

import (
    "fmt"
)

func main() {
    var teacher map[string]int

    // 对引用类型的数据进行初始化操作,用于分配内存地址
    teacher = make(map[string]int, 10)

    teacher["尹正杰"] = 20

    fmt.Println(teacher)

}

3.new和make的区别

相同点:
    二者都是用来做内存分配的。

不同点:
  - make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身:
        - make只能对内置数据类型进行申请内存;
        - 返回是数据值本身;

  - 而new用于类型的内存分配,返回的是指向类型的指针:
        - new方法主要给struct等非内置数据类型申请空间
        - 返回的是一个指针类型
目录
相关文章
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
144 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
72 4
Golang语言文件操作快速入门篇
|
3月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
118 3
Golang语言之gRPC程序设计示例
|
3月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
101 4
|
3月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
74 4
Golang语言goroutine协程篇
|
3月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
80 3
Golang语言之Prometheus的日志模块使用案例
|
2月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
38 0
|
3月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
55 3
|
3月前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
74 3
Golang语言之函数(func)进阶篇
|
3月前
|
Go
Golang语言之函数(func)基础篇
这篇文章深入讲解了Golang语言中函数的定义和使用,包括函数的引入原因、使用细节、定义语法,并通过多个案例展示了如何定义不返回任何参数、返回一个或多个参数、返回值命名、可变参数的函数,同时探讨了函数默认值传递、指针传递、函数作为变量和参数、自定义数据类型以及返回值为切片类型的函数。
87 2
Golang语言之函数(func)基础篇