在继承关系里面, 在派生类中如果没有显示定义这六个成员 函数, 编译系统则会默认合成这六个默认的成员函数。
构造函数。
调用关系先看一段代码:
- 1 class Base
- 2 {
- 3 public :
- 4 Base()
- 5 {
- 6cout <<"B() "<< endl;
- 7}~ 8 Base()
- 9 {
- 10cout <<"~B() "<< endl;
- 11 }
- 12 private:
- 13 int _pri;
- 14 protected:
- 15 int _pro;
- 16 public:
- 17 int _pub;
- 18 };
- 19 classDerived :public Base
- 20 {
- 21 public :
- 22 Derived()
- 23 {
- 24cout <<"D() "<< endl;
- 25 }
- 26~Derived()
- 27 {
- 28cout <<"~D() "<< endl;
- 29 }
- 30 private:
- 31 int _d_pri;
- 32 protected:
- 33 int _d_pro;
- 34 public:
- 35 int _d_pub;
- 36 };
- 37 void Test()
- 38 {
- 39 Derived d;
- 40 }
- 41 int main()
- 42 {
- 43 Test();
- 44 getchar();
- 45 return 0;
- 46}
输出结果为: 代码中,我们利用派生类 Derived,创建了一个对象 d,根据输出结果看到,貌似创建对象 d 的过程是:先调用基类的构造函数,再调用子类的构造函数;而析构对象时先调用子类的析构函数,再调用基类的析构函数。但是我们不能被表象所迷惑,我们转到反汇编来看看具体是怎么实现的:我们看到创建对象 d 的时候是先调用 Derived 类的析构函数 D(),但是在 cout <<"D()" <<endl; 这句代码执行之前,编译器还做了一堆的其他工作,其中最重要的是 call Base::Base (0E41041h) 这条指令它跳转到了基类中,如图示:屏幕上输出:B() 、D() 即先执行 cout <<"B() "<<endl; 语句,再执行 cout <<"D()" <<endl; 语句,所以,才有屏幕上的结果。在析构对象 d 的时候先调用~ D() 但是没那么简单,再看图中在语句 cout <<"~D() "<<endl; 执行完之后用掉 call Base::~Base (0E410D7h) 指令跳转到~ B() 中 此时~ D() 输出,接着~ B() 输出然后,~D() 才算执行完。
分析:基类是派生类的一部分,创建派生类对象时必须调用派生类构造函数,而派生类构造函数必须使用基类的构造函数。程序首先创建基类对象,所以基类对象在程序进入派生类构造函数之前被创建。实际上 C++ 使用成员初始化列表语法来完成这项工作,即 B() 相当于在函数 D() 的初始化列表中被使用,如果不调用基类构造函数,程序将使用默认的基类构造函数。在执行完 B() 的函数体之后,继承的数据成员被初始化,执行 D() 函数体初始化新增的数据成员。析构对象时,先调用派生类的析构函数,执行完函数体析构完新增部分之后,使用基类的析构函数析构继承自基类的部分。所以才有上述现象。调用关系总结如下图:
总结:创建派生类对象时程序调用派生类构造函数,然后在初始化列表部分调用基类构造函数初始化继承的数据成员,而派生类构造函数主要初始化新增的数据成员。派生类总是调用一个基类构造函数。可以使用初始化列表语法指明要使用的基类构造函数,否则将使用默认的基类构造函数。派生类对象过期时,程序将先调用派生类析构函数,在函数体执行完之后调用基类析构函数。(可以看到,继承的数据成员生命周期长, 新增的数据成员生命周期短。)
构造函数带参情况
构造派生对象时,派生类构造函数默认调用参数缺省的基类构造函数,若基类构造函数带有参数,则派生类中必须显式定义构造函数,并在初始化列表中传参。本例中,若 B() 带有参数,则 D() 中必须显式定义构造函数并传参;如图,B() 带有参数,则 D() 中构造函数无参时编译不能通过。传参之后可以编译用过。
当基类中显示定义构造函数,而派生类中没有定义构造函数,则使用默认合成的派生类构造函数,并在默认合成的派生类构造函数调用基类构造函数。即基类 Base 显式定义了构造函数,而派生类 Derived 中没有定义,则用默认合成的构造函数 D() 实例化对象,且在初始化参数列表部分调用基类构造函数 B()。
同理当派生类中显示定义构造函数,而基类中没有定义构造函数,则在派生类构造函数中调用默认合成的基类构造函数。
使用拷贝构造函数的情况有:
运行成功,输出结果为:这里我没有给基类定义拷贝构造函数,但是编译器自动给基类生成了一个拷贝构造函数,因为我基类中定义的没有指针成员,所以浅拷贝可以满足我的要求,但是如果在基类成员中有指针变量,必须要进行显式定义拷贝构造函数,即进行深拷贝。不然会造成同一块内存空间被析构两次的问题。
- 1 class Base
- 2 {
- 3 public :
- 4 Base()
- 5 {
- 6cout <<"B() "<< endl;
- 7 }
- 8~Base()
- 9 {
- 10cout <<"~B() "<< endl;
- 11 }
- 12 private:
- 13 int _pri;
- 14 protected:
- 15 int _pro;
- 16 public:
- 17 int _pub;
- 18 };
- 19 classDerived :public Base
- 20 {
- 21 public :
- 22 Derived()
- 23 {
- 24cout <<"D() "<< endl;
- 25 }
- 26Derived(constBase &tp)
- 27:Base(tp)//拷贝构造函数
- 28 {
- 29cout <<"Derive()"<< endl;
- 30 }
- 31~Derived()
- 32 {
- 33cout <<"~D() "<< endl;
- 34 }
- 35 private:
- 36 int _d_pri;
- 37 protected:
- 38 int _d_pro;
- 39 public:
- 40 int _pub;
- 41 };
- 42 void Test()
- 43 {
- 44 Derived d;
- 45 Derived i(d);
- 46 }
- 47 int main()
- 48 {
- 49 Test();
- 50 getchar();
- 51 return 0;
- 52}
默认的赋值操作符用于处理同类对象之间的赋值,赋值不是初始化,如果语句创建新的对象,则使用初始化,如果语句修改已有对象的值,则为赋值。 赋值运算符是不能被继承的,原因很简单。派生类继承的方法的特征与基类完全相同,但赋值操作符的特征随类而异,因为它包含一个类型为其所属类的形参。 如果编译器发现程序将一个对象赋给同一个类的另一个对象,它将自动为这个类提供一个赋值操作符。这个操作符的默认版本将采用成员赋值,即将原对象的相应成员赋给目标对象的每个成员。 如果对象属于派生类,编译器将使用基类赋值操作符来处理派生对象中基类部分的赋值,如果显示的为基类提供了赋值操作符,将使用该操作符。注意:赋值运算和拷贝构造是不同的,赋值是赋值给一个已有对象,拷贝构造是构造一个全新的对象
- 1 class Base
- 2 {};
- 3 int main()
- 4 {
- 5 Base a;
- 6Base b = a;//初始化
- 7 Base c;
- 8c = a;//赋值
- 9}
将派生类对象赋给基类对象时。
- 1 class Base
- 2 {
- 3 public:
- 4 int data;
- 5 };
- 6 classDerive:public Base
- 7 {
- 8 public:
- 9 int d;
- 10 };
- 11 int main()
- 12 {
- 13 Base a;
- 14 Derive dd;
- 15a = dd;
- 16}
上面的 a=dd;语句将使用谁的赋值操作符呢。 实际上,赋值语句将被转换成左边的对象调用的一个方法
- a.operator = (dd); //左边的为基类对象
简而言之,可以将派生对象赋给基类对象,但这只涉及到基类的成员。如图示
基类对象赋给派生类对象。
- 1 class Base
- 2 {
- 3 public:
- 4 int data;
- 5 };
- 6 classDerive:public Base
- 7 {
- 8 public:
- 9 int d;
- 10 };
- 11 int main()
- 12 {
- 13 Base a;
- 14 Derive dd;
- 15dd = a;
- 16}
上述赋值语句将被转换为:
- d.operator = (a); //Derive::operator=(const Derive&)左边的对象为派生类对象,不过派生类引用不能自动引用基类对象,所以上述代码不能运行。或者运行出错。除非有函数Derive(const Base&){}
- 总结:
来源: http://www.cnblogs.com/33debug/p/6685551.html