1. 程式人生 > >【C++學習筆記】父類指標指向子類物件

【C++學習筆記】父類指標指向子類物件

        虛擬函式的作用主要是實現了多型的機制。簡而言之就是用父型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。但僅僅可呼叫父類含有的函式,非父類函式不能呼叫。

普通虛擬函式呼叫

假設我們有下面的類層次:

#include <iostream>
using namespace std;

class A
{
public:
    A(){};
    ~A(){}

    virtual void foo()
    {
        cout << "A::foo() is called" << endl;
    }
};
class B :public A
{
public:
    B(){}
    ~B(){}

    void foo()
    {
        cout << "B::foo() is called" << endl;
    }
    void fun()
    {
        cout << "B::fun() is called" << endl;
    }

    virtual void fun1()
    {
        cout << "B::fun() is called" << endl;
    }
};

int main(void)
{
    A *a = new B();
    a->foo();     // 在這裡,a雖然是指向A的指標,但是被呼叫的函式(foo)卻是B的!
    // a->fun();  // 這裡呼叫錯誤,提示:error C2039: “fun”: 不是“A”的成員, e:\debug\base\test\main.cpp(8) : 參見“A”的宣告
    // a->fun1(); // 這裡呼叫錯誤,提示:error C2039: “fun1”: 不是“A”的成員, e:\debug\base\test\main.cpp(8) : 參見“A”的宣告

    return 0;
}

以上例子說明,子類的虛擬函式替換了父類的同名虛擬函式(參見【C++學習筆記】虛擬函式實現多型原理)實現通過父類呼叫子類函式實現的功能。非父類含有的函式無法呼叫,可能因為父類中未包含子類中相關函式資訊

建構函式與解構函式呼叫

假設有如下類層次:

class A
{
public:
    A(){ cout << "A::A() is called" << endl; };
    ~A(){ cout << "A::~A() is called" << endl; }

    virtual void foo()
    {
        cout << "A::foo() is called" << endl;
    }
};
class B :public A
{
public:
    B(){ cout << "B::B() is called" << endl; }
    ~B(){ cout << "B::~B() is called" << endl; }

    void foo()
    {
        cout << "B::foo() is called" << endl;
    }
};

int main(void)
{
    A *a = new B();
    a->foo();     // 在這裡,a雖然是指向A的指標,但是被呼叫的函式(foo)卻是B的!

    delete a;

    return 0;
}

程式輸出:

A::A() is called

B::B() is called

B::foo() is called

A::~A() is called

由程式輸出可以看出,程式先構造父類,再構造子類,析構時未呼叫子類的解構函式。(這是否可以理解為:父類中未包含子類的資訊,所以沒有被呼叫)。若子類的建構函式存在動態記憶體分配,則會存在記憶體洩漏的問題。若父類解構函式為虛擬函式,則程式輸出如下:

A::A() is called

B::B() is called

B::foo() is called

B::~B() is called

A::~A() is called

子類的動態記憶體分配就可以被釋放。

雖然呼叫delete釋放父類指標a,但是子類B的解構函式也被呼叫了。為何父類A解構函式為虛擬函式,釋放父類指標可以呼叫子類解構函式,父類A解構函式不為虛擬函式,釋放父類指標不呼叫子類解構函式?父類中也沒有包含子類解構函式的資訊呀!!!!!!!!!

父子類指標強制轉換的安全性

假設有如使用:

class A
{
public:
    A(){ cout << "A::A() is called" << endl; };
    virtual~A(){ cout << "A::~A() is called" << endl; }

    virtual void foo()
    {
        cout << "A::foo() is called" << endl;
    }
};
class B :public A
{
public:
    B()
    { 
        cout << "B::B() is called" << endl; 

    }
    virtual~B()
    { 
        cout << "B::~B() is called" << endl; 
        delete [] pt;
    }

    void foo()
    {
        cout << "B::foo() is called" << endl;
    }

    void fun()
    {
        cout << "B::fun() is called" << endl;
    }
};

int main(void)
{
    A *a = new A();

    B *b = static_cast<B*>(a); // 需顯示強制轉換
    b->fun(); 

    delete b;

    return 0;
}

程式輸出:

A::A() is called

B::fun() is called

A::~A() is called

父類指標強制轉換為子類指標,並呼叫了子類函式,釋放時釋放的是父類A指標,並未釋放B指標。父類指標a強制轉換過程中並未呼叫子類B建構函式,釋放時也未呼叫子類B解構函式。原父類動態分配的記憶體強制轉換為子類,向下強制轉換為子類存在較大風險,如下所示:

class A
{
public:
    A(){ cout << "A::A() is called" << endl; };
    virtual~A(){ cout << "A::~A() is called" << endl; }

    virtual void foo()
    {
        cout << "A::foo() is called" << endl;
    }
};
class B :public A
{
public:
    B()
    { 
        cout << "B::B() is called" << endl; 
        pt = new int[10];
        for (int i = 0; i < 10; i++)
            pt[i] = i;

    }
    virtual~B()
    { 
        cout << "B::~B() is called" << endl; 
        delete [] pt;
    }

    void foo()
    {
        cout << "B::foo() is called" << endl;
    }

    void fun()
    {
        cout << "B::fun() is called" << endl;
        cout << pt[0] << endl;
    }

    int *pt;
};

int main(void)
{
    A *a = new A();

    B *b = static_cast<B*>(a);// 需顯示強制轉換
    b->fun(); 

    delete a;

    return 0;
}

程式輸出異常,因為子類B呼叫的函式使用了需要在子類建構函式中動態分配的記憶體。因為強制轉換過程中,未呼叫子類B的建構函式,pt記憶體沒有進行動態分配。

int main(void)
{
    A *a = new B();

    B *b = static_cast<B*>(a);
    b->fun(); 

    delete a;

    return 0;
}

程式輸出:

A::A() is called

B::B() is called

B::fun() is called

0

B::~B() is called

A::~A() is called

父類指標強制轉換為子類指標,並呼叫了子類函式(存在使用動態記憶體分配部分)。此情況說明:父類指標a指向子類指標,再將父類指標轉換為子類B使用的安全的。