《C++进阶:引用补充、内联函数与nullptr 核心用法》

😘个人主页:@Cx330❀

👀个人简介:一个正在努力奋斗逆天改命的二本觉悟生

📖个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

🌟人生格言:心向往之行必能至

前言:上篇博客中,我们掌握了引用的小部分,这篇博客会接着把引用剩余的部分讲解给大家,然后还会给大家认识内联函数与nullptr的核心用法,掌握号这些知识,我们就可以进入类和对象的学习中去了


目录

一.引用(补充)

const引用:

关键点:

举例说明:

图示如下:

指针和引用的关系:(面试考点)

二.inline内联函数

关键点:

举例说明:

三.nullptr

关键点:

举例说明:


一.引用(补充)

const引用:

关键点:
  • 可以引用⼀个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。
  • 需要注意的是,在一些场景下,比如类型转换中会产生临时对象存储中间值,也就是说我下面的rb和rp引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要使用常引用才可以
  • 所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
举例说明:

1.引用和指针的权限放大和缩小问题(放大不行,缩小可以):

#include<iostream>
using namespace std;
int main()
{const int a = 0;//权限放大(不能)//int& b = a;int c = 0;//权限缩小(能)const int& d = c;return 0;
}
//权限的放大和缩小,只存在于const指针和const引用
//我们再来看看指针#include<iostream>
using namespace std;int main()
{const int a = 0;const int* p1 = &a;//int& p2 = p1;//这个也属于权限的放大,得写成下面这样const int* p2 = p1;//但是权限缩小还是可以的int c = 0;int* p3 = &c;const int* p4 = p3;return 0;
}

2.const可以引用常量,作为函数参数时如果不是为了让形参的改变可以影响实参,是可以const修饰引用的,这样传参的时候选择更多 :

#include<iostream>
using namespace std;int main()
{int i = 0;double d = i;//这个是可以通过编译的,涉及隐式类型转换,因为int和double本质上都是关于数据类型大小的。//像整型和指针就只能用强制类型转换,如下int p = (int)&i;//但是我们再来看看引用里面的使用int j = 1;//double& rd = j;//不行const double& rd = j;//这个就可以了//为什么呢?--我们先不急再看一个例子//int& rp = (int)&j;//不行const int& rp = (int)&j;//可以//-------------------------具体原因分析(配合图片)------------------------------------//这是因为在引用里面,转换的过程中会产生一个临时对象保存中间值。//所以实际上rb,rp引用的都是中间值,在C++里这个临时对象是具有常性的(即被const修饰)//因此我们这里如果直接转换的话,就会出现权限放大的错误,我们必须使用常引用(即const修饰)return 0;
}
图示如下:

指针和引用的关系:(面试考点)

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,相得益彰。功能有重叠性,但是也有各自的特点,互相不可替代:

  • 语法概念上引用是一个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。(我们一般尽量取谈语法层,底层只在一些特殊场景下用来辅助了解)
  • 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
  • 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
  • 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。
  • 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
  • sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)

二.inline内联函数

关键点:

  • 用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,可以提高效率。
  • inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。

  • C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。
  • vs编译器 debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置⼀下两个地方(后面的举例说明中会有)。
  • inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。

举例说明:

先来看看之前C语言中宏函数里面的一些坑吧,以ADD函数为例:

错误一:

#define ADD(int x,int y) return x+y;

这个错误很离谱,我们要牢记宏是一种替换机制,这里直接写成了一个函数,很明显是错误的

错误二:

//宏是一种替换机制
//#define ADD(int x,int y) return x+y;
//错误写法二:
//#define ADD(a,b) a+b;
//宏定义不要带分号
//我们把分号去掉,但是还是有问题的
#define ADD(a,b) a+busing namespace std;int main()
{int ret1 = ADD(1, 2);//展开之后:int ret1 = 1 + 2;;,会出现两个分号,这里还不会报错,我们再来看看下面的//int ret2 = ADD(1, 2) * 3;//这里就出问题了//我们就算不带分号,上面这个ret2最后的值也是错的int ret2 = ADD(1, 2) * 3;//我们想要得到的是9,但是我们打印出来是7cout << ret2 << endl;//因为展开之后:1 + 2 * 3 = 7//这里的优先级被影响了return 0;
}

宏定义时,不要加分号,还需要加上()来保持优先级

错误三:

#define ADD(a,b) (a+b)
#include<iostream>
using namespace std;int main()
{//这样写ret2打印出来的结果是我们想要的9int ret2 = ADD(1, 2) * 3;cout << ret2 << endl;//但是这种写法还是存在一些问题的int x = 0, y = 1;ADD(x | y, x & y);//展开会变成:(x | y + x & y)//+号的优先级高于 |和& 所以这里相当于(x|(y+x)&y)return 0;
}

带上了外面的括号,ret2的问题解决了。但是在一些场景下还是有问题

正确写法:

//正确写法:
#define ADD(a,b) ( (a) + (b) )
#include<iostream>
using namespace std;int main()
{//这样写ret2打印出来的结果是我们想要的9int ret2 = ADD(1, 2) * 3;cout << ret2 << '\n';//这种写法也没问题了int x = 0, y = 1;ADD(x | y, x & y);//展开会变成:( (x | y) + (x & y) ),符合我们的目的return 0;
}

💡Tips:

宏函数这么复杂,容易写出问题,还不能调试。

那我们为什么还要用它呢,它的优势在于什么呢?
优点:高频调用小函数时,写成宏函数,可以提高效率,预处理阶段宏会替换,提高效率,不建立栈帧

我们在C++中使用inline内联函数代替宏函数该怎么写:

inline int ADD(int x, int y)
{return x + y;
}

和函数的写法差不多,但是是不一样的。它编译是直接展开的跟宏一样,不会创建栈帧空间 

因为默认debug版本下,为了方便调试,inline也不展开。我们需要完成两设置:

代码:

#include<iostream>
using namespace std;//转反汇编看,发现还是有call还是创建了栈帧,这是为什么
inline int ADD(int a, int b)
{return a + b;
}
//因为默认debug版本下,为了方便调试,inline也不展开。
//我们需要设置一下--这里大家可以自己测试看看,最号=好用低版本的vsint main()
{int ret2 = ADD(1, 2) * 3;cout << ret2 << '\n';//打印出来也是9,完全没有问题return 0;
}

设置步骤:

  1. 右键单击解决方案资源管理器中的项目,选择“属性”。
  2. 在弹出的属性对话框中,找到“C/C++”选项卡,点击“常规”。
  3. 在“调试信息格式”下拉菜单中,选择“程序数据库(/Zi)”。
  4. 接着点击“C/C++”下的“优化”选项。
  5. 在“内联函数的扩展”下拉菜单中,选择“只适用于_inline(/Ob1)”。

inline只是一个建议,展开还是创建空间由编译器说的算,递归和代码多的函数可能就不会展开:

#include<iostream>
using namespace std;inline int ADD(int a, int b)
{a *= 2;a *= 2;a *= 2;a *= 2;a *= 2;a *= 2;a *= 2;a *= 2;a *= 2;a *= 2;//5个的时候还是可以展开的,10个就不行了return a + b;
}int main()
{int ret2 = ADD(1, 2) * 3;cout << ret2 << '\n';return 0;
}

图示如下:

思考:为什么只是建议呢?

如果完全交给程序员,可能会出现代码指令恶性膨胀的问题,导致可执行程序(安装包)过大,这是特别不好的。所以编译器会自己把握这个展开还是不展开,有其自己的逻辑和判断。

inline不建议声明和定义放离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出错:(注意看注释)

拿顺序表为例,我直接给正确改法了,然后它的.cpp文件和.h文件这里是截图的

SeqList.h:(内联函数直接在.h文件中实现就可以了)

SeqList.cpp:(在.cpp文件中不需要再实现内联函数了,可以看出我这里注释掉了)

test.cpp:

#include"SeqList.h"int main()
{SL s;//我实现用的引用所以不用传地址SLInit(s); // call 地址return 0;
}

三.nullptr

NULL实际是⼀个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

关键点:

  • C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到⼀些麻烦,本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。f((void*)NULL);调用会报错。
  • C++11中引入nullptr,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(NULL);//f((void*)0);--有个图片//用上面的都会执行出来函数1,而不会是函数2f(nullptr);//但是用nullptr就很清晰了,可以很好处理这个问题int* p1 = NULL;char* p2 = NULL;//以后我们在C++里面置为空都这样写int* p3 = nullptr;char* p4 = nullptr;return 0;
}

这里可以看出用NULL时并没有达到我想要的效果,但是用nullptr可以 

完整源代码:

CPP专属仓库: 【CPP知识学习仓库】 - Gitee.com


往期回顾:

《C++起源与核心:版本演进+命名空间法》

《C++基础:输入输出、缺省参数,函数重载与引用的巧妙》

总结:这篇博客到这里就结束了,我们C++人们知识也就告一段落,接下来就会进入我们类和对象的学习中去,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    如若转载,请注明出处:http://www.pswp.cn/diannao/96824.shtml
    繁体地址,请注明出处:http://hk.pswp.cn/diannao/96824.shtml

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    通过python程序将实时监测数据写入excel软件进行保存是常用和非常实用的功能,本文教会大家怎么去搞定此功能

    目录 一、功能介绍 二、具体的程序示例 三、实际应用建议 一、功能介绍 本方案的核心功能是持续监听一个数据源&#xff08;如传感器、API接口、消息队列、其他应用程序等&#xff09;&#xff0c;将获取到的实时数据流以追加的方式写入到Excel文件中。同时&#xff0c;方案…

    在 Linux 中全局搜索 Word 文档内容的完整指南

    文章目录 为什么不能直接使用 grep 搜索 Word 文档? 解决方案:使用 Pandoc 转换后搜索 步骤 1:安装 Pandoc 步骤 2:创建搜索脚本 步骤 3:执行搜索(两者选其一) 一行命令解决方案 高级用法与优化 1. 忽略大小写搜索 2. 显示匹配内容 3. 性能优化 注意事项 结论 在日常工作中…

    基于STM32单片机智能农业大棚控制系统-插件款 DIY 设计开源(实物+程序+原理图+其他资料)

    目录 一、项目成品展示 二、功能介绍 三、硬件组成 四、PCB展示 五、程序设计 六、资料分享 资料获取 查看主页介绍&#xff1a;兆龙电子单片机设计 一、项目成品展示 项目成品图片展示&#xff1a; 哔哩哔哩视频链接&#xff1a; STM32单片机智能农业大棚控制系统-插件…

    如何实现二维CAD与3D建模工程图关联一体化出图 | 中望3D 2026新亮点

    本文为CAD芯智库整理&#xff0c;未经允许请勿复制、转载&#xff01;原文转自&#xff1a;www.xwzsoft.com/h-nd-609.htmlwww.xwzsoft.com/h-nd-609.html许多企业在同时使用二三维CAD软件时&#xff0c;往往因为2D和3D是不同软件商开发&#xff0c;很容易遇到问题&#xff1a;…

    深入理解 Roo Code 的自动批准功能

    在软件开发过程中&#xff0c;效率与安全往往是两个需要不断平衡的主题。 Roo Code 中一项能够显著提升效率但也需要谨慎使用的功能——自动批准&#xff08;Auto-Approval&#xff09;。如果你经常与 AI 助手协作编码&#xff0c;这个功能可能会改变你的工作流&#xff0c;但错…

    《一次高并发场景下疑难Bug的深度排查与复盘》

    常规Bug如同路上的小石子,弯腰便可清理;但有些隐藏在架构深处、仅在特定场景下爆发的疑难Bug,却像深渊中的暗礁,不仅会让程序骤然停摆,更可能消耗团队数周甚至数月的精力。我曾亲历过这样一场“战役”—一个仅在高并发峰值时段出现、无规律触发系统崩溃的Bug,从最初的毫无…

    互联网大厂Java面试实录:Spring Boot与微服务架构解析

    第一轮&#xff1a;基础技术栈 面试官: 小C&#xff0c;你能否简要介绍一下Java SE 8中的Lambda表达式&#xff1f; 小C: Lambda表达式就是Java中的匿名函数&#xff0c;可以简化代码&#xff0c;让代码更优雅。我记得它可以用来替代匿名类&#xff0c;特别是在集合操作中很有用…

    渗透测试报告编写平台 | 简化和自动化渗透测试报告的生成过程。

    工具介绍 这是一个基于 FastAPI 和 Vue.js 的 Web 应用程序&#xff0c;旨在简化和自动化安全测试报告的生成过程。 <AI编写 能用就行> 主要功能 模板管理: 上传和管理 .docx 格式的报告模板。报告生命周期管理: 创建、编辑、查看和删除安全测试报告。漏洞知识库: 管理和…

    Vulkan 学习路线图

    按阶段拆解&#xff0c;告诉你每个阶段要写哪些 Demo&#xff0c;逐步从三角形走到完整渲染器。&#x1f539; 第一阶段&#xff1a;入门&#xff08;Hello Vulkan&#xff09;目标&#xff1a;跑通 Vulkan 的最小化程序&#xff0c;理解基本对象。 要做的 Demo&#xff1a;创建…

    C语言指针5

    文章目录1.sizeof和strlen对比1.1sizeof1.2strlen1.3sizeof和strlen的对比2.数组和指针的笔试题2.1一维数组2.2字符数组2.3二维数组3.指针运算笔试题1.sizeof和strlen对比 1.1sizeof 在学习操作符的时候&#xff0c;我们学习了 sizeof。sizeof 用于计算变量所占内存空间的大小…

    【二叉树 - LeetCode】617. 合并二叉树

    题目&#xff1a; 617. 合并二叉树 - 力扣&#xff08;LeetCode&#xff09; 题解&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(n…

    AI + 医疗:除了影像诊断,智能技术还在改写哪些诊疗环节?

    一、引言在科技飞速发展的当下&#xff0c;人工智能&#xff08;AI&#xff09;已成为医疗领域变革的重要驱动力。提及 AI 在医疗中的应用&#xff0c;大众首先想到的往往是医学影像诊断&#xff0c;AI 的确在该领域成果斐然&#xff0c;如快速识别肺结节、精准分析影像细节&am…

    立轴式小型混凝土搅拌机的设计含14张CAD

    摘要 目前&#xff0c;混凝土搅拌机在国内外都有着飞速的发展&#xff0c;国际竞争力在不断提高。 为了满足市场需求&#xff0c;完善产品系列&#xff0c;适应小型建筑施工和实验室工作的需求&#xff0c;设 计了此混凝土搅拌机。 本课题主要研究立轴式混凝土搅拌机的工作原理…

    深度剖析Spring AI源码(七):化繁为简,Spring Boot自动配置的实现之秘

    深度剖析Spring AI源码&#xff08;七&#xff09;&#xff1a;化繁为简&#xff0c;Spring Boot自动配置的实现之秘“Any sufficiently advanced technology is indistinguishable from magic.” —— Arthur C. Clarke Spring Boot的自动配置就是这样的"魔法"。只需…

    PNP机器人介绍:全球知名具身智能/AI机器人实验室介绍之多伦多大学机器人研究所

    PNP机器人介绍&#xff1a;全球知名具身智能/AI机器人实验室介绍之多伦多大学机器人研究所1&#xff0c;多伦多大学机器人研究所介绍多伦多大学机器人研究所&#xff08;University of Toronto Robotics Institute, 简称UTRI&#xff09;是加拿大规模最大、跨学科最多样化的机器…

    计算机网络-1——第一阶段

    文章目录一、网络结构体系1.1 OSI七层模型&#xff08;理论标准&#xff09;2. TCP/IP 四层模型&#xff08;实际应用&#xff09;二、计算机网络设备三、网络的分类及IP地址介绍3.1 网络分类3.2 IP地址介绍四、常见协议4.1 TCP协议与UDP协议4.1.1 TCP协议4.1.2 UDP协议4.1.3 T…

    数据结构青铜到王者第三话---ArrayList与顺序表(2)

    续接上一话&#xff1a; 目录 一、ArrayList的使用&#xff08;续&#xff09; 1、ArrayList的扩容机制&#xff08;续&#xff09; 五、ArrayList的相关练习 1、杨辉三角 2、简单的洗牌算法 六、ArrayList的问题及思考 一、ArrayList的使用&#xff08;续&#xff09; …

    [Vid-LLM] docs | 视频理解任务

    链接&#xff1a;https://github.com/yunlong10/Awesome-LLMs-for-Video-Understanding docs&#xff1a;Vid-LLM 本项目是关于视频大语言模型(Vid-LLMs)的全面综述与精选列表。 探讨了这些智能系统如何处理和理解视频内容&#xff0c;详细介绍了它们多样的架构与训练方法、旨…

    构建高可用Agent状态管理API:Gin+GORM全流程解析

    继写给 Javaer 看的 Go Gin 教程 之后新写一篇真实的go开发教程:技术栈​&#xff1a;Go 1.21 Gin 1.9 GORM 2.0 MySQL 5.7 Docker一、技术选型&#xff1a;为什么是GinGORM&#xff1f;1.​性能与简洁性平衡​•​Gin​&#xff1a;基于httprouter的高性能框架&#xff0c…

    [Java恶补day51] 46. 全排列

    给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例 2&#xff1a; 输入&#xff1a;nums …