我们都知道一个人不仅有自己的名字,还会有一个甚至多个外号。就像张三,不仅“张三”可以表示他,“法外狂徒”这个外号也可以表示他。
目录
1.概念:
引用(&)就是相同的道理,并不是新定义了一个变量,而是给已存在的变量取了一个外号(别名)。编译器自然也就不会为引用变量开辟空间,所以它和引用的变量共用一块内存空间。
2.引用示例:
类型& 引用变量名(对象名) = 引用实体;
int main()
{
int a = 0;
int& aa = a;//引用示例
a = 10;
std::cout << a << std::endl;
std::cout << aa << std::endl;
return 0;
}
3.使用引用的注意事项
- 引用变量必须与引用实体是同类型
给一个人取外号也是要有来源的,张三之所以叫“法外狂徒”,是因为他杀人放火无所不干,与”法外狂徒“一样是无视法律的。当你管一个乐于助人的三好市民叫“法外狂徒”就显得有些许不合适了。
int main()
{
int a = 0;
char& aa = a;//类型不同,编译时会出错
a = 10;
std::cout << "a:" << a << std::endl;
std::cout << "aa:" << aa << std::endl;
return 0;
}
- 引用在定义时必须初始化
外号是需要有对应的人的,空有一个外号没有人与之对应,也是很奇怪的。
int main()
{
int a = 0;
int& aa;//没有初始化,会报错
a = 10;
std::cout << "a:" << a << std::endl;
std::cout << "aa:" << aa << std::endl;
return 0;
}
- 一个变量可以有多个引用
张三既可以有“法外狂徒”这个外号,也可以有“张狗蛋”这个外号。
int main()
{
int a = 0;
int& aa = a;
int& aaa = a;
a = 10;
std::cout << "a:" << a << std::endl;
std::cout << "aa:" << aa << std::endl;
std::cout << "aaa:" << aaa << std::endl;
return 0;
}
- 一个引用变量只能引用一个实体
我家的狗叫“狗蛋”,张三也叫“狗蛋”。那我喊“狗蛋”,叫的是我家的狗还是张三呢,这是不是对我家的狗不太公平呢?(doge
int main()
{
int a = 0;
int b = 1;
int& aa = a;
int& aa = b;//重定义,会报错
a = 10;
std::cout << "a:" << a << std::endl;
std::cout << "aa:" << aa << std::endl;
return 0;
}
- 不能对常量进行引用
int main()
{
int a = 0;
//int& aa = 10;//10为常量,不能引用
a = 10;
std::cout << "a:" << a << std::endl;
std::cout << "aa:" << aa << std::endl;
return 0;
}
- 引用变量权限不能大于实体
外号朋友之间是可行的,但不具有法律效益,所以姓名的权限是大于外号的。
int main()
{
int a = 0;
int& aa = a;//权限等于实体,可以引用
const int& aaa = a;//权限小与实体,可以引用
const int b = 0;
int& bb = b;//权限大于实体,不能引用
//输出
std::cout << "a:" << a << std::endl;
std::cout << "aa:" << aa << std::endl;
std::cout << "aa:" << aaa << std::endl;
std::cout << "b:" << b << std::endl;
std::cout << "bb:" << bb << std::endl;
return 0;
}
4.使用场景
(1)做参数
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
这里的a与b都是实参的别名,改变a,b的值就相当于改变对应实参的值,能很好地进行数据间的交换,就不用再传实参的地址,这样可以有效地减少代码的复杂度了。
(2)做返回值
int& Count()
{
int n = 0;
n++;
// ...
return n;
}
注意:
- 函数运行时,系统需要给函数开辟独立的栈空间,用来保存该函数的形参,局部变量以及一些寄存器信息等。
- 函数运行结束后,该函数的栈空间就被系统回收了。
- 空间被回收指该块栈空间暂时不能使用,但内存还在。
就像是住旅店时,房间到期退房后,已经不能使用这个房间了,但是房间还在没有被拆除。
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
那么,引用做返回值时到底有什么作用呢?
答案是:
1.减少拷贝,提高效率
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
(3)值和引用的作为返回值类型的性能比较
#include <time.h>
struct A
{
int a[100000];//40万字节
};
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.修改返回值
//偶数*2
for (size_t i = 0; i < SLSize(&sl); ++i)
{
if (SLAt(&sl,i) % 2 == 0)
{
SLAt (&sl,i) *= 2;
}
}
这一特性在循序表会有较大用处。
5.引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 一个引用变量只能引用一个实体 ,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全