如果变量在使用之前没有正确初始化或清除,将导致程序出错。(自我检讨一下,曾经因为没有对一个变量进行初始化就进行了使用,查了一晚上也没查出问题出在哪,后来发现后只想说,我是不是傻o(╥﹏╥)o )所以各位一定要切记对对象进行正确的初始化。
对对象进行初始化的一种方法是编写初始化函数,然而很多用户在解决问题时,常常忽视这些函数,以至于给程序带来了隐患。为了方便对象的初始化和清理工作,C++提供了两个特殊的成员函数:构造函数和析构函数。
构造函数的功能是在创建对象时,给数据成员赋初值,即对象的初始化。析构函数的功能是释放一个对象,在对象删除之前,用它来做一些内存释放等清理工作,它的功能与构造函数的功能正好相反。
2.1 构造函数
构造函数是一种特殊的成员函数。它的特点为对象分配空间,进行初始化,并且在对象创建时会被系统自动执行。
定义构造函数原型的格式为:
类名(形参列表);
在类外定义构造函数的格式为:
类名::类名(形参列表)
{
//函数语句;
}
说明:
- 构造函数名字必须与类名相同,否则系统会将它当成一般成员函数处理。
- 构造函数可以有任意类型的参数,但是没有返回值,也不能指定为void类型。
- 定义对象时,系统会自动的调用构造函数。
- 通常构造函数被定义在公有部分。
- 在实际应用中,通常要给每个类定义构造函数,如果没有定义构造函数,系统会自动的生成一个默认的构造函数,这个默认函数不带任何参数,只负责对象的创建,为对象开辟一个存储空间,而不做任何初始化工作,此时成员函数的值是随机的。
- 构造函数可以重载。(重载这一概念会在后续章节介绍到)
【例】构造函数应用举例:输出日期
#include <iostream>
using namespace std;
class Data {
private:
int year, month, day;
public:
Data(int y, int m, int d);
void Print();
};
int main()
{
Data today(2019, 9, 10);
count << "today is ";
today.Print();
return 0;
}
Data::Data(int y, int m, int d)
{
year = y;
month = m;
day = d;
}
void Data::Print()
{
cout << year << "-" << month << "-" << day << endl;
}
说明:主函数main没有显示调用构造函数Data,构造函数是在创建对象today时,系统自动调用的。即在创建对象today时,系统自动调用构造函数today.Data,并将数据成语year、month、day初始化为2019、9和10。
- 构造函数可以不带参数,例如:
class Fruit{
private:
int x,y;
public:
Fruit();
//......
};
Fruit::Fruit()
{
x=0;
y=0;
}
- 构造函数也可以采用构造初始化表对数据成员进行初始化,例如:
class Data{
private:
int year,month,day;
public:
Data(int y,int m,int d):year(y),month(m),day(d);
//构造函数初始化表对数据成员进行初始化
};
- 如果数据成员是数组,则应该在构造函数中使用相应的语句进行初始化,例如:
class Fruit{
private:
char name[20];
int num;
public:
Fruit(char name[ ],int n);
};
Fruit::Fruit(char na[ ],int n):num(n)
{
strcpy(name,na); //name是字符数组,所以用strcpy函数进行初始化
}
2.2 析构函数
在对象生存期结束前,通常需要进行必要的清理工作。这些相关的清理工作由析构函数完成。析构函数也是一种特殊的成员函数,当删除对象时就会调用析构函数,也就是在对象的生存期即将结束时,由系统自动调用,随后这个对象也就消失了。注意,析构函数的目的是在系统回收对象内存前执行清理工作,以便内存可被重新用于保存新对象。
定义析构函数的一般格式:
~类名();
例如:
class Fruit{
public:
~Data();
};
特点:
- 析构函数名是由“~”和类名组成的。
- 析构函数没有参数,也没有返回值,而且也不能重载。
- 通常析构函数被定义在公有部分,并由系统自动调用。
- 一个类中有且仅有一个析构函数。
说明:
- 与类的其他函数成员一样,析构函数可以在类内定义,也可以在类外定义。如果不定义,系统会自动生成一个默认的析构函数:类名::~类名(){}。
- 析构函数的功能是释放对象所占用的内存空间,析构函数在对象生存期结束前由系统自动调用。
- 析构函数与构造函数两者的调用次序相反,即最先构造的对象最后被析构,最后构造的对象最先被析构。
【例】构造函数与析构函数的执行顺序 — Point类的多个对象的创建与释放。
#include <iostream>
using namespace std;
class Point
{
public:
Point(int a,int b);
~Point();
private:
int x, y;
};
int main()
{
Point p1(1,2), p2(3,4);
return 0;
}
Point::Point(int a,int b) //定义构造函数
{
x = a;
y = b;
cout << "constructor......" << endl;
cout << "(" << x << "," << y << ")" << endl;
}
Point::~Point() //定义析构函数
{
cout << "destructor......" << endl;
cout << "(" << x << "," << y << ")" << endl;
}
运行结果
说明:调用构造函数的顺序与主函数main中创建对象的顺序一致,先创建对象p1,然后再创建对象p2;调用析构函数的顺序与创建对象的顺序相反,先析构对象p2,然后再析构对象p1。
- 除了显式撤销对象时,系统会自动调用析构函数,在下列情况下,析构函数也会被调用:
①如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数会被自动调用。
#include <iostream>
using namespace std;
class Point
{
public:
Point(int a,int b);
~Point();
private:
int x, y;
};
void fun(Point p);
int main()
{
cout << "inside main" << endl;
Point p1(1, 2);
fun(p1);
cout << "outside main" << endl;
return 0;
}
Point::Point(int a,int b)
{
x = a;
y = b;
cout << "constructor......" << endl;
}
Point::~Point()
{
cout << "destructor......" << endl;
}
void fun(Point p)
{
cout << "inside fun" << endl;
}
运行结果
说明:在主函数main中定义对象p1时,系统自动的调用p1的构造函数;当调用函数fun时,实参p1将值对应的赋给了形参p;当函数fun执行完,系统自动调用对象p的析构函数;当主函数main结束时,系统自动调用对象p1的析构函数。由此可见,只要对象超出它的作用域,系统就自动调用析构函数。
如果一个对象使用new运算符动态创建,在使用delet运算符释放它时,delet会自动调用析构函数(在程序中如果不显式撤销该对象,系统不会自动调用析构函数。也就是说new运算符动态创建的对象,如果不用delet运算符释放它,系统不会自动调用析构函数)详细用法将会在下节介绍到。