引用
1. 引用定义
引用:就是某一变量(目标)的别名,对引用的操作与对变量直接操作完全一样。
定义:类型名 &引用名 = 同类型的某变量名;
1) 引用只是变量的别名,并不是定义了一个新的变量,因此引用本身不占内存,而是和目标变量共同指向目标变量的内存地址。
2) 此表达式中的取地址符&不再是取变量的地址,而是用来表示该变量是引用类型的变量。
3) 定义一个引用,必须对其初始化,初始化后,它就一直引用该变量,不会再引用别的变量。
4) 引用只能引用对象,也就是有地址的,不能是一个常数或者表达式。而且类型要匹配。
2. 引用 vs 指针
本质:引用是别名,指针是地址
区别:
① 不存在空引用,引用必须连接到一块合法的内容。
② 引用总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。指针可以在任何时候指向另一个人对象。
③ 从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域。
④ 理论上,对于指针的级数没有限制,但是引用只能是一级。如下:
3. 引用作为函数的返回值
例如:
int n = 1;
int & getValue()
{
return n; //返回对n的引用
}
① 定义函数时需要在函数名前加 &
② 用引用作为函数的返回值的最大好处是:在内存中不产生被返回值的副本。
引用作为返回值,必须遵守以下规则:
① 不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
② 不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况,又面临其它尴尬局面。如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
string& foo()
{
string* str = new string("abc");
return *str;
}
这样写很容易造成内存泄露。程序员在编写代码时,为了避免内存泄露,必须保证对每个用new产生的指针调用delete释放。
string str = foo(); 显然new生成的这块内存将无法释放。
只能这样:
string& tmp = foo();
string str = tmp;
delete &tmp;
这样就不会造成内存泄露了。 但是每次的这样就是谁都觉得烦。而且暗藏杀机啊,比如:string str = "hello" + foo(); 上式不知不觉就造成内存泄露了。所以,即使很小心的程序员也难免会造成内存泄露。
③ 可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
4. 参数传引用
传值和传引用的区别
"传值"是指: 函数的形参是实参的一个拷贝,在函数执行的过程中,形参的改变不会影响实参。
传引用: 形参是对应的实参的引用。也就是说,形参和对应的实参是一回事,形参的改变会影响实参。
5. 常引用
定义引用时,在前面加 const 关键字,则该引用就成为"常引用"。
常引用和普通引用的区别在于:不能通过常引用去修改其引用的内容。注意,不是常引用所引用的内容不能被修改,只是不能通过常引用去修改而已,但可以用别的办法修改。例如下面的程序片段:
int n = 100;
const int & r = n;
r = 200; //编译出错,不能通过常引用修改其引用的内容
n = 300; //没问题,n的值变为300
6. 引用和多态
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
class A;
class B:public A{ ... ... }
B b;
A &Ref = b;//用派生类对象初始化基类对象的引用
// 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。