跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 核心特性解析:引用、内联函数与 nullptr 用法

C++ 引用、内联函数与 nullptr 是提升代码效率与安全性的三大基石。引用作为别名避免拷贝开销,内联函数通过编译期展开减少调用成本,nullptr 则解决了空指针的类型歧义问题。掌握这些特性有助于编写更健壮、高性能的 C++ 程序。

数字游民发布于 2026/3/16更新于 2026/9/1248 浏览
C++ 核心特性解析:引用、内联函数与 nullptr 用法

C++ 核心特性解析:引用、内联函数与 nullptr 用法

C++ 的演进之路,是不断在性能与安全、灵活与严谨之间寻求平衡的艺术。

本文将深入剖析三大特性:"引用"、"内联函数"、"nullptr"。理解它们,不仅是掌握语法,更是洞察 C++ 设计哲学,书写更高效、更健壮代码的关键一步。

一、引用:C++ 前期重难点

1.1 概念与定义

引用不是重新定义变量,而是给已经定义的变量起一个别名。编译器不会为引用变量开辟新的内存空间,它与原变量共用同一块内存区域。

形式: 类型& 引用别名 = 引用对象;

C++ 为了避免引入太多运算符,会复用 C 语言的一些符号。比如 <<, >>,引用也和取地址使用了同一个符号 &,注意从使用方法角度来区分。

引用的核心特性:

  1. 引用在定义时必须初始化。
  2. 一个变量可以有多个引用(别名)。
  3. 引用一旦绑定一个实体,就不能再引用其他实体。
#include <iostream>
using namespace std;

int main()
{
    int i = 10;
    // 引用:j 是 i 的别名
    int& j = i;
    // 多个引用
    int& k = i;
    // 给别名取别名
    int& a = j;
    
    // 看地址,应该都是一样的
    cout << &i << '\n';
    cout << &j << '\n';
    cout << &k << '\n';
    cout << &a << endl;
    return 0;
}

在这里插入图片描述

(观察地址都是一个变量!)

如果未初始化引用就使用,程序会崩溃或行为未定义:

#include <iostream>
using namespace std;

int main()
{
    // 未初始化引用
    int& j;
    cout << j << endl;
    return 0;
}

在这里插入图片描述

如果尝试让引用指向另一个变量,实际上是赋值操作,而非改变引用关系:

#include <iostream>
using namespace std;

int main()
{
    int a = 10;
    int& b = a;
    // 再次引用其他变量相当于赋值
    int c = 20;
    b = c;
    cout << "b=" << b << endl;
    return 0;
}

在这里插入图片描述

1.2 划重点:引用的正确使用

引用的主要实践用途是通过引用传参和引用返回来减少数据拷贝提高效率,以及在修改引用对象时同步改变被引用的原对象。(引用返回值相对复杂,先简单了解。)

引用传参跟指针传参功能类似,但引用传参语法上更方便一些。引用和指针相辅相成,并不能完全替代。

1. 引用传参——代替指针(大部分情况引用可以代替指针)
#include <iostream>
using namespace std;

void Swap(int* a, int* b)
{
    if (*a > *b)
    {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
}

void Swap(int& rx, int& ry)
{
    if (rx > ry)
    {
        int tmp = rx;
        rx = ry;
        ry = tmp;
    }
}

int main()
{
    int x = 2;
    int y = 1;
    Swap(&x, &y);
    cout << x << ' ' << y << '\n';
    Swap(x, y);
    cout << x << ' ' << y << '\n';
    return 0;
}

在这里插入图片描述

在逻辑上,rx, ry 是 x, y 的别名,本质上就是 x, y,所以交换 rx, ry 就是交换 x, y。 对于函数传参时,引用的初始化:因为只有调用函数才会定义引用,传参就相当于赋值 int& rx = x, int& ry = y。

2. 一级指针的引用

有时候我们需要修改指针本身(让它指向别处),这时候需要用到指针的引用:

#include <iostream>
using namespace std;

void swap(int** x, int** y)
{
    int* tmp = *x;
    *x = *y;
    *y = tmp;
}

void swap(int*& x, int*& y)
{
    int* tmp = x;
    x = y;
    y = tmp;
}

int main()
{
    // swap 函数交换指针
    int a = 10;
    int b = 17;
    int* pa = &a;
    int* pb = &b;
    
    swap(&pa, &pb); // 用指针
    cout << *pa << " " << *pb << '\n';
    
    swap(pa, pb); // 用引用
    cout << *pa << " " << *pb << '\n';
    return 0;
}

在这里插入图片描述

**对于链表、树等,节点定义位置,只能使用指针。**因为 C++ 的引用无法改变指针指向,但是节点一定存在改变指向的情况。

3. 引用返回值

先看传值返回:

#include <iostream>
using namespace std;

// 传值返回
int func()
{
    int ret = 0;
    // ...
    return ret;
}

int main()
{
    int x = func();
    // func() += 1; // 报错
    return 0;
}

在这里插入图片描述

看传值返回,func 函数返回的是 ret 的一个拷贝(相当于临时变量)。调用结束,函数销毁,看下面的 func() += 1; 就会报错,因为右值不能赋值。

在这里插入图片描述

对比看传引用返回:

#include <iostream>
using namespace std;

// 传引用返回
int& func()
{
    int ret = 0;
    // ...
    return ret;
}

int main()
{
    int x = func();
    cout << x << endl;
    return 0;
}

在这里插入图片描述

看传引用返回,实际上函数返回的是 ret 的别名。与传值返回不同的是,函数销毁后,将空间返回操作系统(但仍然指向这块空间),如果别人对空间进行操作,就会改变,这相当于野指针的访问!很危险!!

在这里插入图片描述

验证上面说法

一个很奇怪的结果:

#include <iostream>
using namespace std;

int& func1()
{
    int ret = 0;
    // ...
    return ret;
}

int& func2()
{
    int y = 123;
    // ...
    return y;
}

int main()
{
    int& x = func1();
    cout << x << endl;
    func2();
    cout << x << endl;
    return 0;
}

在这里插入图片描述

可以看到,我们并没有修改 x 的值,为什么再次输出 x 却是 y 的数值?

在这里插入图片描述

已经知道,函数销毁后,将空间返回给操作系统(但是别名 x 仍然指向这块空间),意味着这块空间可以分配给其他操作。那么新创建的函数就会在这块空间上,又因为故意的将两个函数结构写的类似,代表二者的栈帧一样大。 既然栈帧一样大,x 就会接收 func2 返回的别名,也就是 y 的值。

1.3 存疑的地方

1. 传引用返回相当于野指针访问,为什么不报错?

要知道,越界不一定报错!数组的越界存在越界抽查。越界抽查:在临近数组结束的位置进行检查。

  • 越界读:没事
int main()
{
    int a[10];
    // 越界读:没事
    a[10];
    return 0;
}

在这里插入图片描述

  • 越界写:有事
int main()
{
    int a[10];
    // 越界写:有事
    a[11] = 1;
    a[15] = 1;
    return 0;
}

在这里插入图片描述

1.4 const 引用

  1. 可以对 const 对象进行引用,但是必须在类型前加 const。const 引用也可以引用普通对象,因为对象的访问权限在引用过程中能够减小但是不能放大。
  2. 类似 int& rb = a * 3;(表达式计算值会存到临时对象中)或者 double d = 12.34; int& rd = d;(类型转换过程将中间值存在临时对象)。C++ 规定这类对象具有常性(只读),也就是说需要常引用(避免权限放大)。
  3. 临时对象:编译器需要一个空间暂存表达式的计算结果时创建的一个未命名的对象,C++ 规定为临时对象。
解释第 1 条
#include <iostream>
using namespace std;

int main()
{
    // const 对象
    const int a = 10;
    // 权限放大:不能
    // int& ra = a;
    return 0;
    // 权限缩小:可以
    int b = 10;
    const int& rb = b;
    // 不属于权限放大
    const int c = 1;
    int rc = c;
    // 权限放大缩小,对 const 指针&引用
    // 权限放大
    const int* p1 = &a;
    int* p2 = p1;
    // 权限缩小
    int e = 1;
    int* p3 = &e;
    const int* p4 = p3;
}

不能权限放大: 变量 a 被 const 修饰,代表只能读不能写,但是后面的引用仿佛在说'可以通过别名对变量进行读写'。但是本体都只能读,一个别名带还想翻天?!,这肯定是错的!(指针同理。但不是别名)

在这里插入图片描述

在这里插入图片描述

注意下面不属于权限放大:属于拷贝复制

#include <iostream>
using namespace std;

int main()
{
    const int c = 1;
    int rc = c;
    return 0;
}

可以权限缩小: 本体 b 可以读写,对于别名 rb 只进行读是允许的。

函数传参使用 const

以后函数传参都会使用引用,一方面是减少拷贝提高效率,另一方面则是形参会影响实参(少部分)。既然如此,传参以后建议 const 修饰,这样就可以传普通对象、const 对象、常量。

void func(const int& x)
{
}

int main()
{
    // const int& a = 10;
    int y = 0;
    func(y);
    const int z = 1;
    func(z);
    func(2);
    return 0;
}
解释第 2 条

类型转换有:隐式类型转换、显式类型转换(强制类型转换)。

#include <iostream>
using namespace std;

int main()
{
    // C 语言隐式转换
    int i = 0;
    double d = i;
    // 强制转换:整型、指针
    int p = (int)&i;
    return 0;
}

**对于引用的类型转换:**必须用常引用!

#include <iostream>
using namespace std;

int main()
{
    int i = 1;
    // double& ri = i; // 不行
    const double rd = i; // 可以
    // int& pi = (int)&i; // 不行
    const int& rp = (int)&i; // 可以
    return 0;
}

在这里插入图片描述

在这里插入图片描述

这就是因为上面说的:会产生临时对象(只读属性),需要对应常引用。

在这里插入图片描述

1.5 引用和指针的关系(面试必看)

通常从语法层面来区分,底层层面只是在特定情况下辅助了解。

  • 语法概念上,引用是变量的别名,不需要另开空间;指针存储一个变量的地址,需要额外空间。
  • 引用在定义时必须初始化;指针建议初始化,语法上不必须。
  • 引用只能引用一个实体,不能改变;指针可以不断改变指向对象。
  • 指针容易出现空指针、野指针的情况;引用很少。
  • 引用可以直接访问引用对象;指针需要解引用。
  • 在 sizeof 中,引用结果为引用类型的大小;指针始终是地址空间所占的字节数(32 位 - 4 字节,64 位 - 8 字节)。

二、inline 内联函数

  1. 用 inline 修饰的函数称为内联函数,编译时 C++ 编译器会在调用函数的位置展开函数,这样就不需要建立栈帧,提高效率。
  2. 我使用的是 VS 编译器,debug 版本默认不展开 inline 便于调试。若需要展开,请看下面如何设置。
  3. inline 对于编译器只是一个建议,编译器可以选择执行与否(不同编译器也不同)。inline 适用于频繁调用的小函数,对于递归函数、代码多的函数,编译器会忽略 inline。
  4. C 语言实现的宏函数会在预处理时展开,但实现复杂,易出错,不方便调试。C++ 设计 inline 就为了替换宏函数。
  5. inline 函数的声明、定义不能分到两个文件(放在同一头文件)。分离会导致内联无法展开,也找不到有效的函数定义,导致连接错误。

2.1 对要点的详细解释

1. 解释第 2 条:如何设置?

找到解决方案资源管理器,鼠标右键你的项目:(红框框)

在这里插入图片描述

在弹出的小界面选择属性后:看图示

2. 解释第 3 条:inline 只是建议

从汇编辅助理解:以简单的函数为例,得出编译器的选择应该有临界值,超过就不展开。

inline int Add(int a, int b)
{
    a++;
    a++;
    a++;
    a++;
    a++;
    return a + b;
}

int main()
{
    int ret = Add(1, 2) * 3;
    cout << ret << endl;
    return 0;
}
  • 5 个 a++,选择展开:

在这里插入图片描述

  • 10 个 a++ 就不展开了:看框框,call 就代表调用函数,创建栈帧

在这里插入图片描述

  • 划重点,划重点:面试问题:为什么只是'建议'??!

要完全将选择权交给程序员的话,那么就会发生代码指令恶性膨胀问题,导致可执行程序(安装包)过大!! 参照上后面的 5 个 a++,指令变多了,要是调用的多,都要展开,更多了。但是不展开,就是单次展开 + 次数 * 函数体,这就少多了。 为了避免问题,就将权力给了编译器。

3. 解释第 4 条:替代宏函数

(C++ 通过 const, enum, inline 替代宏。)

为什么 C 语言的宏函数很坑? 拿 ADD 宏函数为例:先记住宏就是替换

// ADD 宏函数:第 1 种
#include <iostream>
#define ADD(int a,int b)return a+b;

第 1 种错误:根据宏的形式,后面多 ';',在后面替换时会导致有两个分号。

#include <iostream>
using namespace std;
#define ADD(a,b) a + b
int main()
{
    int ret = ADD(1, 2) * 3;
    cout << ret << endl;
    return 0;
}

第 2 种错误:替换后看似正确,期望输出 9,但是由于优先级输出 7。

在这里插入图片描述

#include <iostream>
#define ADD(a, b)(a + b)
int main()

第 3 种错误(最接近正确):改进了第 2 种,确保输出 9。但在 a,b 都是表达式时,就又会发生优先级的问题。

#include <iostream>
#define ADD(a, b)((a)+(b))

第 4 种:考虑了参数是表达式的问题,最终正确。

从上面来看,用宏实现简单的加法函数就这么麻烦,要考虑很多,但是人有 **存在的意义:**将高频调用的小函数写成宏函数,可以提高效率,预处理阶段宏会替换,提高效率,不建立栈帧。

inline 内联函数代替宏函数
inline int Add(int a, int b)
{
    return a + b;
}

int main()
{
    int ret = Add(1, 2) * 3;
    cout << ret << endl;
    return 0;
}

正常定义函数,只需要前面加上关键字 inline。使得函数象宏函数一样,不会再创建栈帧。

三、宏:nullptr

nullptr 是一个宏,在传统的 C 头文件 stddef.h 中

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
  • C++ 中 NULL 可能被定义为字面常量 0,或者 C 中被定义为无类型指针 (void*) 的常量。不论如何定义,在使用空值的指针时,都会遇到麻烦,本想通过 f(NULL) 调用指针版本的 f(int*) 函数,但由于 NULL 被定义成 0,调用了 f(int x),因此与程序的初衷相悖。f((void*)NULL); 调用会报错。
  • C++11 中引入 nullptr,nullptr 是一个特殊的关键字,是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用 nullptr 定义空指针可以避免类型转换的问题,因为 nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。
#include <iostream>
using namespace std;

void f(int x)
{
    cout << "f(int x)" << endl;
}

void f(int* ptr)
{
    cout << "f(int* ptr)" << endl;
}

int main()
{
    f(0); // 调用 f(int x)
    f(NULL); // 想调用第 2 个函数,但是 NULL 被定义为 0/(void*)0,导致调用了第 1 个函数
    f((int*)NULL); // 调用 f(int* ptr)
    // f((void*)NULL); // void* 不能隐式转换为 int*,需要额外的类型转换
    f(nullptr); // 调用 f(int* ptr)
    return 0;
}
// 不要这样写
int* p1 = NULL;
int* p2 = 0;
// 应该这样写
int* p3 = nullptr;
f(nullptr); // 明确调用指针版本的重载函数

总结

引用解决了指针传参的繁琐与风险,提供了更安全的别名机制;内联函数在编译时权衡空间与时间,取代了宏函数的不可预测性;nullptr 则以类型安全的方式终结了空指针的歧义。 它们共同展现了一个理念:在保持 C 语言效率同时,通过类型系统和语言机制提供更多安全保障。这正是 C++ 能够在系统编程领域保持四十年前沿地位的重要原因。

目录

  1. C++ 核心特性解析:引用、内联函数与 nullptr 用法
  2. 一、引用:C++ 前期重难点
  3. 1.1 概念与定义
  4. 1.2 划重点:引用的正确使用
  5. 1. 引用传参——代替指针(大部分情况引用可以代替指针)
  6. 2. 一级指针的引用
  7. 3. 引用返回值
  8. 验证上面说法
  9. 1.3 存疑的地方
  10. 1. 传引用返回相当于野指针访问,为什么不报错?
  11. 1.4 const 引用
  12. 解释第 1 条
  13. 函数传参使用 const
  14. 解释第 2 条
  15. 1.5 引用和指针的关系(面试必看)
  16. 二、inline 内联函数
  17. 2.1 对要点的详细解释
  18. 1. 解释第 2 条:如何设置?
  19. 2. 解释第 3 条:inline 只是建议
  20. 3. 解释第 4 条:替代宏函数
  21. inline 内联函数代替宏函数
  22. 三、宏:nullptr
  23. 总结

更多推荐文章

查看全部
  • 利用Python与Virtual Audio Cable实现系统音频的实时捕获与处理
  • FPGA 实现 CAN 总线接口与数据帧解析
  • python-docx-template 模板化生成 Word 文档指南
  • Ollama 本地大模型部署与使用指南
  • Android 应用冷启动流程深度解析
  • AI 产品经理岗位需求分析与职业发展路径
  • Cookie 与 Session:Web 用户状态管理机制解析
  • Neo4j Desktop 2 本地部署与图数据库实战指南
  • Web 版 IM 聊天消息加密的三种算法实现方案
  • MCP Server 案例:Excel 表格一键生成可视化图表 HTML 报告
  • 宜搭低代码高级认证:待办列表自定义页面实战指南
  • 前端首屏加载优化落地清单与实战指南
  • OpenClaw 多智能体配置指南:持久代理与子代理
  • 前端 GraphQL 客户端:高效获取数据的最佳实践
  • AIGC 实战:优化图文生成 20 秒与 30 秒视频的成本差异
  • PGvector 在 Spring AI 中实现向量数据库存储与相似性搜索
  • 基于 FastAPI 的 Web 上位机系统设计与实现
  • openclaw 对接完飞书群机器人配置踩坑记:消息不回、Gateway 断开问题排查
  • 基于 LLaMA-Factory 微调 Qwen-0.6B 模型至高通 NPU 部署指南
  • NestJS 管理员增删改查功能实现

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online