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

简介: 我的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;
}

代码运行图:在这个代码中,定义了一个变量a为70,int& b = a; 这里b是a的引用,给a取了一个外号b,int& c = a; 这里c是a的引用,又给a取了一个外号是c,因此我们对c还是对b进行修改,a都会发生改变,这是因为编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。


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

🌉引用特性

  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

引用一旦引用一个实体,再不能引用其他实体

引用本质上就是给原变量添加一个别名,它的内存地址就是原变量的地址。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:一级指针可以改变指向,如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

🌠使用场景

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

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

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

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


这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,有关链表操作,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,引用也不是完全替代指针的

回归正题,这里还有一个小注意点:作用域的不同,因此,在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**作为参数传递给TestFunc1。TestFunc1内部操作的实际上是a_copy,对a_copy的修改不会影响实参a。TestFunc1返回时,临时对象a_copy会被销毁。TestFunc1以值方式传递结构体A作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含10000个int成员的大结构体,拷贝开销很大。


按引用传递(TestFunc2):

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

总结:

TestFunc1值传递,效率低是因为值拷贝开销大

TestFunc2引用传递,效率高是因为避免了值拷贝,直接操作的就是实参a本身

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

我的C++奇迹之旅:值和引用的本质效率与性能比较2:https://developer.aliyun.com/article/1474578

相关文章
|
4月前
|
存储 安全 C++
C++中的引用和指针:区别与应用
引用和指针在C++中都有其独特的优势和应用场景。引用更适合简洁、安全的代码,而指针提供了更大的灵活性和动态内存管理的能力。在实际编程中,根据需求选择适当的类型,能够编写出高效、可维护的代码。理解并正确使用这两种类型,是掌握C++编程的关键一步。
55 1
|
3月前
|
存储 安全 C++
浅析C++的指针与引用
虽然指针和引用在C++中都用于间接数据访问,但它们各自拥有独特的特性和应用场景。选择使用指针还是引用,主要取决于程序的具体需求,如是否需要动态内存管理,是否希望变量可以重新指向其他对象等。理解这二者的区别,将有助于开发高效、安全的C++程序。
24 3
|
3月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
【7月更文挑战第28天】在 Android 开发中, NDK 让 Java 与 C++ 混合编程成为可能, 从而提升应用性能。**为何选 NDK?** C++ 在执行效率与内存管理上优于 Java, 特别适合高性能需求场景。**环境搭建** 需 Android Studio 和 NDK, 工具如 CMake。**JNI** 构建 Java-C++ 交互, 通过声明 `native` 方法并在 C++ 中实现。**实战** 示例: 使用 C++ 计算斐波那契数列以提高效率。**总结** 混合编程增强性能, 但增加复杂性, 使用前需谨慎评估。
106 4
|
3月前
|
Rust 安全 编译器
Rust与C++的区别及使用问题之Rust中的bound check对性能产生影响的问题如何解决
Rust与C++的区别及使用问题之Rust中的bound check对性能产生影响的问题如何解决
|
3月前
|
存储 自然语言处理 编译器
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
|
4月前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
57 5
|
4月前
|
C++
C++引用
C++引用
|
4月前
|
存储 安全 编译器
【C++入门】—— C++入门 (中)_引用
【C++入门】—— C++入门 (中)_引用
33 5
|
4月前
|
C语言 C++ 编译器
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数