C++ 引用

C++ 中的 引用 是给变量定义一个别名,也就是说它给一个已经存在的变量取另一个名字,就好比我们有身份证上的名字,还有乳名、笔名、绰号等等,但无论名字怎么变,都是同一个人。

提示:引用针对的是变量,这个要和以后学到的 typedef 针对类型区分开来。

一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

引用声明

定义引用变量的语法为:

type &varname = destVarName;

就是在数据类型和变量名之间加上一个 & 符号来声明一个引用,就像下面这样:

int a = 8;
int &b = a;

创建引用是不能直接指向数据,只能通过原始变量。例如,下面这种声明方式是错误的:

int &b = 8;

示例:引用的声明和使用

#include <iostream>
using namespace std;
int main()
{
    int a = 1992;  // 普通变量
    int &ra = a;   // 引用变量

    cout << "before a = " << a << endl;

    ra = 2021;     // 通过引用修改变量

    cout << "after  a = " << a << endl;

    return 0;
}

编译和运行以上示例,输出结果如下:

before a = 1992
after  a = 2021

常引用

不可以被修改的引用,叫做常引用。声明语法如下:

const identify &referName = dstVarName;

用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为 const 类型,达到了引用的安全性。

示例:

#include <iostream>
using namespace std;
int main()
{
    int a = 1992;  // 普通变量
    const int &ra = a;   // 引用变量

    cout << "before a = " << a << endl;

    ra = 2021;     // 通过引用修改变量

    cout << "after  a = " << a << endl;

    return 0;
}

编译时出现如下错误信息:

error: assignment of read-only reference ‘ra’

这是因为 ra 是常引用,不能修改。常引用的一个常见用法是在函数的形式参数中。例如:

#include <iostream>
using namespace std;

void printNum(const int &num)
{
    cout << "Num = " << num << endl;
}

int main()
{
    int a = 1992;  // 普通变量
    printNum(a);   // 函数调用
    return 0;
}

在这个示例中,不可以在 printNum 函数里面修改参数 num 的值。

引用返回

在 C++ 中,引用也可以用作函数返回值,使用引用做函数返回值的最大好处是,在内存中不产生被返回值的副本。

引用作为函数返回值时的规范:

  • 不能返回局部变量的引用,因为局部变量会在函数返回后被销毁,因此被返回的引用就成为了“无所指”的引用,程序会进入未知状态。
  • 不能返回函数内部 new 分配的内存的引用,虽然不存在局部变量的被动销毁问题,但对于这种情况,会面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由 new 分配)就无法释放,造成内存泄漏(memory leak)。
  • 可以返回类成员的引用,但最好是 const 类型的。主要原因是当对象的属性是与某种业务规则相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

以引用返回函数值,定义函数时需要在函数名前加 &。语法格式如下:

type &funcName(paramlist)

示例:

#include <iostream>
using namespace std;

float s;
float &Circle(float r)
{
    s = 3.14 * r * r;
    return s;
}

int main()
{
    float r = 10.0;
    Circle(r);
    cout << "S = " << s << endl;
    return 0;
}

这里定义了一个函数 Circle,该函数返回了一个引用,因此,返回值没有重新生成副本。输出结果如下:

S = 314

使用注意

  • 引用符 & 在此不是求地址运算,而是起标识作用。
  • 声明引用时,必须同时对其进行初始化。
  • 引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
  • 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址,&ra&a 相等。
  • 不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。

引用 vs 指针

引用很容易与指针混淆,从使用方式来看,它们之间主要有下面两个不同点。

1、不存在空引用。引用必须连接到一块合法的内存,也就是不能有如下的声明。

int &a;          // 禁止
int &b = NULL;   // 禁止
int *c;          // 允许
int *d = NULL;   // 允许

2、引用必须在创建时被初始化。指针可以在任何时间被初始化。

int &b;          // 禁止

更具体来看,引用和指针的区别有以下几个方面。

  • 从现象上看,指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。这句话可以理解为:指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。
  • 从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。
  • 从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。

记住:引用的本质是别名,指针的本质是地址。

Leave a Reply