C++语法02-构造函数和析构函数

1. 对象的初始化和构造函数

类是一种抽象的数据类型,它不占存储空间,不能容纳具体的数据。因此在类声明中不能给数据成员赋初值。与使用变量一样,使用对象时也应该先定义,后使用。在定义对象时,对数据成员赋初值,称为对象的初始化。在定义对象时,如果某一数据成员没有被赋值,则它的值是不可预知的。对象是一个实体,在使用对象时,它的每一个数据成员都应该有确定的值。

如果一个类中的所有成员,都是共有的,则可以在定义对象时对数据成员进行初始化。例如:

1
2
3
4
5
6
class Complex{
public:
double real;
double imag;
};
Complex c1={1.1,2.2};

说明:这种方法类似于结构体变量初始化的方法。但是,如果类中包含私有的或保护的成员时,就不能用这种方法进行初始化。除了调用公有成员函数来给数据成员赋值,C++提供了一个更好的方法,利用构造函数来完成对象的初始化。

构造函数是一种特殊的成员函数,它主要用于为对象分配对象,进行初始化。构造函数的名字必须和类名相同,而不能由用户任意命名。它可以有任意类型的参数,但不能具有返回值类型。它不需要用户来调用,而是在建立对象时自动执行的。

在建立对象的同时,采用构造函数给成员赋初值,通常有以下两种形式。

形式1:

类名 对象名[(实参表)]

形式2:

类名 *指针变量名=new 类名[(实参表)]

说明:这时编译系统开辟了一段内存空间,并在此空间中存放了一个Complex类对象,同时调用了该类的构造函数给数据成员赋初值。这个对象没有名字,称为无名对象。但是该对象有地址,这个地址存放在指针变量pa中。访问用new动态建立的对象一般是不用对象的,而是通过指针访问。

注意:

(1)当new建立的对象使用结束时,不再需要它时,可以用delete运算符予以释放delete pa

(2)与普通成员函数一样,构造函数的函数体可写在类体内(系统将构造函数作为内联函数处理),也可以写在类体外。

(3)构造函数一般声明为公有函数,它是在定义对象的同时被自动调用的,而且只执行一次。

(4)如果没有给类定义构造函数,则编译系统自动地生成一个默认构造函数。(这个默认的构造函数不带任何参数,函数体是空的,它只能为对象开辟数据成员存储空间,而不能给对象中的数据成员赋值)

(5)构造函数可以不带参数,例如:

1
2
3
4
5
6
Complex(){   //不带参数的构造函数
real=0;
imag=0;
}

Complex A; //定义类Complex的对象A时调用不要带参数,也不要要带括号!!!

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
2
3
4
5
6
例如在一个类中有以下构造函数的声明
Complex(double r=0.0, double i=0.0);
Complex(double r);

如果用以下语句定义对象
Complex S2(1.1); //无法判断应该调用以上哪个构造函数

因此,一般不要同时使用构造函数的重载和有默认参数的构造函数。

5. 析构函数

析构函数也是一种特殊的成员函数。它执行与构造函数相反的操作,通常用于执行一些清理任务,如释放分配给对象的内存空间等。析构函数有如下一些特点:

(1)析构函数名与类名相同,但它前面必须加一个波浪号(~)。

(2)析构函数不返回任何值。在定义析构函数时,是不能说明它的类型的,甚至说明为void也不行。

(3)析构函数没有参数,因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。

(4)撤销对象时,编译系统会自动地调用析构函数。

说明:

(1)每个类必须有一个析构函数。若没有显示地为一个类定义析构函数,则编译系统会自动地生成一个默认析构函数。**对于大多数而言,这个默认析构函数就能满足要求。但是,如果在一个对象撤销之前需要完成另外一些处理工作的话,则应该显示地定义析构函数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class String_data{
public:
String_data(char *s){
str=new char[strlen(s)+1];
strcpy(str, s);
}
~String_data(){
delete str;
}
void get_info(char*);
void sent_info(char*);
private:
char *str;
}

这是构造函数和析构函数最常见的用法,即在构造函数中用运算符new为字符串分配存储空间,最后在析构函数中用运算符delete释放已分配的存储空间。

(2)除了在主函数结束(或调用exit函数)时,对象被撤销,系统会自动调用析构函数外,在以下情况,析构函数也会被调用:

  • 如果一个对象被定义在一个函数体内,则当这个函数被调用结束时,该对象将释放,析构函数被自动调用。
  • 若一个对象时使用new运算符动态创建的,在使用delete运算符释放它时,delete会自动调用析构函数。
-------------The End-------------
0%