Logo成贤计协指南

指针

探索指针的奥秘

在学习 C/C++ 的过程中,指针是一个非常重要但又让很多初学者头疼的概念。指针不仅是 C/C++ 的核心特性之一,也是理解底层编程、内存管理、数据结构等高级内容的基础。

本节将带你一步步认识指针,理解它的本质、用法和常见的坑,让你不再害怕指针。

变量的本质

在前面的章节中,我们已经知道,变量是用来存储数据的“盒子”。但在计算机的底层,变量其实就是内存中的一块空间。每当你定义一个变量,计算机就会在内存中为它分配一块区域,用来存放数据。

比如:

int a = 10;

这行代码的含义是:在内存中开辟一块可以存放 int 类型数据的空间,并把它命名为 a,然后把 10 存进去。

每个变量在内存中都有一个唯一的地址,就像每个房间都有自己的门牌号。这个地址就是变量在内存中的“定位信息”,计算机通过地址来找到变量并访问它的内容。

你可以用取地址符 & 来获取变量的地址:(在后面的章节我们会详细讲到 &

int a = 10;
cout << "a 的地址是:" << &a << endl;

输出的结果是一个十六进制的数字,比如 0x7ffee4b3c8ac,这就是变量 a 在内存中的地址。

什么是指针

简单来说,指针就是一个变量,它存储的是另一个变量的地址。你可以把指针想象成一个“路标”,它可以向内存中的某个位置,而这个位置存放着具体的数据。

举个例子:

假如你有一个盒子(变量),指针就是一张纸条,上面写着这个盒子的地址。你可以通过纸条找到盒子,并访问盒子里的内容。

上面我们提到,每个变量在内存中都有自己的地址。针就是用来保存这些地址的变量,。

指针的声明和使用

定义指针的基本语法如下:

<数据类型>* <指针变量名>;

例如:

int a = 10;
int* p; // 定义一个指向 int 类型的指针变量 p
p = &a; // &a 表示变量 a 的地址

& 是取地址符,可以用来获取一个变量的地址。上面的代码中,p 就是一个指针变量,它存储了变量 a 的地址。

指针变量也有自己的地址哦,所以你可以通过 &p 来获取指针变量 p 的地址。

而指针的使用也很简单。指针本身存储的是地址,我们可以通过解引用操作符 * 来访问指针所指向的变量的值:

cout << "a 的值是:" << *p << endl; // 输出 a 的值

也可以通过指针来修改变量的值:

*p = 20; // 通过指针修改 a 的值,此时 a 变量的值也变为了 20

PS

实际上,int* pint * pint *p 是完全等价的,都是定义一个指向 int 类型的指针变量 p。而使用哪个则看个人的习惯和团队的代码风格。

很多人会喜欢,int* p 的写法,因为它强调了类型是 int*。但是,很多人会喜欢 int *p 的写法,因为它更直观地表示了 p 是一个指针变量。

当然,同时定义多个变量时,推荐使用 int *p, *q; 的写法,这样可以避免误解,因为 int* p, q 实际上是定义一个 指针变量 p 和一个 普通的 int 变量 q

指针可以指向不同内存区域的变量,比如局部变量(通常在栈上分配)和动态分配的变量(在堆上分配)。关于“堆”和“栈”的详细区别,我们会在后续章节(如动态内存分配)中深入讲解。

指针与数组

actually, 指针和数组有着密切的关系。当你定义一个数组时,比如:

int arr[5] = {1, 2, 3, 4, 5};

编译器会在内存中为它分配一块连续的空间,每个元素紧挨着下一个元素。假设 arr 的起始地址是 0x1000,那么:

  • arr[0]0x1000
  • arr[1]0x1004
  • arr[2]0x1008
  • 以此类推(假设 int 占 4 字节)

在 C/C++ 中,数组名本质上就是指向第一个元素的指针。也就是说,arr 等价于 &arr[0]

你可以这样写:

int* p = arr; // p 指向 arr[0]

此时,p 就是一个指向数组首元素的指针。

因为数组是连续存储的,指针可以通过“指针运算”来遍历数组的每一个元素:

for (int* p = arr; p < arr + 5; ++p) {
    cout << *p << " ";
}

这里,arr 是第一个元素的地址,arr + 5 是最后一个元素的下一个地址。每次 p++,指针就指向下一个元素。

但是请不要试图 arr + 5,因为此时已经超出了数组的范围,即越界,访问 arr + 5 的值会导致数据异常或者程序崩溃。

空指针

有时候,我们可能会遇到一种特殊的指针,叫做空指针(Null Pointer)。空指针是一个不指向任何有效内存地址的指针,通常用来表示“没有对象”或者“未初始化”。

在早期的 C/C++ 中,空指针通常用 NULL 来表示:

int* p = NULL; // 定义一个空指针

而在 C++ 11 之后(现代 C++),引入了一个新的关键字 nullptr,它专门用来表示空指针。

int* p = nullptr; // 定义一个空指针

Tips

NULL 实际上是一个宏,通常定义为整数 0,或者 (void*)0。在 C++ 代码里,NULL 其实就是 0,它不是一个专门的指针类型。

由于 NULL 只是整数 0,它在某些情况下会引发类型歧义编译错误,尤其是在函数重载和模板中。例如

void func(int);
void func(int*);

func(NULL); // 这里会调用 func(int),而不是 func(int*)!

因为 NULL0,编译器会优先匹配 int 类型的参数,而不是指针类型。这可能导致程序行为和你的预期不一致,甚至出现难以发现的 bug。

nullptr 是 C++11 新增的关键字,专门表示空指针,类型为 std::nullptr_tnullptr 只能用于指针类型,不能自动转换为整数类型,所以使用 nullptr 可以消除所有与空指针相关的类型歧义,让代码更安全、更清晰。因此,如果你再写项目的时候,强烈推荐使用 nullptr 代替 NULL。(但是如果是 C 语言和早期的 C++ 98/03,还是只能使用 NULL

小知识:指针数组与数组指针的区别

在刚开学的时候,来自东南大学的沈军老师曾经提到过一个问题:指针数组和数组指针有什么区别?彼时的大家可能还不是很能理解,但是在学完指针和数组以后,我们就可以进行解释了。

其实看名字就可以大概理解:

  • 指针数组是一个由指针组成的数组
  • 数组指针是一个指向数组的指针

例如:

int* ptrArr[5];
int (*arrPtr)[5];

指针数组 ptrArr 是一个数组,里面的每个元素都是一个指向 int 的指针。而数组指针 arrPtr 是一个指针,它指向一个包含 5 个 int 元素的数组。