C++ 入门学习经验 06——指针(二):解引用、空指针和野指针到底怎么理解
大家好啊这里是阳阳的博客一个正在努力学习技术的大学生。上一篇我们聊了指针的上半部分主要说了指针到底是什么、地址可以怎么理解以及为什么很多同学一看到指针就会有点害怕。那今天这篇我们就接着把指针这个话题往下聊一聊。先看个例子int a 10; int* p a; cout p endl; cout *p endl;这里的p是什么*p又是什么a是什么 为什么有时候*是定义指针有时候又像是在取值刚开始看确实容易绕因为同一个符号在不同地方看起来像有不同意思。如果老师讲得快一点自己又没多敲几遍就很容易出现一种感觉课上好像听懂了自己写的时候大脑直接空白。所以这篇文章咱们就先把几个最容易让新手混乱的地方讲清楚指针变量、解引用、空指针、野指针以及指针和普通变量之间到底是什么关系。好了废话不多说咱们直接开始。一、先把这三个东西分清楚变量、地址、指针我们先来看一段最基础的代码int a 10; int* p a;这两行代码看着很短但里面其实有三个很重要的东西a 普通变量里面放的是 10 a a 这个变量在内存里的地址 p 指针变量里面放的是 a 的地址这里我觉得可以用一个生活里的例子来理解。假设有一个房间房间里面住着一个人。a就像这个房间本身里面放着一个值10a就像这个房间的门牌号p就像一张纸条纸条上写着这个房间的门牌号所以指针本身不是那个房间它只是保存了房间地址的一张“纸条”。很多同学一开始容易把p和a混在一起觉得p好像就是a。其实不是。a里面放的是数字10。p里面放的是地址。 只不过这个地址指向了a。我们可以看代码#include iostream using namespace std; int main() { int a 10; int* p a; cout a endl; cout a endl; cout p endl; return 0; }这里输出的结果大概会是这样10 0x61ff08 0x61ff08当然每个人电脑上地址不一定一样这个不用纠结。重点是a 和 p 输出出来通常是一样的因为p里面存的就是a的地址。这里大家可以先记一句比较简单的话普通变量装的是值指针变量装的是地址。只要这句话能先记住后面很多东西就没那么乱了。二、*p到底是什么意思接下来就是比较容易卡住的地方*p。还是这段代码int a 10; int* p a;如果我们写cout p endl;输出的是地址。如果我们写cout *p endl;输出的是10。那*p到底是什么意思呢我自己的理解是p 是门牌号 *p 是根据这个门牌号找到房间然后看看房间里住着什么也就是说p只是地址而*p是通过这个地址找到对应的变量再访问里面的值。这个过程就叫解引用。这个名字听起来有点官方刚开始不用太纠结它的术语。你可以先理解成通过指针找到它指向的那个东西。比如#include iostream using namespace std; int main() { int a 10; int* p a; cout p endl; // 输出 a 的地址 cout *p endl; // 输出 a 的值 return 0; }这里p和*p的区别非常重要p 地址 *p 地址对应位置里的值很多同学刚学的时候会写懵尤其是看到这个*p 20;心里可能会想这是什么意思是把 p 改成 20 吗其实不是。*p 20; 的意思是通过 p 这个地址找到它指向的那个变量然后把那个变量改成 20。比如#include iostream using namespace std; int main() { int a 10; int* p a; *p 20; cout a endl; return 0; }这段代码输出什么答案是20为什么因为p指向的是a所以*p 20;实际上就是把a改成了20。可以这样理解*p 20; 约等于把 p 指向的那个变量改成 20。这里我觉得需要注意一下不是p变成了20而是p指向的那个变量变成了20。这两个差别特别大。三、如何 区分int* p和*p呢这里还有一个需要理解的地方int* p a;和*p 20;这两个地方都有*但是意思好像不一样。其实确实不完全一样。在定义变量的时候int* p;这里的*是在说明p是一个指针变量。你可以把它理解成我要定义一个 int 类型的指针 p。而在使用变量的时候*p这里的*是在解引用表示访问 p 指向的那个变量。所以我们可以简单分成两种情况定义时int* p; 表示 p 是指针 使用时*p 表示访问 p 指向的内容刚开始学的时候大家可以不要一上来就追求特别严谨的语言可以先用这种方式区分左边带类型的时候多半是在定义指针。 没有带类型、单独使用 *p 的时候多半是在通过指针取值或改值。比如int* p a; // 定义一个指针 p让它指向 a cout *p; // 输出 p 指向的内容 *p 30; // 修改 p 指向的内容这样一对比其实会清楚很多。大家多敲几遍类似的代码慢慢就会顺起来。四、空指针暂时不指向任何有效位置再往下我们会遇到一个词空指针。一般我们会这样写int* p nullptr;或者有些老代码里会看到int* p NULL;现在更推荐用nullptr。那空指针是什么意思呢简单来说这个指针现在没有指向任何有效的变量。还是用门牌号的例子。如果普通指针像一张纸条上面写着某个房间的门牌号那么空指针就像一张纸条上面明确写着我现在没有指向任何房间。这样做的好处是至少我们知道它现在是“空的”。比如int* p nullptr;这比下面这种写法要安全很多int* p;为什么呢因为int* p;只是定义了一个指针变量但没有给它一个明确的地址。这个时候它里面可能是一个乱七八糟的地址。这就像你拿到一张纸条上面不知道原来写了什么奇怪门牌号。你要是根据这个门牌号去找房间就很危险。所以我觉得可以这样int* p nullptr;暂时不知道指向谁就先让它等于nullptr。后面要用的时候再让它指向一个明确的变量int a 10; p a;这里还要注意一点空指针不能直接解引用。比如int* p nullptr; cout *p endl;这就是很危险的写法。因为p根本没有指向有效变量你还要通过*p去访问它指向的内容那程序很可能直接崩掉。所以在使用指针前可以先判断一下if (p ! nullptr) { cout *p endl; }刚开始写代码时不一定每次都能想到这么严谨但至少要知道空指针可以存在但不要直接*p。五、野指针不应该存在的指针什么是野指针简单理解就是指针里面保存了一个不确定的、无效的、或者已经不该访问的地址。比如最常见的一种情况int* p; cout *p endl;这个p没有初始化它里面到底是什么地址我们不知道。这个时候直接*p就像拿着一张不知道哪里来的门牌号硬要去开别人家的门。结果可能是程序崩溃输出奇怪的值还有一种情况也容易造成野指针比如指向的东西已经不存在了。这个对初学者来说可能暂时遇得少一点后面学动态内存的时候会更明显。现阶段大家先记住一个比较实用的原则指针定义时尽量初始化。 不知道指向谁就先写 nullptr。 不要随便解引用一个不知道指向哪里的指针。比如不要这样int* p; *p 10;而应该这样int a 0; int* p a; *p 10;这里p明确指向了a所以*p 10;才有意义。六、指针和普通变量的关系不是复制值而是找到原件我们前面在值传递那一篇里提过函数参数如果只是普通变量很多时候传进去的是一份“复印件”。比如void change(int x) { x 20; } int main() { int a 10; change(a); cout a endl; }输出还是10因为change函数里面改的是x不是外面的a。那指针为什么能修改外面的变量呢我们来看#include iostream using namespace std; void change(int* p) { *p 20; } int main() { int a 10; change(a); cout a endl; return 0; }这次输出是20为什么因为我们传进去的是a的地址。change(a)的意思是把a的地址传给函数。函数里面的p虽然也是一个局部变量但它保存的是a的地址。所以当我们写*p 20;就是通过这个地址找到了外面的a然后把a改成了20。这里可以继续用复印件和原件来理解。普通值传递像是你把文件复印了一份交给别人。 别人怎么改复印件都不会影响你的原件。指针传递像是你把原件所在的位置告诉了别人。 别人按照这个位置找到原件就可以直接改原件。所以指针的重点不只是“传了一个东西”而是传了一个可以找到原变量的位置。这也是为什么指针一方面很有用另一方面也需要小心。七、总结指针这个东西刚开始不要急着一次学完。今天这篇内容看起来不少但其实核心就几句话。普通变量装的是值指针变量装的是地址。p 表示地址*p 表示通过这个地址找到里面的值。int* p 是定义指针*p 是使用指针访问内容。不知道指向谁的指针先写 nullptr。不要解引用空指针也不要解引用没初始化的野指针。指针能改外面的变量是因为它拿到了变量的地址。总之这篇我们把指针下半部分几个最容易绕的点先捋了一遍指针变量、解引用、空指针、野指针还有指针为什么能修改外面的变量。不用一开始就把所有东西都理解得特别透先多敲、多试、多对比慢慢就会顺起来。希望这篇文章能帮你少走一点弯路也能在遇到指针问题的时候心里更有底一点。如果这篇文章对你有帮助麻烦点赞、关注和收藏吧谢谢有什么问题或者想法欢迎在评论区留言我们一起交流我们下篇见