(1.4.1)虚继承、虚函数继承、多重继承和“含对象成员类的构造顺序”

构造函数不可以为虚函数,因为构造函数不能被继承。

析构函数可以为虚函数。

内联函数不可以为虚函数。

静态成员函数不能为虚函数。

只有类的成员函数才能为虚函数。


继承会导致子类保存一个父类的副本,也就是说子类至少要有父类那么大

1、多次继承:

被多次继承的基类的构造函数会被多次执行

2、虚函数继承:

----fn为普通函数,A指针指向它,就调用A的函数

----vfn为虚函数,编译时不能确定,由A指针指向的类B决定,B为父类则调用父类的,B为子类则调用子类的

3、虚继承

------sizeof   子类继承父类,子类持有父类的副本

------sizeof   子类虚继承父类,子类持有父类的副本+一个虚指针

------sizeof   子类继承n个“虚继承父类的子类”,子类持有最顶级的父类副本一个+n个虚指针

4、对象成员和构造函数

先对象成员,后自身的构造函数

5、虚继承

首先执行初始基类的构造函数,多个初始基类的构造函数按照被继承的顺序构造;

执行虚继承初始基类的类的构造函数,多个虚继承类的构造函数按照被继承的顺序构造;

执行成员对象的构造函数,多个成员对象的构造函数按照申明的顺序构造;

执行派生类自己的构造函数;

析构以与构造相反的顺序执行;

mark

从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。

在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。


(0)虚函数继承

 

复制代码
#include <stdio.h>

class A {
public:
    void fn() { printf("fn in A\n"); }
    virtual void v_fn() { printf("virtual fn in A\n"); }
};

class B : public A {
public:
    void fn() { printf("fn in B\n"); }
    virtual void v_fn() { printf("virtual fn in B\n"); }
};

int main() {
    A *a = new B();   //实例一个子类对象,用父类指针指向它
    a->fn();          //fn为普通函数,A指针指向它,就调用A的函数    “fn in A"
    a->v_fn();        //V_fn为虚函数,编译时不能确定,A指针指向的子类B中v_fn函数  "virtual fn in B"
    return 0;
}
复制代码

  基类A有两个成员函数fn和v_fn,派生类B继承自基类A,同样实现了两个函数,然后在main函数中用A的指针指向B的实例(向上转型,也是实现多态的必要手段),然后分别调用fn和v_fn函数。结果是“fn in A"和"virtual fn in B"。这是因为fn是普通成员函数,它是通过类A的指针调用的,所以在编译的时候就确定了调用A的fn函数。而v_fn是虚函数,编译时不能确定,而是在运行时再通过一些机制来调用指针所指向的实例(B的实例)中的v_fn函数。假如派生类B中没有实现(完全一样,不是重载)v_fn这个函数,那么依然会调用基类类A中的v_fn;如果它实现了,就可以说派生类B覆盖了基类A中的v_fn这个虚函数。这就是虚函数的表现和使用,只有通过虚函数,才能实现面向对象语言中的多态性。


(1)虚函数继承

#include "stdafx.h"
#include "stdio.h"
#include "string.h"


class Father
{
public:
	name()
	{printf("father name\n");};
	
	virtual call()
	{printf("father call\n");};
	
};



class Son: public Father
{
public:
	name()
	{printf("Son name\n");};
	
	virtual call()
	{printf("Son call\n");};
	
};

main()
{
	
	Son *Son1=new Son();                  //实例一个子类
	Father *father1=(Father *)Son1;     //子类指针转换为父类
	
	father1->call();                      //call虚函数,编译时不能确定,由father指针指向的子类son决定,子类实现call函数,Son call
	father1->name();                    //name为普通函数,father指针调用,则调用father的name函数,father name

	((Son *)(father1))->call();           //虚函数,son指针指向的father指向的son对象,son的call,Son call
	((Son *)(father1))->name();        //普通函数,son指针,调用son的name函数,Son name

	Father *f2=new Father();          //实例一个父类
	Son *s2=(Son*)f2;                  //父类指针强转为子类指针,f2还是指向父类实例,father call
	s2->call();                             //虚函数,编译时不能确认,由son指针指向的父类决定,父类的call,father call
	s2->name();                           //普通函数,son指针调用,调用son的name,Son name
	
	((Father *)(s2))->call();          //虚函数,编译时不能确认,父类指针指向的子类指针指向的父类,调用父类call,father call 
	((Father *)(s2))->name();       //普通函数,father指针,调用father的name,father name
}
output::
Son call
father name
Son call
Son name
father call
Son name
father call
father name

(2)虚函数继承

一、父类不是虚的,你个儿子再虚也不行,还是没你老子坚挺!

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class A  
  5. {  
  6. public :  
  7.     void find()  
  8.     {  
  9.         cout<<"find::A"<<endl;  
  10.     }  
  11.   
  12.     virtual void get()  
  13.     {  
  14.         cout<<"get::A"<<endl;  
  15.     }  
  16. };  
  17.   
  18. class B:public A  
  19. {  
  20.     public :  
  21.     void find()  
  22.     {  
  23.         cout<<"find::B"<<endl;  
  24.     }  
  25.   
  26.     virtual void get()  
  27.     {  
  28.         cout<<"get::B"<<endl;  
  29.     }  
  30. };  
  31.   
  32. int main()  
  33. {  
  34.     A *a=new B();    // 实例子类,父指针指向它
  35.     a->find();        //普通函数,a指针指向它,调用a类的find  find A
  36.     a->get();         //虚函数,编译时不能确认,由a指针只想的子类B决定,调用B的get,get B
  37.     return 0;  
  38. }  

大家看上面代码,会输出什么呢?

答案是:

find::A

get::B

假如我们把上面的代码:A中的get()方法去掉virtual关键字,改为一般的函数。结果又是如何的呢?

答案:

find::A

get::A      


(3)虚函数继承

  1. class A  
  2. {  
  3. public :  
  4.     virtual void get()  
  5.     {  
  6.         cout<<"A"<<endl;  
  7.     }  
  8. };  
  9.   
  10. class B:public A  
  11. {  
  12.     public :  
  13.   
  14.     virtual void get()  
  15.     {  
  16.         cout<<"B"<<endl;  
  17.     }  
  18. };  
  19.   
  20. int main()  
  21. {  
  22.     A *pa=new A();    
  23.     pa->get();      //A
  24.     B *pb=(B*)pa;   //B的指针指向A
  25.     pb->get();       //get为虚函数,编译时不能确认,由pb指针指向的父类A决定,调用A的get  A
  26.   
  27.     delete pa,pb;  
  28.     pa=new B();     //pa指向一个B的实例
  29.     pa->get();      //get为虚函数,编译时不能确认,由pa指向的子类B决定   B
  30.     pb=(B*)pa;      //B
  31.     pb->get();       //get为虚函数,编译时不能确认,由pb指向的子类B决定  B
  32.     return 0;  
  33. }  

输出: A A B B

以上两个例子都是关于虚函数覆盖虚函数的问题。 B *pb=(B*)pa; 这个我们看到强制转换,但是pa这个指针并没有发生改变,所指向的内容也没有发生改变,所以我们知道pb指向的依然是pa所指向的内容。

delete pa,pb。删除了pa,pb所指向的地址,但是pa,pb并没有被删除,属于悬挂指针。我们重新给pa赋值,指向B,故pa->get();输出了B



(4)虚继承示例

  1. // MicroTest.cpp : Defines the entry point for the console application.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <iostream>  
  6. using namespace std;  
  7.   
  8. class A  
  9. {  
  10. public:  
  11.     void getA()  
  12.     {  
  13.         cout<<"A";  
  14.     }  
  15.   
  16.     virtual void getAA()  
  17.     {  
  18.         cout<<"1";  
  19.     }  
  20. };  
  21.   
  22. class B:public A  
  23. {  
  24. public:  
  25.   
  26.     virtual void getA()  
  27.     {  
  28.         cout<<"B";  
  29.     }  
  30.   
  31.     virtual void getAA()  
  32.     {  
  33.         cout<<"2";  
  34.     }  
  35. };  
  36.   
  37. class C:virtual public A  
  38. {  
  39. public:  
  40.   
  41.     void getA()  
  42.     {  
  43.         cout<<"C";  
  44.     }  
  45.   
  46.     virtual void getAA()  
  47.     {  
  48.         cout<<"3";  
  49.     }  
  50. };  
  51. int main(int argc, char* argv[])  
  52. {  
  53.     printf("Hello World!\n");  
  54.     A *a=new B();  
  55.     A *b=new C();  
  56.   
  57.     a->getA();  //普通函数,A指针指向它,调用a的getA   A
  58.     a->getAA();  //虚函数,编译时不能确定,由A指针指向的B决定,B为子类,2
  59.   
  60.     b->getA();  //普通函数,A指针指向它,调用a的getA   A
  61.     b->getAA();  //虚函数,编译时不能确定,由A指针指向的C决定,C为子类,3
  62.   
  63.     return 0;  
  64. }  

这个会输出什么呢?

答案是:A2A3

(4-1)虚继承示例

 1: //-----------------------------------------------------  
 2: //名称:blog_virtual_inherit.cpp  
 
3: //说明:C++虚拟继承学习演示  
 4: //环境:VS2005  
 5: //blog:pppboy.blog.163.com  
 6: //----------------------------------------------------  
 7: #include "stdafx.h"  
 8: #include <iostream>
 9: using namespace std;  
 10:
 11: int gFlag = 0;  
 12:
 13: class Base  
 14: {
 15: public:  
 16: Base(){cout << "Base called : " << gFlag++ << endl;}  
 17: void print(){cout << "Base print" <<endl;}  
 18: };
 19:
 20: class Mid1 : virtual public Base  
 21: {
 22: public:  
 23: Mid1(){cout << "Mid1 called" << endl;}  
 24: private:  
 25: };
 26:
 27: class Mid2 : virtual public Base  
 28: {
 29: public:  
 30: Mid2(){cout << "Mid2 called" << endl;}  
 31: };
 32:
 33: class Child:public Mid1, public Mid2  
 34: {
 35: public:  
 36: Child(){cout << "Child called" << endl;}  
 37: };
 38:
 39: int main(int argc, char* argv[])  
 40: {
 41: Child d;
 42:
 43: //这里可以这样使用  
 44: d.print();
 45:
 46: //也可以这样使用  
 47: d.Mid1::print();
 48: d.Mid2::print();
 49:
 50: system("pause");  
 51: return 0;  
 52: }
 53:
 1: Base called : 0   //child d;   child继承mid1,mid1继承base,先执行base的构造函数
 2: Mid1 called       //child d;   再执行mid1的构造函数
    Base called : 1   //由于为虚继承,只需要装填一次基类base,此处不需要在执行
 3: Mid2 called      //child d;    child还继承mid2,执行mid2的构造函数
 4: Child called    //最后执行child的构造函数
 5: Base print     
 6: Base print
 7: Base print
 8: 请按任意键继续. . .

(5)虚继承的sizeof

复制代码
#include <stdio.h>

class A {//4
public:
    int a;
};

class B : virtual public A {//4+4+4
public:
  int b; };
class C : virtual public B {//12+4 };
class D : public B, public C{//副本a4+两个虚指针4*2=12
};
int main() { printf("%d\n", sizeof(C)); //16
    printf("%d\n", sizeof(D));  //12
return 0; }
复制代码

  程序运行的结果是16,按照之前的理论,大概会这么想。基类A里有1个变量,4个字节。B类虚继承了A,所以它有一个A的副本和一个vbtable,还有自己的一个变量,那就是12字节。然后C类又虚继承了B类,那么它有一个B的副本,一个vbtable,16字节。但实际上通过调试和反汇编发现,C中保存分别保存了A和B的副本(不包括B类的vbtable),8字节。然后有一个vbtable指针,4字节,表里面包含了A副本和B副本的偏移量。最后还有一个无用的4字节(?),一共16字节。不仅是这样,每经过一层的虚继承,便会多出4字节。这个多出来的四字节在反汇编中没发现实际用途,所以这个有待探讨,不管是编译器不够智能,还是有待其它作用,虚继承和多重继承都应该谨慎使用。

D从B,C类派生出来,而B和C又同时虚继承了A。输出的结构是12,实际调试反汇编的时候发现,D中继承了B和C的vbtable,这就是8字节,而同时还保存了一个A的副本,4字节,总共12字节。它和上面的多重虚继承例子里的12字节是不一样的。之前一个例子中只有一个vbtable,一个A的实例,末尾还有一个未知的4字节。而这个例子中是有两个仅挨着的vbtable(都有效)和一个A的实例。


(6)多重继承

1: //-----------------------------------------------------  
 2: //名称:blog_virtual_inherit.cpp  
 
3: //说明:C++虚拟继承学习演示  
 4: //环境:VS2005  
 5: //blog:pppboy.blog.163.com  
 6: //----------------------------------------------------  
 7: #include "stdafx.h"  
 8: #include <iostream>
 9: using namespace std;  
 10:
 11: int gFlag = 0;  
 12:
 13: class Base  
 14: {
 15: public:  
 16: Base(){cout << "Base called : " << gFlag++ << endl;}  
 17: void print(){cout << "Base print" <<endl;}  
 18: };
 19:
 20: class Mid1 : public Base  
 21: {
 22: public:  
 23: Mid1(){cout << "Mid1 called" << endl;}  
 24: private:  
 25: };
 26:
 27: class Mid2 : public Base  
 28: {
 29: public:  
 30: Mid2(){cout << "Mid2 called" << endl;}  
 31: };
 32:
 33: class Child:public Mid1, public Mid2  
 34: {
 35: public:  
 36: Child(){cout << "Child called" << endl;}  
 37: };
 38:
 39: int main(int argc, char* argv[])  
 40: {
 41: Child d;
 42:
 43: //不能这样使用,会产生二意性  
     //d.print();  
 45:
 46: //只能这样使用  
 47: d.Mid1::print();
 48: d.Mid2::print();
 49:
 50: system("pause");  
 51: return 0;  
 52: }
 53:

 Base called : 0    //child d;   child继承mid1,mid1继承base,先执行base的构造函数
 Mid1 called        //child d;   再执行mid1的构造函数
 Base called : 1   //child d;    child还继承mid2,mid2继承base,再次执行base的构造函数
 Mid2 called        //child d;   再执行mid2的构造函数
 Child called       //child d;    最后执行child的构造函数
 Base print
 Base print

(7)“含对象成员类的构造顺序”

#include <iostream>

using namespace std;

class A
{
private:
 int m;
public:
 A(){ cout<<"In A's constructor..."<<endl;}
 ~A(){ cout<<"In A's destructor..."<<endl;}
};
class B
{
private:
 A a;
 int n;
public:
 B(){cout<<"In B's constructor..."<<endl;}
 ~B(){cout<<"In B's destructor..."<<endl;}
};
class C
{
private:
 B b;
 A a;
 int p;
public:
 C():a(),b(){cout<<"In C's constructor..."<<endl;}
 ~C(){cout<<"In C's destructor..."<<endl;}
};

void main()

{

C c;

cout<<"back in main..."<<endl;
}

1、当主函数运行时,遇到要创建C类的对象,于是调用其构造函数C(),该构造函数启动时,首先分配对象空间(包含一个A对象、一个B对象和一个int型数据),然后根据在类中声明的对象成员的次序依次调用其其构造函数(而不是根据它们在成员初始化列表中的说明次序进行调用)。这里先调用B对象的构造函数(B类中包含A类的对象成员,所以也需要先调用A类的构造函数),再调用A类的构造函数,最后调用C类的构造函数。析构函数的调用次序是:先调用本身类的析构函数,再调用成员对象类的析构函数。如果有多个成员对象,则成员对象析构函数的调用次序与它们的构造函数的调用次序正好相反。
2、与变量定义类似,在用默认构造函数创建对象时,如果创建的对象时全局的或者时静态的,则对象的位模式全为0,否则对象值时随机的。

输出:

   In A's constructor...
   In B's constructor...
   In A's constructor...
   In C's constructor...
   back in main...
   In C's destructor...
   In A's destructor...
   In B's destructor...
   In A's destructor...



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值