完壁归赵 發表於 2025-10-17 21:31:00

奶奶都能看懂的 C++ —— 手把手指针

<h2 id="引用">引用</h2>
<p>在正式介绍指针之前,先来看看什么是引用。</p>
<pre><code class="language-cpp">int a = 10;
int &amp;ref1 = a;
</code></pre>
<p>你可能注意到了,上面的代码里有个 <code>&amp;</code>。这就是我们的主角,引用。在变量名之前加上该符号,就可以指出它是个引用。</p>
<p>我们常说的引用,就是把别人的东西拿过来自己用。C++ 的引用也是如此,就是把另外一个对象拿过来用,然后起个名字。也就是说:</p>
<pre><code class="language-c++">// a = 10
ref1 = 11;
// 现在,a = 11
</code></pre>
<p>对象就像瓶子,引用就是瓶子上面的标签。访问引用时,就是找到标签所对应的瓶子。</p>
<p>引用必须满足以下条件:</p>
<ul>
<li>引用指向的是一个对象,而不是值</li>
<li>引用类型和它指向的对象匹配</li>
<li>引用必须在声明时初始化</li>
<li>引用初始化后不能更改绑定的对象</li>
</ul>
<p>要注意的是,<strong>引用必须在声明时初始化</strong>。下面代码会产生编译错误:</p>
<pre><code class="language-cpp">int &amp;ref2; // Error!
</code></pre>
<p>另外要注意的一点是,可以一次声明多个引用,但都要加上 <code>&amp;</code>。</p>
<pre><code class="language-cpp">int &amp;ref1=a, ref2=a;// ref1 是引用,ref2 则是 a 值的拷贝
int &amp;ref1=a, &amp;ref2=a;// 都是引用
</code></pre>
<p>实际上,把 <code>&amp;</code> 和类型名称放一起也是可行的,但是考虑到上面这个一次声明多个的问题,我还是建议和变量名放一起,否则有歧义。</p>
<h2 id="指针">指针</h2>
<p>好好好,现在我们来到了正题。</p>
<p><strong>先把上面的引用忘了</strong>,我们到最后再来讲指针和引用的差别。</p>
<h3 id="创建指针">创建指针</h3>
<pre><code class="language-cpp">int a = 10;
int *p;
p = &amp;a;
</code></pre>
<p>这里又有 <code>*</code> 又有 <code>&amp;</code>,看晕了都。所以我把它拆成了三行,我们一行一行来。</p>
<p>首先,第二行,有个<strong>星号</strong>。这就是我们的主角,指针。<code>*</code> 表示创建的是指针。这一行声明了一个 int 类型的指针,但是并没有初始化。</p>
<p>第三行,把指针 <code>p</code> 指向 <code>a</code> 的地址。你肯定注意到这里有个老熟人 <code>&amp;</code>。当然啦,我让你先把引用忘了是有原因的,因为<strong>这里的 <code>&amp;</code> 和上面引用那里的完全不是一个东西</strong>。</p>
<p>这里的 <code>&amp;</code> 叫做 <code>取地址符</code>。它和一个变量一起用可以返回那个变量的地址。各位都知道你的内存很大,位置很多,取地址符就是用于查找变量的位置的。</p>
<p><strong>Warning! 这里不初始化指针拆成两行的方法是不推荐的,因为未初始化的指针行为未知。实际请务必初始化!</strong></p>
<p>既然得到了位置,我们自然就知道指针的用法了——“一个指针对应一个对象的位置”。</p>
<p><strong>注意:</strong></p>
<ol>
<li>引用不是对象,<strong>没有地址</strong></li>
<li>指针自己是对象,所以<strong>可以用指针指向指针</strong>。这个后面再说。</li>
</ol>
<p>ohhhhhhhh 恭喜你,你已经明白了怎么创建指针,接下来就用一下吧。</p>
<h3 id="用指针">用指针</h3>
<pre><code class="language-cpp">cout &lt;&lt; *p;
// a = 10, output: 10
*p = 20;
cout &lt;&lt; *p;
// a = 20, output: 20
</code></pre>
<p>嗯,现在熟悉的东西又来了。我们在创建指针的时候已经用了星号了,现在访问时又出现了。</p>
<p>或许你已经猜到了。很遗憾,这里的星号和前面的含义也截然不同。<code>*</code> 叫<strong>解引用符</strong>(别看名字,它和引用没半毛钱关系),用于<strong>从某个地址获取其对应的对象</strong>。</p>
<p>啥意思?我们的变量对象在内存里,<code>&amp;</code> 找到了对象的位置用指针存起来,然后想要用的时候,再用 <code>*</code> 根据位置找到对象。</p>
<p>哎,回到上面的三行代码。1、4 行输出了对象,3 行则改变了对象的值。我们可以看到,由于根据位置找到的对象还是 a,所以 a 的值也发生了变化。</p>
<pre><code class="language-cpp">int b =30;
p = &amp;b;
</code></pre>
<p>我们先前提到指针是对象,所以它本身也可以改变。</p>
<p>你可以用其它对象的地址重新赋值给指针,就像上面一样。这样指针就指向其它对象了。</p>
<p>再次恭喜你,你现在已经明白了怎么用指针了。接下来再介绍点特殊的指针。</p>
<h4 id="在继续之前">在继续之前……</h4>
<p>再强调一下,<code>*</code> <code>&amp;</code> 两个符号存在多重含义。</p>
<p><code>*</code>:</p>
<ul>
<li>在声明变量时,在变量前,<strong>声明它是个指针</strong></li>
<li>在使用变量时,在变量前,是<strong>通过地址找对象</strong>(解引用符)</li>
</ul>
<p><code>&amp;</code>:</p>
<ul>
<li>在声明变量时,在变量前,<strong>声明它是个引用</strong></li>
<li>在使用变量时,在变量前,是<strong>根据对象找地址</strong></li>
</ul>
<p>也就是说:</p>
<p><strong>声明前面是类型,其它时候在寻找。指针配上找对象,引用配上找地址。</strong></p>
<h3 id="空指针">空指针</h3>
<pre><code class="language-cpp">int *p = nullptr;
int *p1 = 0;
</code></pre>
<p>我的天哪,这两个指针并没有指向某个对象的地址!会不会报错啊!</p>
<p>其实并不会,它们叫做<strong>空指针</strong>。顾名思义,就是空的指针。空指针什么都不指向。就是个指针而已,空的,用不了。通常你没理由这么干,除非你真的暂时不知道该指向什么,以后再指。<strong>这样你用的时候就可以检查指针是否有指向东西(是否为空),而不是未初始化指针的未知行为</strong>。</p>
<pre><code class="language-cpp">if(p){
...
}
if(p1){
...
}
</code></pre>
<p>如果指针是空的,那么它在 if 里相当于 false。所以可以像上面那样检查指针是否为空。</p>
<p><strong>Warning! 未初始化和空指针不是一个东西。未初始化的指针的行为是未知的,不能这样检验。所以确保初始化。</strong></p>
<h3 id="指针的嵌套">指针的嵌套</h3>
<p>前面提到了指针是对象,也就是说指针也有地址,也就是自己的位置。那么我们就可以套娃了,cpp 允许你嵌套,比如指向指针的指针。</p>
<pre><code class="language-cpp">int a = 10;
int *p1 = &amp;a;
int **p2 = &amp;p1;
cout&lt;&lt;*p2&lt;&lt;endl;
cout&lt;&lt;**p2&lt;&lt;endl;
cout&lt;&lt;*p1&lt;&lt;endl;
</code></pre>
<p>先想想取地址符和解引用符的作用,想想上面代码的输出是什么。</p>
<p>示例输出:</p>
<pre><code class="language-text">0x7ffe065143d4
10
10
</code></pre>
<p>嗯,你的输出第一行肯定和我不一样,且每次运行的输出肯定不一样。</p>
<p>如果你学过点底层知识,一定能看出来第一行是个十六进制数。没错它就是个地址。</p>
<p>为什么会有这样的结果呢?</p>
<p>通过图片解析下你就明白了(第一行变量名,第二行变量的值,第三行变量的地址。注意 p2 p1 地址未知,是假设的)</p>
<p><img src="https://img2024.cnblogs.com/blog/2014033/202510/2014033-20251017212922674-452358276.png"></p>
<p>可以看到,<code>*p2</code> 实际上指的是 <code>p1</code>,而它的值则是 <code>a</code> 的地址。而 <code>**p2</code> 才指的是 <code>a</code> 本身。也就是说,解引用一次,就找一次地址对应的对象。要想获得 <code>a</code>,则必须解引用两次。</p>
<p>再再再恭喜你一下,你已经完全明白了指针的简单使用。</p>
<h2 id="指针和引用">指针和引用</h2>
<p>通过上面的讲解,我们不难得出结论:</p>
<ul>
<li>指针是对象</li>
<li>引用不是对象</li>
<li>指针、引用可以指向的是对象</li>
<li>(推论)指针可以指向指针</li>
</ul>
<p>所以显然可以推出:</p>
<ul>
<li>引用可以指向指针</li>
<li>指针不能指向引用</li>
</ul>
<p><strong>引用只是给对象贴了标签(起别名)而已。而指针则是创建了另一个对象来存储对象的位置。在这个过程中,最重要的是分清 <code>&amp;</code> 和 <code>*</code> 到底是在声明类型,还是作为取地址和解引用运算符。</strong></p>
<p>嗯,够清晰,够明白。</p>
<p>下一篇,我们将进一步探索 const 限定,了解什么是指针常量、指向常量的指针。依旧是奶奶级,拆碎了给你看。</p>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文作者:SamHou0。原文链接:https://www.cnblogs.com/samhou/p/19148878/cpp-pointer-guide</p>
<p>本文的所有发布平台:About</p>
<p>本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 SamHou's blog!</p><br><br>
来源:https://www.cnblogs.com/samhou/p/19148878/cpp-pointer-guide
頁: [1]
查看完整版本: 奶奶都能看懂的 C++ —— 手把手指针