类是一种用户自定义的类型,定义一个类对象时,编译程序要为对象分配存储空间,进行必要的初始化。在C++中,这项工作是由构造函数来完成的。与构造函数对相应的是析构函数,当撤销类对象时,析构函数回收存储空间,并做一些善后工作。构造函数和析构函数都属于类,即可以由用户提供,也可以由系统自动生成。
8.1 构造函数和析构函数的定义
构造函数的作用是在对象被创建时利用特定的值构造对象,将对象初始化为一种特定的状态,使该对象具有去别于其他对象的特征。构造函数在对象被创建的时候由系统自动调用。
构造函数也是类的成员函数,但它是一种特殊的成员函数,它除了具有一般成员函数的特性之外,还具有一些特殊的性质。
(1)构造函数的名字必须与类名相同;
(2)构造函数不指定返回类型,它隐含有返回值,由系统内部使用;
(3)构造函数可以有一个或多个参数,因此构造函数可以重载;
(4)在创建对象时,系统会自动调用构造函数。
初始化列表位于构造函数的形参列表之后,函数体代码之前,由一个冒号和由逗号分隔的若干项构成。每一个构造函数的初始化列表项都由数据成员标识符和其后的括号表达式构成,即每个独立的成员都必须按照如下形式初始化:
成员名(表达式)
在构造函数中,初始化优先于赋值。在调用构造函数对类对象初始化时,先执行初始化列表对各个成员进行初始化,再执行构造函数体。初始化列表中各个初始化项的执行顺序取决于类成员在类中声明的顺序,而与初始化列表中给出的初始化项顺序无关。如果在构造函数的初始化列表中没有对某个成员进行显式初始化,则表示对该成员进行了默认初始化。如果对所有成员都进行了默认初始化,那么可以省略构造函数的初始化列表。
对于大多数数据成员而言,既可以使用初始化列表的方式获得显式初值,也可以在获得默认初值后,再在构造体中使用赋值语句将表达式的值赋给数据成员。但在两种方式中,初始化方式使初始化情况更加明显,并且可能带来效率上的优势。
例1 分析下列程序的输出结果
#include<iostream>
using namespace std;
class Date
{
private:
int year, month, day;
public:
Date(int y, int m, int d);
Date(int y = 2000) :year(y)
{
month = 10;
day = 1;
cout << "一个带参的日期" << endl;
}
int Is();
void Print() { cout << year << "." << month << "." << day << endl; }
};
Date::Date(int y, int m, int d)
{
year = y;
month = m;
day = d;
cout<<"三个带参的日期"<<endl;
}
int Date::Is()
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int main()
{
Date date1(2002, 10, 1),date2;
cout << "date1:";
date1.Print();
cout << "date2:";
date2.Print();
if (date2.Is())cout << "date2是闰年" << endl;
else cout << "date2不是闰年" << endl;
return 0;
}
从上面的例子可以看出,在主函数main中,没有显式调用构造函数,构造函数是在定义对象date1和date2时系统自动调用的。另外由于两个构造函数满足函数重载的条件,因此系统调用时会根据函数的参数自动解析。
析构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的一些清理工作,也就是专门做扫尾工作的,一般情况下,析构函数在对象的生存期即将结束的时候由系统自动调用。它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
析构函数也是类中的一种特殊的成员函数,它具有以下一些特性:
(1)析构函数名是在类名前加求反(求补)符号~;
(2)析构函数不指定返回类型,它不能有返回值;
(3)析构函数没有参数,因此析构函数不能重载,一个类中只能定义一个析构函数;
(4)在撤销对象时,系统会自动调用析构函数。
8.2 默认构造函数和默认析构函数
默认构造函数就是被调用时不必为其提供参数的构造函数。默认析构函数的名与类名相同,它的参数列表或者为空,或者它的所有参数都具有默认值。
如果类中定义了一个默认构造函数,则使用该函数;如果一个类中没有定义任何构造函数,编译器将生成一个不带参数的公有默认构造函数,它的定义格式如下:
<类名>::<类名>()
{
}
每个类都必须有一个析构函数。如果一个类没有定义析构函数,编译器将生成一个公有的析构函数,即默认的析构函数,它的定义格式如下:
<类名>::~<类名>()
{
}
8.3 复制构造函数
类中有一种特殊的构造函数叫作复制构造函数,它用一个已知的对象初始化一个正在创建的同类对象。复制构造函数的格式如下:
<类名>::<类名>(const<类名>&<引用对象名>)
{
//复制构造函数体
}
复制构造函数具有以下特点:
(1)也是一种构造函数,因此函数名与类名相同,并且不能指定返回值类型。
(2)也是只有一个参数,是对同类的某个对象的引用。
(3)每个类中都必须有一个复制构造函数。如果类中没有定义复制构造函数,编译器会自动生成一个具有上述形式的公有复制构造函数。
例2 分析下列程序的输出结果
#include<iostream>
using namespace std;
class Date
{
private:
int year, month, day;
public:
Date(int y, int m, int d);
Date(int y = 2000) :year(y)
{
month = 10;
day = 1;
cout << "一个带参的日期" << endl;
}
Date(const Date& d);
~Date() { cout << "析构:" << year << "." << month << "." << day << endl; };
int Is();
void Print() { cout << year << "." << month << "." << day << endl; }
};
Date::Date(int y, int m, int d)
{
year = y;
month = m;
day = d;
cout << "三个带参的日期" << endl;
}
Date::Date(const Date&d)
{
year = d.year;
month = d.month;
day = d.day;
cout << "拷贝构造" << endl;
}
int Date::Is()
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int main()
{
Date date1(2002, 10, 1);
Date date2(date1);
cout << "date1:";
date1.Print();
cout << "date2:";
date2.Print();
return 0;
}
例3 分析下列程序的输出结果
#include<iostream>
using namespace std;
class Date
{
private:
int year, month, day;
public:
Date(int y, int m, int d);
Date(int y = 2000) :year(y)
{
month = 10;
day = 1;
cout << "一个带参的日期" << endl;
}
Date(const Date& d);
~Date() { cout << "析构:" << year << "." << month << "." << day << endl; };
int Is();
void Print() { cout << year << "." << month << "." << day << endl; }
};
Date::Date(int y, int m, int d)
{
year = y;
month = m;
day = d;
cout << "三个带参的日期" << endl;
}
Date::Date(const Date&d)
{
year = d.year;
month = d.month;
day = d.day;
cout << "拷贝构造" << endl;
}
int Date::Is()
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
Date fun(Date d)
{
Date temp;
temp = d;
return temp;
}
int main()
{
Date date1(2002, 10, 1);
Date date2(0,0,0);
Date date3(date1);
date2 = fun(date3);
cout << "date1:";
date1.Print();
cout << "date2:";
date2.Print();
cout << "date3:";
date3.Print();
return 0;
}
在上述程序中,复制构造函数一共调用了3次,第一次是在执行语句Date date3(date1)时,用已经建立的对象date1对正在建立的对象date3进行初始化;第二次是在调用fun函数时,由于是传递调用,因此实参对象date3要对形参对象d进行初始化;第三次是在执行fun函数中的返回语句return temp;时系统自动用返回值初始化一个匿名对象时使用了复制构造函数。
在上述程序中要注意析构函数的自动调用,尤其是对匿名函数的处理。由于temp对象的作用域仅在函数fun中,因此不能将temp对象直接赋值给对象date2,此时必须生成一个匿名对象,首先用temp初始化匿名对象,然后再将匿名对象赋值给对象date2,赋值完毕后,匿名对象被释放。
复制构造函数在以下三种情况下会被调用:
(1)用类的一个已知的对象去初始化该类的另一个正在的对象。
(2)采用传值调用方式,对象作为函数实参传递给形参。
(3)对象作为函数返回值。
参考《全国计算机等级考试二级教程——C++语言程序设计》