构造函数和析构函数我们在前面已经学过了,但是继承机制下的构造函数和析构函数又是如何调用以及定义的喃?上节我们没有讲构造函数和析构函数,就是这部分比较难,很多小伙伴不知道如何使用,所以我单独来讲解。
构造函数在派生类的生成过程中,派生类继承基类的大部分成员,但不继承基类的构造函数和析构函数。(包括拷贝构造函数);
继承机制下的构造函数的调用顺序派生类对象的数据结构与基类中声明的数据成员和派生类中声明的数据成员共同构成。由于构造函数不能被继承,所以,在定义派生类的构造函数时,除了对自己的对象初始化外,还要调用基类中的构造函数来初始化基类数据成员,这和初始化子对象有相似之处。派生之类的构造函数的一般格式为:
<派生类名>::<派生类>(<参数>):<基类>(<参数>),~
{
<派生类中成员的初始化>
}
当说明派生类的一个对象时,首先调用基类的构造函数,对基类成员进行初始化,然后再执行派生类的构造函数。如果某个基类仍是一个派生类,则这个过程递归进行。
#includeusing namespace std; class Baseclass { public: Baseclass(int i);//基类的构造函数; private: int a; }; Baseclass::Baseclass(int i) { a = i; cout << "constructing Baseclass a=" << a << endl; } class Derivedclass :public Baseclass { public: Derivedclass(int =0, int =0); private: int b; }; Derivedclass::Derivedclass(int i, int j) :Baseclass(i)//派生类的构造函数; { b = j; cout << "constructing Derivedclass b=" << b << endl; } int main() { Derivedclass x(5, 6); return 0; }
通过上面的代码例子以及运行结果,我们可以发现,先是基类的构造函数被调用,然后是派生类构造函数。那我们是不死就可以得到继承关系下的构造函数调用的顺序规则;
如果派生类还包括子对象,则对子对象的构造函数的调用,仍然在初始化列表中完成。此时,当说明派生类的一个对象时,首先基类构造函数被调用,子对象所在的类构造函数次之,最后执行派生类构造函数,在有多个子对象的情况下,子对象的调用顺序取决于他们在派生类中被说明的顺序。
下面我们来看一个例子,在包括子对象的时候,其构造函数的调用顺序:
//#include//using namespace std; //class Baseclass //{ //public: // Baseclass(int i);//基类的构造函数; //private: // int a; //}; //Baseclass::Baseclass(int i) //{ // a = i; // cout << "constructing Baseclass a=" << a << endl; //} //class Derivedclass :public Baseclass //{ //public: // Derivedclass(int =0, int =0); //private: // int b; //}; //Derivedclass::Derivedclass(int i, int j) :Baseclass(i)//派生类的构造函数; //{ // b = j; // cout << "constructing Derivedclass b=" << b << endl; //} //int main() //{ // Derivedclass x(5, 6); // return 0; //} #include using namespace std; class Base1 //基类 { public: Base1(int = 0); private: int a; }; Base1::Base1(int i) { a = i; cout << "constructing Base1 a=" << a << endl; } class Base2 //子对象f所属类; { public: Base2(int = 0); private: int b; }; Base2::Base2(int i) { b = i; cout << "constructing Base2 b=" < public: Base3(int =0); private: int c; }; Base3::Base3(int i) { c = i; cout << "constructing Base3 c=" << c << endl; } class Derivedclass :public Base1 //派生类 { public: Derivedclass(int = 0, int = 0, int = 0,int =0); private: int d; Base2 f; Base3 g; }; Derivedclass::Derivedclass(int i, int j, int k, int m) :Base1(i), g(i), f(k) { d = m; cout << "constructing Derivedclass d=" << d << endl; } int main() { Derivedclass x(5, 6, 7, 8); return 0; }
上面的程序我们可以看出,在构造函数的调用顺序中,先调用的是基类的构造函数,然后才是子对象的调用函数,然后是派生类的构造函数的调用;
注意:子对象的调用顺序只取决于它们在派生类中被说明的顺序,与它们在成员初始化列表的顺序有关。
多继承方式下派生类的构造函数序同时负责该派生类所有基类构造函数的调用。构造函数恶调用顺序是:先调用所有基类的构造函数,再调用派生类中子对象类的构造函数(如果派生类中有子对象),最后调用派生类的构造函数。处于同一层次的各基类构造函数的调用顺序取决于定义派生类所指定的基类顺序,与派生类构造函数中定义的成员初始化列表顺序无关。
#includeusing namespace std; class Base1 //基类 { public: Base1(int = 0);//基类构造函数; private: int a; }; Base1::Base1(int i) { a = i; cout << "constructing Base1 a=" < public: Base2(int = 0); //基类构造函数; private: int b; }; Base2::Base2(int i) { b = i; cout << "constructing Base2 b=" << b << endl; } class Derivedclass :public Base1, public Base2 //派生类;在派生类构造函数的调用顺序为定义的顺序; { public: Derivedclass(int = 0, int = 0, int = 0); private: int d; }; Derivedclass::Derivedclass(int i, int j, int k):Base2(i),Base1(j) { d = k; cout << "constructing d=" << d << endl; } int main() { Derivedclass x(4, 5, 6); return 0; }
通过上面的程序,我们可以看出,在多继承的机制下,基类构造函数的调用顺序取决于在派生类中定义的顺序,和在初始化成员列表顺序无关。但是,如果在有虚拟基类的情况下,调用顺序又是怎样的?
我们先来看一段代码吧。
#includeusing namespace std; class Base1 { public: Base1(); }; Base1::Base1() { cout << "constructing Base1" << endl; } class Base2 { public: Base2() { cout << "constructing Base2" << endl; } }; class Derived1 :public Base2, virtual public Base1 { public: Derived1() { cout << "constructing Derived1" << endl; } }; class Derived2 :public Base2, virtual public Base1 { public: Derived2() { cout << "constructing Derived2" << endl; } }; class Derived3 :public Derived1, virtual public Derived2 { public: Derived3() { cout << "construct Derived3" << endl; } }; int main() { Derived3 obj; return 0; }
如果派生类有一个虚基类作为祖先类,那么在派生类构造函数的初始化列表中需要列出对虚基类构造函数的调用,如果未列出则表明用的是虚基类的无参构造函数,不论成员初始化列表顺序如何,对虚基类的构造函数的调用总是先于普通基类的构造函数。
如果是在若干类层次中,从虚基类直接或间接派生出来的子类的构造函数初始化列表均有对该虚基类构造函数的调用,那么创建一个子类对象时只有该子类列出的虚基类的构造函数被调用,其他类列出的将被忽略,这样就保证虚基类的唯一副本只被初始化一次。
这个理解起来可能很难,我们可以通过上面的程序来理解,在派生类调用构造函数Derived1的时候,虚基类Base1没有被调用,表明,虚基类只能被调用一次,也就是副本只能被初始化一次。
下main,我们通过一张图来展示:
前面的例子中,都是在调用基类中的构造函数。实际上,在基类中定义有默认构造函数或者没有定义任何构造函数时,派生类构造函数中可以省略对基类构造函数的调用,既可以采用隐式调用。
派生类中有构造函数,基类没有#includeusing namespace std; class Baseclass { private: int a; }; class Derivedclass :public Baseclass { public: Derivedclass();//派生类默认我无参构造函数; Derivedclass(int i);//派生类有参构造函数; private: int b; }; Derivedclass::Derivedclass() { cout << "default constructor Derivedclass" << endl; } Derivedclass::Derivedclass(int i) { b = i; cout << "constructint Derivedclass b=" << b << endl; } int main() { Derivedclass x1(5); Derivedclass x2; return 0; }
通过对结果的分析,我们可以发现,基类的构造函数并没有被调用。
//#include派生类有构造函数,基类有默认构造函数//using namespace std; //class Base1 //{ //public: // Base1(); //}; //Base1::Base1() //{ // cout << "constructing Base1" << endl; //} //class Base2 //{ //public: // Base2() // { // cout << "constructing Base2" << endl; // } //}; //class Derived1 :public Base2, virtual public Base1 //{ //public: // Derived1() // { // cout << "constructing Derived1" << endl; // } //}; //class Derived2 :public Base2, virtual public Base1 //{ //public: // Derived2() // { // cout << "constructing Derived2" << endl; // } //}; //class Derived3 :public Derived1, virtual public Derived2 //{ //public: // Derived3() // { // cout << "construct Derived3" << endl; // } //}; //int main() //{ // Derived3 obj; // return 0; //} //#include //using namespace std; //class Baseclass //{ //private: // int a; //}; //class Derivedclass :public Baseclass //{ //public: // Derivedclass();//派生类默认我无参构造函数; // Derivedclass(int i);//派生类有参构造函数; //private: // int b; //}; //Derivedclass::Derivedclass() //{ // cout << "default constructor Derivedclass" << endl; //} //Derivedclass::Derivedclass(int i) //{ // b = i; // cout << "constructint Derivedclass b=" << b << endl; //} //int main() //{ // Derivedclass x1(5); // Derivedclass x2; // return 0; //} #include using namespace std; class Baseclass1 { public: Baseclass1();//不能不定义; { cout << "default constructing Baseclass1" << endl; } Baseclass1(int =0);//可以没有; private: int a; }; Baseclass1::Baseclass1(int i) { a = i; cout << "constructing Baseclass a=" << a << endl; } class Baseclass2 :public Baseclass1 { private: int b; }; int main() { Baseclass2 x; return 0; }
若派生类有构造函数,且基类有默认构造函数,则创建派生类对象的时候,基类的默认构造函数会自动调用。
#includeusing namespace std; class Baseclass { public: Baseclass() //无参构造函数; { cout << "default constructing Banseclass" << endl; } Baseclass(int i) //有参构造函数; { a = i; cout << "constructing Baseclass a=" << a << endl; } private: int a; }; class Derivedclass :public Baseclass { public: Derivedclass(int i); Derivedclass(int i, int j); private: int b; }; Derivedclass::Derivedclass(int i) //调用基类默认构造函数; { b = i; cout << "constructing Derivedclass b=" << b << endl; } Derivedclass::Derivedclass(int i, int j) :Baseclass(i) //调用基类有参构造函数; { b = j; cout << "constructing Derivedclass b=" << b << endl; } int main() { Derivedclass x1(5, 6); Derivedclass x2(7); return 0; }
我们可以看到,在基类有构造函数的时候,派生类定义对象的时候,基类构造函数也会被调用,他们的调用顺序就是我们前面讲的派生类与基类的调用关系,先调用基类构造函数。
若基类和派生类都有构造函数,但是基类没有默认构造函数的时候,派生类的每一个构造函数必须在其成员初始化列表中显示要被调用的基类构造函数,否则,基类构造函数不能准确获得执行机会;
#includeusing namespace std; class Baseclass { public: Baseclass(int i) //有参构造函数; { a = i; cout << "constructing Baseclass a=" << a << endl; } private: int a; }; class Derivedclass :public Baseclass { public: Derivedclass(int i); Derivedclass(int i, int j); private: int b; }; Derivedclass::Derivedclass(int i) //不能通过,没有显示调用基类构造函数; { b = i; cout << "constructing Derivedclass b=" << b << endl; } Derivedclass::Derivedclass(int i, int j) :Baseclass(i) //调用基类有参构造函数; { b = j; cout << "constructing Derivedclass b=" << b << endl; } int main() { Derivedclass x1(5, 6); Derivedclass x2(7); return 0; }
讲完了构造函数,下面我们来讲一下析构函数;
继承机制下析构函数的调用顺序由于析构函数也不能被继承,硬刺在执行派生类的析构函数的时候,也要调用其基类的析构函数。其执行顺序如下:
(1).先调用派生类的析构函数;
(2).再调用派生类中子对象的析构函数,(如果派生类中有子对象);
(3).再调用基类中的析构函数;
(4).最后是虚基类的析构函数;
我们会发现,析构函数的调用顺序刚好和构造函数的调用顺序相反。这也就验证了我们前面的类容,析构函数的调用是与构造函数的顺序相反的。
#includeusing namespace std; class Base1 { public: Base1(int i) //基类有参构造函数; { a = i; cout << "constructing Base1 a=" << a << endl; } ~Base1() //析构函数; { cout << "destructing Base1" << endl; } private: int a; }; class Base2 //子对象f所属类; { public: Base2(int i) { b = i; cout << "constructing Base2 b=" << b << endl; } ~Base2() //析构函数; { cout << "distructing Base2" << endl; } private: int b; }; class Base3 //子对象g所属类; { public: Base3(int i) //构造函数; { c = i; cout << "constructing Base3 c=" << c << endl; } ~Base3() //析构函数; { cout << "distructing Base3" << endl; } private: int c; }; class Derivedclass :public Base1 //派生类; { public: Derivedclass(int i, int j, int k, int m); ~Derivedclass();//析构函数; private: int d; Base2 f; Base3 g; }; Derivedclass::Derivedclass(int i, int j, int k, int m) :Base1(i), f(j), g(k) { d = m; cout << "constructing Derivedclass d=" << d << endl; } Derivedclass::~Derivedclass() { cout << "distruct Derivedclass" << endl; } int main() { Derivedclass x(5, 6, 7, 8); return 0; }
其中有一个地方,base3的析构函数调用顺序在Base2之前,原因是虽然先定义的Base2,但是析构函数的调用顺序是相反的,其实我们可以借助其它的概念理解,建房子的时候从地基开始,但是,修房子的时候,从房顶开始拆。
说了这么多,可能还是有很多人不知道如何去使用,没有分清,下面我们借助一个应用实例来分析:
#include私有继承using namespace std; class Point { public: Point(int myx, int myy) { x = myx, y = myy; } void displayxy() { cout << "The position of center" << endl; cout << "(" << x << "," << y << ")" << endl; } protected: int x, y; //不能定义为私有成员,最好为保护成员; }; class Circle :public Point //定义派生类,公有继承; { public: Circle(int myx, int myy, int myr) :Point(myx, myy) //构造函数; { r = myr; } void displayr() { cout << "The radius of circle:" << r << endl; } private: int r; }; class Cylinder :public Circle //定义派生类,公有继承; { public: Cylinder(int myx, int myy, int myr, int myh) :Circle(myx, myy, myr) { h = myh; } void displayh() { cout << "The height of cylinder:" << h << endl; } private: int h; }; //测试函数; int main() { Cylinder v(4, 5, 6, 7);//派生类对象; cout << "The data of cylinder" << endl; v.displayxy(); v.displayr(); v.displayh(); return 0; }
私有继承的时候,基类中的公有成员不能被外模块使用,需要在派生类中增加新的接口;
例如:
#includeusing namespace std; class Point { public: Point(int myx, int myy) { x = myx, y = myy; } void displayxy() { cout << "The position of center" << endl; cout << "(" << x << "," << y << ")" << endl; } protected: int x, y; //不能定义为私有成员,最好为保护成员; }; class Circle :private Point //定义派生类,公有继承; { public: Circle(int myx, int myy, int myr) :Point(myx, myy) //构造函数; { r = myr; } void displayxy() { Point::displayxy(); } void displayr() { cout << "The radius of circle:" << r << endl; } private: int r; }; class Cylinder :public Circle //定义派生类,公有继承; { public: Cylinder(int myx, int myy, int myr, int myh) :Circle(myx, myy, myr) { h = myh; } void displayh() { cout << "The height of cylinder:" << h << endl; } private: int h; }; //测试函数; int main() { Cylinder v(4, 5, 6, 7);//派生类对象; cout << "The data of cylinder" << endl; v.displayxy(); v.displayr(); v.displayh(); return 0; }
上面的代码,把Point定义为私有继承,所以下面我们对程序也进行了增加接口的处理,私有继承,其实可以理解为,把基类Point作为派生类中的私有成员;



