指针是 C++ 中非常重要的一种数据类型,它专门用来存储其它变量的内存地址,通过指针,我们可以直接访问和操作存储在内存中的数据。
在讨论指针前,我们先来看看 C++ 中变量的内存地址这个概念。
在 C++ 中,所有的变量、数据、甚至代码都是存储在内存中的,在这里我们不讨论具体的内存模型是怎么样的(想要深入了解可以自行查阅资料,我在后面也会给出一些参考资源),我们现在主要关心的是变量所在的内存地址。
假设我们有一个变量 a
,在变量的前面加上取地址符号 &
,&a
会得到这个变量所在的内存地址。
运行该程序,输出结果为:
你运行的结果应该大致看起来和我差不多,但具体的地址肯定不同,这是当程序在创建变量时由系统自行分配的。
另外,你每次运行程序时,都会得到不同的地址,这是因为每次运行程序都会重新创建变量,因此分配的内存地址也应该也是不同的。
这里的地址 0x7fff282891bc
看起来有点奇怪,有数字又有字母,实际上,前缀0x
代表十六进制,后面的一串数字则是由十六进制表示的实际内存地址。
细心的同学还会发现,这里三个变量的地址刚好差 4,实际上是指 4 个字节(byte),因为 int
变量刚好是占 4 个字节。
在 C++ 中,符号
&
根据上下文的不同有多种含义。
- 作为取地址运算符,例如:&a,表示取变量 a 的地址
- 作为按位与运算符,例如: a & b,表示对变量 a 和 b 进行与运算
- 用于声明引用的分隔符,例如:int& ref,表示声明了一个引用,在函数参数中用得较多
数组与指针是两种完全不同的对象,但又有着密不可分的关系,接下来我们来看看它们之间有哪些共同和不同的地方。
一个数组的名字,实际上就是一个指针,该指针指向该数组存放的起始地址,也就是数组第一个元素所在的地址,这一点使得数组与指针之间产生了密切的联系。
例如,有如下代码:
q
与 &q[0]
的输出结果是完全相同,因为他们代表的是同一个元素的地址。
【TODO】
在 C++ 中,指针是一种特殊的变量,它是专门用来存储变量的内存地址的。直接看例子容易明白。
这里定义了一个指针变量 pointer_a
,该变量存储了变量 a
的地址。程序的运行结果如下:
可以看到,这里指针 pointer_a
的值与变量 a
的地址是完全一致的。
在定义指针变量时,除了正常的变量名称,需要在前面加上 *
表明这是一个指针变量,*
的位置可以加在变量名前,也可以紧跟在变量类型后面。
推荐使用位置2,紧跟在变量类型后,这样就不会误解,以为 *pointer_a
这个整体是变量名,指针变量名是不包含 *
的。
实际上,point_a
和 *pointer_a
代表的完全不同的意思,在指针变量名前加星号,如 *pointer_a
,是直接获取该指针中存储的地址所指向的值。
下面的示意图显示了普通变量 a
,a 的地址 &a
,以及指针变量 pointer_a
这几者之间的关系。
更改指针变量所指向的值
既然指针变量存储的是变量的地址,我们就可以通过这个地址去修改变量的值。
下面是一个简单的例子:
这里我们添加了一行,通过指针去修改它所指向的值,可以发现,原始的变量 a
的值也被修改了。
需要注意的是,指针变量也有大小,它是指针本身在内存中占用的空间,而不是它所指向的数据的大小,别弄混淆了。指针的大小是由操作系统和编译器决定的,通常在 32 位系统上是 4 个字节,在 64 位系统上是 8 字节。
在前面讲解函数的章节中,我们已经看到函数的参数传递分为值传递和引用传递两种方式。
引用传递实际上就是将实参变量的地址传递给函数,当时讲到一个交换两个变量的函数 swap
,大家还记得吗?
在上面的代码中,我们通过引用传递实现了两个变量的交换。
既然指针又是存储内存地址的变量,我们也可以通过传递指针变量的方式来实现同样的功能。
两者实现的效果一样,不同的是函数参数的定义和调用函数的方式有所区别,通过这两种不同的写法,可以更好地帮助理解指针与内存地址之间的关系。
如果指针 指向数组元素 ,那么
例如下面的代码:
前面有讲到,数组名本身就是数组所在的内存地址,所以我们可以使用 数组名 + i
来遍历数组中的元素。如下面的代码所示:
也可以使用指针来动态分配内存,例如:
这里的 new
运算符用于动态分配指定类型的内存空间,将分配的内存空间地址存储在指针变量 p
中,然后使用 *p
来设定该地址所指向的值。
需要注意的是,在使用完动态分配的内在后,需要使用 delete
运算符去释放对应的内存空间。
上面是单个对象动态创建,同样的方式也可以用于动态创建数组,下面是一个动态创建数组的完整示例:
可以发现,使用 new
运算符动态创建数组,数组元素的个数可以不用是常量(这里是通过外部读入的),也就是说数组元素的个数是在运行时确定的,相比普通的数组创建来说有更高的灵活度。
另外,销毁数组是用 delete[]
运算符,别忘了 []
符号。
指针可以直接操作内存地址,这是 C++ 强大的功能之一,但也正是因为它的强大与灵活,一旦没使用好,很容易造成程序崩溃、内存泄漏或逻辑错误等问题。在使用时要特别小心。
这里介绍几种使用指针时的常见错误用法。
修改后:
运行代码,会遇到 int*
到 int
的类型转换错误。
当我们想在一行定义两个同类型的指针,初学者容易这样写,这是一个常见的声明指针的错误。
修改后:
写这段代码的本意是希望将 p 指针指向的值增加 1,但运行该程序得到的结果可能是一个很大或很小的,错误的整数。
修改后:
C++ 中的指针是一种强大的工具,它们提供了直接访问和操作内存的能力,使用得当,可以极大的简化一些编程任务的执行,但如果未使用好,容易造成一些不易调试的内存访问问题。因引在使用指针时需要谨慎,多积累一些内存管理的技巧,养成良好的编码实践习惯。