从底层入手搞定C++引用和内联函数

news2025/1/11 4:13:56

C++引用和内联函数

在这里插入图片描述


文章目录

  • C++引用和内联函数
  • 一、引用
    • 1.1引用的概念
      • 1.1.1代码展示
      • 1.1.2图示
    • 1.2引用的特性
    • 1.3常引用
    • 1.4引用的使用场景
    • 1.5 传值、传引用效率比较
    • 1.6 引用和指针的区别
  • 二、内联函数
    • 2.1.内联函数的概念
    • 2.2内联函数的特性
  • 总结



一、引用

首先我们来看一下引用的概念:

1.1引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

类型& 引用变量名(对象名) = 引用实体;

1.1.1代码展示

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int main()
{
	int i = 0;
	int& k = i; // 这里的k就是变量i的引用,可以理解为k是i的一个别名
				//这里的k和i可以理解为同一个空间
	int j = i;//这里的j是一个全新的变量,是将变量i的值赋给了j

	cout << &i << endl;
	cout << &k << endl;
	cout << &j << endl;
	++k;
	++j;
	int& m = i;
	int& n = k;
	++n;
	return 0;
}

1.1.2图示

在这里插入图片描述
我们可以结合上图分析代码,k是i的引用,可以理解为k是i的别名,就比如宋江和宋公明,名字看起来不一样但是是同一个人,这里的k和i是同一个空间,k和i任何一个的变化都会影响另外一个变量的变化。
我们再来看一下三个变量的地址:
在这里插入图片描述
我们发现三个变量中i,k两个变量的地址完全相同,所以说明k没有实际开辟空间,只是i变量的一个别名而已。
我们在C语言中有一种特殊情况:二级指针,因为我们都直到形参的改变不会影响到实参,如果我们想在形参变化的时候让实参也变化就得地址传递,操作同一块空间,我们C++就可以利用引用来解决这一问题,我们直接让形式参数是实参的别名就好。
注意:引用类型必须和引用实体是同种类型的

1.2引用的特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
void TestRef()
{
   int a = 10;
   // int& ra;   // 该条语句编译时会出错,因为没有初始化
   int& ra = a;
   int& rra = a;
   printf("%p %p %p\n", &a, &ra, &rra);  
}

1.3常引用

void TestConstRef()
{
    const int a = 10;
    //int& ra = a;   // 该语句编译时会出错,a为常量
    const int& ra = a;
    // int& b = 10; // 该语句编译时会出错,b为常量
    const int& b = 10;
    double d = 12.34;
    //int& rd = d; // 该语句编译时会出错,类型不同
    const int& rd = d;
}

1.4引用的使用场景

  1. 做参数
void Swap(int& left, int& right)
{
   int temp = left;
   left = right;
   right = temp;
}

  做参数就是为了解决形参的改变不影响实参这个问题,我们在C语言中想要做到输出型参数,就得利用指针来实现,但是在C++中我们就可以用引用做参数,我们的形参就是实参的别名,这里一旦形参发生了改变实参也会发生相应的改变,也就是说这里是输出型参数。
2.做返回值

int& Count()
{
   static int n = 0;
   n++;
   // ...
   return n;
}

我们先来看一下普通函数的调用:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是我们再来分析一下下面的代码:
在这里插入图片描述
在这里插入图片描述
如果还给了操作系统还使用传引用返回,那么结果就是未定义的

1.5 传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
我们可以测试一下他们的效率:

  1. 值和引用的作为函数参数的性能比较:
#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
void TestRefAndValue()
{
 A a;
 // 以值作为函数参数
 size_t begin1 = clock();
 for (size_t i = 0; i < 10000; ++i)
 TestFunc1(a);
 size_t end1 = clock();
 // 以引用作为函数参数
 size_t begin2 = clock();
 for (size_t i = 0; i < 10000; ++i)
 TestFunc2(a);
 size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
 cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

在这里插入图片描述
2. 值和引用的作为返回值类型的性能比较

#include <time.h>
struct A{ int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a;}
// 引用返回
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{
 // 以值作为函数的返回值类型
 size_t begin1 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc1();
 size_t end1 = clock();
 // 以引用作为函数的返回值类型
 size_t begin2 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc2();
 size_t end2 = clock();
 // 计算两个函数运算完成之后的时间
 cout << "TestFunc1 time:" << end1 - begin1 << endl;
 cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。

1.6 引用和指针的区别

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间,

int main()
{
int a = 10;
int& ra = a;
cout<<"&a = "<<&a<<endl;
cout<<"&ra = "<<&ra<<endl;
return 0;
}

在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}

我们来看下引用和指针的汇编代码对比:
在这里插入图片描述
引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
    一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

二、内联函数

2.1.内联函数的概念

  以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
注意:C++推荐用const和enum替代宏常量用inline去替代宏函数

  在c语言中宏是在预处理阶段直接替换的,所以对于宏函数来说,是替换而不是调用,所以优点就是可以节省时间,因为不用调用函数建立栈帧。
我们来看一下什么是宏函数:

#define ADD(x, y) ((x)+(y)) //这个就是宏函数

我们来解释一下为什么它的形式是((x)+(y))

int main()
{
	ADD(1, 2) * 3; // ((1)+(2))*3;
	//上面就是解释了为什么外面有一对括号
	int a = 1, b = 2;
	ADD(a | b, a & b); // ((a | b) + (a & b));;
	//这里就解释了为什么x和y要单独括起来
	return 0;
}

我们来看一下普通函数的调用在汇编代码下的情况:
在这里插入图片描述
  如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2019的设置方式)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.2内联函数的特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
    《C++prime》第五版关于inline的建议:
    内联函数只是向编译器发送的一个请求,编译器可以忽略这个请求
    一般来说,内联机制用于优化规模较小,流程直接,频繁调用的函数,很多编译器都不支持内联递归函数,而且一个75行的函数也不太可能在调用点内联的展开。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
 cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
 f(10);
 return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl 
f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用

总结

  我们这篇博客主要涉及C++语言中的引用和内联函数,深入分析引用和内联函数中的很多细节问题,同时从底层汇编入手来分析引用和内联函数的底层原理,希望对大家有所帮助~

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

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

相关文章

每天10个前端小知识 【Day 2】

前端面试基础知识题 1. arguments 这种类数组&#xff0c;如何遍历类数组&#xff1f; for(var i 0, len arrayLike.length; i < len; i) { …… }使用ES6的 … 运算符&#xff0c;我们可以轻松转成数组。 function func(...arguments) { console.log(arguments); // […

蓝牙和射频技术的关系

蓝牙和射频技术的关系 提到蓝牙大家的比较熟悉&#xff0c;但射频技术很多都没有明白什么意思&#xff1f; 现简单介绍下他们的关系&#xff0c;让想了解射频技术的朋友更清楚。 1&#xff1a;定义&#xff1a; 射频&#xff08;RF&#xff09;是Radio Frequency的缩写&#xf…

Fluent的模型参数化(2)

前言&#xff1a;本文基于2023R1版本。在《Fluent的模型参数化&#xff08;1&#xff09;》中&#xff0c;对将Fluent模型进行参数化的方法进行了概述。本文主要基于已参数化的模型&#xff0c;进行参数分析的方法。基本概念&#xff1a;输入参数&#xff1a;工况的设置数据&am…

SpringMVC之入门案例

目录 一&#xff1a;概述 二代码实操&#xff1a; 步骤1:创建Maven项目&#xff0c;并导入对应的jar包 步骤2:创建控制器类 步骤3:创建配置类 步骤4:创建Tomcat的Servlet容器配置类 步骤5:配置Tomcat环境 步骤6:启动运行项目 步骤7:浏览器访问 知识点1&#xff1a;Co…

Cesium的设计结构与零基础入门

关于cesium我最近会写一系列的文章教程,带大家一步一步的从零开始学习cesium,看过我的文章的人都清楚我的讲课方式就是从一个小白的视角,从一个什么都不懂的视角,一点一点的循序渐进为大家讲清楚一个知识,好废话不多说我们开始! 首先在学习之前,你必须清楚cesium是个什…

DynaSLAM-7 DynaSLAM中双目运行流程(Ⅰ):加载Mask R-CNN网络部分MaskNet.cc

目录 1.执行流程 2. SegmentDynObject::SegmentDynObject 3. SegmentDynObject::GetSegmentation 1.执行流程 我们输入到命令行五个参数&#xff1a; stereo_kitti path_to_vocabulary path_to_settings path_to_sequence (path_to_masks) 分别是DynaSLAM双目例程中的可执行…

血氧仪/额温枪/电子体温计等 LED数显/数码管显示驱动控制电路(IC/芯片)-VK1S68C资料 SSO24小体积封装,FAE技术支持

产品品牌&#xff1a;永嘉微电/VINKA 产品型号&#xff1a;VK1S68C 封装形式&#xff1a;SSOP24 概述&#xff1a; VK1S68C是一种带键盘扫描接口的数码管或点阵LED驱动控制专用芯片&#xff0c;内部集成有3线串行接口、数据锁存器、LED 驱动、键盘扫描等电路。SEG脚接LED阳极…

Go 项目(一)

目录基础环境包管理编码规范命名规范注释import 规范错误处理RPC内置 RPC改协议改调用基础 基础部分参考这个系列接下来的这部分是对上面的更新和重构&#xff0c;更加深入理解框架部分 环境 基础环境&#xff0c;主要在Linux上搞&#xff1b;最主要是 docker&#xff0c;do…

Mac 可以玩游戏吗,有哪些游戏可以玩?

Mac 可以玩游戏吗&#xff0c;有哪些游戏可以玩&#xff1f; 新款的 MacBook Pro 入手有一段时间了&#xff0c;期间一直在熟悉 MacOS 系统及日常工作使用&#xff0c;一直都听说 MacBook 是工作本&#xff0c;不得不说工作使用确实很强&#xff0c;但用的久了就还是特别想折腾…

flex 布局:实现一行固定个数,超出强制换行(流式布局)

一、flex 布局基础知识 flex 布局的知识想必不用多说&#xff0c;一些常用的属性如下&#xff1a; 设置在父容器上的属性&#xff1a;display&#xff1a;flex&#xff0c; align-items, justify-content, flex-wrap。 设置在子容器上的属性&#xff0c;通过 flex: 1&#x…

最终一致分布式事务方案解析

业来主流的分布式事务的解决方案主要归位两大类&#xff1a;强一致性分布式事务和最终一致性分布式事务&#xff0c;本文不对强一致性分布式事务做过多描述&#xff0c;主要针对最终一致性方案解析。 根据笔者的工作经验来看&#xff0c;最终一致性方案适用用大部分互联网场景…

SpringBoot 2.7.8 自定义 Starter 自动配置

文章目录SpringBoot 2.7.8 自定义 Starter前言本次练习的代码仓库代码简要说明custom-springboot-starter-demo 的pom文件customer-starter 的pom文件test 的pom文件配置类配置信息SpringBoot 2.7.8 自定义 Starter 前言 前段时间&#xff0c;SpringBoot 出 3.x 版本了。听说…

如何与他人交流 (如何跟老板提涨工资) 第16章

最重要的事情 ---强有力的论证上期我们说根据场景来优化策略,是在隔靴搔痒,然而一个容易给出理由的人很容易成为演讲的高手.说服别人的理性与感性举个例子&#xff0c;我们如果说服别人买一件商品。采用打广告的形式&#xff0c;往往有两种途径。比如说我们要买一辆车。展示它的…

Power BI瀑布图

瀑布图&#xff08;Waterfall Plot&#xff09;也被称为阶梯图&#xff0c;它出现的历史并不长&#xff0c;最初为麦肯锡所创&#xff0c;因自上而下形似瀑布而得名&#xff0c;面世之后以其展示效果清晰而流畅被广为接受&#xff0c;经常在经营和财务分析中使用。 瀑布图是根…

4d view软件 .vol .4dv转 dcom文件

一、 .vol转 dcom文件 1、4d view软件打开vol文件 2、settings--dicom configuration-add&#xff0c;设置如下&#xff08;前面的alias、ae title等设的可以随便一些&#xff0c;我都设了1&#xff09;&#xff0c;然后save&exit&#xff08;第6步设置也可以&#xff09…

Vue13-计算属性computed

首先使用methods方法实现属性计算 步入正题&#xff1a; 计算属性&#xff1a;拿已有的属性计算得出新的属性 1.vue中属性和计算属性是分开的&#xff0c;属性在data中&#xff0c;计算属性在computed中 computed中计算属性以对象的形式存贮 这里是将fullName以及get的返回值…

计算机网络基础学习指南(一)

前言 计算机网络基础是研发/运维工程师都需掌握的知识&#xff0c;但往往会被忽略。 今天&#xff0c;我将献上一份详细 & 清晰的计算机网络基础学习指南&#xff0c;涵盖 TCP / UDP协议、Http协议、Socket等&#xff0c;希望你们会喜欢。 1. 计算机网络体系结构 1.1 简…

深入理解ThreadLocal看这篇就够了-应用场景、内部原理、内存泄漏以及父子线程如何共享数据

为了帮助大家在项目中更好使用ThreadLocal&#xff0c;本文向大家介绍ThreadLocal原理和常见问题&#xff0c;具体内容如下&#xff1a;ThreadLocal是什么ThreadLocal的应用场景ThreadLocal的内部原理ThreadLocal内存泄露问题父子线程如何共享数据ThreadLocal是什么java.lang.T…

CCS10新建TMS320F28335工程

CCS10新建TMS320F28335工程 1. 新建工程 点击Project → New CCS Project。选择芯片类型(TMS320F28335)、仿真器类型(XDS200V3)、新建工程名称、选择新建一个空工程。 2. 配置工程选项 右键项目工程名&#xff0c;进入配置选项Proprrties。或者AltEnter打开配置选项。在工程…

gd32f103vbt6 串口OTA升级-问题记录-2-平衡OTA弊端

走在路上的时候&#xff0c;我想起了这个OTA的弊端&#xff0c;那我想有没有办法解决呢&#xff1f;其实是有的。 那就是我还是把app程序放在flash的最开始的位置&#xff0c;而把OTA的程序放到后面&#xff08;flash的最后12k&#xff09;去。 这样也带来新的弊端&#xff1…