關於C++對象模型的經典問題

關於C++ 的繼承 多態 和與之相關的對象模型知識,各個書本上很多了
今天來看幾個對象模型的問題 在實踐中理解C++ 對象模型:

1、 在下列代碼中:

class A
{
public :
    A()
        :_a(1)
    {}
    virtual void FunTestA()
    {}
    int _a;
};

class B
{
public:
    B()
        :_b(2)
    {}
    virtual void FuntestB()
    {}
    int _b;
};

class C :public A, public B
{
public :
    C()
    :A()
    , B()
    , _c(3)
    {}
    int _c;
};

void Fun()
{
    C c;
    cout << sizeof(c) << endl;
    A* pA = &c;
    B* pB = &c;
    C* pC = &c;
}

`
nt main()
{
    Fun();
    return 0;
}

選項:
A pA、pB、pC的取值相同
B、 pC 和 pA不相同
C、pB和pC不相同
D、pC不等於pA,也不等於pB。

解析:在繼承體系中,當用一個基類的指針或引用指向派生類對象時(賦值兼容規則),這時會將派生類對象的地址偏移一個合適偏移量,因爲派生類對象也是一個基類對象,實際也就是讓基類指針指向派生類中屬於基類的那部分。所以一般先繼承的那個類(基類)的指針和派生類的對象的地址相等。

這裏寫圖片描述

2、 類A 與類 B毫無關係

class A
{
public :
    A()
    {
        m_a = 1;
        m_b = 2;
    }

    void fun()
    {
        cout << m_a <<"  "<< m_b << endl;
    }
private :
    int m_a;
    int m_b;
};

class B
{
public :
    B()
    {
        m_c = 3;
    }

    void fun()
    {
        cout << m_c << endl;
    }

private :
    int m_c;
};

void Fun()
{
    A a;
    B* pb = (B*)(&a);
    pb->fun();
}

int main()
{
    Fun();
    return 0;
}

A 類與 B 類毫無關係,則pd->fun();一定到用的是派生類的fun()函數,
但是pb又是被強制指向A類對象的,
那麼肯定會打印前A類對象前四個字節的值,也就是1。(因爲B類大小就是4個字節)。因爲B類的大小的4個字節,所以B類的指針管理的內存範圍是4個字節。

這裏寫圖片描述

3、 當B類共有繼承A類的時候

class A
{
public :
    A()
    {
        m_a = 1;
        m_b = 2;
    }

    void fun()
    {
        cout << m_a <<"  "<< m_b << endl;
    }
private :
    int m_a;
    int m_b;
};

class B:public A
{
public :
    B()
        :A()
    {
        m_c = 3;
    }

    void fun()
    {
        cout << m_c << endl;
    }

private :
    int m_c;
};

void Fun()
{
    A a;
    B* pb = (B*)(&a);//切記:這裏是派生類的指針指向基類對象
    //沒有定義基類。
    pb->fun();
}

int main()
{
    Fun();
    return 0;
}

這裏pd->fun()還是調用的派生類的fun函數,可這時派生類繼承了基類,所以派生類的指針管理的範圍就是 (4 + 8) = 12個字節。而繼承基類A的派生類B 並沒有被定義,m_c的空間還沒有開闢出來,所以這時候打印m_c結過時崩潰或隨機數。

這裏寫圖片描述

4、當A類的fun函數被聲明爲虛函數時,並且定義了B類時。

class A
{
public :
    A()
    {
        m_a = 1;
        m_b = 2;
    }

    virtual void fun()//虛函數
    {
        cout << m_a <<"  "<< m_b << endl;
    }
private :
    int m_a;
    int m_b;
};

class B:public A
{
public :
    B()
        :A()
    {
        m_c = 3;
    }

    void fun()//重寫
    {
        cout << m_c << endl;
    }

private :
    int m_c;
};

void Fun()
{
    A a;
    B b;//注意這裏定義了B類
    B* pb = (B*)(&a);//切記:這裏是派生類的指針指向基類對象,不會形成多態
    pb->fun();//在A類的虛表內找fun函數(調用的是A類的fun函數),輸出1和2
    A* pa = (A*) &b;
    pa->fun();//這裏形成了多態 打印 。
}

這裏雖然定義了B類,但是沒有形成多態,pb實際上指向的是A的空間pd->fun()是在A的虛表中找vptr 函數指針 所以調用的是A的fun()函數。

4、 如果是基類fun函數是普通函數,派生類fun()函數是虛函數

class A
{
public:
    A()
    {
        m_a = 1;
        m_b = 2;
    }

     void fun()
    {
        cout << m_a << "  " << m_b << endl;
    }
private:
    int m_a;
    int m_b;
};

class B :public A
{
public:
    B()
        :A()
    {
        m_c = 3;
    }

    virtual void fun()
    {
        cout << m_c << endl;
    }

private:
    int m_c;
};

void Fun()
{
    A a;
    B b;
    B* pb = (B*)(&a);//切記:這裏是派生類的指針指向基類對象
    pb->fun();
    A* pa = (A*) &b;
    pa->fun();
}

int main()
{
    Fun();
    system("pause");
    return 0;
}

這時候仍然沒有形成多態, 但是B類的fun()函數是虛函數,pb指向&a.所以pb->fun()這一句,會在 A對象的前四個字節中尋找虛表,所以他把m_a當成虛表使得程序崩潰。

這裏是因爲沒有形成多態,所以pb雖然指向&a但是pb的類型是B* pb會按照B類的結構來解釋在A類內存空間中的數據。

這裏寫圖片描述

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