1、引用不存在的对象
例如:
#include <iostream>
using namespace std;
class A
{
public:
void set(int i){x=i;}
int get(){return x;}
private:
int x;
};
A &fun()
{
A a;
a.set(20);
return a;
}
int main()
{
A &r=fun();
cout<<r.get()<<endl;
return 0;
}
输出结果:1245056
分析:在函数fun的定义中创建了一个局部变量对象a,函数返回这个对象的外号,但是此时注意a的生命已经随着fun的结束而结束了,因此此时再为这个空对象创建一个外号时,输出的结果将是一个随机数。
讨论:如果把fun函数定义那一行改成按值返回,则输出的值将是20。这是因为如果饮用的是一个临时变量,那么这个临时变量的生命周期将不小于这个饮用的周期,因此直到main函数结束时,这个r的饮用和其饮用的临时变量的值才会结束。但是注意!指针就没有这个特性,如果改成A *r=fun();的话,那么输出的仍然是上面的随机数。
2、引用按值返回的堆中对象
问题:如果在一个函数中创建一个堆中对象a,并按值返回这个对象,那么返回的这个对象是不是堆中的对象呢?
看下面的程序:
#include <iostream>
using namespace std;
class A
{
public:
A(int i){x=i;cout<<"构造函数执行"<<endl;}
A(A&){cout<<"复制构造函数执行"<<endl;}
~A(){cout<<"析构函数执行"<<endl;}
int get(){return x;}
private:
int x;
};
A fun()
{
A *p=new A(20);
cout<<p<<endl;
return *p;
}
int main()
{
A &r=fun();
cout<<r.get()<<endl;
return 0;
}
输出结果:
分析:注意函数中创建一个堆中对象并用指针p指向它,返回的是*p,即堆中的数据的值,因此属于按值传递,将调用复制构造函数创建副本,但这个过程是存放在栈中的,因此这个副本不是堆中的对象,在主函数中,为这个副本创建了一个外号,用外号可以访问其数据成员的值。
另外,这个程序只调用了一次析构函数,说明在堆中空间那个没有被析构,将出现内存泄露。
如果尝试为这个外号再创建一个指针*p1,指向这个复制构造函数创建的副本,而这个副本是在栈中的,因此当删除delete *p1时,会出现程序崩溃的情况。
3、引用按别名返回的堆中对象
为了避免出现内存泄露的现象,因此不能按值返回堆中的对象,而应该按址或外号返回,因此在第13行改成返回一个地址即可:
#include <iostream>
using namespace std;
class A
{
public:
A(int i){x=i;cout<<"构造函数执行"<<endl;}
A(A&){cout<<"复制构造函数执行"<<endl;}
~A(){cout<<"析构函数执行"<<endl;}
int get(){return x;}
private:
int x;
};
A &fun()
{
A *p=new A(20);
cout<<"p:"<<p<<endl;
return *p;
}
int main()
{
A &r=fun();
cout<<"&r:"<<&r<<endl;
cout<<"r的值:"<<r.get()<<endl;
A *p1=&r;
delete p1;
cout<<"r的值:"<<r.get()<<endl;
return 0;
}
输出结果:
分析:这一次,按地址返回后,该地址指向的是堆中空间的值,因此定义一个新的指针p1后,通过第25行的代码即可删除这块堆中空间。
问题:我们看到第26行再次调用r时,输出了一个随机数,这是因为不能引用空引用,因此我们对此程序做下面的修改:
#include <iostream>
using namespace std;
class A
{
public:
A(int i){x=i;cout<<"构造函数执行"<<endl;}
A(A&){cout<<"复制构造函数执行"<<endl;}
~A(){cout<<"析构函数执行"<<endl;}
void set(int i){x=i;}
int get(){return x;}
private:
int x;
};
A &fun(A&r)
{
r.set(30);
return r;
}
int main()
{
A *p=new A(20);
cout<<"p指向的值:"<<p->get()<<endl;
fun(*p);
cout<<"p指向的值:"<<p->get()<<endl;
delete p;
return 0;
}
输出结果:
分析:
本程序中,在堆中创建空间放到main函数中,将其空间用一个指针p指向,第一次输出时输出的是p指向空间初始化的值。在第23行调用fun函数,将*p作为参数传递给fun函数,fun函数以一个外号接收,而返回r的引用,因此在fun函数中把r指向的值做修改后,也把对空间中原来的值给修改了,因为是外号,修改就会同时修改原值。故输出结果如上面所示。而r这个别名在fun函数结束后生命周期就结束了,因此不会出现引用混乱的情况。
总的来说,总原则是在哪里创建就要记得在哪里释放。