1. 指针

如果你使用过C或C++,那你肯定对指针这个概念不陌生。

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

1.1. 内存和地址

D:\Work\Program\go

内存的存取速度快,其中有许多存储单元用来存储数据,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等值,而是其他变量的地址。

bababa

还可以有指针的指针:

1.3. 指针的使用

声明一个指针:

var p *int
*intpintpintp
&
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
pnil
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语言中也有结构体。

stringintdog

结构体的声明方式:

type 结构体名字 struct {
    字段名1 类型1
    字段名2 类型2
    ...
}
dog
type dog struct {
    name string
    age int
}

声明了结构体后,就可以使用它。

intstringdogd.
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
newnewnew(T)TT*TT
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) //小狗
}