菱形继承(虚函数)->菱形虚拟继承(虚函数)->多态系列问题

读者注意:阅读这篇文章时,对继承中的对象模型要有一定了解;因为本篇文章有的例子没有给出类的成员变量,如果不了解继承的对象模型,琢磨起来可能会很慢,觉得自己不确定的话单击下面的“继承”。

继承

多态

多态按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。直白点理解就是不同的对象收到相同的消息时,产生不同的动作。(随风倒)

多态可以分为静态多态和动态多态。
静态多态:是在编译器期间完成的,也叫早期绑定;
动态多态:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法,也叫动态绑定。

动态多态的构成条件:要有虚函数,并且一定要重写,一个在基类,一个在派生类。(虚函数的函数原型、返回值、函数名、参数列表都必须一样),必须通过基类类型的引用或者指针调用虚函数。

举个简单的例子:

class A
{
public:
    A(int x)//构造函数
    {
        a = x;
    }
    virtual void print()//虚函数
    {
        cout << "A::" << a << endl;
    }
private:
    int a;
};

class B :public A
{
public:
    B(int x, int y)
        :A(x)
    {
        b = y;
    }
    virtual void print()//重写
    {
        cout << "B::" << b << endl;
    }
private:
    int b;
};

void test()
{
    A a(10);
    B b(15,20);
    A *c = &a;
    c->print();
    c = &b;
    c->print();

}

这里写图片描述

有一个例外:协变;返回值类型不同(基类里面返回基类的引用或指针,派生类里面返回派生类的引用或指针)

重载&同名隐藏&重写三者的区别

重载:重载只能是类的成员函数,必须在同一个作用域内(也就是同一个类里),成员函数的名字必须相同,参数列表(个数,顺序,类型)可以不同。

同名隐藏:只要成员(可以使成员变量也可以是成员函数)的名字一样(一个在基类,一个在派生类),和参数列表、返回值没有关系,并且要用派生类的对象来调用同名成员。

重写:有虚函数,并且一个在基类,一个在派生类,函数的原型相同(协变除外)

哪些成员函数可以作为虚函数,可不可采用?哪些不可以作为虚函数?

1.构造函数(拷贝构造)
不可以,因为有了构造函数才能构造出对象,对象中才能有虚表;而虚函数先调用虚表,在找到虚表中的构造函数;两者前后矛盾。

2.内联函数
不可以,原因其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)

3.静态成员函数
不可以,因为静态成员函数是全局的,对于所有的类来说,是一份公共的代码,它不归某个具体对象所有,所以他也没有要动态邦定的必要性。

4.友元函数
不可以,因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

5.赋值运算符重载
可以,但是不采用,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆。

6.析构函数
可以,而且最好将基类的析构函数声明为虚函数。因为析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理,减少了空间开销。

继承

在上一篇博客中我们提到了继承的对象模型,这一次我们依然谈到继承的对象模型,只不过这次在类种新增了虚函数。说到虚函数,我们先剖析一下,举个简单的例子:

class A
{
public:
    virtual void Funtest1()
    {
        cout << "A::Funtest1" << endl;
    }
    virtual void Funtest2()
    {
        cout << "A::Funtest2" << endl;
    }
    virtual void Funtest3()
    {
        cout << "A::Funtest3" << endl;
    }
    virtual void Funtest4()
    {
        cout << "A::Funtest4" << endl;
    }
};

typedef void(*PVTF)();

void PrintVpt()
{
    A a;
    PVTF* pVtf = (PVTF*)(*(int*)&a);

    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
}
void test()
{
    cout << sizeof(A) << endl;

    PrintVpt();
}

这里写图片描述

也就是说在一个类里定义了虚函数,那么创建实例化对象之后,编译器会创建一张虚表,对象的前四个字节存放虚表的地址,虚表里存放各个虚函数的地址。

单继承

第一种情况:派生类中没有重写虚函数,也没有自己的虚函数,则直接继承基类的虚函数,派生类会重新生成一张虚表,基类里的虚表不会继承下来。(派生类中的虚表地址和基类的虚表地址不一样,虚表里地址也不一样,但是虚函数是一样的)

class A
{
public:
    virtual void Funtest1()
    {
        cout << "A::Funtest1" << endl;
    }
    virtual void Funtest2()
    {
        cout << "A::Funtest2" << endl;
    }
    virtual void Funtest3()
    {
        cout << "A::Funtest3" << endl;
    }
    virtual void Funtest4()
    {
        cout << "A::Funtest4" << endl;
    }
};

class B :public A
{

};
typedef void(*PVTF)();//函数指针

void PrintVpt()//打印虚表
{
    B b;
    PVTF* pVtf = (PVTF*)(*(int*)&b);

    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
}
void test()
{
    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl;

    PrintVpt();
}

这里写图片描述

这里写图片描述

第二种情况:派生类中重写了基类中的某个虚函数并且还有自己的虚函数。

如果在派生类中重写了基类里的虚函数,用派生类重写后的虚函数代替基类里的虚函数。

如果派生类定义了新的虚函数,按照声明顺序放在基类虚函数的后面。

class A
{
public:
    virtual void Funtest1()
    {
        cout << "A::Funtest1" << endl;
    }
    virtual void Funtest2()
    {
        cout << "A::Funtest2" << endl;
    }
    virtual void Funtest3()
    {
        cout << "A::Funtest3" << endl;
    }
    virtual void Funtest4()
    {
        cout << "A::Funtest4" << endl;
    }
};

class B :public A
{
public:
    virtual void Funtest2()
    {
        cout << "B::Funtest2" << endl;
    }
    virtual void Funtest5()
    {
        cout << "B::Funtest5" << endl;
    }
};
typedef void(*PVTF)();//函数指针

void PrintVpt()//打印虚表
{
    B b;
    PVTF* pVtf = (PVTF*)(*(int*)&b);

    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
}
void test()
{
    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl;

    PrintVpt();
}

这里写图片描述

多继承

在这里我把所有的可能性混合到一块:就是说派生类中对基类A、B的虚函数都进行了重写,并且还拥有自己的虚函数。

如果派生类中对基类A、B中的虚函数进行了重写,分别替换基类A、B中的虚函数;

如果派生类中有自己的虚函数,按照声明顺序放在第一个基类(也就是A类)虚表中虚函数的后面。

多继承中派生类不会重新生成表,而是直接将基类A、B的虚表直接继承下来。

class A
{
public:
    virtual void Funtest1()
    {
        cout << "A::Funtest1" << endl;
    }
};

class B
{
public:
    virtual void Funtest2()
    {
        cout << "B::Funtest2" << endl;
    }
};

class C :public A, public B
{
public:

    virtual void Funtest1()
    {
        cout << "C::Funtest1" << endl;
    }
    virtual void Funtest2()
    {
        cout << "c::Funtest2" << endl;
    }
    virtual void Funtest4()
    {
        cout << "C::Funtest4" << endl;
    }
};


typedef void(*PVTF)();//函数指针

void PrintVpt()//打印虚表
{
    C c;
    PVTF* pVtf = (PVTF*)(*(int*)&c);

    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
    A a;
    B &b = c;
    pVtf = (PVTF*)*(int*)&b;
    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
}
void test()
{

    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl;
    cout << sizeof(C) << endl;
    PrintVpt();
}

这里写图片描述

这里写图片描述

菱形继承(钻石继承)

这里我列举的这种情况是派生类中有自己的虚函数,继承时没有重写虚函数,这样举例是为了更能直观的看到它的对象模型。因为一旦有重写,在对象模型中你会分不清它到底是派生类的还是基类的。

包含虚函数的菱形继承,派生类的对象模型其实是这样的,本例中没有给出类中成员变量,加上成员变量(好理解点的话就理解为功能)的话,模型是这样的:

这里写图片描述

B1的虚表中包含了最初的基类A,以及派生类C的虚函数,B2的虚表中包含了最初的基类A的虚函数。同样的,如果派生类中对基类B1、B2中的虚函数进行了重写,分别替换基类B1、B2虚表中的虚函数,同理B1、B2中对A类的虚函数进行了重写,也会替换。

class A
{
public:
    virtual void Funtest1()
    {
        cout << "A::Funtest1" << endl;
    }
};

class B1:public A
{
public:
    virtual void Funtest2()
    {
        cout << "B1::Funtest2" << endl;
    }
};

class B2 :public A
{
public:

    virtual void Funtest3()
    {
        cout << "B2::Funtest3" << endl;
    }

};

class C :public B1,public B2
{
public:

    virtual void Funtest4()
    {
        cout << "C::Funtest4" << endl;
    }
};


typedef void(*PVTF)();//函数指针

void PrintVpt()//打印虚表
{
    C c;
    PVTF* pVtf = (PVTF*)(*(int*)&c);

    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
    B2 &b = c;
    pVtf = (PVTF*)*(int*)&b;
    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
}
void test()
{

    cout << sizeof(B1) << endl;
    cout << sizeof(B2) << endl;
    cout << sizeof(C) << endl;
    PrintVpt();
}

这里写图片描述

虚拟继承

单继承(虚拟继承)

对象模型应该是这样的:

class A
{
public:
    virtual void Funtest1()
    {
        cout << "A::Funtest1" << endl;
    }
};

class B :virtual public A
{
public:
    virtual void Funtest2()
    {
        cout << "B::Funtest2" << endl;
    }
    virtual void Funtest1()
    {
        cout << "B::Funtest1" << endl;
    }
};
typedef void(*PVTF)();//函数指针

void PrintVpt()//打印虚表
{
    B b;
    PVTF* pVtf = (PVTF*)(*(int*)&b);

    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
    A &a = b;
    pVtf = (PVTF*)*((int*)&a);
    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
}
void test()
{
    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl;

    PrintVpt();
}

这里写图片描述

这里写图片描述

菱形虚拟继承

class A
{
public:
    virtual void Funtest1()
    {
        cout << "A::Funtest1" << endl;
    }

    int _a;
};
class B1 :virtual public A
{
public:
    virtual void Funtest2()
    {
        cout << "B1::Funtest2" << endl;
    }

    int _b1;
};
class B2 :virtual public A
{
public:
    virtual void Funtest3()
    {
        cout << "B2::Funtest3" << endl;
    }
    virtual void Funtest1()
    {
        cout << "B2::Funtest1" << endl;
    }

    int _b2;
};
class C :public B1, public B2
{
public:
    virtual void Funtest3()
    {
        cout << "C::Funtest3" << endl;
    }
    virtual void Funtest4()
    {
        cout << "C::Funtest4" << endl;
    }

    int _c;
};

typedef void(*PVTF)();

void PrintVft()
{

    C c;//创建派生类对象d
    c._a = 1;
    c._b1 = 2;
    c._b2 = 3;
    c._c = 4;
    PVTF* pVtf= (PVTF*)*(int*)&c;

    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }

    cout << endl << endl;

    pVtf = (PVTF*)*((int*)&c + 3);
    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }

    cout << endl << endl;

    pVtf = (PVTF*)*((int*)&c + 7);
    while (*pVtf)
    {
        (*pVtf)();
        pVtf++;
    }
}
void test()
{
    cout << sizeof(C) << endl << endl;
    PrintVft();

}

这里写图片描述

对象模型:

这里写图片描述

在这里总结一下:从包含虚函数的菱形虚拟继承中我们可以看出来,它其实和不包含虚函数的菱形虚拟继承对象结构差不了多少,只是派生类中的虚函数要写到第一个基类里面,在原有的偏移量表格地址上把虚表也继承过来了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章