构造函数与析构函数
在c++中有2个特殊的函数:构造函数和析构函数,它们分别对类对象进行初始化和清理工作。
1. 构造函数
构造函数,与类名相同,当创建类对象时会自动调用该函数。如果创建类对象时没有手动创建构造函数,系统会自动创建一个默认的构造函数,这个默认的构造函数函数体是空的,无任何功能。
构造函数是作为类的成员函数,它可以访问类中所有的数据成员,可以是内联函数,可以带参数及默认形参值,还可以重载。
构造函数主要是在创建对象时初始化对象,它与其他成员函数的区别:
- 构造函数必须与类同名,一般函数不能与类同名。
- 构造函数无返回值,也不需要使用void来修饰,而其他普通函数如果没有返回值则要用void来修饰(在java语言中如果构造函数/方法如果用void来修饰则会变成普通函数/方法)。
- 构造函数不能直接被调用,在创建类对象时编译器会自动的调用构造函数,普通成员函数在程序执行到它是被调用。
#include<iostream>
usingnamespace std;
classPerson
{
int idNum;
int age;
char name[20];
public:
Person(int idNum=0,int age=1)
{
this->idNum=(idNum>=0&&idNum<=100)?idNum:0;
this->age=(age>=0&&age<=150)?age:0;
}
void showInfo()
{
cout<<"idNum:"<<idNum<<endl;
cout<<"age:"<<age<<endl;
}
};
int main()
{
Person p(9,30);
p.showInfo();
return0;
}
2. 析构函数
析构函数,在类对象消失前自动调用的函数,它的形式如下:
~funName()
{
operation;
}
在析构函名与类名相同,相对于构造函数,析构函数作用刚刚相反,即是一个“逆构造函数”,在它前面有个~
符号。
析构函数具有如下特点:
- 析构函数没有任何参数,不能被重载,但可以是一个虚函数,一个类只有一个析构函数。
- 析构函数没有返回值,也不用修饰符修饰。
- 析构函数前面有一个
~
符号来区别构造函数。 - 析构函数一般有用户自动定义,在类对象消失前调用,如果用户没有定义析构函数,系统会自动生成一个内容为空的析构函数。
#include<iostream>
usingnamespace std;
classPerson
{
private:
int age;
intNumber;
public:
Person(int age=0,intNumber=0)
{
this->age=(age>=0&&age<=150)?age:0;
this->Number=(Number>=0&&Number<=99)?Number:0;
cout<<"Constructor;";
cout<<"age:"<<age;
cout<<";Nunber:"<<Number<<endl;
}
~Person()
{
cout<<"destructor;"<<"age:"<<age<<";Number:"<<Number<<endl;
}
};
int main()
{
Person p3(5,9);
Person p4(8,7);
return0;
}
Person p1(6,11);
Person p2(9,10);
运行结果:
Constructor;age:6;Nunber:11
Constructor;age:9;Nunber:10
Constructor;age:5;Nunber:9
Constructor;age:8;Nunber:7
destructor;age:8;Number:7
destructor;age:5;Number:9
destructor;age:9;Number:10
destructor;age:6;Number:11
从运行结果来看,构造函数的调用顺序是
st=>start:Start
e=>end:End
op1=>operation: p1::Person()
op2=>operation: p2::Person()
op3=>operation: p3::Person()
op4=>operation: p4::Person()
st->op1->op2->op3->op4->e
而析构函数的调用顺序是
st=>start:Start
e=>end:End
op1=>operation: p4::~Person()
op2=>operation: p3::~Person()
op3=>operation: p2::~Person()
op4=>operation: p1::~Person()
st->op1->op2->op3->op4->e
这是因为p1和p2对象属于全局对象,p3和p4属于局部对象,在程序运行时会先创建全局对象栈,然后才是局部对象栈。当对象消失时,先是局部栈中栈顶的p4对象先弹出来,然后是p3对象,再之后是全局栈中的对象。
3. 拷贝函数
拷贝构造函数又叫拷贝函数,它是一种特殊的构造函数,它作用是在建立新对象时将已经存在对象的数据成员拷贝给新对象。既然拷贝函数也是一个构造函数,那么它的函数名也是和类同名的,其形参是本类对象的引用。
同样,它作为构造函数的一种,当用户没有自定义拷贝函数,系统会自动的生成一个默认的拷贝函数来进行对象之间的位拷贝,默认拷贝函数的功能是把初始值对象的每一个数据成员的值依次复制新的对象中。
用户自己定义一个拷贝函数的一般形式为:
类名(类名&对象名)
{
...
}
下面3种情况相当于用一个已经存在的对象去初始化新建的对象。
- 当用类的一个对象去初始化该类的另一个对象时(显示调用)。
- 如果函数的形参是类对象,调用函数时,将对像作为函数实参传递给函数的形参时(隐含调用)。
- 如果函数的返回值是类的对象,函数执行完成,将返回值返回(隐含调用)。
#include<iostream>
usingnamespace std;
classTime{
private:
int Y,M,D;
public:
Time(int y=0,int m=0,int d=0)
{
Y=y,M=m,D=d;
cout<<"constructor:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
}
~Time()
{
cout<<"destructor:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
}
Time(Time& t)
{
cout<<"copy constructor before call:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
Y=t.Y;
M=t.M;
D=t.D;
}
void showTime()
{
cout<<"year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
}
};
Time fun(Time t)
{
return t;
}
int main()
{
Time t1(1991,10,7);
Time t2(1991,11,6);
Time t3(t1);
fun(t2);
Time t4;
t4=t2;
return0;
}
运行结果:
constructor:year is:1991;Monthis:10;day is:7
constructor:year is:1991;Monthis:11;day is:6
copy constructor before call:year is:967104272;Monthis:32767;day is:4197060
copy constructor before call:year is:1;Monthis:0;day is:4197645
copy constructor before call:year is:967104568;Monthis:32767;day is:4197552
destructor:year is:1991;Monthis:11;day is:6
destructor:year is:1991;Monthis:11;day is:6
constructor:year is:0;Monthis:0;day is:0
destructor:year is:1991;Monthis:11;day is:6
destructor:year is:1991;Monthis:10;day is:7
destructor:year is:1991;Monthis:11;day is:6
destructor:year is:1991;Monthis:10;day is:7
分析:
- 从结果上看,首先创建了2个Time对象t1、t2,调用构造函数运行结果为1、2行。
- 然后是运行语句
Time t3(t1);
,这里会显示调用拷贝函数,所以会打印出第3行结果。因为拷贝函数相当于拷贝函数=构造函数+赋值操作,在构造对象后类成员还没有进行赋值就打印出来,所以从结果数据看是一些随机数。 - 之后运行
fun(t2);
语句,首先在这个函数中会创建一个临时形参对象,然后相当于执行了Time 临时形参对象(实参对象);
的语句来调用拷贝函数,在函数return时,又会创建一个临时返回对象,并执行Time 临时返回对象(临时形参对象);
的语句来调用拷贝函数,之后该函数生命周期结束,对创建的2个临时对象调用析构函数(先是调用的临时返回对象的析构函数,然后是临时形参对象的析构函数–个人推测),所以看到第3~7行结果。 - 再然后执行
Time t4;
语句,即创建t4对象并调用构造函数。t4=t2;
语句是对已经存在对象进行赋值,所以既不会调用构造函数也不会调用拷贝函数,在内存中只是使用t2对象的值去覆盖t4对象对应的值。 - 之后是整个main函数生命周期结束调用 它 t1~t4 对象的析构函数,遵循的原则是后创建的先析构,依次是t4、t3、t2、t1,所以看到执行的结果是第9~12行。
注
- 拷贝函数作用是用一个已经存在的类对象去初始化一个新的类对象,在对象之间进行赋值操作,拷贝函数不会被调用(实际上就上面3种情况会调用拷贝函数)。
- 创建对象时,构造函数与拷贝函数有且只有一个被调用。
- 当对象作为函数的返回值时,需要调用拷贝函数,这时系统会在堆中动态创建一个临时对象,将函数返回的对象拷贝给该临时对象,并把该临时对象的地址存储在寄存器中,最后由临时对象完成函数返回值的传递。
4. 总结
最近在学习c++的时候,一直没搞懂 c++中 各个特殊函数的调用情况,然后查看了下以前的课本[c++语言程序设计教程],并做了以上学习笔记方便后面查阅,希望能够对c++ 中这3个特殊函数有个大概了解(毕竟在读书的时候根本没看过,那时学习马马虎虎只是为了过考试而已),而且觉得像这些语言细节特性对后面深入学习和工作还是非常有用的。