C++入门——引用
一、引用的概念
- 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。这就好比《水浒传》中,一百零八位好汉都有自己的绰号。
- 通过
&
符号,我们可以实现对一个变量的引用。
1.使用规则
如图所示:
- 在这里,我们先定义了一个变量a,
int& b
的意思就是给a取别名为b,因为a定义的是一个整型,因此是int&
,如果a是浮点型,则取别名为double&
,如果a为整型指针,那么取别名就应该写为int*&
。 - 我们可以给同一个变量取多个别名,比如这里就是把a取别名为b,把b又取别名为c,其实,b和c都是a的别名,就像《水浒传》中一个好汉可能有好多个称呼,比如鲁智深,他在做和尚之前名为鲁达,作为提辖,人称鲁提辖;后来人们又叫他“花和尚”,一个道理。
- 我们将a以及它的别名b,c的地址打印出来,不难发现都是指向同一块空间。
2.注意事项
- 引用在定义时必须
初始化
,指针没有要求。如图所示:
- 同理,我们也不能引用空指针。
- 我们来看下面两段代码:
在两段代码中,均初始化了两个变量int a=1
,int b=10
;在第一段代码中,我们给变量a取别名为x,然后,我们让x=b
。怎么理解这个x=b
呢?我们先来运行一下,发现此时a和b都是10,说明a发生了变化。如果x=b意思是x刚才是a的别名,现在变成b的别名,那么a的值就不会发生变化;而事实恰恰相反,说明这里的=
并不是改变引用的对象,而是一个赋值,将b的值拷贝给a的别名x,既然a的别名发生了变化,自然而然a也就发生了变化,因为a和x本来就是一个变量,就好比“豹子头”喝了酒就是林冲喝了酒,同样的意思。第二段代码就有所不同了,p作为一个指针,它指向的对象是可以改变的。因此我们可以总结出引用和指针的一个不同点:引用一旦引用一个实体,再不能引用其他实体,而指针可以任意改变指向的对象。 - 引用是编译器自己处理,指针需要显式解引用,
让我们来感受一下:
这是一个经典的交换两个数的函数,我们以前都是用的第一种方法,使用指针,但是在函数中需要写解引用;有了引用(取别名)操作后,我们在传参的时候也不需要传地址,直接传值即可,因为函数形参都是int&
,是实参的别名,因此在函数里直接修改别名即可,更加方便。
二、引用作参数、返回值
1.引用作参数
上文提到的就是一个典型的例子,引用做参数,能够减少拷贝提高效率,尤其是大对象,深拷贝对象(以后会逐渐讲解),使用引用效率更高,我们举个例子:
#include <time.h>struct A
{int a[100000];
};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;
}
运行结果如图:
很明显,传引用效率更高。
2.引用作返回值
- 传值返回:
- 传引用返回:
这里返回的是n的引用 - 引用作返回值,能够减少拷贝提高效率,尤其是大对象,深拷贝对象(以后会逐渐讲解),使用引用效率更高,我们举个例子:
#include <time.h>struct A
{ int a[10000];
};A a;
// 值返回
A TestFunc1()
{return a;
}
// 引用返回
A& TestFunc2()
{ return a;
}void TestReturnByRefOrValue()
{// 以值作为函数的返回值类型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1();size_t end1 = clock();// 以引用作为函数的返回值类型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2();size_t end2 = clock();// 计算两个函数运算完成之后的时间cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;
}int main()
{TestReturnByRefOrValue();return 0;
}
很明显,引用作返回值效率更高。
- 基本所有的场合都可以用引用进行传参。
- 需要注意的是,我们应当谨慎使用引用作为函数的返回值,如果出了函数的作用域,引用的对象不在了,就不能使用引用作返回,还在(比如static int a)就可以用引用返回。
三、引用的权限问题——常引用
1.引用过程中,权限不能放大
如图所示:
这里的a加了const
,是不能被修改的,但是a的别名b没有加const
,是可以被修改的,在引用(取别名)的过程中,a的权限被放大,因此产生报错,作如下修改即可:
这样一来,a和b都是不能被修改的,它们的权限相等。
2.引用的过程中,权限可以平移或缩小
- 权限平移
上文的示例就是一个权限的平移,a和它的引用b都加上了const
,都是不能被修改的。 - 权限缩小
如图所示:
如图所示,这里的a是可以被修改的,它的别名b是不可以被修改的,这就是权限的缩小,那么既然b不可以被修改,为什么a+1后b也+1了呢?我们打个比方,宋江让李逵下山办事,城里的人们都知道“黑旋风”的存在,因此宋江告诉李逵,下山后,人人都知道你是“黑旋风”,你不能喝酒,喝酒会误事,等你回来后,我们再慢慢喝。也就是说,李逵作为“黑旋风”时,不能喝酒,在这里就相当于a的别名b不能被修改;李逵上山后,宋江又允许他喝酒了,这里就相当于a+1,那么,既然李逵上山后喝了酒,“黑旋风”不就喝酒了吗,a+1就是b+1,只不过在只能以b的形式出面时,权限被约束罢了。
本期总结+下期预告
本期内容我们讨论了引用的相关知识,下期内容将为大家带来内联函数,auto关键字,基于范围的for循环等内容!
感谢大家的关注,我们下期再见!