我们知道在C++中系统默认提供了六个函数,为了更好的理解实例化出的对象的生存周期,我们结合默认的的四个函数(构造函数、拷贝构造函数、赋值运算符重载函数、析构函数)来举例说明实例化出的对象的生存周期。
首先先定义一个类CObject;如下:
#include<iostream>
using namespace std;
#pragma waring(disablie:4996)
class CObject
{
public:
CObject()
{
mname=new char[1];
cout<<this<<":"<<mname<<"CObject()"<<endl;
}
CObject(char *name,int num)
{
mname=new char[strlen(name)+1];
strcpy(mname,name);
mnum=num;
cout<<this<<":"<<mname<<"CObject(char *,int)"<<endl;
}
CObject(int num)
{
mname=new char[1];
mnum=num;
cout<<this<<":"<<mname<<"CObject(int)"<<endl;
}
CObject(const CObject &rhs)
{
mname=new char[strlen(rhs.mname)+1];
strcpy(mname,rhs.mname);
mnum=rhs.mnum;
cout<<this<<":"<<mname<<"CObject(const)"<<endl;
}
void operator=(const CObject &rhs)
{
if(this==&rhs)
{
return ;
}
delete[] mname;
mname=new char[strlen(rhs.mname)+1];
strcpy(mname,rhs.mname);
mnum=rhs.mnum;
cout<<this<<":"<<mname<<"operator()"<<endl;
}
~CObject()
{
cout<<this<<":"<<mname<<"~~CObject()"<<endl;
delete[] mname;
mname=NULL;
}
private:
char *mname;
int mnum;
};
我们自己提供了不带参数的构造函数、带有一个参数的构造函数、带有两个的构造函数、拷贝构造函数、赋值运算符重载函数。
下面我们在main函数中实例化对象来进行分析每一种情况下对象对这几个函数的调用。
例1、
//测试普通的对象的生存周期
/*static CObject object1("object1",1);
int main()
{
CObject object3;
CObject object4("object4",4);
CObject *pobject=new CObject("pobject",1);
delete pobject;
CObject object5;
cout<<"--------------------"<<endl;
return 0;
}
static CObject object2("object2",2);
运行之后:
构造对象的顺序:object1->object2->object3->object4->在堆上构造一个对象,且用指针pobject指向它->object5
析构对象的顺序:object5->object4->object3->object2->object1
总结:
(1)先构造全局对象,在构造main函数中的对象;
(2)先构造的后析构,后构造的先析构;
(3)在堆上构造的对象,只有遇到delete之后才被析构;
(4)直接实例化出的对象的生存周期都是从调用点生成,return 0;之后最后一个 } 之前结束。
例2:
int main()
{
CObject *pobject=new CObject("pobject",1);
//在堆上开辟了一个对象,指针pobject指向它;不见delete就不销毁。
CObject object("object",1);
//调用带两个参数的构造函数,生成一个对象;
CObject("vobject",1);
//调用带两个参数的构造函数,显示生成一个临时对象;
delete pobject;
cout<<"--------------------"<<endl;
return 0;
}
运行之后:
总结:
(1)这一个例子证明了在堆上构造的对象只有遇到delete才被析构。(第一行和最后一行)
(2)构造临时对象属于一个表达式,表达式在遇到;或者?之后就运行结束,所以从运行结果看出临时对象CObject("vobject",1)刚开始构造在;之前就被析构。
例3:
int main()
{
CObject object1;
//调用不带参数的默认构造函数,生成对象object1
//析构3
CObject object2("object2",2);
//调用带两个参数的构造函数,生成一个对象object2;
//析构2
object1=CObject("vobject1",1);
//调用带两个参数的构造函数,显示生成一个临时对象;
//调用赋值运算符重载函数生成对象object1;
//再调用析构函数析构临时对象
cout<<"--------------------"<<endl;
return 0;
}
运行结果:
总结:在这里也构造了一个临时对象,但该临时对象的生成是为了给已存在的对象进行赋值,所以临时对象先生成,再调用赋值运算符重载函数进行赋值,使命完成之后,在本表达式结束之前就被析构。
例4:
int main()
{
CObject object3;
//调用不带参数的默认构造函数,生成对象object1
//析构3
CObject object4("object4",4);
//调用带两个参数的构造函数,生成一个对象object4;
//析构2
CObject object5=CObject("vobject",1);
//用构造临时对象的方法构造object5;
//析构1
cout<<"-------------------------------"<<endl; return 0;
}
运行结果:
总结:
(1)在定义时进行赋值称为初始化;在定义后进行赋值称为赋值。
(2)拷贝构造函数也是构造函数。因此在本例子中没有临时对象的生成,而是用构造临时对象的方式构造了对象object5。
(3)是否有临时量产生需要明确产生临时量的目的,如果是为了给已存在的对象赋值,则有临时对象产生;如果为了初始化一个对象,则编译器进行优化,临时对象不产生。
例5:
int main()
{
CObject object6;
//调用不带参数的默认构造函数,生成对象object1 //析构3
object6=(CObject)("object6",6);
//调用带一个参数的构造函数,生成一个临时对象//在逗号结束前析构临时对象6//调用赋值运算符重载函数生成一个对象object6//析构2
cout<<"-----------------------------"<<endl;
return 0;
}
运行结果:
总结:本例中object6=(CObject)("object6",6)这是一个逗号能表达式,所以逗号表达式的结果就是6;最后通过表达式的结果调用带有一个参数的构造函数来构造一个临时对象。
例6:
int main()
{
CObject object7=CObject("object7",7);
object7=2;
//调用拷贝构造函数生成对象object7
//析构3
//调用带有一个参数的构造函数隐式生成一个临时对象
//在逗号前析构临时对象1
//调用赋值运算符重载函数给对象object7赋值
//析构2
cout<<"----------------------------------------"<<endl;
return 0;
}
运行结果:
总结:这里需要注意一下隐式生成的临时对象。
例7:
int main()
{
CObject *pobject1=&CObject("pobject1",1);
CObject &pobject2=CObject("poject2",2);
//调用带两个参数的构造函数生成一个临时对象,用pobject1指向它,在表达式结束之前析构
//在逗号之前析构临时对象
//生成一个对象pobject2
//在return 之后析构
cout<<"----------------------------"<<endl;
return 0;
}
运行结果:
总结:
(1)如果是指针指向的对象,则临时对象生成,在表达式结束之前析构。
(2)如果是通过引用引用一个对象,则临时对象的生存周期被提升为该引用的对象,在main函数结束时析构。
例8:
void fun1(CObject object90)
{}
int main()
{
CObject object8;
fun1(object8);
//调用默认构造函数生成对象object8 //实参初始化形参,调用拷贝构造函数形成形参
//在;结束后析构形参
//在return之后析构object8
cout<<"-------------------"<<endl;
return 0;
}
运行结果:
总结:
(1)实参传形参是初始化的过程。
(2)构造的形参在调用函数结束时析构。
例9:
void fun2(CObject &rhs)
{}
int main()
{
fun2(CObject("objectp",1));
//因为传的是引用,所以为了形参的初始化,临时量存在(调用的是带有两个参数的构造函数)
//在;之前析构临时量
cout<<"----------------------"<<endl;
return 0;
}
运行结果:
总结:调用函数传引用时,尽管是初始化,但是临时量会产生,该临时对象生存周期提升为形参的生存周期。
例10:
void fun2(CObject &rhs)
{}
int main()
{
CObject object9;
fun2(object9);
//传引用不需要有对象的生成,只有object9生成,在return之后析构
cout<<"---------------"<<endl;
return 0;
}
运行结果:
总结:在传引用时不生成对象。
例11:
void fun3(CObject *rhs)
{}
int main()
{
CObject object;
fun3(&object);
//生成一个对象
fun3(&CObject("object",1));
//生成一个临时对象,在;之前析构
cout<<"--------------------"<<endl;
return 0;
}
运行结果:
总结:调用函数传指针时,如果有临时量生成,则临时量的生存周期在表达式解释时析构。
由上面所有的例子简单总结一下:
(1)一般对象的生存周期都是从调用点开始,到main函数结束时析构。
(2)如果有临时对象的产生,当不做引用时一定是在表达式结束时析构,做引用时生存周期与一般对象一样。
(3)调用函数时,传引用不生成对象。
(4)调用构造函数一般是初始化一个对象或者实参传形参时。