指针

指针的概念

指针是存储另一个变量的内存地址的变量。

我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。

一个指针变量可以指向任何一个值的内存地址。

获取变量的地址

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
var a int = 10

fmt.Printf("变量的地址: %x\n", &a )
}

运行结果:

1
变量的地址: 20818a220

声明指针

声明指针,*T是指针变量的类型,它指向T类型的值。

1
var var_name *var-type

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。

1
2
var ip *int        /* 指向整型*/
var fp *float32 /* 指向浮点型 */

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */

ip = &a /* 指针变量的存储地址 */

fmt.Printf("a 变量的地址是: %x\n", &a )

/* 指针变量的存储地址 */
fmt.Printf("ip 变量的存储地址: %x\n", ip )

/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}

运行结果:

1
2
3
a 变量的地址是: 20818a220
ip 变量的存储地址: 20818a220
*ip 变量的值: 20

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

type name int8
type first struct {
a int
b bool
name
}

func main() {
var a = first{1, false, 2}
var b *first = &a
fmt.Println(a.b, a.a, a.name, &a, b.a, &b, (*b).a)
}

运行结果:

1
false 1 2 &{1 false 2} 1 0xc042068018 1

获取指针地址在指针变量前加&的方式

空指针

Go 空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。

空指针判断:

1
2
if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */

获取指针的值

获取一个指针意味着访问指针指向的变量的值。语法是:*a

示例代码:

1
2
3
4
5
6
7
8
9
10
11
package main  
import (
"fmt"
)

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

操作指针改变变量的数值

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
}

运行结果

1
2
3
address of b is 0x1040a124  
value of b is 255
new value of b is 256

使用指针传递函数的参数

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func change(val *int) {
*val = 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)
}

运行结果

1
2
value of a before function call is 58  
value of a after function call is 55

不要将一个指向数组的指针传递给函数。使用切片。

假设我们想对函数内的数组进行一些修改,并且对调用者可以看到函数内的数组所做的更改。一种方法是将一个指向数组的指针传递给函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

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

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

运行结果

1
[90 90 91]

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

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

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

运行结果

1
[90 90 91]

虽然将指针传递给一个数组作为函数的参数并对其进行修改,但这并不是实现这一目标的惯用方法。我们有切片。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

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

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

运行结果:

1
[90 90 91]

Go不支持指针算法。

1
2
3
4
5
6
7
package main

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

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

指针数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

const MAX int = 3

func main() {

a := []int{10,100,200}
var i int

for i = 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i, a[i] )
}
}

输出结果

1
2
3
a[0] = 10
a[1] = 100
a[2] = 200

有一种情况,我们可能需要保存数组,这样我们就需要使用到指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

const MAX int = 3

func main() {
a := []int{10, 100, 200}
var i int
var ptr [MAX]*int

for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
}

for i = 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i, *ptr[i])
}
}

输出结果

1
2
3
a[0] = 10
a[1] = 100
a[2] = 200

指针的指针

指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

1
var ptr **int;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {

var a int
var ptr *int
var pptr **int

a = 3000

/* 指针 ptr 地址 */
ptr = &a

/* 指向指针 ptr 地址 */
pptr = &ptr

/* 获取 pptr 的值 */
fmt.Printf("变量 a = %d\n", a )
fmt.Printf("指针变量 *ptr = %d\n", *ptr )
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}

输出结果

1
2
3
变量 a = 3000
指针变量 *ptr = 3000
指向指针的指针变量 **pptr = 3000

指针作为函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200

fmt.Printf("交换前 a 的值 : %d\n", a )
fmt.Printf("交换前 b 的值 : %d\n", b )

/* 调用函数用于交换值
* &a 指向 a 变量的地址
* &b 指向 b 变量的地址
*/
swap(&a, &b);

fmt.Printf("交换后 a 的值 : %d\n", a )
fmt.Printf("交换后 b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址的值 */
*x = *y /* 将 y 赋值给 x */
*y = temp /* 将 temp 赋值给 y */
}

输出结果

1
2
3
4
交换前 a 的值 : 100
交换前 b 的值 : 200
交换后 a 的值 : 200
交换后 b 的值 : 100

结构体

什么是结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体的定义和初始化

1
2
3
4
5
6
type struct_variable_type struct {
member definition;
member definition;
...
member definition;
}

一旦定义了结构体类型,它就能用于变量的声明

1
variable_name := structure_variable_type {value1, value2...valuen}

初始化结构体

1
2
3
4
5
6
7
// 1.按照顺序提供初始化值
P := person{"Tom", 25}
// 2.通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}
// 3.new方式,未设置初始值的,会赋予类型的默认初始值
p := new(person)
p.age=24

结构体的访问

访问结构体成员(访问结构的各个字段)

通过点.操作符用于访问结构的各个字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

type Books struct {
title string
author string
subject string
book_id int
}

func main() {
var Book1 Books /* 声明 Book1 为 Books 类型 */
var Book2 Books /* 声明 Book2 为 Books 类型 */

/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.runoob.com"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407

/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.runoob.com"
Book2.subject = "Python 语言教程"
Book2.book_id = 6495700

/* 打印 Book1 信息 */
fmt.Printf( "Book 1 title : %s\n", Book1.title)
fmt.Printf( "Book 1 author : %s\n", Book1.author)
fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)

/* 打印 Book2 信息 */
fmt.Printf( "Book 2 title : %s\n", Book2.title)
fmt.Printf( "Book 2 author : %s\n", Book2.author)
fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}

运行结果:

1
2
3
4
5
6
7
8
Book 1 title : Go 语言
Book 1 author : www.runoob.com
Book 1 subject : Go 语言教程
Book 1 book_id : 6495407
Book 2 title : Python 教程
Book 2 author : www.runoob.com
Book 2 subject : Python 语言教程
Book 2 book_id : 6495700

结构体指针

指针指向一个结构体,也可以创建指向结构的指针。

结构体指针

1
var struct_pointer *Books

以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前

1
struct_pointer = &Book1;

使用结构体指针访问结构体成员,使用 “.” 操作符

1
struct_pointer.title;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

type Books struct {
title string
author string
subject string
book_id int
}

func main() {
var Book1 Books /* Declare Book1 of type Book */
var Book2 Books /* Declare Book2 of type Book */

/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.runoob.com"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407

/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.runoob.com"
Book2.subject = "Python 语言教程"
Book2.book_id = 6495700

/* 打印 Book1 信息 */
printBook(&Book1)

/* 打印 Book2 信息 */
printBook(&Book2)
}
func printBook( book *Books ) {
fmt.Printf( "Book title : %s\n", book.title);
fmt.Printf( "Book author : %s\n", book.author);
fmt.Printf( "Book subject : %s\n", book.subject);
fmt.Printf( "Book book_id : %d\n", book.book_id);
}

运行结果:

1
2
3
4
5
6
7
8
Book title : Go 语言
Book author : www.runoob.com
Book subject : Go 语言教程
Book book_id : 6495407
Book title : Python 教程
Book author : www.runoob.com
Book subject : Python 语言教程
Book book_id : 6495700

结构体实例化也可以是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

type Books struct {
}

func (s Books) String() string {
return "data"
}
func main() {
fmt.Printf("%v\n", Books{})
}

解释:

Books类型上定义了一个String方法。这个方法具有一个接收者(s Books),表示它是Books类型的一个实例方法。这个方法返回一个字符串"data"Books{}创建了一个Books类型的实例,然后通过%v打印输出。由于Books类型实现了String方法,该方法将被调用,返回字符串"data"

结构体的匿名字段

结构体的匿名字段

可以用字段来创建结构,这些字段只包含一个没有字段名的类型。这些字段被称为匿名字段。

在类型中,使用不写字段名的方式,使用另一个类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名字段,那么默认Student就包含了Human的所有字段
speciality string
}
func main() {
// 我们初始化一个学生
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
// 我们访问相应的字段
fmt.Println("His name is ", mark.name)
fmt.Println("His age is ", mark.age)
fmt.Println("His weight is ", mark.weight)
fmt.Println("His speciality is ", mark.speciality)
// 修改对应的备注信息
mark.speciality = "AI"
fmt.Println("Mark changed his speciality")
fmt.Println("His speciality is ", mark.speciality)
// 修改他的年龄信息
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
// 修改他的体重信息
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}

输出:

1
2
3
4
5
6
7
8
9
10
His name is  Mark
His age is 25
His weight is 120
His speciality is Computer Science
Mark changed his speciality
His speciality is AI
Mark become old
His age is 46
Mark is not an athlet anymore
His weight is 180

可以使用”.”的方式进行调用匿名字段中的属性值

实际就是字段的继承

其中可以将匿名字段理解为字段名和字段类型都是同一个

基于上面的理解,所以可以mark.Human = Human{"Marcus", 55, 220} mark.Human.age = 46

若存在匿名字段中的字段与非匿名字段名字相同,则最外层的优先访问,就近原则

通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段哦,所有的内置类型和自定义类型都是可以作为匿名字段的。

结构体嵌套

嵌套的结构体
一个结构体可能包含一个字段,而这个字段反过来就是一个结构体。这些结构被称为嵌套结构。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
)

type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}

func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.address = Address {
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:",p.age)
fmt.Println("City:",p.address.city)
fmt.Println("State:",p.address.state)
}

提升字段

在结构体中属于匿名结构体的字段称为提升字段,因为它们可以被访问,就好像它们属于拥有匿名结构字段的结构一样。理解这个定义是相当复杂的。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
)

type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}

func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.Address = Address{
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}

运行结果

1
2
3
4
Name: Naveen  
Age: 50
City: Chicago
State: Illinois

导出结构体和字段

如果结构体类型以大写字母开头,那么它是一个导出类型,可以从其他包访问它。类似地,如果结构体的字段以大写开头,则可以从其他包访问它们。

示例代码:

1.在computer目录下,创建文件spec.go

1
2
3
4
5
6
7
package computer

type Spec struct { //exported struct
Maker string //exported field
model string //unexported field
Price int //exported field
}

2.创建main.go 文件

1
2
3
4
5
6
7
8
9
10
11
package main

import "structs/computer"
import "fmt"

func main() {
var spec computer.Spec
spec.Maker = "apple"
spec.Price = 50000
fmt.Println("Spec:", spec)
}

输出:

1
Spec: {apple  50000}

结构体比较

结构体是值类型,如果每个字段具有可比性,则是可比较的。如果它们对应的字段相等,则认为两个结构体变量是相等的。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
)

type name struct {
firstName string
lastName string
}


func main() {
name1 := name{"Steve", "Jobs"}
name2 := name{"Steve", "Jobs"}
if name1 == name2 {
fmt.Println("name1 and name2 are equal")
} else {
fmt.Println("name1 and name2 are not equal")
}

name3 := name{firstName:"Steve", lastName:"Jobs"}
name4 := name{}
name4.firstName = "Steve"
if name3 == name4 {
fmt.Println("name3 and name4 are equal")
} else {
fmt.Println("name3 and name4 are not equal")
}
}

运行结果

1
2
name1 and name2 are equal  
name3 and name4 are not equal

如果结构变量包含的字段是不可比较的,那么结构变量是不可比较的

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
)

type image struct {
data map[int]int
}

func main() {
image1 := image{data: map[int]int{
0: 155,
}}
image2 := image{data: map[int]int{
0: 155,
}}
if image1 == image2 {
fmt.Println("image1 and image2 are equal")
}
}

invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)

结构体作为函数的参数

结构体作为函数参数使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ackage main

import "fmt"

type Books struct {
title string
author string
subject string
book_id int
}

func main() {
var Book1 Books /* 声明 Book1 为 Books 类型 */
var Book2 Books /* 声明 Book2 为 Books 类型 */

/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.runoob.com"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407

/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.runoob.com"
Book2.subject = "Python 语言教程"
Book2.book_id = 6495700

/* 打印 Book1 信息 */
printBook(Book1)

/* 打印 Book2 信息 */
printBook(Book2)
}

func printBook( book Books ) {
fmt.Printf( "Book title : %s\n", book.title);
fmt.Printf( "Book author : %s\n", book.author);
fmt.Printf( "Book subject : %s\n", book.subject);
fmt.Printf( "Book book_id : %d\n", book.book_id);
}

结构体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type user struct {
name string
password string
}

func (u user) checkPassword(password string) bool {
return u.password == password
}

func (u *user) resetPassword(password string) {
u.password = password
}

func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}

make、new操作

make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。
内建函数new本质上说跟其它语言中的同名函数功能一样:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:new返回指针

内建函数make(T, args)与new(T)有着不同的功能,make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice为nil。对于slice、map和channel来说,make初始化了内部的数据结构,填充适当的值。make返回初始化后的(非零)值。

  • new用于任何类型的内存分配,返回的是类型的指针,指向新分配的零值内存空间。
  • make仅用于切片、映射和通道的内存分配,返回的是经过初始化后的非零值。

指针与结构体举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

type user struct {
name string
password string
}

func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"

fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}

func checkPassword(u user, password string) bool {
return u.password == password
}

func checkPassword2(u *user, password string) bool {
return u.password == password
}

输出:

1
2
3
{wang 1024} {wang 1024} {wang 1024} {wang 1024}
false
false