春秋墨迹 發表於 2025-12-12 09:29:43

C++引用、内联函数与nullptr超全解析(新手避坑指南)

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>前言:</li><li>一、 引用</li><ul class="second_class_ul"><li>1. 引用基础</li><li>2. 引用返回注意事项:</li><li>3.const引用规则:</li><li>4. 指针和引用的关系与区别</li></ul><li>二、inline 和 宏函数</li><ul class="second_class_ul"><li>1. 宏函数</li><li>2. inline(内联函数)</li></ul><li>三、 nullptr</li><ul class="second_class_ul"></ul><li>总结&nbsp;</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>前言:</h2>
<p>继命名空间、缺省参数等基础内容之后,本篇主要讲述三个核心特性:引用、内联函数与 nullptr。它们是简化代码结构、优化程序性能的重要手段,也是不能轻易理解的知识点,尤其是引用。如何正确使用引用避免权限问题?内联函数的适用场景是什么?为何推荐用 nullptr 替代 NULL?带着这些疑问,本文彻彻底底帮你弄清楚这些概念。</p>
<p class="maodian"></p><h2>一、 引用</h2>
<p class="maodian"></p><h3>1. 引用基础</h3>
<p>概念:给已有的变量&ldquo;新名字&rdquo;(别名)</p>
<p>使用:类型&amp;引⽤别名=引⽤对象;</p>
<p>案例:在需要传指针的地方,可以用引用代替,不需要调用该指针,让形参就叫别名,改变形参就是改变实参</p>
<p>特性:</p>
<ul><li>引用在定义的时候必须初始化</li><li>一个变量可以有多个引用</li><li>引用一旦引用一个实体,再不能引用其他实体</li></ul>
<div class="jb51code"><pre class="brush:cpp;">int a = 10;
int&amp; ra; // 编译报错:“ra”: 必须初始化引⽤

int&amp; b = a;
int c = 20;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值 </pre></div>
<p>主要应用:</p>
<ul><li>函数参数传递</li><li>函数返回值</li></ul>
<blockquote><p>引用返回值能不能随便用?</p></blockquote>
<p class="maodian"></p><h3>2. 引用返回注意事项:</h3>
<div class="jb51code"><pre class="brush:cpp;">int&amp; func()
{
int a = 0;
return a;//错的:a是局部的,返回时候已经销毁,类似野指针
}

int&amp; func1(int&amp; ra)
{
ra = 3
return ra;//对的:ra是外部传入的引用,出函数依旧存在
}
</pre></div>
<p class="maodian"></p><h3>3.const引用规则:</h3>
<blockquote><p>权限可缩小不可放大,仅限制引用本身的访问权限</p></blockquote>
<div class="jb51code"><pre class="brush:cpp;">int main()
{
const int a = 0;
int&amp; ra = a;//错的
const int&amp; ra = a;//对的
//权限放大

//注意分辨:
const int a = 0;
int ra = a;//这是可以的,不是权限放大

int b = 0;
const int&amp; rb = b;//对的
//权限缩小
//这个地方没有缩小b的权限,b依旧能该改变
b++;//对
rb++;//错
//类比:公共场合,添加一个身份,做不了一些事情(权限缩小)

int&amp; rc = 30;//错
const int&amp; rc = 30;//对
//const引用可以给常量取别名,单纯的引用不可以

//编译器需要⼀个空间暂存表达式的求值结果(中间值)时临时创建的⼀个未命名的对象
int rd = (a+b);//可以对临时对象赋值
int&amp; rd = (a+b);//不可以对临时对象单纯引用(没法修改)
const int&amp; rd = (a+b);//可以用const引用(不修改)

double d = 12.34;
int i = d;//是隐式类型转换,这个过程有临时对象
int&amp; i = d;//不可以
const int&amp; i = d;//可以

//下面介绍一个很好用的东西
//函数模板,T可以是任意类型
template &lt;class T&gt;
void func(T val)
{
//注意看这里传入的T是任意类型
//所以程序员对它进行一个引用(&amp;T val)(防止传入的值太大拷贝成本高)
//这里传常量/临时对象/带有类型转换可以吗?不行
//最终:const &amp;T val,这样子就什么都能传
// 使用const引用可接收:
      // 1. 普通变量
      // 2. 常量
      // 3. 临时对象
      // 4. 类型转换结果
      // 类似void*的通用性
}
return 0;
}
</pre></div>
<p class="maodian"></p><h3>4. 指针和引用的关系与区别</h3>
<ol><li>引用不额外开空间,指针是开一个空间来存储地址</li><li>引用使用必须初始化,指针不是必须要求的</li><li>引用只能初始化一次,不能更改,指针可以更改指向对象</li><li>引用可以直接访问对象(别名),指针需要解引用。</li><li>sizeof()引用返回的是引用类型的大小,而指针则根据操作系统不一样大小不一样</li><li>指针有野指针和空指针的问题,引用很少有</li></ol>
<div class="jb51code"><pre class="brush:cpp;">int* ptr = nullptr;
int&amp; rptr = *ptr;
rptr++;
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121209221846.png" /></p>
<p class="maodian"></p><h2>二、inline 和 宏函数</h2>
<p class="maodian"></p><h3>1. 宏函数</h3>
<ol><li>在C语言里有通过<code>#define</code>定义的宏函数,为了防止,宏函数的使用往往小心翼翼,要加很多层括号</li><li>本质:<strong>预编译阶段的文本替换</strong>,不是真正的函数调用。</li><li>目的:在避免函数调用栈帧开销的同时,实现代码复用(比普通函数更轻量,比<strong>内联函数</strong>更「强制」替换)。</li><li>展示:</li></ol>
<div class="jb51code"><pre class="brush:cpp;">//正确
#define ADD(a,b) ((a)+(b))
int main()
{
        int add = ADD(3,2);
        return 0;
}

//错误加分号
#define ADD(a,b) ((a)+(b));
int main()
{
        if (ADD(3, 2) &gt; 0)
        {
                //执行某操作
        }
        return 0;
}

#define ADD(a,b) (a+b)
//错误不加内层括号
int main()
{
        int add = ADD(1 ^ 1, 2 ^ 2);
        //替换以后 =》1^(1+2)^2
        return;
}

#define ADD(a,b) (a)+(b);
//错误外层无括号
int main()
{
        int add = ADD(3,2) * ADD(3,2);
        //替换以后 =》3+(2*3)+2
        return 0;
}
</pre></div>
<p>以上是宏函数的使用</p>
<p class="maodian"></p><h3>2. inline(内联函数)</h3>
<p>上面宏函数都二条提到了内联函数,inline是一个关键字,他所修饰的函数称之为内联函数,上文提到宏函数是为减少函数栈帧的开销,inline也有这个作用</p>
<ul><li>内联函数有什么用?<br />内联函数是一个类似&rdquo;宏函数&ldquo;的 <strong>函数</strong> ,不同于宏函数,内联函数是在函数内部计算以后返回值,有的朋友要说:函数不都这样吗?其实不然,内联函数的本质是把函数的独立栈帧省略掉,直接使用调用者的栈帧。<br />这里插入栈帧的概念:</li></ul>
<blockquote><p>栈帧(也叫「活动记录」)是程序运行时,栈内存中为单个函数调用分配的一块独立内存区域,用于存储该函数的:</p>
<ul><li>局部变量</li><li>函数参数</li><li>返回地址(函数执行完后要回到调用者的哪一行代码)</li><li>栈基址(EBP/RBP 寄存器,用于定位栈帧内的变量)</li><li>临时数据(比如表达式计算的中间结果)</li><li>程序的调用栈(Call Stack)就是由多个嵌套的栈帧组成的,比如 <code>main</code>() 调用 <code>funcA</code>(),<code>funcA</code>() 又调用 <code>funcB</code>(),栈中会依次压入:<code>main</code> 栈帧 &rarr; <code>funcA</code> 栈帧 &rarr; <code>funcB</code> 栈帧(栈顶)。</li></ul></blockquote>
<p>比如说:在main函数中调用一个<code>inline int add()</code>的函数,如果去掉<code>inline</code>,他会创建栈帧;而加上<code>inline</code>变为内联函数以后就在<code>main</code>函数的栈帧上创建自己的栈帧,省去了跳转等操作,简单了许多</p>
<blockquote><p>当函数 A 调用函数 B 时:</p>
<ul><li><strong>函数 A 是「调用者(Caller)」</strong>,它的栈帧就是「调用者的栈帧」;</li><li><strong>函数 B 是「被调用者(Callee)」</strong>,它的栈帧就是「被调用者的栈帧」。<br />「调用者的栈帧」就是发起调用的那个函数在栈上的内存区域,在函数调用过程中,它会暂时被「被调用者的栈帧」覆盖(栈顶偏移),但调用结束后,CPU 会回到调用者的栈帧继续执行。</li></ul></blockquote>
<p>那内联函数好处这么多,能不能所有函数都直接前面加<code>inline</code>使其变成内联函数呢?</p>
<p>答案是 <strong>不可以</strong> ,因为内联函数还是在<strong>调用者的内部</strong>把函数展开了,有点类似与你没有使用函数,而是直接给函数内部的代码 <strong>粘贴</strong> 到了原函数处,这大大增加了可执行文件的内存占用量。</p>
<p>除此之外,内联函数还有<strong>很重要的一个缺点</strong>:声明和定义不能分离在两个文件中,会导致链接失败。<br /><strong>值得注意的一点是</strong>,在有些编译器(如vs2022)中,并非程序员添加<code>inline</code>他就变成了内联函数,这得编译器的心情(其实就是debug下,编译器会识别内联函数的代码量,如果代码量很大,就不会展开函数,而是作为一般函数调用)</p>
<p>如果想要调试的话,按照下方的操作可以在debug模式下调试:</p>
<ul><li><strong>菜单栏 - 项目 - 属性</strong>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121209221953.png" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121209221928.png" /></p></li></ul>
<p class="maodian"></p><h2>三、 nullptr</h2>
<p><code>NULL</code>实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:</p>
<div class="jb51code"><pre class="brush:cpp;">#ifndef NULL
        #ifdef __cplusplus
                #define NULL 0
        #else
                #define NULL ((void *)0)
        #endif
#endif
</pre></div>
<ul><li>也就是说,在C++里面<code>NULL</code>,被定义为了0,是一个int类型。</li><li>C++11中引⼊<code>nullptr</code>,<code>nullptr</code>是⼀个特殊的关键字,<code>nullptr</code>是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤<code>nullptr</code>定义空指针可以避免类型转换的问题,因为<code>nullptr</code>只能被隐式地转换为指针类型,⽽不能被转换为整数类型。</li></ul>
<blockquote><p>从此以后,C++里面的空指针使用<code>nullptr</code>,不使用<code>NULL</code>。</p></blockquote>
<p class="maodian"></p><h2>总结&nbsp;</h2>
<p>到此这篇关于C++引用、内联函数与nullptr的文章就介绍到这了,更多相关C++引用、内联函数与nullptr内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>C++入门(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for)</li><li>C++中引用、内联函数、auto关键字和范围for循环详解</li><li>C++类与对象深入之引用与内联函数与auto关键字及for循环详解</li><li>C++命名空间 缺省参数 const总结 引用总结 内联函数 auto关键字详解</li><li>C++示例分析内联函数与引用变量及函数重载的使用</li><li>C++ 引用与内联函数详情</li><li>一步步从底层入手搞定C++引用与内联函数</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: C++引用、内联函数与nullptr超全解析(新手避坑指南)