构造函数和析构函数
一.构造函数
构造函数时一种特殊的函数,它主要用于为对象分配空间,进行初始化。
(注:构造函数没有this指针)
1. 构造函数的几个特点:
函数名与类名相同
参数任意,但是没有返回值,viod也不行
它是在实例化对象的时候自动的调用,而不需要用户调用
构造函数可以重载
构造函数可以写在类体内,也可以写在类体外
2. 构造函数的调用形式
(1) 类名对象名[(实参表)]
(2) 类名 *指针变量名 = new 类名[(实参表)]
下面是构造函数的使用方法
#include<iostream>
usingnamespace std;
classDate
{
private:
int year;
int month;
int day;
public:
Date(int y,int m,int d);
voidsetDate(int y,int m,int d);
voidshowDate();
};
Date::Date(inty, intm, intd)
{
cout << "Constcuting..." << endl;
year = y;
month = m;
day = d;
}
voidDate::setDate(inty, intm, intd)
{
year = y;
month = m;
day = d;
}
inlinevoidDate::showDate()
{
cout <<year << "." << month << "."<< day << endl;
}
int main()
{
Datedate1(2017,3,2);
cout << "date1:" << endl;
date1.showDate();
date1.setDate(2017,3,3);
cout << "date2:" << endl;
date1.showDate();
return 0;
}
上面代码中主函数中的构造函数的使用是采用第一种方式,这里再提供第二种方式
int main()
{
Date *pdate;
pdate = newDate(2017,2,3);
cout << "Date1" << endl;
pdate->showDate();
pdate->setDate(2017,2,3);
cout << "Date2" << endl;
pdate->showDate();
return 0;
}
这段代码中编译器开辟了一个存储空间,并且存放了一个Date类,但是这个对象没有名字,成为无名对象,但是该对象有地址,这个地址存放在pdate中,我们通过指针可以找到他,访问时用new动态建立的对象一般是不用对象名的,而是通过指针进行访问。如果不需要时,可以通过delete进行释放。
二. 成员初始化列表
C++还提供了另一种初始化成员的方法——用成员初始化列表来实现对数据成员的初始化,这种方法不在函数体内用赋值语句,而是在函数首部实现的。
例如在构造函数的定义中可以使用如下的方式:
Date::Date(inty, intm, intd) :year(y),month(m), day(d)
{
cout << "Constcuting..." << endl;
}
带有成员列表的构造函数一般的形式如下:
类名 ::构造函数名([参数表]):[(成员初始化列表)]
{}
成员初始化列表的一般形式为:
数据成员名1(初始值1),数据成员名2(初始值2),…
成员初始化列表有什么用途呢,一般对于const修饰的数据成员,或者是引用类型的数据成员。
注意:使用成员初始化列表初始化的时候,它的初始化的顺序是按照在类中声明的顺序进行初始化的,而不是按照成员初始化列表中的顺序进行初始化。
三. 带默认参数的构造函数(带缺省参数)
在构造函数中,有一些成员值是不变的,这时我们可以使用带默认参数的构造函数
意思就是我们在定义构造函数时,可以在形参的部分队参数进行赋初值。
四. 析构函数
析构函数也是一种特殊的成员函数,它通常用于撤销对象时的一些清理任务
析构函数的特点如下:
析构函数和构造函数名字相同,但是它的前面必须加一个波浪号(~)
析构函数没有参数,也没有返回值,而且不能重载。
析构函数自动被调用
例子:
同样是上文的Date类,我们可以在定义类的时候定义析构函数,如
class Date
{
…
~Date();
}
Date::~Date()
{
cout<<”destruting …”<<endl;
}
在以下情况下,当对象的声明周期结束时,析构函数会被自动调用
(1). 如果定义了一个全局对象,则在程序流离开作用域(如main()函数结束或者调用exit()函数)时,调用该全局对象的析构函数。
(2). 如果一个对象被定义在一个函数体内,则当这个函数调用结束时,该函数应该释放,析构函数自动被调用
(3). 若一个对象是使用new运算符进行动态创建的,在使用delete运算符释放它时,delete会调用析构函数。
五. 默认的构造函数和默认的析构函数
1.默认的构造函数(系统自带的构造函数,全参数构造函数,无参的构造函数都可以叫默认的构造函数)
一般写程序时会定义构造函数,但是如果没有定义构造函数,系统会自动生成一个构造函数,这就是默认的构造函数。上面的程序中,如果没有定义构造函数,而直接使用Date date1;这时系统会为Date类生成下面形式的构造函数:
Date ::Date()
{}
并且使用这个默认的构造函数对date1进行初始化,但是这个构造函数没有任何参数,它只能开辟一个存储空间,而不能给对象中的数据成员赋值,这时的初始值是个随机数,程序运行的时候可能会造成错误。
补充说明:对没有定义构造函数的类,其公有数据成员可以用初始值列表进行初始化。
只要一个类定义了一个构造函数,系统将不再给它提供默认的构造函数
3. 默认的析构函数
每个类都有一个析构函数,如果一个类没有定义析构函数,那么编译系统会自动的生成一个析构函数。
注意:在C++调用构造函数的时候注意不能出现这种形式例如有一个类Date,这个时候实例化一个对象Date date();这里不是调用了这个构造函数,而是一个函数的声明,是错误的,如果不给这个对象传递参数就不要写后面的括号,传递参数的时候写括号,后面在加入参数。
六.拷贝构造函数
拷贝构造函数的形参是本类对象的引用,拷贝构造函数的作用是在建立一个新得对象时,使用一个已经存在的对象去初始化这个新对象。形如:Point p2(p1);
拷贝构造函数的几个特点:
1. 因为拷贝构造函数也是构造函数,所以它的函数名必须与类名相同,而且也没有返回值
2. 拷贝构造函数只有一个参数,而且是同类对象的引用
3. 每个类都有一个拷贝构造函数,可以自己定义用于初始化新的对象,如果没有定义系统会自动的定义,用于复制与数据成员值相同的对象。
拷贝构造函数的使用:
1. 自定义拷贝构造函数
自定义拷贝构造函数的一般形式如下:
类名::类名(const 类名 &对象名)
自定义拷贝构造函数的调用:
代入法:类名 对象2(对象1);
赋值发:类名 对象2 = 对象1;
2. 默认的拷贝构造函数
如果用户没有定义自定义的拷贝构造函数,然后又使用了拷贝构造函数,系统会调用默认的拷贝构造函数,例如Rectangle p2(p1);此时会把p1中各个域的值均复制给p2
3. 调用拷贝构造函数的三种情况
(1) 当用类的一个对象去初始化类的另一个对象的时候
(2) 当函数的形参是类的对象,调用函数进行形参和实参结合时
(3) 当函数的返回值是对象,函数执行完成返回调用者时
下面是具体的程序,还有注释部分,注释部分就是上面各种的讲解
#include<iostream>
usingnamespace std;
classRectangle
{
private:
int_length;
int _width;
public:
Rectangle(int len =10, int wid =10); //构造函数
Rectangle(constRectangle&p); //拷贝构造函数
void disp();
};
Rectangle::Rectangle(intlen, intwid) //构造函数
{
_length = len;
_width = wid;
cout << "usingnormal constructor" << endl;
}
Rectangle::Rectangle(constRectangle &p) //拷贝构造函数
{
_length = 2 * p._length;
_width = 2 * p._width;
cout << "usingcopy constructor" << endl;
}
voidRectangle::disp()
{
cout <<_length << " " << _width << endl;
}
void fun1(Rectanglep)
{
p.disp();
}
Rectangle fun2()
{
Rectanglep4(10,30); //这里调用了普通的构造函数
returnp4; //这里返回的一个Rectangle的对象,我们会使用p2 = fun2();去接受他的返回值,所以这里相当于给p2用了一个拷贝构造函数
}
int main()
{
Rectangle p1(30,40); //定义了p1,调用构造函数
p1.disp();
Rectanglep2(p1); //调用拷贝构造函数把p1里面的值全部复制给p2(情况1)
p2.disp();
Rectangle p3 =p1; //调用拷贝构造函数,把p1的值复制给p3(情况1)
p3.disp();
fun1(p1); //这个时候传入的是p1的一份引用,然后会调用Rectangle的拷贝构造函数
//这里应该思考的一个问题就是,为什么调用的是Rectangle的拷贝构造函数,这里我的一个猜测是,直接传一个对象的时候,就像传数组名一样了,发生了一个转换之类的
//可能就把对象转换成对象的一个引用了吧
p1.disp();
p2 =fun2(); //函数的返回值是对象,属于第三种情况调用拷贝构造函数
p2.disp();
system("pause");
return 0;
}
六. 浅拷贝和深拷贝
所谓浅拷贝,就是如果没有自定义拷贝构造函数而直接使用的话,就只是单纯的进行值得赋值,但是当类的数据成员有指针类型的时候,并且在使用指针的时候,给这个指针动态的开辟了一个新的循存储空间的话,当我实例化一个对象a之后,a里面的一个数据成员p动态的开辟了一个存储空间,然后我有实例化b,而且是通过调用未定义的拷贝构造函数,这样就直接把a里面的内容给了b,b里面的一个指针也指向刚刚a里面的内存空间,这时候就出现了一种情况就是当我调用析构函数清理a 的内存空间的时候,因为b中的指针也指向了那片空间,所以此时b中的那个指针就没有意义了,这就造成了错误。所以建议再使用指针的相关操作的时候,尽量去自定义拷贝构造函数。