指针是Go语言中广泛使用的一种数据类型,通过指针变量可以表示各种数据结构,可以很方便地使用数组和字符串,并能像汇编语言一样处理内存地址,从而编写出精练而高效的程序。在本章的内容中,将详细讲解Go语言指针的知识,并通过具体实例讲解使用函数的方法。
6.1 指针基础
在计算机系统中,指针和内存是密切相关的。本节将详细讲解和指针相关的基本概念,特别是内存和指针的关系。
6.1.1 内存和指针
计算机中的所有数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占两个单元,字符量占一个单元等,在前面已有详细的介绍。为了正确访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元,而内存单元的编号也叫作地址。因为根据内存单元的编号(地址)就可以找到所需的内存单元,所以通常也把这个地址称为指针。
内存单元的指针和内存单元的内容是两个不同的概念,我们可以用一个通俗的例子来说明它们之间的关系。例如,我们到银行去存取款时,银行工作人员将根据我们的账号去找我们的存款单,找到之后在存单上写入存款、取款的金额。在这里,账号就是存单的指针,存款数是存单的内容。对于一个内存单元来说,其地址即为指针,其中存放的数据才是该单元的内容。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址(或称为某内存单元的指针)。如图6-1所示,设有字符变量C,其内容为K(ASCII码为十进制数75),C占用了011A号单元(地址用十六进制数表示)。设有指针变量P,内容为011A,这种情况下,我们称为P指向变量C,或者说P是指向变量C的指针。
图6-1 地址和指针
一个指针是一个地址,是一个常量。一个指针变量却可以被赋予不同的指针值,是变量,但常把指针变量简称为指针。为了避免混淆,我们约定:“指针”是指地址,是常量,“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。既然指针变量的值是一个地址,那么这个地址不仅可以是变量的地址,也可以是其他类型数据的地址。那么,在一个指针变量中存放一个数组或一个函数的首地址有何意义呢?因为数组或函数都是连续存放的,通过访问指针变量取得了数组或函数的首地址,也就找到了该数组或函数。这样一来,凡是出现数组、函数的地方都可以用一个指针变量来表示,只要为该指针变量赋予数组或函数的首地址即可。这样做将会使程序的概念十分清楚,程序本身也变得精练、高效。总而言之,在C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元,用地址这个概念并不能很好地描述一种数据类型或数据结构,而指针虽然实际上也是一个地址,但它却是一个数据结构的首地址,它是指向一个数据结构的,因而概念更为清楚,表示更为明确,这也是引入指针概念的一个重要原因。
6.1.2 和C语言指针的区别
Go语言的指针和C语言的指针在概念上是类似的,但是也有一些区别。
(1)空指针
在Go语言中,空指针的值为nil,而不像C语言中未初始化的指针可能指向任意地址。这使得在Go语言中使用指针更加安全。
(2)指针运算
在C语言中,可以对指针进行算术运算(加、减),指针可以指向数组中的任意一个元素。但在Go语言中,指针不能进行算术运算,也不支持指针计算。同时,在Go语言中,虽然可以使用切片来访问数组中的元素,但是切片并不是指针类型。
(3)指针声明
在C语言中,需要在变量名前面使用一个*号来声明一个指针类型的变量。例如:
int *p;
而在Go语言中,则需要在类型前面使用一个*号来声明一个指针类型的变量。例如:
var p *int
(4)自动垃圾回收
在Go语言中,有垃圾回收机制,自动回收不再使用的内存,这样就避免了指针操作中的很多问题,如内存泄露等。
(5)指针参数传递
在C语言中,指针经常被用于函数参数传递,在函数内部修改变量的值或共享大量数据,而在Go语言中,虽然也可以这样使用指针,但是由于有更加丰富的类型系统和垃圾回收机制,因此可以使用更多的方法来实现类似的操作。
6.2 指针的声明和初始化
经过本书前面知识的学习可知,Go语言的指针和C语言的指针在概念上很相似,但在具体实现和语言特性上还是有一些差异。从本节的内容开始,将详细讲解Go语言指针的知识。
6.2.1 声明指针
在Go语言中,声明一个指针变量需要使用*操作符。指针变量的类型是所指向变量的类型加上*。例如,如果要声明一个指向整数的指针,在Go语言中可以这样写:
var p *int // 声明一个指向整数的指针变量p
如果要创建一个新的整数变量,并将它的地址赋给指针变量p,可以这样写:
x := 123
p = &x // 将x的地址赋值给指针变量p
在Go语言中,还可以使用函数new()来创建一个新的指针变量,并将其初始化为零值(nil)。例如,要创建一个新的整数类型的指针变量,可以这样写:
p := new(int) // 创建一个新的指向整数类型的指针变量p,其值为nil
在此之后,就可以像之前一样将指针p指向一个整数变量,或修改它所指向的变量的值。
6.2.2 初始化指针变量
在定义指针变量后,需要将其初始化为一个有效的值。在Go语言中,可以使用函数new()来分配内存并返回指针变量的地址,例如:
p := new(int) // 定义一个指向整数类型的指针变量p,并分配内存空间
此时,p的值为一个整数类型的零值,即0。
也可以将指针变量初始化为某个变量的地址,例如:
x := 123
p := &x // 定义一个指向整数类型变量x的指针变量p
也可以在一条语句中声明多个指针变量,例如:
var p1, p2 *int // 定义两个指向整数类型的指针变量
可以使用函数make()来初始化一个切片或映射,并返回其指针。例如:
s := make([]int, 10) //返回一个包含10个整数的切片,并分配相应的内存
m := make(map[string]int) //返回一个键为字符串类型,值为整数类型的映射,并分配相应的内存
这种方法可以避免手动进行内存管理,从而减少出错的机会。
注意:在Go语言中声明和使用指针变量需要谨慎处理,避免出现空指针、野指针等问题,以保证程序的正确性和安全性。