乐趣区

关于编程语言:Go语言入门系列五之指针和结构体的使用

Go 语言入门系列后面的文章:

  • Go 语言入门系列 (二) 之根底语法总结
  • Go 语言入门系列 (三) 之数组和切片
  • Go 语言入门系列 (四) 之 map 的应用

1. 指针

如果你应用过 C 或 C ++,那你必定对指针这个概念不生疏。

咱们须要先介绍两个概念:内存和地址。

1.1. 内存和地址

咱们写的代码都存储在外存(C 盘、D 盘)中,比方我存在了 D:\Work\Program\go 目录下。如果你想要运行你的代码,必须先把你的代码加载进内存中,而后交给 CPU 执行计算,而 CPU 计算的后果也会存到内存中。

内存的存取速度快,其中有许多存储单元用来存储数据,CPU 能在内存中间接找到这些数据。这是因为内存中的每个地位都有一个举世无双的地址标识。能够把内存看成一幢有许多房间的大楼,每个存储单元是一个房间,存储的数据是房间中的物品,地址就是房间号。

所以对 CPU 来说,如果想找到某个房间中的物品(从内存中取数据),或者向某个房间中放物品(向内存中存数据),咱们必须晓得房间号(内存地址)。

内存地址通常是一串 16 进制的数字,如果写代码时存个整数 1 或取个整数 1 都须要写这么一串数字,那太麻烦了。所以高级语言为咱们提供了一个便当,用咱们人能记住的“名字”来代替这串数字。

这些“名字”就是 变量名

var a int = 1
var b int = 2
var c int = 333
var d int = 6666

变量名和地址的关联由编译器替咱们做,硬件拜访的依然是内存地址。

1.2. 什么是指针?

简略地来说,指针也是一个变量,只不过这个变量中存的不是咱们平时用到的 1、2、3、”Hello”、true 等值,而是其余变量的地址。

之所以取名指针,是因为指针变量 b 中保留了变量 a 的地址,咱们能够通过该指针变量 b 找到变量 a,如果画图看起来,看起来就像是指针b 指向了变量a

还能够有指针的指针:

1.3. 指针的应用

申明一个指针:

var p *int

*int示意 p 是一个 int 类型指针,p指针中存的是一个 int 类型变量的地址,这意味着 p 中不能存其余类型变量的地址。

如何获取某个变量的地址呢?应用操作符&

var a int = 66 // a 是值为 66 的 int 变量
p = &a // 将 a 的地址赋给指针 p 

那么如何依据指针中的地址找到对应的变量呢?应用操作符*

var b = *p // 依据 p 中的值找到 a,将其值赋给 b
fmt.Println(b) //66

*p = 99 // 依据 p 中的值找到 a,扭转 a 的值
fmt.Println(a) //99

肯定要留神指针的初始化,如果不初始化,则指针的的值是其零值——nil。对未初始化的指针赋值,则会出问题:

var p *int // 只申明,未初始化
*p = 12 // 报错:invalid memory address or nil pointer dereference

起因是指针 p 中没值,是个 nil,天然就无奈依据地址找到变量。 如果你想应用指针,必须先确保你的指针中有非法的内存地址才行。该当这样写:

var a int
var p *int = &a // p 被初始化为 a 的地址
*p = 12 // 依据 p 的值找到 a,12 赋值给 a
// 或者
var a int
var p *int
p = &a // a 的地址赋给 p
*p = 12 // 依据 p 的值找到 a,12 赋值给 a 

上面是一个残缺的例子:

package main

import "fmt"

func main() {
    var a int = 66 // 变量 a
    var p *int = &a // 指针 p 指向 a
    var b = *p // 获取 p 指向的变量 a 的值
    fmt.Println("a =",a, ", b =", b, ", p =", p)
    fmt.Println("a 的地址 =", &a, ", b 的地址 =", &b, ", p 的地址 =", &p)

    *p = 12 // 扭转 p 指向的变量 a 的值

    fmt.Println("a =",a, ", b =", b, ", p =", p)
    fmt.Println("a 的地址 =", &a, ", b 的地址 =", &b, ", p 的地址 =", &p)

    var pp **int = &p // 指针 pp 指向指针 p
    var c = *pp // 获取 pp 指向的 p 的值
    var d = **pp // 获取 pp 指向的 p 指向的 a 的值
    fmt.Println("pp =", pp, ", c =", c, ", d =", d)
    fmt.Println("pp 的地址 =", &pp, ", c 的地址 =", &c, ", d 的地址 =", &d)
}

2. 构造体(struct)

2.1. 根本应用

和 C 语言一样,Go 语言中也有构造体。

构造体就是一组字段 / 属性的汇合 。有了构造体,咱们能够依据本人的需要定义本人的类型。比方狗,必定不能用根本数据类型来示意,因为狗身上有许多属性:string 类型的姓名、int类型的年龄等等,狗是一个领有许多属性的汇合,换句话说,狗是一个构造体。咱们能够定义一个 dog 类型的构造体来示意狗。

构造体的申明形式:

type 构造体名字 struct {
    字段名 1 类型 1
    字段名 2 类型 2
    ...
}

上面是构造体 dog 的申明:

type dog struct {
    name string
    age int
}

申明了构造体后,就能够应用它。

首先,只有你正确申明了构造体后,你就能像应用 intstring 等根本类型申明变量一样去申明 dog 类型的变量,而后,你就能给申明的变量 d 的字段赋值了,通过点号 . 来拜访构造体的字段

var d dog // 申明一个 dog 类型的变量 d
d.name = "哮天犬"
d.age = 3

除此之外,还有几种申明形式。

你能够依照字段程序间接赋值

d := dog{"哮天犬", 3}

或者指定字段赋值,这样能够疏忽字段程序:

d := dog{age:3, name:"哮天犬"}

上面是一个残缺的例子:

package main

import "fmt"

type dog struct {
    name string
    age int
}

func main() {
    var d dog // 申明一个 dog 类型的变量 d
    d.name = "哮天犬"
    d.age = 3

    d1 := dog{"哮地犬", 2}
    
    d2 := dog{age:4, name:"哮人犬"}

    fmt.Println(d, d1, d2)
}

2.2. 构造体指针

咱们能够获取构造体的指针:

d := dog{"哮地犬", 2}
p := &d // 获取到 d 的地址

能够依据构造体指针拜访其字段:

n := (*p).name
fmt.Println(n) // 哮天犬

这种形式比拟麻烦,Go 语言提供了隐式间接援用:

n := p.name // 这样也行
fmt.Println(n)

咱们能够通过 new 函数给构造体调配一个指针。

先介绍一下 new 函数:new函数用于给各种类型的内存调配。new(T)会给 T 类型调配对其适合的内存空间,用 T 类型的零值填充,并返回其地址,是一个 *T 类型的值。换句话说,该函数会返回一个指向 T 类型零值的指针。

p := new(dog)
fmt.Printf("%T\n", p) //*main.dog
fmt.Println(p) //&{0}
fmt.Println(*p) //{0}

从下面打印的三行语句中也能够看出,new(dog)返回的是一个指针。

2.3. 构造体嵌套

一个构造体也能够作为另一个构造体的字段,上面是一个例子:

package main

import "fmt"

type people struct {
    name string
    age int
    d dog
}

type dog struct {
    name string
    age int
}

func main() {a := people{"行小观", 18, dog{"小狗", 2}}
    fmt.Println(a) //{行小观 18 {小狗 2}}
    fmt.Println(a.d) //{小狗 2}
    fmt.Println(a.name) // 行小观
    fmt.Println(a.d.name) // 小狗
}

也能够应用匿名字段,何为匿名字段?顾名思义,只提供类型,不写字段名:

package main

import "fmt"

type people struct {
    name string
    age int
    dog // 匿名字段
}

type dog struct {
    name string
    age int
}

func main() {a := people{"行小观", 18, dog{"小狗", 2}}
    fmt.Println(a) //{行小观 18 {小狗 2}}
    fmt.Println(a.dog) //{小狗 2}
    fmt.Println(a.name) // 行小观
    fmt.Println(a.dog.name) // 小狗
}

作者简介

我是「行小观」,于千万人中的一个普通人。阴差阳错地走上了编程这条路,既然走上了这条路,那么我会尽可能远地走上来。

我会在公众号『行人观学』中继续更新「Java」、「Go」、「数据结构和算法」、「计算机根底」等相干文章。

欢送关注,咱们一起踏上行程。

本文章属于系列文章「Go 语言入门系列」。

如有谬误,还请斧正。

退出移动版