c/assets/notes_01_c-basic_06_xdx_index.md.BBHptzz_.js
2024-08-01 07:09:59 +00:00

24 lines
44 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import{_ as s,c as i,o as t,a6 as a}from"./chunks/framework.CZKtKhAm.js";const d="/c/assets/1.L8V3GBrc.png",l="/c/assets/2.CdvhiwcU.png",e="/c/assets/3.D74t3-Xt.png",y=JSON.parse('{"title":"第一章:颇具争议的指针","description":"","frontmatter":{},"headers":[],"relativePath":"notes/01_c-basic/06_xdx/index.md","filePath":"notes/01_c-basic/06_xdx/index.md","lastUpdated":1722496166000}'),n={name:"notes/01_c-basic/06_xdx/index.md"},o=a('<blockquote><ul><li><code>指针</code>是 C 语言中<code>最重要</code>的概念之一,也是<code>最难以理解</code>的概念之一。</li><li><code>指针</code>是 C 语言的<code>精髓</code>,要想掌握 C 语言就需要深入地了解指针。</li></ul></blockquote><h1 id="第一章-颇具争议的指针" tabindex="-1">第一章:颇具争议的指针 <a class="header-anchor" href="#第一章-颇具争议的指针" aria-label="Permalink to &quot;第一章:颇具争议的指针&quot;"></a></h1><h2 id="_1-1-概述" tabindex="-1">1.1 概述 <a class="header-anchor" href="#_1-1-概述" aria-label="Permalink to &quot;1.1 概述&quot;"></a></h2><ul><li>目前而言,操作系统几乎都是通过 C 语言来编写和维护的;而 C 语言提供了指针的用法,其能直接操作内存地址,是个非常<code>强大</code>和<code>灵活</code>的工具;但是,需要开发者<code>小心谨慎的使用</code>,以确保程序的稳定性和安全性。</li></ul><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p></p><p>之所以指针在 C 语言中颇具争议,是因为一方面其功能强大,直接操作内存地址;另一方面,又很危险,不正确的使用指针的方式,非常容易导致程序崩溃。</p></div><ul><li><p>如果没有能很好的使用指针,就会带来一系列的问题,如:</p><ul><li>① <strong>空指针引用Null Pointer Dereference</strong>当一个指针没有正确初始化或者被赋予了空NULL值时如果程序尝试访问该指针所指向的内存会导致运行时错误甚至导致程序崩溃。</li><li>② <strong>野指针Dangling Pointers</strong>:指针指向的内存地址曾经分配给某个变量或对象,但后来该变量或对象被释放或者移动,导致指针仍指向已经无效的内存位置。对野指针进行操作可能会导致未定义的行为或程序崩溃。</li><li>③ <strong>指针算术错误</strong>:在进行指针运算时,如果没有正确管理指针的偏移量或者超出了数组的边界,可能会导致指针指向错误的内存位置,从而影响程序的正确性和安全性。</li><li>④ <strong>内存泄漏</strong>:如果动态分配的内存通过指针分配,但在不再需要时没有正确释放,会导致内存泄漏,长时间运行的程序可能会耗尽系统资源。</li></ul></li><li><p>为了减少指针带来的风险,开发人员可以采取以下的措施:</p><ul><li>① <strong>良好的编程实践</strong>:确保指针的初始化和使用是安全的,避免空指针引用和野指针问题。</li><li>② <strong>边界检查</strong>:在进行指针运算时,始终确保不会超出数组或内存分配的边界。</li><li>③ <strong>使用指针和引用的适当性</strong>:在可能的情况下,可以考虑使用更安全的语言特性,如:引用(在 C++ 等编程语言中)或者更高级别的数据结构来代替裸指针,从而减少指针使用时的潜在风险。</li></ul></li></ul><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p></p><ul><li>① 既然指针很危险那么通过一系列的手段将指针包装或屏蔽以达到程序安全的目的这是现代化的高级编程语言解决的思路Java、Go、Rust 等)。</li><li>② 之所以指针还需要学习是因为在嵌入式等领域其机器的资源CPU、内存等非常有限而现代化的高级编程语言虽然安全但是需要的系统资源也庞大。</li><li>③ 我们知道编译型的程序不管编译过程如何复杂至少需要两步编译和运行。通常我们也将这两步称为编译期和运行期。C 语言中的指针之所以危险就在于程序要在运行的时候才会发现问题(后知后觉);而现代化的高级编程语言中的编译器在程序编译的时候就会发现问题(提前发现问题)。</li><li>④ C 语言的编译器之所以这么设计的原因,就在于当时的内存和 CPU 是非常有限PDP-7 早期小型计算机CPU18 bit 的电子管逻辑内存4kb 和昂贵72,000 $),如果加入安全限制的功能,会远远超过整个系统的资源。</li></ul></div><h2 id="_1-2-现代化高级编程语言是如何解决指针危险的" tabindex="-1">1.2 现代化高级编程语言是如何解决指针危险的? <a class="header-anchor" href="#_1-2-现代化高级编程语言是如何解决指针危险的" aria-label="Permalink to &quot;1.2 现代化高级编程语言是如何解决指针危险的?&quot;"></a></h2><ul><li><p><code>C++</code>采用了如下的策略和机制,来解决指针危险操作的:</p><ul><li>① <strong>智能指针:</strong> C++ 引入了智能指针(如<code>std::shared_ptr</code>、<code>std::unique_ptr</code>),这些指针提供了自动资源管理和所有权的语义。<code>std::unique_ptr</code>确保只有一个指针可以访问给定的资源,从而避免了传统指针的悬空引用和内存泄漏问题。<code>std::shared_ptr</code>允许多个指针共享一个资源,并在所有引用释放后自动释放。</li><li>② <strong>引用:</strong> C++ 中的引用(如:<code>&amp;</code>符号)提供了更安全的间接访问方法,与指针相比,引用不能重新绑定到不同的对象,从而减少了意外的指针错误。</li></ul></li><li><p><code>Go</code>采用了如下的策略和机制,来解决指针危险操作的:</p><ul><li>① <strong>内存管理和垃圾回收:</strong> Go 语言通过自动垃圾回收器管理内存减少了手动内存管理所带来的指针操作错误。Go 的垃圾回收器定期扫描并释放不再使用的内存,避免了内存泄漏和悬空指针问题。</li><li>② <strong>指针的安全性:</strong> Go 语言的指针是受限的,不支持指针运算,从而减少了指针操作可能带来的风险。</li></ul></li><li><p><code>Rust</code>采用了如下的策略和机制,来解决指针危险操作的:</p><ul><li>① <strong>所有权和借用:</strong> Rust 引入了所有权和借用的概念,编译器在编译时静态分析所有权转移和引用的生命周期。这种机制避免了数据竞争和空指针解引用等运行时错误,使得在编译时就能够保证内存安全。</li><li>② <strong>生命周期:</strong> Rust 的生命周期系统确保引用的有效性和安全性,防止了悬空引用和指针乱用。</li></ul></li><li><p><code>Java</code>采用了如下的策略和机制,来解决指针危险操作的:</p><ul><li>① <strong>引用类型和自动内存管理:</strong> Java 中所有的对象引用都是通过引用来访问的而不是直接的指针。Java 的自动垃圾回收器负责管理内存,从而避免了手动内存管理可能导致的指针错误,如:内存泄漏和悬空指针。</li><li>② <strong>强类型系统和异常处理:</strong> Java 的强类型系统和异常处理机制减少了指针操作带来的风险空指针解引用异常NullPointerException。编译器在编译时能够捕获许多潜在的类型错误进一步增强了程序的安全性和可靠性。</li></ul></li></ul><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p></p><p>总而言之,各种编程语言通过引入不同的策略和机制,如:智能指针、垃圾回收器、所有权和借用,以及强类型系统,有效地减少了指针操作所带来的各种安全性和可靠性问题,提升了程序的稳定性和开发效率。</p></div><h1 id="第二章-回顾知识" tabindex="-1">第二章:回顾知识 <a class="header-anchor" href="#第二章-回顾知识" aria-label="Permalink to &quot;第二章:回顾知识&quot;"></a></h1><h2 id="_2-1-变量" tabindex="-1">2.1 变量 <a class="header-anchor" href="#_2-1-变量" aria-label="Permalink to &quot;2.1 变量&quot;"></a></h2><ul><li>变量就是保存程序运行过程中临时产生的值,其语法如下:</li></ul><div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">数据类型 变量名 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> 值 ;</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p></p><p>变量名(标识符)需要符合命名规则和命名规范!!!</p><ul><li>强制规范: <ul><li>① 只能由<code>小写</code>或<code>大写英文字母</code><code>0-9</code> 或 <code>_</code> 组成。</li><li>② 不能以<code>数字</code>开头。</li><li>③ 不可以是<code>关键字</code>。</li><li>④ 标识符具有<code>长度</code>限制,不同编译器和平台会有所不同,一般限制在 63 个字符内。</li><li>⑤ 严格<code>区分大小写字母</code>Hello、hello 是不同的标识符。</li></ul></li><li>建议规范: <ul><li>① 为了提高阅读性使用有意义的单词见名知意sumnamemaxyear 等。</li><li>② 使用下划线连接多个单词组成的标识符max_classes_per_student 等。</li><li>③ 多个单词组成的标识符,除了使用下划线连接,也可以使用小驼峰命名法,除第一个单词外,后续单词的首字母大写,如: studentId、student_name 等。</li><li>④ 不要出现仅靠大小写区分不同的标识符name、Name 容易混淆。</li><li>⑤ 系统内部使用了一些下划线开头的标识符C99 标准添加的类型 <code>_Bool</code>,为防止冲突,建议开发者尽量避免使用下划线开头的标识符。</li></ul></li></ul></div><ul><li><code>变量名</code>的<code>作用</code>,如下所示: <ul><li>① 当我们<code>编写</code>代码的时候,使用<code>变量名</code>来<code>关联</code>某块内存的<code>地址</code>。</li><li>② 当 CPU <code>执行</code>的时候,会将变量名<code>替换</code>为具体的地址,再进行具体的操作。</li></ul></li></ul><h2 id="_2-2-普通变量和指针变量的区别" tabindex="-1">2.2 普通变量和指针变量的区别 <a class="header-anchor" href="#_2-2-普通变量和指针变量的区别" aria-label="Permalink to &quot;2.2 普通变量和指针变量的区别&quot;"></a></h2><ul><li>根据<code>变量</code>中<code>存储</code>的<code>值</code>的<code>不同</code>,我们可以将<code>变量</code>分为两类: <ul><li><code>普通变量</code>:变量所对应的内存中存储的是<code>普通值</code>。</li><li><code>指针变量</code>:变量所对应的内存中存储的是<code>另一个变量的地址</code>。</li></ul></li><li>如下图所示:</li></ul><p><img src="'+d+'" alt="img"></p><ul><li>普通变量和指针变量的相同点,如下所示: <ul><li>① 普通变量有内存空间,指针变量也有内存空间。</li><li>② 普通变量有内存地址,指针变量也有内存地址。</li><li>③ 普通变量所对应的内存空间中有值,指针变量所对应的内存空间中也有值。</li></ul></li><li>普通变量和指针变量的不同点: <ul><li>① <code>普通变量</code>所对应的内存空间<code>存储</code>的是<code>普通的值</code>,如:整数、小数、字符等;<code>指针变量</code>所对应的内存空间<code>存储</code>的是另外一个变量的<code>地址</code>。</li><li>② <code>普通变量有普通变量的运算方式</code>,而<code>指针变量有指针变量的运算方式</code>(后续讲解)。</li></ul></li></ul><h2 id="_2-3-运算符" tabindex="-1">2.3 运算符 <a class="header-anchor" href="#_2-3-运算符" aria-label="Permalink to &quot;2.3 运算符&quot;"></a></h2><h3 id="_2-3-1-概述" tabindex="-1">2.3.1 概述 <a class="header-anchor" href="#_2-3-1-概述" aria-label="Permalink to &quot;2.3.1 概述&quot;"></a></h3><ul><li>运算符是一种特殊的符号,用于数据的运算、赋值和比较等。</li><li><code>表达式</code>指的是一组运算数、运算符的组合,表达式<code>一定具有值</code>,一个变量或一个常量可以是表达式,变量、常量和运算符也可以组成表达式,如:</li></ul><p><img src="'+l+'" alt="img"></p><ul><li><code>操作数</code>指的是<code>参与运算</code>的<code>值</code>或者<code>对象</code>,如:</li></ul><p><img src="'+e+`" alt=""></p><ul><li>根据<code>操作数</code>的<code>个数</code>,可以将运算符分为: <ul><li>一元运算符(一目运算符)。</li><li>二元运算符(二目运算符)。</li><li>三元运算符(三目运算符)。</li></ul></li><li>根据<code>功能</code>,可以将运算符分为: <ul><li>算术运算符。</li><li>关系运算符(比较运算符)。</li><li>逻辑运算符。</li><li>赋值运算符。</li><li>逻辑运算符。</li><li>位运算符。</li><li>三元运算符。</li></ul></li></ul><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p></p><p>掌握一个运算符,需要关注以下几个方面:</p><ul><li>① 运算符的含义。</li><li>② 运算符操作数的个数。</li><li>③ 运算符所组成的表达式。</li><li>④ 运算符有无副作用,即:运算后是否会修改操作数的值。</li></ul></div><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p></p><p>普通变量支持上述的所有运算符;而指针变量并非支持上述的所有运算符,且支持运算符的含义和普通变量相差较大!!!</p></div><h3 id="_2-3-2-运算符的优先级" tabindex="-1">2.3.2 运算符的优先级 <a class="header-anchor" href="#_2-3-2-运算符的优先级" aria-label="Permalink to &quot;2.3.2 运算符的优先级&quot;"></a></h3><ul><li>C 语言中运算符的优先级,如下所示:</li></ul><table tabindex="0"><thead><tr><th><strong>优先级</strong></th><th><strong>运算符</strong></th><th><strong>名称或含义</strong></th><th><strong>结合方向</strong></th></tr></thead><tbody><tr><td><strong>1</strong></td><td><code>[]</code></td><td>数组下标</td><td>➡️(从左到右)</td></tr><tr><td></td><td><code>()</code></td><td>圆括号</td><td></td></tr><tr><td></td><td><code>.</code></td><td>成员选择(对象)</td><td></td></tr><tr><td></td><td><code>-&gt;</code></td><td>成员选择(指针)</td><td></td></tr><tr><td><strong>2</strong></td><td><code>-</code></td><td>负号运算符</td><td>⬅️(从右到左)</td></tr><tr><td></td><td><code>(类型)</code></td><td>强制类型转换</td><td></td></tr><tr><td></td><td><code>++</code></td><td>自增运算符</td><td></td></tr><tr><td></td><td><code>--</code></td><td>自减运算符</td><td></td></tr><tr><td></td><td><code>*</code></td><td>取值运算符</td><td></td></tr><tr><td></td><td><code>&amp;</code></td><td>取地址运算符</td><td></td></tr><tr><td></td><td><code>!</code></td><td>逻辑非运算符</td><td></td></tr><tr><td></td><td><code>~</code></td><td>按位取反运算符</td><td></td></tr><tr><td></td><td><code>sizeof</code></td><td>长度运算符</td><td></td></tr><tr><td><strong>3</strong></td><td><code>/</code></td><td>除</td><td>➡️(从左到右)</td></tr><tr><td></td><td><code>*</code></td><td>乘</td><td></td></tr><tr><td></td><td><code>%</code></td><td>余数(取模)</td><td></td></tr><tr><td><strong>4</strong></td><td><code>+</code></td><td>加</td><td>➡️(从左到右)</td></tr><tr><td></td><td><code>-</code></td><td>减</td><td></td></tr><tr><td><strong>5</strong></td><td><code>&lt;&lt;</code></td><td>左移</td><td>➡️(从左到右)</td></tr><tr><td></td><td><code>&gt;&gt;</code></td><td>右移</td><td></td></tr><tr><td><strong>6</strong></td><td><code>&gt;</code></td><td>大于</td><td>➡️(从左到右)</td></tr><tr><td></td><td><code>&gt;=</code></td><td>大于等于</td><td></td></tr><tr><td></td><td><code>&lt;</code></td><td>小于</td><td></td></tr><tr><td></td><td><code>&lt;=</code></td><td>小于等于</td><td></td></tr><tr><td><strong>7</strong></td><td><code>==</code></td><td>等于</td><td>➡️(从左到右)</td></tr><tr><td></td><td><code>!=</code></td><td>不等于</td><td></td></tr><tr><td><strong>8</strong></td><td><code>&amp;</code></td><td>按位与</td><td>➡️(从左到右)</td></tr><tr><td><strong>9</strong></td><td><code>^</code></td><td>按位异或</td><td>➡️(从左到右)</td></tr><tr><td><strong>10</strong></td><td><code>|</code></td><td>按位或</td><td>➡️(从左到右)</td></tr><tr><td><strong>11</strong></td><td><code>&amp;&amp;</code></td><td>逻辑与</td><td>➡️(从左到右)</td></tr><tr><td><strong>12</strong></td><td><code>||</code></td><td>逻辑或</td><td>➡️(从左到右)</td></tr><tr><td><strong>13</strong></td><td><code>?:</code></td><td>条件运算符</td><td>⬅️(从右到左)</td></tr><tr><td><strong>14</strong></td><td><code>=</code></td><td>赋值运算符</td><td>⬅️(从右到左)</td></tr><tr><td></td><td><code>/=</code></td><td>除后赋值</td><td></td></tr><tr><td></td><td><code>*=</code></td><td>乘后赋值</td><td></td></tr><tr><td></td><td><code>%=</code></td><td>取模后赋值</td><td></td></tr><tr><td></td><td><code>+=</code></td><td>加后赋值</td><td></td></tr><tr><td></td><td><code>-=</code></td><td>减后赋值</td><td></td></tr><tr><td></td><td><code>&lt;&lt;=</code></td><td>左移后赋值</td><td></td></tr><tr><td></td><td><code>&gt;&gt;=</code></td><td>右移后赋值</td><td></td></tr><tr><td></td><td><code>&amp;=</code></td><td>按位与后赋值</td><td></td></tr><tr><td></td><td><code>^=</code></td><td>按位异或后赋值</td><td></td></tr><tr><td></td><td><code>|=</code></td><td>按位或后赋值</td><td></td></tr><tr><td><strong>15</strong></td><td><code>,</code></td><td>逗号运算符</td><td>➡️(从左到右)</td></tr></tbody></table><div class="warning custom-block github-alert"><p class="custom-block-title">WARNING</p><p></p><ul><li>① 不要过多的依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量<code>使用小括号来控制</code>表达式的执行顺序。</li><li>② 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它<code>分成几步</code>来完成。</li><li>③ 运算符优先级不用刻意地去记忆,总体上:一元运算符 &gt; 算术运算符 &gt; 关系运算符 &gt; 逻辑运算符 &gt; 三元运算符 &gt; 赋值运算符。</li></ul></div><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p></p><ul><li>① 取值运算符 <code>*</code> 和取地址运算符 <code>&amp;</code> 的优先级相同,并且运算方向都是从右向左!!!</li><li>② 逗号运算符 <code>,</code> 的优先级最低,并且运算方向是从左向右!!!</li></ul></div><h1 id="第三章-指针的理解和定义-⭐" tabindex="-1">第三章:指针的理解和定义(⭐) <a class="header-anchor" href="#第三章-指针的理解和定义-⭐" aria-label="Permalink to &quot;第三章:指针的理解和定义(⭐)&quot;"></a></h1><h2 id="_4-1-概述" tabindex="-1">4.1 概述 <a class="header-anchor" href="#_4-1-概述" aria-label="Permalink to &quot;4.1 概述&quot;"></a></h2><ul><li></li></ul><h2 id="_4-2-普通变量和指针变量的区别" tabindex="-1">4.2 普通变量和指针变量的区别 <a class="header-anchor" href="#_4-2-普通变量和指针变量的区别" aria-label="Permalink to &quot;4.2 普通变量和指针变量的区别&quot;"></a></h2><p>在 CLion 中使用 GDB 调试时,可以通过反编译代码来查看指针变量和普通变量的区别。下面是具体的步骤:</p><h3 id="设置-gdb-调试器" tabindex="-1">设置 GDB 调试器 <a class="header-anchor" href="#设置-gdb-调试器" aria-label="Permalink to &quot;设置 GDB 调试器&quot;"></a></h3><ol><li>打开 CLion并加载你的项目。</li><li>确保你使用的是带有 GDB 支持的工具链(如 GCC 工具链)。</li><li>在 CLion 的设置中,确保调试器设置为 GDB。</li></ol><h3 id="编译你的代码" tabindex="-1">编译你的代码 <a class="header-anchor" href="#编译你的代码" aria-label="Permalink to &quot;编译你的代码&quot;"></a></h3><p>确保在编译你的代码时使用了调试信息生成选项(如 <code>-g</code>)。你可以在 CMakeLists.txt 文件中添加以下行:</p><div class="language-cmake vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cmake</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">set</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">CMAKE_CXX_FLAGS</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;\${CMAKE_CXX_FLAGS} -g&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h3 id="开始调试" tabindex="-1">开始调试 <a class="header-anchor" href="#开始调试" aria-label="Permalink to &quot;开始调试&quot;"></a></h3><ol><li>设置断点:在代码中你想要查看变量的地方设置一个断点。</li><li>启动调试:点击调试按钮启动调试会话。</li></ol><h3 id="查看变量" tabindex="-1">查看变量 <a class="header-anchor" href="#查看变量" aria-label="Permalink to &quot;查看变量&quot;"></a></h3><p>当调试器在断点处暂停时,你可以在调试控制台中使用 GDB 命令来查看变量。以下是一些常用的 GDB 命令:</p><ul><li><code>print variable_name</code>:打印变量的值。</li><li><code>info locals</code>:打印当前作用域中的所有局部变量。</li><li><code>whatis variable_name</code>:显示变量的类型。</li></ul><h3 id="区分指针变量和普通变量" tabindex="-1">区分指针变量和普通变量 <a class="header-anchor" href="#区分指针变量和普通变量" aria-label="Permalink to &quot;区分指针变量和普通变量&quot;"></a></h3><p>指针变量和普通变量的主要区别在于它们的类型和存储的内容。指针变量存储的是地址,而普通变量存储的是实际的值。通过 GDB 命令可以很容易地看到这种区别。</p><h4 id="示例" tabindex="-1">示例 <a class="header-anchor" href="#示例" aria-label="Permalink to &quot;示例&quot;"></a></h4><p>假设有如下代码:</p><div class="language-cpp vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">cpp</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">#include</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &lt;iostream&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> int</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">p </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &amp;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">a;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> std</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::cout </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;a: &quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;&lt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;, p: &quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;&lt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> p </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;&lt;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> std</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::endl;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>在 CLion 中设置断点并开始调试,程序将在 <code>std::cout</code> 行暂停。此时在调试控制台中输入以下命令:</p><ul><li><code>print a</code>:输出变量 <code>a</code> 的值,应该是 <code>10</code>。</li><li><code>print p</code>:输出指针变量 <code>p</code> 的值,即 <code>a</code> 的地址。</li><li><code>print *p</code>:输出指针 <code>p</code> 指向的值,即 <code>10</code>。</li></ul><p>通过这些命令,你可以看到指针变量 <code>p</code> 实际上存储的是一个地址,而普通变量 <code>a</code> 存储的是一个整数值。</p><h4 id="使用反汇编" tabindex="-1">使用反汇编 <a class="header-anchor" href="#使用反汇编" aria-label="Permalink to &quot;使用反汇编&quot;"></a></h4><p>在某些情况下,你可能需要查看反汇编代码来更深入地理解变量的存储方式。使用以下命令可以查看当前函数的反汇编代码:</p><ul><li><code>disassemble</code>:反汇编当前函数的代码。</li><li><code>x/4wx &amp;a</code>:查看变量 <code>a</code> 的内存内容。</li></ul><h3 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;"></a></h3><p>在 CLion 中使用 GDB 调试时,通过查看变量值和反汇编代码,可以清楚地区分指针变量和普通变量。指针变量存储地址,而普通变量存储实际的值,通过适当的 GDB 命令可以轻松辨别两者的区别。</p><h1 id="第五章-指针的运算-⭐" tabindex="-1">第五章:指针的运算(⭐) <a class="header-anchor" href="#第五章-指针的运算-⭐" aria-label="Permalink to &quot;第五章:指针的运算(⭐)&quot;"></a></h1><h2 id="_5-1-概述" tabindex="-1">5.1 概述 <a class="header-anchor" href="#_5-1-概述" aria-label="Permalink to &quot;5.1 概述&quot;"></a></h2><h2 id="_5-2-总结" tabindex="-1">5.2 总结 <a class="header-anchor" href="#_5-2-总结" aria-label="Permalink to &quot;5.2 总结&quot;"></a></h2><ul><li><p>在 C 语言中,<code>普通变量</code>是直接存储<code>数据</code>的<code>变量</code>。对于普通变量,支持的操作包括:</p><ul><li>① <strong>赋值操作</strong>:给变量赋值,如:<code>int a = 5</code>。</li><li>② <strong>算术运算</strong>:可以对数值类型的普通变量进行加、减、乘、除等运算,如:<code>a + b</code>、<code>a - b</code>、<code>a * b</code>、<code>a / b</code></li><li>③ <strong>关系运算</strong>:可以进行比较运算(大于、小于、等于等),如: <code>a &gt; b</code>、<code>a == b</code>。</li><li>④ <strong>逻辑运算</strong>:对布尔类型的值进行与、或、非运算,如: <code>a &amp;&amp; b</code>、<code>a || b</code>、<code>!a</code>。</li><li>⑤ <strong>位运算</strong>:对整数类型的值进行位操作(与、或、异或、取反、左移、右移等),如: <code>a &amp; b</code>、<code>a | b</code>、<code>a ^ b</code>、<code>~a</code>、<code>a &lt;&lt; 2</code>、<code>a &gt;&gt; 2</code>。</li><li>⑥ <strong>自增自减运算</strong><code>a++</code>、<code>--a</code> 等。</li></ul></li><li><p>在 C 语言中,<code>指针变量</code>存储的是<code>另一个变量</code>的<code>地址</code>。对于指针变量,支持的操作包括:</p><ul><li>① <strong>赋值操作</strong>:可以将一个地址赋值给指针,如: <code>int *p = &amp;a;</code>。</li><li>② <strong>解引用操作</strong>:通过指针访问它指向的变量,如: <code>*p = 10;</code> 修改指向变量的值。</li><li>③ <strong>指针运算</strong> <ul><li><strong>指针和整数值的加减运算</strong>:指针可以进行整数的加减运算,用于访问数组或结构体成员,如: <code>p + 1</code>。</li><li><strong>指针的自增和自减运算</strong>:指的是内存地址的向前或向后移动,如:<code>p++</code>、<code>p--</code>。</li><li><strong>指针间的相减运算</strong>:两个指向同一数组的指针相减可以得到它们之间的元素个数,如: <code>ptr1 - ptr2</code>。</li><li><strong>指针间的比较运算</strong>:可以比较两个指针的大小,比较的是各自内存地址的大小,如: <code>p1 == p2</code>、<code>p1 != p2</code>。</li></ul></li><li>④ <strong>数组访问</strong>:指针可以用于访问数组中的元素,通过 <code>*(p + i)</code> 访问第 <code>i</code> 个元素。</li><li>⑤ <strong>指向指针的指针</strong>:可以声明指向指针的指针,即多级指针,如:<code>int **pp = &amp;p;</code>。</li></ul></li></ul><div class="warning custom-block github-alert"><p class="custom-block-title">WARNING</p><p></p><p>在使用指针时,务必小心避免野指针和内存泄漏等问题。</p></div><p>在C语言中同类指针相减的结果是一个整数它表示两个指针之间相隔多少个指向的对象单位而不是它们在内存中的字节偏移量。这种对象单位是指针所指向的具体类型的大小。</p><p>举个例子来说,如果你有两个指向整数数组元素的指针 <code>p</code> 和 <code>q</code>,那么 <code>p - q</code> 的结果将是 <code>p</code> 指向的数组元素的索引与 <code>q</code> 指向的数组元素索引之间的差值。这个差值代表了在数组中相隔多少个整数元素,而不是它们在内存中的字节偏移量。</p><p>这种设计的优势在于,它使得指针运算更加直观和便于理解,特别是在处理数组和其他连续存储的数据结构时。因为指针运算结果的单位是根据指针所指向的具体类型来计算的,这样可以确保不同平台上的程序行为是一致的,不会受到底层硬件架构或者字节对齐规则的影响。</p><p>在C语言中数组名和指针有很多相似之处但数组名并不是指针变量。数组名实际是一个常量它指向数组的第一个元素的地址。为了证明这一点可以通过以下几个方面来说明</p><ol><li><p><strong>数组名表示数组首地址</strong> 数组名可以作为一个指针使用,数组名本身表示的是数组首地址。</p><div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> arr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ptr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> arr;</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 这句话是合法的ptr现在指向arr[0]</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">%p\\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, arr);</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 打印数组名,会打印数组首地址</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">%p\\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&amp;</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">arr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]);</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 打印第一个元素的地址</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div></li><li><p><strong>数组名是常量指针</strong> 数组名是一个常量指针,不能改变它指向的位置,而指针变量可以改变它指向的位置。</p><div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> arr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ptr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> arr;</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 合法ptr指向arr[0]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ptr</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">++</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 合法ptr现在指向arr[1]</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// arr++; // 非法,编译错误,因为数组名是常量,不能改变</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li><li><p><strong>sizeof运算符的结果不同</strong> 使用<code>sizeof</code>运算符对数组名和指针变量会得到不同的结果,数组名会返回整个数组的大小,而指针变量会返回指针本身的大小。</p><div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> arr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ptr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> arr;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;sizeof(arr) = </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">%lu\\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">sizeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(arr));</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 返回数组的大小5 * sizeof(int)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;sizeof(ptr) = </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">%lu\\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">sizeof</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(ptr));</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 返回指针的大小通常是4或8字节</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li><li><p><strong>地址运算符的结果不同</strong> 使用地址运算符<code>&amp;</code>对数组名和指针变量会得到不同的结果,对数组名使用<code>&amp;</code>会返回数组的地址,而对指针变量使用<code>&amp;</code>会返回指针变量本身的地址。</p><div class="language-c vp-adaptive-theme line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> arr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">];</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> *</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ptr </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> arr;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Address of array: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">%p\\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&amp;</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">arr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 返回整个数组的地址</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">printf</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Address of pointer: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">%p\\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&amp;</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">ptr</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // 返回指针变量ptr的地址</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li></ol><p>综上所述,通过这些示例和解释,可以看出数组名虽然在某些场合下可以像指针一样使用,但它并不是一个真正的指针变量,而是一个常量,表示数组的首地址。</p>`,73),p=[o];function r(h,c,k,g,u,E){return t(),i("div",null,p)}const m=s(n,[["render",r]]);export{y as __pageData,m as default};