首页 > 编程笔记 > C++笔记 阅读:35

引用是什么,C++中的引用(非常详细,附带实例)

通义灵码
在现实世界中,每个人通常都有好几个称呼,例如:
虽然这些称呼各不相同,但实际上指的是同一个人。

C++ 的世界中,也有类似的现象。一个变量除定义时所用到的变量名外,为了便于使用,还可能有多个名字。虽然这些名字各不相同,但实际上指的是同一个变量、同一个数据,通过这些名字,我们都可以访问这个变量。

在现实世界中的多个称呼被称为绰号,而变量在 C++ 世界中的多个名字则被更专业地称为“引用”。

引用的本质就是变量的别名,通俗地说,就是变量的绰号。对变量的引用进行的任何操作,实际上就是对变量本身的操作,就像不管是叫你的小名,还是叫你的绰号,都是在叫你这个人。

在 C++ 中,为某个变量定义引用的语法格式如下:
数据类型& 引用名 = 变量名;

例如,定义一个整型变量的引用:
  • // 首先定义一个整型变量
  • int nValue = 1;
  • // 定义一个整型引用 nRef 并将它与整型变量 nValue 关联起来
  • int& nRef = nValue;
这样,就定义了 nRef 是变量 nValue 的引用。

建立引用和变量的关联后,任何对引用 nRef 的操作都相当于对变量 nValue 的操作。例如:
  1. // 通过变量直接修改变量的值
  2. nValue = 1;
  3. cout << "通过变量直接修改后, " << endl;
  4. cout << "变量的值为" << nValue << endl;
  5. cout << "引用的值为" << nRef << endl;
  6.  
  7. // 通过引用修改变量的值
  8. nRef = 2;
  9. cout << "通过引用间接修改后, " << endl;
  10. cout << "变量的值为" << nValue << endl;
  11. cout << "引用的值为" << nRef << endl;
程序编译运行后,输出结果如下:

通过变量直接修改后,
变量的值为 1
引用的值为 1
通过引用间接修改后,
变量的值为 2
引用的值为 2

从输出结果中可以看到,无论是通过变量名 nValue 直接修改数据,还是通过引用 nRef 间接修改数据,都是对变量 nValue 中所保存数据的修改。这验证了对一个变量的引用操作,实际上就是对这个变量本身的操作。

这里需要注意的是,引用在定义时必须初始化,将其与某个变量关联起来,否则会产生编译错误。这就像给一个人取绰号,只有这个人存在时,我们才能给他取绰号。我们不可能先把绰号取好,再找到这个绰号所指向的人。

大家可能已经注意到,前面介绍的指针与引用有一些相似之处:它们就像一对孪生兄弟,都是某个变量的指代,都能以一种间接的方式访问它们所指代的变量。虽然是它们很像,但仍然存在一些细微的差别。那么,指针和引用的区别在哪里呢?

C++引用和指针的区别

1) 初始化的要求不同

引用在定义时必须初始化,而指针则没有这一强制要求。

我们可以在定义指针时进行初始化,也可以在定义完成后的任何合适时机完成初始化。正是因为指针没有初始化的强制要求,这往往会使我们可能错误地使用尚未初始化的指针,从而产生严重的错误。

例如:
  • int x = 0;
  • int* pInt; // 指针在定义时可以不进行初始化,这时它指向一个随机的地址
  • *pInt = 1; // 使用尚未初始化的指针可能会导致严重的错误
  • pInt = &x; // 在合适的时机完成指针的初始化
  • int& rInt = x; // 引用在定义时必须初始化,rInt 是变量 x 的引用

2) 与变量关联的紧密性不同

引用只是变量的别名,不可能存在空的引用,也就是说引用必须与某个合法的、事先存在的变量关联。而指针则可以为空指针(nullptr),不与任何变量建立关联。

3) 对重新关联的要求不同

引用一旦被初始化,与某个变量建立了关联,就不能再改变这种引用关系。引用与它所关联的变量之间的关系是从一而终、固定不变的。而指针则可以随时改变所指向的变量。

例如:
  • // 定义另一个整型变量
  • int y = 1;
  • // 这条语句不是改变 rInt 关联的变量将其关联到 y
  • // 而是对其进行赋值,此时引用 rInt 和变量 x 的值都是 1
  • rInt = y;
  • // 重新改变指针 pInt 所指向的变量,从 x 变为 y
  • pInt = &y;

C++引用的应用场景

取绰号的目的是什么?没错,是为了让别人称呼起来更加方便。引用是变量的绰号,它的作用也是为了让所关联的变量使用起来更加方便。在进行普通计算时,通常是直接使用变量,无须引用出场。但是,当变量作为函数参数或者返回值,尤其是一些“大腕”数据(大体积数据),需要在函数间进行频繁传递时,引用就非常有用了。

引用的作用与指针相似,它们都是数据的某种指代。在函数间传递数据时,传递数据的引用要比传递数据本身轻松得多,但效果完全一样。因此,引用的主要应用是在函数间传递参数和返回值。

同样值得注意的是,与指针一样,我们不能将函数内局部变量的引用作为返回值返回。例如:
  • // 给整型数加 1
  • // 利用整型引用作为函数参数
  • void Increase(int& nVal)
  • {
  • nVal += 1;
  • }
  •  
  • int nInt = 1;
  • Increase(nInt); // 变量 nInt 的值变为 2
这里利用了一个整型引用作为 Increase() 函数的形式参数。当用一个整型变量作为实际参数调用它时,实际上是用这个整型变量对引用参数进行初始化,让两者建立关联。这样,在函数内部对引用参数的操作就相当于操作实际参数变量本身,实现了函数数据的传入和传出。

学完 C++ 引用之后,我们就掌握了以下三种传递函数参数和返回值的方式:
传递函数参数和返回值的方式这么多,选择哪种方式最合适?不同的传递方式有什么区别?在具体应用中应如何选择?

下面的例子将比较这三种在函数间传递数据的方式,帮助我们理解如何做出最佳选择:
  1. #include <iostream>
  2. using namespace std;
  3. // 通过传值来传入参数和传出返回值
  4. int FuncByValue(int x)
  5. {
  6. x = x + 1;
  7. return x;
  8. }
  9.  
  10. // 通过传指针来传入参数和传出返回值
  11. int* FuncByPointer(int* p)
  12. {
  13. *p = *p + 1;
  14. return p;
  15. }
  16.  
  17. // 通过传引用来传入参数和传出返回值
  18. int& FuncByRef(int& r)
  19. {
  20. r = r + 1;
  21. return r;
  22. }
  23.  
  24. int main()
  25. {
  26. int n = 0;
  27. cout << "n的初始值,n = " << n << endl;
  28.  
  29. // 以传值方式调用函数,变量 n 的值不发生改变
  30. FuncByValue(n);
  31. cout << "传值,n = " << n << endl;
  32.  
  33. // 以传指针方式调用函数,实现数据的同时传入传出
  34. // 变量 n 的值发生改变
  35. FuncByPointer(&n);
  36. cout << "传指针,n = " << n << endl;
  37.  
  38. // 以传引用方式调用函数,实现数据的同时传入传出
  39. // 变量 n 的值发生改变
  40. FuncByRef(n);
  41. cout << "传引用,n = " << n << endl;
  42.  
  43. return 0;
  44. }
编译并运行程序后,可以得到如下的输出结果:

n的初始值, n = 0
传值, n = 0
传指针, n = 1
传引用, n = 2

从程序的输出结果可以看出,这三种传递参数方式的区别在于:
1) 在函数 FuncByValue() 中,形式参数 x 只是外部实际参数 n 的一份拷贝。函数内部对 x 的运算不能改变函数外部 n 的值,所以输出的 n 值仍然为 0,与初始值相同。

2) 在函数 FuncByPointer() 中,指针 p 指向外部变量 n,在函数内部改变指针 p 所指向的数据值,实际上就是改变函数外部变量 n 的值,所以输出的 n 值为 1。

3) 在函数 FuncByRef() 中,引用 r 与函数外部变量 n 相关联,它们实际上是同一个数据。在函数内部改变 r 的值,同样也会修改函数外部 n 的值,因此最终输出的 n 值为 2。

对比以上这三种传递参数的方式,可以发现:
综上所述,在传递小体积的参数时,例如某个 int 类型的数据,如果只需传入数据,则选择传值方式;如果需要同时传入和传出数据,则选择传引用方式。在传递大体积的参数时,例如大型对象,优先选择传引用方式。

三种传递参数的方式如下图所示。


图 1 优先选择传引用

相关文章