1. 对象的初始化和构造函数
类是一种抽象的数据类型,它不占存储空间,不能容纳具体的数据。因此在类声明中不能给数据成员赋初值。与使用变量一样,使用对象时也应该先定义,后使用。在定义对象时,对数据成员赋初值,称为对象的初始化。在定义对象时,如果某一数据成员没有被赋值,则它的值是不可预知的。对象是一个实体,在使用对象时,它的每一个数据成员都应该有确定的值。
如果一个类中的所有成员,都是共有的,则可以在定义对象时对数据成员进行初始化。例如:
1 | class Complex{ |
说明:这种方法类似于结构体变量初始化的方法。但是,如果类中包含私有的或保护的成员时,就不能用这种方法进行初始化。除了调用公有成员函数来给数据成员赋值,C++提供了一个更好的方法,利用构造函数来完成对象的初始化。
构造函数是一种特殊的成员函数,它主要用于为对象分配对象,进行初始化。构造函数的名字必须和类名相同,而不能由用户任意命名。它可以有任意类型的参数,但不能具有返回值类型。它不需要用户来调用,而是在建立对象时自动执行的。
在建立对象的同时,采用构造函数给成员赋初值,通常有以下两种形式。
形式1:
类名 对象名[(实参表)]
形式2:
类名 *指针变量名=new 类名[(实参表)]
说明:这时编译系统开辟了一段内存空间,并在此空间中存放了一个Complex类对象,同时调用了该类的构造函数给数据成员赋初值。这个对象没有名字,称为无名对象。但是该对象有地址,这个地址存放在指针变量pa中。访问用new动态建立的对象一般是不用对象的,而是通过指针访问。
注意:
(1)当new建立的对象使用结束时,不再需要它时,可以用delete运算符予以释放delete pa
。
(2)与普通成员函数一样,构造函数的函数体可写在类体内(系统将构造函数作为内联函数处理),也可以写在类体外。
(3)构造函数一般声明为公有函数,它是在定义对象的同时被自动调用的,而且只执行一次。
(4)如果没有给类定义构造函数,则编译系统自动地生成一个默认构造函数。(这个默认的构造函数不带任何参数,函数体是空的,它只能为对象开辟数据成员存储空间,而不能给对象中的数据成员赋值)
(5)构造函数可以不带参数,例如:
1 | Complex(){ //不带参数的构造函数 |
2. 用成员初始化列表对数据成员初始化
C++还提供另一种初始化数据成员的方法——用成员初始化列表对数据成员初始化。这种方法不在函数体内用赋值语句对数据成员初始化,而是在函数首部实现的。
带有成员初始化列表的构造函数的一般形式如下:
类名::构造函数名([参数表])[:(成员初始化列表)]{
//构造函数体
}
成员初始化列表的一般形式为:
数据成员名1(初始值1),数据成员名2(初始值2),…..
注意:
(1)对于用const
修饰的数据成员,或是引用类型的数据成员,是不允许用赋值语句直接赋值的。因此,只能用成员初始化列表对其进行初始化。
(2)数据成员是按照它们在类中声明的顺序进行初始化的,与它们在成员初始化列表中列出的顺序无关。
3. 构造函数的重载
与一般的成员函数一样,C++允许构造函数重载,以适应不同的场合。
注意:
(1)使用无参构造函数创建对象时,应该用语句“Date date1;”,而不能用语句“Date date1();”。因为语句“Date date1();”表示声明一个名为date1的普通函数,此函数的返回值为Date类型。
(2)如果在类中用户没有定义构造函数,系统会自动提供一个函数体为空的默认构造函数。但是,只要类中定义了一个构造函数(不一定是无参构造函数),系统将不再给它提供默认构造函数。
4. 带默认参数的构造函数
对于带参数的构造函数,在定义对象时必须给构造函数的形参传递参数的值,否则构造函数将不被执行。但在实际使用中,有些构造函数的参数值在大部分情况是相同的,只有在特殊情况下才需要改变它的参数值。
说明:
(1)如果构造函数在类的声明外定义,那么默认参数在类内声明构造函数原型时指定,而不能在类外构造函数定义时指定。因为类的声明是放在头文件中的,用户可以看到,而构造函数的定义是类的实现细节,用户往往看不到。因此,在声明时指定默认参数,可以保证用户在建立对象时知道怎么使用默认参数。
(2)如果构造函数的全部参数都指定了默认值,则在定义对象时可以指定1个或几个实参,也可以不给出实参,这时的构造函数也属于默认构造函数。因为一个类只能有一个默认构造函数,因此不能同时再声明无参数的默认构造函数。否则,编译系统将无法识别应该调用哪个构造函数,因此产生了二义性。
(3)在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。
1 | 例如在一个类中有以下构造函数的声明 |
因此,一般不要同时使用构造函数的重载和有默认参数的构造函数。
5. 析构函数
析构函数也是一种特殊的成员函数。它执行与构造函数相反的操作,通常用于执行一些清理任务,如释放分配给对象的内存空间等。析构函数有如下一些特点:
(1)析构函数名与类名相同,但它前面必须加一个波浪号(~)。
(2)析构函数不返回任何值。在定义析构函数时,是不能说明它的类型的,甚至说明为void也不行。
(3)析构函数没有参数,因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。
(4)撤销对象时,编译系统会自动地调用析构函数。
说明:
(1)每个类必须有一个析构函数。若没有显示地为一个类定义析构函数,则编译系统会自动地生成一个默认析构函数。**对于大多数而言,这个默认析构函数就能满足要求。但是,如果在一个对象撤销之前需要完成另外一些处理工作的话,则应该显示地定义析构函数。例如:
1 | class String_data{ |
这是构造函数和析构函数最常见的用法,即在构造函数中用运算符new为字符串分配存储空间,最后在析构函数中用运算符delete释放已分配的存储空间。
(2)除了在主函数结束(或调用exit函数)时,对象被撤销,系统会自动调用析构函数外,在以下情况,析构函数也会被调用:
- 如果一个对象被定义在一个函数体内,则当这个函数被调用结束时,该对象将释放,析构函数被自动调用。
- 若一个对象时使用new运算符动态创建的,在使用delete运算符释放它时,delete会自动调用析构函数。