引用
在c语言中函数传参有2种方式,即传值和传址:
1.传值
void Swap(int left, int right)
{
int temp = left;
left = right;
right = temp;
}
传值:在函数调用过程中会生成一份临时变量,最终把实参的值传递给新
分配的临时变量即形参
优点:避免了函数调用的副作用
缺点:无法改变形参的值
如果想通过形参改变实参的值,只能通过指针传递
2.传址
void Swap(int* pLeft, int* pRight)
{
int temp = *pLeft;
*pLeft = *pRight;
*pRight= temp;
}
指针可以解决问题,但不是很形象友好,不安全 ,是否有一种类型:可以向值一样传递,同时又能达到指针的效果呢?所以在c++里我们引入了引用。
那什么又叫引用呢?
从应用层方面来理解引用:
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间 。从底层来理解引用的话,这段话是有问题的,后面我会来讲。
类型& 引用变量名(对象名) = 引用实体;
注意:引用变量必须和引用实体是同种类型的 。
引用特性
引用在定义时必须初始化
一个变量可以有多个引用
引用一旦引用一个实体,再不能引用其他实体
int a=10;
int b=20;
int &ra=a;
int &ra=b;//ra引用了a后不可以再引用b
常引用
void TestConstRef()
{ const int a = 10;
//int& ra = a; // 该语句编译时会出错, a 为常量
可改为 const int& ra = a;
// int& b = 10; // 该语句编译时会出错, b 为常量
可改为 const int& b = 10;
int c=10;//c可读可写
const int& rc=c;//c的值是什么就是什么,rc只能读无权修改
double d = 12.34; //int& rd = d; // 该语句编译时会出错,类型不同
可改为 const int& rd = d;
}
上述最后一个为什么加const就可以了呢?
让int类型的rd去引用double类型的d,是无法引用的。底层是怎么处理的呢?实际上找了一个中间变量过渡,把d的整数部分12复制到临时变量中,然后让rd引用这个临时变量,所以d不是rd所引用的实体,中间变量才是。为什么要加const呢?因为存放临时变量的空间不知道名字,不知道地址,所以访问不了,既然访问不了,这块空间就有一个常属性,所以要用常引用。
数组也是可以引用的,如下:
int array[10];
int (&rarray)[10]=array;//int [10]是数组的类型
引用的使用场景
作为函数形参
作为函数返回值
int& TestRefReturn(int& a)
{
a += 10;
return a;
}
我们看一下下图这段程序存在什么问题:
比较奇怪的一点是,第二次第三次打印出来的是随机值,这是为什么呢?
代码从主函数的位置跳到Add函数的位置,Add函数要执行系统给分配的栈函数,Add函数返回一个引用类型的变量,ret以引用的形式来接收Add函数的返回值,ret实际上是a的别名,因此打印的地址是一样的,Add栈里有一个a的空间,Add函数返回会把对应的空间归还给系统,归还时没有把栈空间遗留的垃圾带走,_cdecl有一个说法是下一次哪一个函数调用到这块空间了,由主调函数自己去清理。所以第一次得到30,第二三次得到清理过后的随机值。
引用和指针的区别
相同点
底层的实现方式相同,都是按照指针的方式来实现。
从底层来看,底层已经把引用变量当成指针了,指针变量会保存它所引用的实体的地址,当然这个指针它肯定是需要空间来保存的,因此在底层的处理方式这个引用变量实际上编译器也给它分配了空间,空间里面保存了它所引用实体的地址。
不同点
引用在定义时必须初始化,指针没有要求
一旦一个引用被初始化为指向一个对象,就不能再指向其他对 象,而指针可以在任何时候指向任何一个同类型对象
没有NULL引用,但有NULL指针
在sizeof中含义不同:引用结果为引用类型的大小,但指针始 终是地址*空间所占字节个数
引用自加改变变量的内容,指针自加改变了指针指向
有多级指针,但是没有多级引用
指针需要手动寻址,引用通过编译器实现寻址
引用比指针使用起来相对更安全
这段代码在执行到left=right就异常终止了,这是为什么呢?
如果我们真的从底层理解了引用的话,那这个问题应该很容易就想明白了。
之所以在left=right出错,是因为引用在底层当成指针来处理,对NULL解引用,所以程序崩溃。