C++随记(七)--引用变量

时间:2022-09-01 12:17:51


作者:teeyohuang

邮箱:teeyohuang@163.com

本文系原创,供交流学习使用,转载请注明出处,谢谢



C++随记(七)--引用变量

引用变量是C++新增的一种复合类型。引用是 已定义的变量的一个别名(另一个名称)。引用变量的主要用途是用作函数的形参,如果将引用变量作参数,那么就相当于是在对原始变量进行直接操作。


1、引用变量的创建

int Bruce_Wayne;
int & Batman = Bruce_Wayne;

首先我们有一个int 类型的变量:Bruce_Wayne,然后我们定义了一个引用变量:Batman,上述声明允许将Bruce_Wayne和Batman互换,因为它们指向相同的值和内存单元!所以Batman就是Bruce_Wayne,Bruce_Wayne就是Batman。
符号 int &表示符合类型—引用,而且是一个指向int变量的引用。


注意:引用必须在声明的同时进行初始化!

int Bruce_Wayne;
int & Batman;
Batman = Bruce_Wayne;//非法,引用必须要在声明的同时进行初始化,一旦与某个变量关联起来,就必须一直效忠于它。


2、引用作为函数参数

引用经常被作为函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递函数的方法叫做----按引用传递。
按引用传递允许被调用的函数能够访问调用函数中的变量。
区别于C语言,C语言提供了按值传递,导致被调用函数使用调用程序的值的拷贝。
当然,C语言也提供了按指针传递来避开按值传递的方式。

简单例子:

void swap1( int & a, int & b)
{
int temp;
temp = a;
a =b;
b =temp;
}

void swap2( int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}

比较这两个函数,唯一不同的地方就是在函数头的位置,前者是引用变量作形参。
假设现在使用这两个函数

swap1( AAA, BBB);//设AAA,BBB是我已经定义好了的int 变量
swap2( AAA, BBB);


能实现二者的值交换的,只有第一个函数,因为第二个函数中的a、b是拷贝了AAA、BBB的值,然后实现大量a、b自己的交换,但是没有对AAA、BBB造成影响,而第一个函数使用引用作为形参,那么第一个函数中的a、b就可以当作是AAA、BBB,所以我实际是在对AAA、BBB进行操作,这就是引用的妙处。
顺便说一句,这里并没有违反引用要在声明的同时初始化的原则,因为函数调用的时候,自动就使用实参来初始化形参。

要注意,一般情况下,将引用变量作形参时,实参应该为变量,例如我这里的AAA、BBB都是int类型的变量,而不能将表达式比如x+3 这种拿来作实参,现在的大多数编译器都会指出这一错误


3、常引用

常引用也是一个非常有用的技巧,我们上面谈到,引用变量和原变量是一回事,也就是说我如果在函数中对引用变量进行操作,就能改变原来的变量,我上面交换数值的函数用的就是这一性质,但是有时候我们会希望避免函数中一些操作对我的原变量产生影响,导致无意中修改了变量数值,那么就可以考虑常引用。



常引用声明方式:const  类型标识符 & 引用名 = 目标变量名;
例如有一函数为:

void exam( const int & a )
{

}

那么就表明在此函数中, 引用变量 a 是不能改变值的,这样就能规避a的改变导致我原来作为实参的原变量的改变,整个函数中,可以利用的就只是a的确定的值。
你可能会有疑问,既然只用到了形参的值,那么按值传递不就可以了吗?比如:

void exam(int a )
{

}

这样既能够把实参的值传递进来给a,而且也不用担心a的改变会影响原实参的数值。这么考虑确实是对的,但是注意一个问题,我如果使用按值传递的方法,我是 创建了新的变量的!即int a 这一步,我会开辟一个新的内存空间来存放值,函数调用完之后又一般会释放这个值。而我的引用呢? const int & a没有创建新的变量!a仍然使用原来实参的储存空间和地址!也就是我没有创建变量这一过程。
在程序运行中,创建新变量消耗的时间是很多的,当然这里的int变量可能感受不到,但是当你的引用类型是结构体、类的时候,你就能明显感觉到了,我曾经把一个程序中的这些按值传递的都改成了常引用,发现最后程序快了几秒!这已经是很可观的了,因为我的程序总共就运行几十秒的时间(当时在千方百计减少程序的运行时间,还是我的老师点拨了我一下)。
所以常引用的优势在于: 既保护了原来的实参受到误修改,又充分提升了效率节省了空间!


4、函数返回值为引用

先来看两个函数的对比:

int test1( int a, int b, int sum)
{
sum = a + b+ sum;
return sum;
}
//////////////////////////////////////////////////
int & test2( int a, int b, int & sum)
{
sum = a+b;
return sum;
}

这两个函数有什么不同呢? 很明显test2 的函数头多了两个 &,这就是函数返回值为引用的意思。 首先看到函数被声明为 int & test2, 且参数列表中也至少有一个引用变量作形参, int & sum。
现在来分析这两个函数运行时的区别,

情况①:

int Price = test1( price_1, price_2, result);

首先,price_1, price_2把值传递给a和b,result的值传递给sum,然后经过计算,sum的结果被函数返回, 此时将结果复制到一个临时的位置 ,然后将此值再赋值给 Price。
情况②:

int Price = test2( price_1, price_2, result);

同样price_1, price_2把值传递给a和b,而sum此时是作为result的引用值,所以相当于直接把result拿进去参与计算(当然这不是重点),重点是:result由于和sum是一回事,因此sum被修改后,result也更新了,函数return sum,实际就是return result, 所以不需将值复制到一个临时的位置,而是相当于直接把result的值,复制给Price


同时你会发现当返回值为引用时,以下语句是合法的:

test2( price_1, price_2, result) = 33;

看起来是在对函数赋值,但是这个函数实际上是 result 的别名,所以其实你会发现result的值被修改为33. 当然,如果你不希望返回的引用值出现修改的情况,可以使用const:

const int & test2( int a, int b, int & sum){....}

这样再对其赋值就不合法了,因为定义为了const,所以返回的引用就不能被修改了。