派生类的构造函数与析构函数
与一般成员函数不同,派生类并 不继承基类的构造函数和析构函数。在创建派生类对象时,系统要调用派生类的构造函数。由于派生类中 包含从基类继承来的成员和派生类中新声明的数据成员,因此在执行派生类构造函数体前,系统首先要 调用基类的构造函数对从基类继承来的数据成员初始化,而后再执行自己的函数体,对新增成员进行初始化。所以 派生类构造函数必须调用基类构造函数。当派生类对象的作用域结束时,要先执行派生类的析构函数,然后再调用基类的析构函数(与构造函数执行的顺序相反)。(栈顺序)
1. 派生类构造函数的定义
在派生类构造函数的定义中,要在函数体前调用基类构造函数以实现对继承自基类数据成员的初始化。调用语法为:调用基类 带指定实参的构造函数:
派生类名::派生类名(形参表):基类名(实参表)
{
新增成员初始化
}
调用基类的无实参构造函数:
派生类名::派生类名(形参表): 基类名()
{
新增成员初始化
}
注意:1)若省略对基类构造函数的显式调用,派生类构造函数将调用基类的无参构造函数。(无参构造函数时可省略 :基类名(实参表))2)在派生类的构造函数的 原型说明中不对基类构造函数的调用进行声明。例如,从Circle类中派生出Cylinder类,并增加构造函数的代码如下:
- //circle.h
- #ifndef CIRCLE_H
- #define CIRCLE_H
- const double pi=3.14;
- class Circle
- {
- public:
- Circle();
- Circle(double r);
- void setRadius(double r);
- double getRadius();
- double getArea();
- double getPerimeter();
- protected:
- double radius;//不设置成protected,radius成员不会继承的。想清楚继承关系。
- };
- #endif
- //cylinder.h
- #ifndef CYLINDER_H
- #define CYLINDER_H
- #include "circle.h"
- class Cylinder:public Circle//派生类的声明
- {
- public:
- Cylinder();
- Cylinder(double h);
- Cylinder(double r,double h);
- void setHeight(double h);
- double getHeight();
- double getSurf();
- double getVolume();
- protected:
- double height;
- private:
- };
- #endif
- //circle.cpp
- #include "circle.h"
- Circle::Circle()
- {
- radius=1;
- }
- Circle::Circle(double r)
- {
- radius=r;
- }
- void Circle::setRadius(double r)
- {
- radius=r;
- }
- double Circle::getRadius()
- {
- return radius;
- }
- double Circle::getArea()
- {
- return pi*radius*radius;
- }
- double Circle::getPerimeter()
- {
- return pi*2*radius;
- }
- //cylinder.cpp
- #include "circle.h"
- #include "cylinder.h"
- Cylinder::Cylinder()//调用基类的无参构造函数
- {
- height=1;
- }
- Cylinder::Cylinder(double h)//调用基类的无参构造函数
- {
- height=h;
- }
- Cylinder::Cylinder(double r,double h):Circle(r)//:基类明(实参表)
- // 调用基类带指定实参的构造函数:
- // 派生类名::派生类名( 形参表 ) : 基类名( 实参表 )
- //{
- // 新增成员初始化
- //}
- {
- height=h;//新增成员
- }
- double Cylinder::getHeight()
- {
- return height;
- }
- void Cylinder::setHeight(double h)
- {
- height=h;
- }
- double Cylinder::getSurf()
- {
- return 2*getArea()+getPerimeter()*height;//体现了继承的特点
- }
- double Cylinder::getVolume()
- {
- return getArea()*height;
- }
- //test.cpp
- #include "circle.h"
- #include "cylinder.h"
- #include <iostream>
- using namespace std;
- void main()
- {
- Cylinder a;
- Cylinder b(10);
- Cylinder c(15,20);
- cout<<a.getSurf()<<endl;
- cout<<b.getSurf()<<endl;
- cout<<c.getSurf()<<endl;
- }
由于Cylinder类构造函数中未显式调用Circle 类构造函数,此时编译器将自动调用基类Circle的无参构造函数, 此时Circle类中必须有不需要参数的构造函数,否则会出现语法错误。在派生类构造函数定义时,派生类构造函数名后面括号内的参数列表包括参数的类型和名称,而基类构造函数名后面括号内的参数表只有参数名,而无参数类型,因为这里是调用基类构造函数而不是定义基类构造函数,这些参数是 实参而不是形参。注意:在类中对派生类构造函数作声明时,不包括基类构造函数名及其参数表列, 只在定义函数时才将它列出。
派生类构造函数的定义与基类的构造函数有关:若基类中有不需要参数的构造函数(自定义了无参构造函数或没有定义构造函数或定义了带默认参数的构造函数), 此时派生类可不定义构造函数,或在派生类构造函数中省略对基类构造函数的显式调用。创建派生类对象时,系统会自动调用基类的无参构造函数,而后执行派生类构造函数的函数体。若基类只有需要提供参数的构造函数,则派生类 必须定义构造函数,且必须给出对基类构造函数的 显式调用,以 提供将参数传递给基类构造函数的途径。在有的情况下, 派生类构造函数体可能为空,仅起到参数传递作用。下面对两种情况进行分析。Case1:派生类可不定义构造函数或省略对基类构造函数的显式调用。
- // circle.h文件
- class Circle
- {
- protected:
- double radius;
- public:
- Circle();
- Circle(double);
- double getArea();
- double getPerimeter();
- double getRadius();
- void setRadius(double);
- };
- // cylinder.h文件
- #include "circle.h"
- class Cylinder: public Circle
- {
- protected:
- double height;
- public:
- Cylinder();
- double getSurfaceArea();
- double getVolume();
- double getHeight();
- void setHeight (double h);
- };
基类中有 无参构造函数,因此派生类 可不定义构造函数或省略对基类构造函数的显式调用,此时创建派生类对象时将 自动调用基类的无参构造函数。
- Cylinder::Cylinder()
- { height=1; }
等价于:
- Cylinder::Cylinder() :Circle()
- { height=1; }
Case2:若基类中只有有参的构造函数,则派生类必须定义构造函数,且必须显式调用基类构造函数。
- // circle.h文件
- class Circle
- {
- protected:
- double radius;
- public:
- Circle(double);
- double getArea();
- double getPerimeter();
- double getRadius();
- void setRadius(double);
- };
- // cylinder.h文件
- #include "circle.h"
- class Cylinder: public Circle
- {
- protected:
- double height;
- public:
- Cylinder();
- Cylinder(double h);
- Cylinder(double r,double h);
- double getSurfaceArea();
- double getVolume();
- double getHeight();
- void setHeight (double h);
- };
在实现派生类的三个构造函数时:
- Cylinder::Cylinder() :Circle(1)
- {
- height=1;
- }
- Cylinder::Cylinder(double h) :Circle(1)
- {
- height=h;
- }
- Cylinder::Cylinder(double r,double h):Circle(r)
- { height=h; }
对基类的构造函数进行了 显式调用。注意这一参数传递的方法。
派生类构造函数的函数体有时可为空,在下例中:
Circle类派生出 Sphere球类, Sphere的构造函数体为空, 仅起到参数传递作用。
- // circle.h文件
- class Circle
- {
- protected:
- double radius;
- public:
- Circle(double);
- double getArea();
- double getPerimeter();
- double getRadius();
- void setRadius(double);
- };
- // sphere.h文件
- #include "circle.h"
- class Sphere: public Circle
- {
- public:
- Sphere();
- Sphere(double);
- double getSurfaceArea();
- double getVolume();
- };
- //sphere.cpp
- Sphere::Sphere():Circle(1)
- { }
- Sphere::Sphere(double r):Circle(r)
- { }
- double Sphere::getSurfaceArea()
- {
- return 4*getArea();
- }
- double Sphere::getVolume()
- {
- return getSurfaceArea()/3*radius;
- }
2. 构造函数链和析构函数链
构造函数链constructor chaining:创建对象时,会导致继承链上的所有基类的构造函数都被依次调用。每个基类的构造函数都会先于其派生类的构造函数被调用。首先调用其基类的构造函数来初始化从基类中继承的数据成员;然后执行派生类自身的构造函数体,初始化在派生类中新增数据成员。析构函数链destructor chaining:所有析构函数都按与构造函数相反的顺序被自动调用,先执行派生类的析构函数,再执行基类的析构函数,其顺序与执行构造函数时的顺序正好相反。若派生类中还有对象成员,则 先调用基类构造函数后,还要调用对象成员所属类的构造函数。
若要将某个类设计为基类,最好为它 设计一个无参构造函数,以避免编程错误。例如:
- class Fruit
- {
- public:
- Fruit(int id)
- { }
- };
- class Apple : public Fruit
- {
- public:
- Apple() //error
- { }
- };
由于Apple是Fruit的派生类, Apple的构造函数会自动调用Fruit的无参构造函数,但是Fruit没有无参构造函数,因此会产生一个编译错误。
3. 避免基类重复定义
若从Circle类派生出Cylinder和Sphere类,那么在main函数中创建Cylinder和Sphere类的对象时,就会出现编译错误:Circle类的重复定义。编译器只需要在编译过程中对文件读入一次,当它看到#include语句时,就会读入相应文件。而如果编译器需要对同一个文件进行多次读入,那么它就会认为程序中重复定义了一些内容,即在一个程序中多次包含同一个头文件,此时会出现编译错误。如果一个文件需要被包含多次,那么就必须告诉编译器:如果你还没有读入该文件,那么请读入它;而如果已经读过了,就不用再读一次了。此时需要使用 条件编译命令。
#ifndef#define#endif