我的C++奇迹之旅:值和引用的本质效率与性能比较

这篇具有很好参考价值的文章主要介绍了我的C++奇迹之旅:值和引用的本质效率与性能比较。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用


📝引用

🌠引用概念

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

定义:类型&引用变量名(对象名) = 引用实体;
例如以下代码,在变量名前加一个&,意思是一个引用类型,ba的别名,也就是a有了一个外号,但还是a本身:

int a = 70;
int& b = a; //引用:b是a的别名

我们接下来看看引用后的地址是否会发生改变:
例如以下例子:

int main()
{
	int a = 70;
	int& b = a; //引用:b是a的别名
	int& c = a; //引用:c是a的别名
	c = 80;

	cout << a << endl;
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;

	return 0;
}

代码运行图:在这个代码中,定义了一个变量a70int& b = a; 这里ba的引用,给a取了一个外号bint& c = a; 这里ca的引用,又给a取了一个外号是c,因此我们对c还是对b进行修改,a都会发生改变,这是因为编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用

注意:引用类型必须和引用实体是同种类型的

🌉引用特性

  1. 引用必须在定义时初始化:
    引用必须在定义时初始化,不能在之后单独为它赋值
int a = 10; 
int& ra = a; // 正确,ra初始化为a
int& ra; // 错误,引用必须在定义时初始化
  1. 一个变量可以有多个引用
int a = 10;

int& ref1 = a; 
int& ref2 = a;

ref1++; // a的值变为11
cout << a << endl; // 输出11

ref2--; // a的值变为10
cout << a << endl; // 输出10
  1. 引用一旦引用一个实体,再不能引用其他实体
    引用本质上就是给原变量添加一个别名,它的内存地址就是原变量的地址。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:一级指针可以改变指向,如p可以从指向a改为指向其他变量,二级指针可以改变一级指针指向的地址,如pp可以改变p指向的地址

而引用更像一个const指针:定义后不能改变指向的对象,就像const指针定义后不能改变指向
但可以通过这个“const指针”来修改原对象,就像通过const指针可以修改原对象

int a = 10;
int b = 20;

int& ref = a; 
ref = b; // 错误!引用ref已经引用a,不能再引用b

cout << ref << endl; // 输出10,ref依然引用a

如图:ref引用了a,这里的值发生改变是因为b赋值给了ref
我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用

🌠使用场景

🌉做参数(传值与传地址)

当引用用来做参数时将会对代码进行大大的优化,并且具有可读性,如:当我们看了很多遍的交换了两个数的函数:

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

int main()
{
	int ra = 88;
	int rb = 99;
	Swap(&ra, &rb);
	return 0;
}

形参是实参的一份临时拷贝,所以如果想交换需要,传地址,不能传值。

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int ra = 88;
	int rb = 99;
	Swap(ra, rb);
	return 0;
}

ab分别是rarb的别名,当你调换ab的纸时,其实是修改了rarb的地址的值,这样的好处就是,当你看代码时,引用ab给人一种感觉,就是操作rarb本身。这隐藏了底层是通过地址操作原变量rarb的实现细节。从使用者的角度看,代码读起来就像直接交换rarb,而不是通过复杂的地址操作实现

这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,有关链表操作,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,引用也不是完全替代指针的
我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用
回归正题,这里还有一个小注意点:作用域的不同,因此,在Swap函数里,取别的名字都可以,任由发挥,结果都相同。

void Swap(int& x, int& x)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int ra = 88;
	int rb = 99;
	Swap(ra, rb);
	return 0;
}

🌉传值、传引用效率比较

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

#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;
}

int main()
{
	TestRefAndValue();
	return 0;
}

按值传递(TestFunc1):
TestFunc1(a)时,会将a进行拷贝,生成一个临时对象**a_copy**。**a_copy**作为参数传递给TestFunc1TestFunc1内部操作的实际上是a_copy,对a_copy的修改不会影响实参aTestFunc1返回时,临时对象a_copy会被销毁。TestFunc1以值方式传递结构体A作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含10000int成员的大结构体,拷贝开销很大。

按引用传递(TestFunc2):
调用TestFunc2(a)时,不会进行值拷贝,直接传递a的引用。TestFunc2内部操作的仍然是实参a本身。TestFunc2返回时,不需要销毁任何对象。TestFunc2以引用方式传递A。这种方式下,函数内直接操作的就是实参a本身,不会有任何拷贝开销。

总结:
TestFunc1值传递,效率低是因为值拷贝开销大
TestFunc2引用传递,效率高是因为避免了值拷贝,直接操作的就是实参a本身

我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用

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

🌠引用做返回值

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

我们先看看下面代码会输出什么结果?

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

在Vs编译运行图:结果是7,真的是正确吗?
我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用

问题分析:
如果函数返回时,返回的对象已经超出了函数作用域(即已经被销毁),那么不能返回该对象的引用,必须返回值。

在第一个示例中:

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}

这里函数返回了局部变量c的引用,但c在函数返回后就已经被销毁了,所以这是一个未定义行为,输出结果是不确定的。

而在第二个示例中:

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

这里同样是返回了局部变量c的引用,但是在main函数中又调用了一次Add函数,这时第一次调用返回的引用ret已经指向了一个不存在的对象,所以输出结果也是未定义的。

函数返回引用时必须确保返回的对象在调用者作用域内仍然存在,否则就会产生未定义行为。这是C++中函数返回引用需要特别注意的地方。
我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用

答案思考:
Visual Studio上运行这段代码,输出结果是:

Add(1, 2) is :7

这个结果确实是未定义行为,但在某些情况下可能会输出7。之所以会出现这种情况,是因为Visual Studio的编译器在处理这种未定义行为时可能会做一些特殊的优化或处理,导致在某些环境下能够得到一个看似合理的结果。但这种行为是不可靠的,因为它依赖于具体的编译器实现细节。在不同的编译器或环境下,输出可能会完全不同。

正确的做法:是要么返回值,要么返回一个在调用者作用域内仍然存在的对象的引用。这样可以确保代码的行为是可预测和可移植的。

🌉引用和指针的区别

  1. 语法概念:

    • 引用是变量的别名,没有独立的存储空间,而是和其引用的实体共用同一块内存空间。
    • 指针是一个独立的变量,存储了另一个变量的内存地址。
  2. 声明语法:

    • 引用使用&符号声明,如int& ref = x;
    • 指针使用*符号声明,如int* ptr = &x;
  3. 操作方式:

    • 引用直接访问和操作其引用的实体,如ref = 10;
    • 指针需要先解引用(*)才能访问其指向的实体,如*ptr = 10;
  4. Null值:

    • 引用不能为空(Null),必须在声明时初始化为一个有效的实体。
    • 指针可以为空(Null),指向空地址0x0

让我们看看例子来说明引用和指针的区别:
假设我们有一个整型变量x,值为10。
使用引用:

int x = 10;
int& ref = x; // 声明引用ref,它是x的别名
ref = 20; // 通过ref修改x的值
cout << "x = " << x << endl; // 输出 x = 20

refx的引用,它们共享同一块内存空间。通过ref修改值,实际上是在修改x的值。 输出x的值为20,因为x的值已经被修改了。

使用指针:

int x = 10;
int* ptr = &x; // 声明指针ptr,存储x的地址
*ptr = 20; // 通过ptr解引用并修改x的值
cout << "x = " << x << endl; // 输出 x = 20

ptr是一个指向x的指针,存储了x的内存地址。通过*ptr解引用并修改值,实际上是在修改x的值。输出x的值为20,因为x的值已经被修改了。

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

int main()
{
	int a = 10;
	int& ra = a;
	ra = 20;

	int* pa = &a;
	*pa = 20;
	return 0;
}

我们来看下引用和指针的汇编代码对比得出:在汇编中引用的底层逻辑还是指针,经过编译转换成汇编,还是进行指针的操作
我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用
引用和指针的不同点:

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

🌠常引用

从上述代码中,我们可以得出以下关于常引用的结论:

  1. 常量引用:
    const int a = 10;
    //int& ra = a;   // 该语句编译时会出错,a为常量
    const int& ra = a;

对于常量对象a,我们可以使用常引用const int& ra = a;来引用它。这样做可以避免对常量进行修改,直接使用非常引用int& ra = a;会在编译时报错,因为不允许对常量进行非常引用。
2. 字面量常引用:

// int& b = 10; // 该语句编译时会出错,b为常量

const int& b = 10;

我们可以使用常引用const int& b = 10;来引用字面量常量。这样做可以避免创建临时变量, 直接使用非常引用int& b = 10;会在编译时报错,因为字面量不能被非常引用。
3. 类型不匹配的常引用:

double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;

根据类型不同的变量,如double d = 12.34;,我们可以使用常引用const int& rd = d;来引用它,直接使用非常引用int& rd = d;会在编译时报错,因为类型不匹配。


🚩总结

我的C++奇迹之旅:值和引用的本质效率与性能比较,【C++的奇迹之旅】,c++,kotlin,android,性能优化,C++引用文章来源地址https://www.toymoban.com/news/detail-859338.html

到了这里,关于我的C++奇迹之旅:值和引用的本质效率与性能比较的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++奇迹之旅:深入理解赋值运算符重载

    运算符重载是C++中的一个重要特性,他允许我们为自定义的类型定义自己的运算符行为。通过运算符重载, 我们可以使用与内置数据类型相同的语法来操作自定义类型,从而提高代码的可读性和可维护性 。 还是我们熟悉的日期函数: 然后我们定义两个日期对象d1和d2: 当你想

    2024年04月26日
    浏览(39)
  • C++奇迹之旅:探索类对象模型内存的存储猜想

    上回我们学习了类的定义,初步了解了什么是类?类的定义,以及类的三个访问限定符: public , private , protected ,本小节将讲解类的实例化,类对象模型的猜想存储,及三种简单类的计算。 在 C++ 中,类的实例化是指创建一个类的对象。当我们定义了一个类之后,就可以根据

    2024年04月12日
    浏览(39)
  • C++奇迹之旅:从0开始实现日期时间计算器

    通过前面学完了 C++ 的默认成员函数,实践出真知,本小节我们将一起来实现一个简单上手的日期时间计算器,阿森和你一起一步一步的操作实现! 完整代码在文章末尾哦 为了代码的维护性和可观型,我们在设置三个文件头文件 Date.h ,源文件 Date.cpp , Test.cpp 我们先把头文

    2024年04月28日
    浏览(36)
  • 【C++的奇迹之旅(二)】C++关键字&&命名空间使用的三种方式&&C++输入&输出&&命名空间std的使用惯例

    C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式 等。熟悉C语言之后,对C++学习有一定的帮助,本章节主要目标: 补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,比如:作用 域方面、IO方面、函数方面、

    2024年04月09日
    浏览(88)
  • 【C++】C++ 引用详解 ① ( 变量的本质 - 引入 “ 引用 “ 概念 | 引用语法简介 | 引用做函数参数 | 复杂类型引用做函数参数 )

    \\\" 引用 \\\" 语法 是 C++ 语言中 特有的 , 在 C 语言中是没有 引用 这个概念的 ; 分析 引用 之前 , 先回顾下 变量 : 在 【C 语言】变量本质 ( 变量概念 | 变量本质 - 内存空间别名 | 变量存储位置 - 代码区 | 变量三要素 ) 博客中 , 介绍了变量的本质 : 变量 的本质是 内存空间 的 \\\" 别名

    2024年02月11日
    浏览(49)
  • 开启C++之旅(下):引用、内联函数及现代特性(auto和范围for循环)

    上次介绍了:开启C++之旅(上):探索命名空间与函数特性(缺省参数和函数重载) 今天就接着进行c++入门的知识讲解 引用 不是新定义一个变量,而是给已存在 变量取了一个别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量 共用 同一块内存空间。通过引用,

    2024年01月17日
    浏览(83)
  • C++性能优化笔记-6-C++元素的效率差异-7-类型转换

    在C++语法中,有几种方式进行类型转换: 这些不同的方法有完全相同的效果。使用哪种方法是一个编程风格的问题。下边讨论下不同转换的时间损耗。 。。。 有符号与无符号整数间的转换只是让编译器以不同的方式解释整数的比特。不检查溢出,代码不需要额外时间。 一个

    2024年02月03日
    浏览(61)
  • 我的AI之旅开始了

    知道重要,但是就是不动。 今天告诉自己,必须开始学习了。 用这篇博文作为1月份AI学习之旅的起跑点吧。 从此,无惧AI,无惧编程。 AI之路就在脚下。 AI,在我理解,就是让机器变得更加智能,能够以人思考和行为的方式去实行某种操作,更大更快更强。 编程和AI的关系

    2024年01月16日
    浏览(47)
  • 我的单片机入门之旅

    单片机作为现代电子技术的重要组成部分,广泛应用于各个领域。而作为一个初学者,我对单片机一无所知。但是,通过不断的学习和实践,我逐渐掌握了单片机的基本概念和使用方法。在我的单片机入门之旅中,经历了许多困难和挑战,但也取得了很大的进步和收获。 在开

    2024年03月22日
    浏览(43)
  • 我的苹果手机的越狱之旅

    最近因为业务需要,需要一台越狱手机;就把测试机6plus拿来做越狱使用,在此之前先大致说明一下越狱的原理、应用、流程以及可能存在的问题: 越狱是指通过一些技术手段,使iOS设备可以访问到iOS系统的全部控制权,从而可以实现更多的自定义和操作。以下是苹果手机越

    2024年02月11日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包