指针
探索指针的奥秘
在学习 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 变量的值也变为了 20PS
实际上,int* p、int * p 和 int *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]在0x1000arr[1]在0x1004arr[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++,指针就指向下一个元素。
空指针
有时候,我们可能会遇到一种特殊的指针,叫做空指针(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*)!因为 NULL 是 0,编译器会优先匹配 int 类型的参数,而不是指针类型。这可能导致程序行为和你的预期不一致,甚至出现难以发现的 bug。
nullptr 是 C++11 新增的关键字,专门表示空指针,类型为 std::nullptr_t。nullptr 只能用于指针类型,不能自动转换为整数类型,所以使用 nullptr 可以消除所有与空指针相关的类型歧义,让代码更安全、更清晰。因此,如果你再写项目的时候,强烈推荐使用 nullptr 代替 NULL。(但是如果是 C 语言和早期的 C++ 98/03,还是只能使用 NULL)
小知识:指针数组与数组指针的区别
在刚开学的时候,来自东南大学的沈军老师曾经提到过一个问题:指针数组和数组指针有什么区别?彼时的大家可能还不是很能理解,但是在学完指针和数组以后,我们就可以进行解释了。
其实看名字就可以大概理解:
- 指针数组是一个由指针组成的数组
- 数组指针是一个指向数组的指针
例如:
int* ptrArr[5];
int (*arrPtr)[5];指针数组 ptrArr 是一个数组,里面的每个元素都是一个指向 int 的指针。而数组指针 arrPtr 是一个指针,它指向一个包含 5 个 int 元素的数组。