C++虛擬函式與多型實戰 (轉)

陳利人發表於2012-11-10
/**************************************************************

虛擬函式:
    1、在基類用【virtual宣告】的成員函式即為虛擬函式
    2、在派生類中的定義此函式,要求
        函式名、函式型別、函式引數個數和型別全部與基類的虛擬函式相同
        並根據派生類的需要重新定義函式體
    3、當一個成員函式宣告為虛擬函式後,其派生類中的同名函式都自動成為虛擬函式,無論是否加virtual關鍵字
    4、用基類的指標或引用指向派生類的物件
       通過用基類的指標或引用呼叫虛擬函式
       實際執行的將使派生類物件中定義的虛擬函式

虛擬函式表:
    1、如果類中包含有虛成員函式,在用該類例項化物件時,
    物件的第一個成員將是一個指向虛擬函式表的指標[pvftable]。
    2、虛擬函式表記錄執行過程中實際應該呼叫的虛擬函式的入口地址
    3、類中所有的虛擬函式形成虛擬函式表
        [可以通過除錯檢視:
            cout<<sizeof(aa3)<<endl;]

虛解構函式: -- 防止記憶體洩漏
    1、如果一個基類的指標指向派生類的物件,
    並且想通過該指標delete派生類物件,
    系統將只會執行基類的解構函式
    而不會執行派生類的解構函式
    2、為避免這種情況的發生,往往把解構函式宣告為虛的,
    此時,系統將 先執行派生類物件的解構函式
    然後再執行基類的解構函式
    3、如果基類的解構函式宣告為虛的,
    派生類的解構函式也將自動成為虛構函式
    無論派生類解構函式宣告中是否加virtual
    4、防止記憶體洩漏
    當派生類中有指標時,物件銷燬時,
    也需要釋放指標所指向的地址
    5、特別注意:
        5.1、
        基類名*  指標 = new 子類名();
            只有當一個父類型別的指標 指向 子類物件[new 的物件]時
            只有將 基類的解構函式 宣告為 虛擬函式virtual  
        在 delete 指標時-->
            才會:
                先執行派生類物件的解構函式
                然後再執行基類的解構函式
            否則:
                只執行基類的解構函式
        5.2、
        類名*  指標 = new 類名();
            當指標的型別是物件型別的本身時
        在 delete 指標時-->
                基類的解構函式 【不是】虛擬函式virtual  
            也會:
                先執行派生類物件的解構函式
                然後再執行基類的解構函式
        5.3、在棧中分配分配空間時【子類分配空間】
            5.3.1、不需要 delete 
                在 該區域 執行完後 會自動釋放 該棧空間
            5.3.2、基類的解構函式 【不是】虛擬函式virtual  
            也會:
                先執行派生類物件的解構函式
                然後再執行基類的解構函式

純虛擬函式和抽象類:
    1、純虛擬函式具有如下的一般形式:
        virtual 返回型別 函式名(引數列表)=0;
    2、純虛擬函式沒有函式體,即只有函式的宣告而沒有函式的定義
    3、通常在基類中宣告純虛擬函式,在派生類中定義該虛擬函式
    如果派生類中也沒有定義該純虛擬函式,則該函式在派生類中任然為純虛擬函式
    4、不能例項化物件的類稱為[抽象類],
    具有純虛擬函式的類是不能被例項化物件的
    所以具有純虛擬函式的類是一種抽象類
    5、雖然抽象類不能例項化物件,但是可以用抽象類的指標指向派生類物件
    並呼叫派生類的虛擬函式的實際實現
    6、子類在繼承抽象類時:
    必須全部實現抽象類的 純虛擬函式,該派生類才能被例項化
    如果只實現部分 純虛擬函式,則該派生類還是 抽象類
    7、當一個的所有函式都是 純虛擬函式,
    則該類就是 介面【C++沒有介面】
    介面是抽象類的一種特例

子類呼叫基類的函式:
    基類類名::函式名();

多型:
    1、父類的函式為 虛擬函式, 即在 返回型別前加 virtual
        virtual void Print(){.....};
    2、子類對該函式進行重寫[返回型別 和 函式名 相同]
        void Print(){......};
    3、父類的[指標]指向父類物件或子類物件
        //通過指標
            void test1(a* temp){
                temp->Print();
            }
        3.1、在 【棧中】 分配空間
            父類名* 地址變數名 =  &父類名[子類名]();
                a* aa = &b(); 
                //等價於 
                    a* aa; 
                    b bb;  
                    aa=&bb;

                test1(aa);
        3.2、在 【堆中】 分配空間
            父類名* 地址變數名 = new 父類名[子類名]();
                a* cc = new c();
                test1(cc);

注意點:
    1、指標呼叫函式的方式:
        指標名->函式名();
    2、引用呼叫函式的方式:
        引用名.函式名();
    3、繼承方式
        class 子類名:public 父類名{....};
***************************************************************/

class Person{ //抽象類
public:
    char* name;
public:
    virtual void Eat()=0;
    virtual void Sleep()=0;
};

class Stu:public Person{
public:
    void Eat(){
        cout<<"Eat......."<<endl;
    }
    void Sleep(){
        cout<<"Sleep.........."<<endl;
    }
};

void main(){
    Person* person = new Stu();
    person->Eat();
}



class a{
public:
    virtual void Print(){
        cout<<"a............."<<endl;
    }
    virtual void Prafint(){
        cout<<"a............."<<endl;
    }
public:
    a(){
        cout<<"new a............"<<endl;    
    }
public:
    virtual ~a(){
        cout<<"destory a............"<<endl;
    }
};

//通過指標
void test1(a* temp){
    temp->Print();
}

//通過引用
void test2(a& temp){
    temp.Print();
}

class b:public a{
public:
    void Print(){
        cout<<"b............."<<endl;
    }
public:
    b(){
        cout<<"new b............"<<endl;    
    }
public:
    ~b(){
        cout<<"destory b............"<<endl;
    }
};

class c:public a{
public:
    void Print(){
        cout<<"c............."<<endl;
    }
public:
    c(){
        cout<<"new c............"<<endl;    
    }
public:
    ~c(){
        cout<<"destory c............"<<endl;
    }
};



void test22(){

     /*
        b bb;
        注意: 
            在棧中例項化 派生類
            1、先 呼叫父類的 建構函式 再呼叫派生類的建構函式
            2、不需要手動釋放記憶體
            3、在 該 區域執行完後 會自動釋放記憶體空間
        new a............
        new b............
        destory b............
        destory a............
     */
     //a aa = bb;
    //aa.Print();
    a* aa = new b();
    //a* aa = new b();  
    //aa->Print();
    delete aa; 
    /*
    a* aa = new b();
    在堆中分配空間:
        1、先 呼叫父類的 建構函式 再呼叫派生類的建構函式
        2、先呼叫派生類的解構函式 再呼叫基類的解構函式 
            2.1、2的滿足條件:需要把 基類的解構函式 宣告 虛擬函式
        3、如果基類的 解構函式 不是虛擬函式
            3.1、在 delete 時只會呼叫基類的解構函式
    */
    /*
    new a............
    new b............
    b.............
    destory b............
    destory a............
    */
}

int test11(){
    /*通過指標*/
    a* aa1 = &b();
    /*等價於 
        a* aa1; 
        b bb;  
        aa1=&bb;
    */
    test1(aa1);
    a* cc1 = new c(); //動態繫結  就是虛擬函式中的內容 在例項化的時候動態載入
    test1(cc1);

    /*通過引用*/
    a& aa2 = b();
    test2(aa2);
    a* cc2 = new c();
    test1(cc2);

    a aa3;
    cout<<sizeof(aa3)<<endl;

    //cout<<&(a::Print)<<endl;
    /*
    //列印函式的地址
        printf("a::Print=%x\n", &(a::Print));
        printf("b::Print=%x\n", &(b::Print));
        printf("c::Print=%x\n", &(c::Print));

    列印結果:
        a::Print=401271
        b::Print=401271
        c::Print=401271    
    分析:
            a
           / \
          b   c
        1、b繼承a , c繼承a
        2、Print()在a類中是 虛擬函式
        3、類b類c 重寫了a的虛擬函式,所以該函式也為虛擬函式
        4、三個類中的函式統一載入一個地址區:
            意味著三個只能有一個存在
        5、具體 401271 裡面的內容就由呼叫的例項在例項化的時候動態載入
    */
    return 0;
} 

相關文章