多態(tài)

1.所謂多態(tài)
多態(tài)就是一種特殊的機(jī)制,執(zhí)行這種機(jī)制的結(jié)果就是,調(diào)用同一個(gè)接口,在不同的情況下
可以實(shí)現(xiàn)不同的功能。

(1)c語(yǔ)言模擬的多態(tài)
    講c語(yǔ)言時(shí)強(qiáng)調(diào)過(guò),實(shí)際上c語(yǔ)言也是可以模擬多態(tài),主要時(shí)利用函數(shù)指針
    進(jìn)行函數(shù)的回掉實(shí)現(xiàn)的,這個(gè)實(shí)現(xiàn)過(guò)程往往會(huì)借助于結(jié)構(gòu)體進(jìn)行封裝實(shí)現(xiàn)。


(2)面向?qū)ο笳Z(yǔ)言的多態(tài)
    在面向?qū)ο蟮恼Z(yǔ)言中多態(tài)的實(shí)現(xiàn)依賴(lài)三個(gè)機(jī)制
    (1)繼承
    (2)函數(shù)重寫(xiě)
    (3)向上轉(zhuǎn)型
    (4)動(dòng)態(tài)綁定


以上多態(tài)實(shí)現(xiàn)方式中,不管是那種實(shí)現(xiàn)方式,在邏輯上都實(shí)現(xiàn)了調(diào)用同一個(gè)函數(shù)接口時(shí)調(diào)
用的是不同派生類(lèi)的重寫(xiě)函數(shù),從而得到不同的執(zhí)行結(jié)果。



2.基類(lèi)與派生類(lèi)成員函數(shù)同名

分為兩種情況,一種是函數(shù)簽名完全相同,另一種是同函數(shù)名但不同參數(shù)列表。

(1)函數(shù)簽名完全相同(也叫函數(shù)的重寫(xiě))
    (1)子類(lèi)訪(fǎng)問(wèn)基類(lèi)同名函數(shù)時(shí),先使用using進(jìn)行聲明,然后使用
        “基類(lèi)名::成員函數(shù)()”的方式進(jìn)行調(diào)用即可
        比如:
            using People::show;
            People::show();
        

    (2)如果基類(lèi)成員函數(shù)是public, 在外界希望對(duì)通過(guò)派生類(lèi)基類(lèi)同名
        函數(shù)進(jìn)行訪(fǎng)問(wèn)時(shí),使用static_cast<>()將派生類(lèi)向上強(qiáng)制轉(zhuǎn)換
        為基類(lèi)后調(diào)用的就是基類(lèi)的被同名的函數(shù)。

    
    (3)注意:
        如果基類(lèi)中該函數(shù)又被重載多次,那么在子類(lèi)內(nèi)部活在在外部希望通過(guò)
        子類(lèi)訪(fǎng)問(wèn)父類(lèi)的同名函數(shù)時(shí),可以通過(guò)參數(shù)列表的進(jìn)行重載的區(qū)分區(qū)分
        調(diào)用,局提請(qǐng)看下面的例子。
    

    例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;

    class People {
    public:
            People(const string name="zhanagsan"):name(name) { }
            void show() {
                    cout << "111基類(lèi)name:"<<name<<endl;
            }
        //重載的show函數(shù)
            void show(int a) {
                    cout << "222基類(lèi)name:"<<name<<endl;
            }

    public:  
            string name;
    };

    class Student : public People {
    public:
            Student(const string name="wangwu"):name(name) { } 
            using People::show;

            void show() {
            People::show(10);//通過(guò)參數(shù)列表區(qū)分重載函數(shù)
                    cout << "派生類(lèi)name:"<<name<<endl;
            }

    public:
            string name;
    };


    int main(void)
    {
            Student stu1 = Student();

            stu1.show();//通過(guò)參數(shù)列表區(qū)分重載函數(shù)

            static_cast<People>(stu1).show();//通過(guò)參數(shù)列表區(qū)分重載函數(shù)

            return 0;
    }
    


(2)同名但參數(shù)列表不同
    (1)這種情況下,如果希望在子類(lèi)中訪(fǎng)問(wèn)基類(lèi)同名但不同參數(shù)列表的
        成員函數(shù),針對(duì)這種情況時(shí),以下方式都可以:

        比如還是以show函數(shù)為例:
        (1)聲明:using People::show;
             調(diào)用:People::show(10);

        (2)聲明:using People::show;
             調(diào)用:show(10);//可以省略People::
        
        (3)調(diào)用:People::show(10);//可以直接省略u(píng)sing聲明


    (2)如果希望在外部通過(guò)子類(lèi)訪(fǎng)問(wèn)基類(lèi)的同名但不同參數(shù)列表的成員函數(shù)
        時(shí),不需要做向基類(lèi)的轉(zhuǎn)換,可以通過(guò)參數(shù)列表的不同直接區(qū)分
        調(diào)用的是基類(lèi)的函數(shù)還是子類(lèi)的函數(shù)。

    例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;

    class People {
    public:
            People(const string name="zhanagsan"):name(name) { }
            void show(int a) {
                    cout << "基類(lèi)name:"<<name<<endl;
            }

    public:
            string name;
    };

    class Student : public People {
    public:
            Student(const string name="wangwu"):name(name) { } 
            using People::show;

            void show() {
                    People::show(10);
                    cout << "派生類(lèi)name:"<<name<<endl;
            }

    public:
            string name;
    };

    int main(void)
    {
            Student stu1 = Student();

            stu1.show();//通過(guò)參數(shù)區(qū)分,調(diào)用的時(shí)子類(lèi)的函數(shù)

            stu1.show(10);//通過(guò)參數(shù)區(qū)分,調(diào)用的時(shí)基類(lèi)的函數(shù)

            return 0;
    }


3.成員函數(shù)重寫(xiě)
(1)什么是成員函數(shù)的重寫(xiě)
派生類(lèi)中函數(shù)簽名與基類(lèi)函數(shù)簽名完全相同的情況也被稱(chēng)為重寫(xiě)。

注意區(qū)分重寫(xiě)和重載,
重載:在通過(guò)一個(gè)命名空間的,比如同一個(gè)類(lèi)成員函數(shù)重載,或者外部函數(shù)
    重載。

重寫(xiě):重寫(xiě)對(duì)于多態(tài)機(jī)制的實(shí)現(xiàn)非常的重要,c++中多態(tài)的實(shí)現(xiàn)靠的就是子類(lèi)對(duì)
    父類(lèi)虛函數(shù)的重寫(xiě)實(shí)現(xiàn)的。



(2.普通函數(shù)的重寫(xiě)與靜態(tài)綁定

(1)靜態(tài)綁定的規(guī)則
    調(diào)用某個(gè)對(duì)象的函數(shù)時(shí),只會(huì)調(diào)用該對(duì)象內(nèi)實(shí)現(xiàn)該函數(shù),即便該函數(shù)在派生類(lèi)中有被
    重寫(xiě),但它不會(huì)調(diào)用該函數(shù)在派生類(lèi)中的重寫(xiě)版本。

(2)靜態(tài)綁定由誰(shuí)決定
    靜態(tài)綁定是由編譯器決定的,靜態(tài)綁定后,這種綁定關(guān)系不可以在程序運(yùn)行時(shí)被改變。

(3)什么情況下遵守靜態(tài)規(guī)則
    對(duì)普通函數(shù)進(jìn)行重寫(xiě)時(shí),函數(shù)的調(diào)用遵守靜態(tài)規(guī)則,這一點(diǎn)與java不一樣,java對(duì)普通
    函數(shù)的重寫(xiě)是動(dòng)態(tài)綁定。     
    

注意:實(shí)際上對(duì)普通函數(shù)進(jìn)行重寫(xiě)并沒(méi)有太大的實(shí)際意義。


    
(4)靜態(tài)綁定的例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;

    class Box
    {
    protected:
            int length;
            int width;
            int height;

    public:
            Box(int length, int width, int height)
                    :length(length),width(width),height(height) { }

            int volume() {return length*width*height;}
            void showvolume() {cout<<"體積:"<<volume()<<endl;}
                
    };

    class Thinbox : public Box {
    public:
            Thinbox(int length, int width, int height):Box(length, width, height) { }

            int volume() {return length*width*height*(1-0.2);}
        //void showvolume() {cout<<"體積:"<<volume()<<endl;}打開(kāi))
    };

    int main(void)
    {
            Thinbox tbox(3, 3, 3);
            Box *boxp = &tbox;

            tbox.showvolume();
            boxp->showvolume();

            return 0;
    }

    運(yùn)行結(jié)果:
    體積:27
    體積:27

    
    例子分析:

    例子中,定義了一個(gè)Box類(lèi),類(lèi)中實(shí)現(xiàn)了兩個(gè)函數(shù),一個(gè)用于計(jì)算體積的
    volume函數(shù),該函數(shù)計(jì)算體積時(shí),沒(méi)有考慮盒子壁的厚度。       

    另一個(gè)是用于顯示體積的函數(shù),
        void showvolume() {cout<<"體積:"<<volume()<<endl;}
    
    從Box中派生了一個(gè)Thinbox類(lèi),在這個(gè)類(lèi)中并沒(méi)有新增任何的成員,只是
    重寫(xiě)了基類(lèi)的volume()函數(shù),
        int volume() {return length*width*height*(1-0.2);}

    考慮到盒子的厚度,因此體積*(1-0.2),進(jìn)行了體積估算。  
    
    但是沒(méi)有重寫(xiě)showvolume()函數(shù),所以通過(guò)子類(lèi)調(diào)用的showvolume()函數(shù)是
    從基類(lèi)繼承而來(lái)的showvolume函數(shù)。
    
    在主函數(shù)中,定義了一個(gè)派生類(lèi)Thinbox的對(duì)象tbox,然后在再定義了一個(gè)
    基類(lèi)Box類(lèi)型的指針boxp,該指針指向派生類(lèi)tbox。

    面函數(shù)中通過(guò)如下兩條語(yǔ)句顯示體積。
            tbox.showvolume();  
            box->showvolume();
    
    通過(guò)tbox調(diào)用showvolume()函數(shù)時(shí),因?yàn)榕缮?lèi)中沒(méi)有該函數(shù),就進(jìn)入派生類(lèi)
    對(duì)象中的基類(lèi)對(duì)象中調(diào)用基類(lèi)的該函數(shù)(繼承過(guò)來(lái)的函數(shù)),基類(lèi)該函數(shù)在調(diào)
    用volume函數(shù)時(shí),發(fā)現(xiàn)在基類(lèi)中存在,根據(jù)靜態(tài)綁定規(guī)則,不管這個(gè)函數(shù)有沒(méi)
    有重寫(xiě),調(diào)用的是這個(gè)函數(shù)本身,而不是它在派生類(lèi)中的重寫(xiě)函數(shù)。

    boxp->showvolume()是通過(guò)基類(lèi)指針調(diào)用基類(lèi)showvolume()函數(shù),由于boxp是
    基類(lèi)類(lèi)型的指針,所以直接進(jìn)入派生類(lèi)的基類(lèi)對(duì)象中調(diào)用showvolume()函數(shù),
    該函數(shù)在調(diào)用volume函數(shù),過(guò)程同樣遵循靜態(tài)綁定規(guī)則。
    
    從如下顯示結(jié)果來(lái)看:
        體積:27
        體積:27
    說(shuō)明派生類(lèi)的volume()函數(shù)并沒(méi)有被調(diào)用,因?yàn)闆](méi)有*(1-0.2)進(jìn)行體積縮減。  
    

    如果我們?cè)谂缮?lèi)中將派生類(lèi)中將派生類(lèi)的如下顯示函數(shù)打開(kāi),
        void showvolume() {cout<<"體積:"<<volume()<<endl;}打開(kāi))
    然后再次編譯運(yùn)行,結(jié)果如下
        體積:21
        體積:27   

    在tbox.showvolume();調(diào)用時(shí),首先在當(dāng)前類(lèi)對(duì)象查找,由于在派生類(lèi)對(duì)象tbox
    中有定義showvolume();函數(shù),會(huì)根據(jù)靜態(tài)規(guī)則調(diào)用該函數(shù),以及涉及到的volume函數(shù)。
        

4.虛函數(shù)的重寫(xiě)和動(dòng)態(tài)綁定(多態(tài))

(1)動(dòng)態(tài)綁定的規(guī)則
調(diào)用某對(duì)象中的函數(shù)時(shí),如果函數(shù)在派生類(lèi)中被重寫(xiě)了的話(huà),那么優(yōu)先調(diào)用派生類(lèi)中
被重寫(xiě)的函數(shù),否則才會(huì)調(diào)用自己定義的函數(shù)。


(2)如何才能實(shí)現(xiàn)動(dòng)態(tài)綁定
對(duì)虛函數(shù)進(jìn)行重寫(xiě),只有虛函數(shù)才能實(shí)現(xiàn)動(dòng)態(tài)綁定。


(3)靜態(tài)綁定與動(dòng)態(tài)綁定對(duì)比
(1)靜態(tài)綁定先到當(dāng)前類(lèi)對(duì)象和基類(lèi)對(duì)象中尋找
(2)動(dòng)態(tài)綁定與靜態(tài)綁定相反,先到當(dāng)前類(lèi)的派生類(lèi)對(duì)象中尋找,找不到才使用當(dāng)前類(lèi)和基類(lèi)中的函數(shù)
(3)重寫(xiě)普通函數(shù)時(shí)遵守的都是靜態(tài)綁定規(guī)則,重寫(xiě)虛函數(shù)時(shí)遵守的都是動(dòng)態(tài)綁定規(guī)則。


(4)虛函數(shù)的聲明格式
virtual 返回值 fun(...) { }

注意:
(1)如果函數(shù)的定義和聲明是分開(kāi)的,那么virtual應(yīng)該放在函數(shù)的聲明前。
(2)派生類(lèi)中對(duì)基類(lèi)虛函數(shù)進(jìn)行重寫(xiě)后,這個(gè)重寫(xiě)函數(shù)仍然還是一個(gè)虛函數(shù),不管有沒(méi)有顯式表明virtual關(guān)鍵字。
(3)在派生類(lèi)中,為了讓語(yǔ)義更加清晰,應(yīng)該在重寫(xiě)的虛函數(shù)前面加virtual關(guān)鍵字
(4)如果基類(lèi)中的虛函數(shù)是const,那么子類(lèi)中重寫(xiě)的函數(shù)必須也是const


(5)動(dòng)態(tài)綁定舉例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>

using namespace std;

class Box
{
protected:
        int length;
        int width;
        int height;

public:
        Box(int length, int width, int height)
                :length(length),width(width),height(height) { } 

        virtual int volume() {return length*width*height;}
        virtual void showvolume() {cout<<"體積:"<<volume()<<endl;}
                
};

class Thinbox : public Box {
public:
        Thinbox(int length, int width, int height):Box(length, width, height) { }

        int volume() {return length*width*height*(1-0.2);}
};

int main(void)
{
        Thinbox tbox(3, 3, 3);

        tbox.showvolume();

        return 0;
}

運(yùn)行結(jié)果:
體積:21

例子分析:
在例子中,通過(guò)派生類(lèi)對(duì)象tbox調(diào)用showvolume();函數(shù)時(shí),由于該函數(shù)在派生類(lèi)中并
沒(méi)有被重寫(xiě),所以調(diào)用的實(shí)際上時(shí)基類(lèi)的showvolume(),基類(lèi)的showvolume()調(diào)用
volume函數(shù)計(jì)算體積時(shí),由于該函數(shù)時(shí)虛函數(shù),并且該函數(shù)被派生類(lèi)重寫(xiě)了,根據(jù)動(dòng)
態(tài)綁定的規(guī)則,需要優(yōu)先調(diào)用被派生類(lèi)中重寫(xiě)的函數(shù),因此結(jié)果是21。    


5. 動(dòng)態(tài)綁定和多態(tài)

(1)向上轉(zhuǎn)型和向下轉(zhuǎn)型

有了動(dòng)態(tài)綁定,距離多態(tài)的實(shí)現(xiàn)也就只有一步之遙,如果需要利用動(dòng)態(tài)綁定實(shí)現(xiàn)多態(tài),我
們還需要加入向上轉(zhuǎn)型的操作。所謂向上轉(zhuǎn)型,就是使用基類(lèi)的指針或者引用指向派生類(lèi)
對(duì)象。利用基類(lèi)指針調(diào)用派生類(lèi)中重寫(xiě)的虛函數(shù)時(shí),實(shí)現(xiàn)的就是多態(tài)的調(diào)用。

(1)向上轉(zhuǎn)型舉例
    向上轉(zhuǎn)型例子:

    Thinbox tbox(3, 3, 3);      
    Box *boxp = &tbox;


    向上轉(zhuǎn)型其實(shí)就是類(lèi)型的強(qiáng)制轉(zhuǎn)換,只是向上轉(zhuǎn)型都是自動(dòng)進(jìn)行的,不需要進(jìn)行
    顯式的強(qiáng)制轉(zhuǎn)換。
    

(3)轉(zhuǎn)型涉及的靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型
    在向上轉(zhuǎn)型的過(guò)程中,=兩邊涉及兩種類(lèi)型,=左邊的類(lèi)型被稱(chēng)為靜態(tài)類(lèi)型,
    右邊的類(lèi)型稱(chēng)為動(dòng)態(tài)類(lèi)型,動(dòng)態(tài)的意思表示對(duì)象的類(lèi)型不定,可以與左邊類(lèi)型
    相同,也可以是左邊類(lèi)型的任何一個(gè)派生類(lèi)型的對(duì)象。
    

(4)向下轉(zhuǎn)型
    有向上轉(zhuǎn)型就有向下轉(zhuǎn)型,但是向上轉(zhuǎn)型時(shí)是無(wú)條件成立的,因?yàn)槎x派生類(lèi)對(duì)象
    時(shí)一定會(huì)實(shí)例化其基類(lèi)對(duì)象,但是向下轉(zhuǎn)型不一定會(huì)成功,只有向上轉(zhuǎn)型后的指針
    或者引用才能向下轉(zhuǎn)型,因?yàn)檫@樣才能保證派生類(lèi)對(duì)象相對(duì)于基類(lèi)多出的空間一定存在。 


    向上轉(zhuǎn)型時(shí)大空間向小空間轉(zhuǎn)換,而向下轉(zhuǎn)型時(shí)小空間向大空間轉(zhuǎn)換,所有向下轉(zhuǎn)型
    時(shí)要格外小心。向下轉(zhuǎn)型時(shí)必須顯式的進(jìn)行類(lèi)型的強(qiáng)制轉(zhuǎn)換。
    

    向下轉(zhuǎn)型舉例:
    (1)錯(cuò)誤例子
        Box box(3, 3, 3);       
        Box *boxp= &box;
    
        Thinbox *tboxp = (Thinbox *)boxp;

    (2)正確例子
        Thinbox tbox(3, 3, 3);      
        Box *boxp= &box;
    
        Thinbox *tboxp = (Thinbox *)boxp;


(2)多態(tài)舉例
多態(tài)只有兩種形式,一種是通過(guò)指針?lè)绞綄?shí)現(xiàn),另一種是通過(guò)引用實(shí)現(xiàn)。

(1)指針實(shí)現(xiàn)初始化
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;

    /* 普通箱子 */
    class Box
    {
    protected:
            int length;
            int width;
            int height;

    public:
            Box(int length, int width, int height)
                 :length(length),width(width),height(height) { }
            /* 虛函數(shù) */
            virtual int volume() const {return length*width*height;}
    };
    /* 薄箱子 */
    class Thinbox : public Box {
    public:
            Thinbox(int length, int width, int height):Box(length, width, height) { }
            /* 由于基類(lèi)的volume函數(shù)是虛汗數(shù),就算這里不知定virtual,也是虛函數(shù) */
            virtual int volume() const {return length*width*height*(1-0.2);}
    };

    /* 厚箱子 */
    class Thickbox : public Box {
    public:
            Thickbox(int length, int width, int height):Box(length, width, height) { }

            virtual int volume() const {return length*width*height*(1-0.5);}
    };

    int main(void)
    {
            Thinbox thinbox(3, 3, 3);
            Thickbox thickbox(3, 3, 3);
            Box *boxp = NULL;

            cout <<"選則顯示那個(gè)箱子的體積"<<endl;
            cout <<"1.薄箱子"<<endl;
            cout <<"2.厚箱子"<<endl;
        cout <<"選擇:";
            int select = 0;
            cin >> select;

            if(1 == select) boxp = &thinbox;
            else if(2 == select) boxp = &thickbox;
            cout <<"箱子體積:"<< boxp->volume() << endl;

            return 0;
    }

    運(yùn)行結(jié)果:
    運(yùn)行時(shí)提示,選擇顯示社么箱子的體積,選擇1,表示顯示薄箱子的體積,
    如果選擇2,表示選擇厚箱子的體積,選擇1,運(yùn)行結(jié)果如下。

       選則顯示那個(gè)箱子的體積
           1.薄箱子
       2.厚箱子
       選擇:1
       箱子體積:271


    例子分析:
    main函數(shù)中定義了一個(gè)Box基類(lèi)的指針,根據(jù)不同選擇,基類(lèi)指針?lè)謩e指向不同
    派生類(lèi)對(duì)象,通過(guò)基類(lèi)調(diào)用volume函數(shù)時(shí),根據(jù)情況的不同,實(shí)際上調(diào)用的是
    不同派生類(lèi)對(duì)象的volume函數(shù),這就是多態(tài)。         

(2)引用實(shí)現(xiàn)多態(tài)
    
    還是上面的例子,但是將main函數(shù)改為如下形式,并增加一個(gè)子函數(shù)。

    void showvolume(const Box &box) {
            cout <<"箱子體積:"<< box.volume() << endl;
    }

    int main(void)
    {
            Thinbox thinbox(3, 3, 3);
            Thickbox thickbox(3, 3, 3);

            cout <<"選則顯示那個(gè)箱子的體積"<<endl;
            cout <<"1.薄箱子"<<endl;
            cout <<"2.厚箱子"<<endl;
            int select = 0;
            cin >> select;

            if(1 == select) showvolume(thinbox);
            else if(2 == select) showvolume(thickbox);

            return 0;
    }
    
    
    例子分析:
    引用只能初始化,不能賦值,這一點(diǎn)顯然與指針不同,因此通過(guò)引用實(shí)現(xiàn)多態(tài),
    通常都是通過(guò)函數(shù)形參實(shí)現(xiàn)的,多次調(diào)用該函數(shù),可以多次對(duì)引用類(lèi)型的形參
    進(jìn)行初始化。      


(3)虛函數(shù)指針表vtable
    之所以能夠?qū)崿F(xiàn)多態(tài),是因?yàn)閷?duì)象中會(huì)維護(hù)一張?zhí)摵瘮?shù)指針表vtable,當(dāng)使用基
    類(lèi)被重寫(xiě)的接口調(diào)用虛函數(shù)時(shí),會(huì)到表中查找需要?jiǎng)討B(tài)綁定調(diào)用的重寫(xiě)函數(shù),這
    個(gè)實(shí)現(xiàn)機(jī)制會(huì)使實(shí)現(xiàn)多態(tài)的對(duì)象多消耗幾個(gè)字節(jié)來(lái)存放虛函數(shù)表。但是對(duì)于多態(tài)
    帶來(lái)的好處來(lái)說(shuō),多消耗幾個(gè)字節(jié)資源算不上什么。

    因此,如果我們的多態(tài)實(shí)現(xiàn)有錯(cuò)誤時(shí),編譯時(shí)往往可能會(huì)提示vtable表有問(wèn)題。
    
    
6. 純虛函數(shù)
(1)什么是純虛函數(shù)
只有虛函數(shù)的聲明,沒(méi)有函數(shù)定義的就是純虛函數(shù)。


純虛函數(shù)的格式:virtual 返回值 函數(shù)名(參數(shù)列表) = 0;

如果函數(shù)是const類(lèi)型的函數(shù),=0需要放在const的后面,定義純虛函數(shù)時(shí),必寫(xiě)=0的標(biāo)識(shí)。


(2)為什么需要純虛函數(shù)
很多時(shí)候,基類(lèi)中的虛函數(shù)只需要留下一個(gè)接口即可,不需要具體實(shí)現(xiàn),因?yàn)樘摵瘮?shù)的具
體實(shí)現(xiàn)應(yīng)該留給派生類(lèi)的重寫(xiě)函數(shù)來(lái)完成。

在java中普通函數(shù)就可以實(shí)現(xiàn)多態(tài)的,但是在c++中,只有虛函數(shù)才能實(shí)現(xiàn)多態(tài),C++中的
純虛函數(shù)就是java中的抽象函數(shù)。c++和java的實(shí)現(xiàn)多態(tài)時(shí),函數(shù)重寫(xiě)的的對(duì)應(yīng)關(guān)系是這樣的。

c++中的虛函數(shù)重寫(xiě)   <===對(duì)應(yīng)===> java中普通函數(shù)的重寫(xiě)
c++中的純虛函數(shù)重寫(xiě) <===對(duì)應(yīng)===> java中抽象函數(shù)的重寫(xiě)

比如完全可以將前面多態(tài)例子中的Box基類(lèi)中的volume()函數(shù)設(shè)置純虛函數(shù)。
        virtual int volume() const = 0;

程序運(yùn)行的結(jié)果與之前是一樣。


(3)抽象類(lèi)  
(1)什么是抽象類(lèi)
    只要有包含純虛函數(shù)的類(lèi)就是抽象類(lèi)。


(2)抽象類(lèi)的作用
    抽象類(lèi)用于給定函數(shù)接口,搭建程序框架,所有的函數(shù)的具體實(shí)現(xiàn)全部由派生類(lèi)重寫(xiě)
    實(shí)現(xiàn)。抽象類(lèi)固定了接口,但是代碼的實(shí)現(xiàn)卻很靈活,都掌握在派生類(lèi)手中。


(3)抽象類(lèi)特點(diǎn)
    (1)不能實(shí)例化對(duì)象,只能通過(guò)派生類(lèi)實(shí)例化對(duì)象
        
        
    (2)如果派生類(lèi)不全部實(shí)現(xiàn)抽象基類(lèi)的純虛函數(shù)的話(huà),派生類(lèi)也是一個(gè)抽象類(lèi),
        被實(shí)現(xiàn)了的純虛函數(shù)就變?yōu)榱似胀ㄌ摵瘮?shù)。

        如果在子類(lèi)中繼續(xù)將函數(shù)保持為純虛函數(shù)的話(huà),=0標(biāo)志可以不用設(shè)置。
        

    (3)抽象類(lèi)雖然不能直接實(shí)例化,但是有自己構(gòu)造函數(shù),因?yàn)橥ㄟ^(guò)派生類(lèi)間接實(shí)現(xiàn)
        抽象類(lèi)實(shí)例化時(shí),需要在派生類(lèi)的初始化列表中調(diào)用抽象類(lèi)的構(gòu)造函數(shù)初始
        化其成員。
    
        抽象類(lèi)的構(gòu)造函數(shù)不能是純虛函數(shù),通過(guò)派生類(lèi)對(duì)象實(shí)例化抽象基類(lèi)時(shí),沒(méi)
        有辦法調(diào)用虛構(gòu)造函數(shù)初始化抽象類(lèi)的成員,這會(huì)導(dǎo)致不確定的結(jié)果。     


    (4)由于抽象類(lèi)不能創(chuàng)建對(duì)象,因此不能把抽象類(lèi)作為參數(shù)類(lèi)型和返回類(lèi)型,因?yàn)?        作為參數(shù)和返回類(lèi)型時(shí)都需要開(kāi)辟對(duì)象空間,但是抽象類(lèi)并不允許被實(shí)例化。


    (5)由于抽象類(lèi)不會(huì)直接實(shí)例化,因此抽象類(lèi)的構(gòu)造函數(shù)通常都是由子類(lèi)初始化
        列表調(diào)用,外部不會(huì)調(diào)用,因此通常做法是將抽象類(lèi)的構(gòu)造函數(shù)設(shè)置為
        protected。

    
        7.虛析構(gòu)函數(shù)
    (1)通過(guò)基類(lèi)指針釋放派生類(lèi)對(duì)像時(shí)遇到的問(wèn)題

常常會(huì)使用基類(lèi)指針指向動(dòng)態(tài)分配的派生類(lèi)對(duì)象,當(dāng)我們通過(guò)基類(lèi)指針釋放派生類(lèi)的空間
時(shí),往往會(huì)出現(xiàn)問(wèn)題,比如還是利用前面Box的例子來(lái)說(shuō)明這個(gè)問(wèn)題。

例子:
        #include <stdio.h>
            #include <stdlib.h>
            #include <string.h>
            #include <iostream>
            #include <string>
            #include <cassert>
            #include <errno.h>

            using namespace std;

            /* 普通箱子 */
            class Box
            {
            protected:
                    int length;
                    int width;
                    int height;

            public:
                    Box(int length, int width, int height)
                     :length(length),width(width),height(height) { }

                    ~Box() {
                            cout<<"Box' destructor"<<endl;
                    }

                    /* 虛函數(shù) */
                    virtual int volume() const = 0;
                    virtual int volume1() const { }
            };

        /* 薄箱子 */
            class Thinbox : public Box {
            public:
                    Thinbox(int length, int width, int height):Box(length, width, height) { }
                    ~Thinbox() {
                            cout<<"thinbox' destructor"<<endl;
                    }

                    /* 由于幾類(lèi)的volume函數(shù)是虛汗數(shù),就算這里不知定virtual,也是虛函數(shù) */
                    virtual int volume() const {return length*width*height*(1-0.2);}
            };


            /* 厚箱子 */
            class Thickbox : public Box {
            public:
                    Thickbox(int length, int width, int height):Box(length, width, height) { }
                    ~Thickbox() {
                            cout<<"Thickbox' destructor"<<endl;
                    }

                    virtual int volume() const {return length*width*height*(1-0.5);}
            };


            int main(void)
            {
                    Box *boxp[] = {new Thinbox(3,3,3), new Thickbox(3,3,3)};

                    for(int i=0; i<sizeof(boxp)/sizeof(boxp[0]); i++) {
                            delete(boxp[i]);
                    }

                    return 0;
            }

    運(yùn)行結(jié)果:
    Box' destructor
    Box' destructor

    例子分析:
    例子中分別在基類(lèi)和派生類(lèi)中加入了各自的析構(gòu)函數(shù),主函數(shù)中定義了一個(gè)
    基類(lèi)指針數(shù)組,元素分別指向派生類(lèi)對(duì)象,通過(guò)基類(lèi)指針釋放派生類(lèi)對(duì)象時(shí),
    希望調(diào)用派生類(lèi)各自的析構(gòu)函數(shù),然后再調(diào)用基類(lèi)的析構(gòu)函數(shù)。

    顯然從結(jié)果看,沒(méi)有達(dá)到我們想要的結(jié)果,因?yàn)椴](méi)有調(diào)用派生類(lèi)的析構(gòu)函數(shù)。


(2)虛析構(gòu)函數(shù)
通過(guò)基類(lèi)指針釋放派生類(lèi)對(duì)象時(shí),如果希望調(diào)用派生類(lèi)析構(gòu)函數(shù)的話(huà),就應(yīng)該使用虛析
構(gòu)函數(shù)來(lái)解決這個(gè)問(wèn)題。

使用虛析構(gòu)函數(shù)時(shí),應(yīng)該將基類(lèi)的析構(gòu)函數(shù)設(shè)置為虛析構(gòu)函數(shù),設(shè)置派生類(lèi)的虛析構(gòu)函
數(shù)并不起作用。

如果在上面例子中基類(lèi)的虛構(gòu)函數(shù)前面添加virtual關(guān)鍵字即可。
    
                    virtual ~Box() {
                            cout<<"Box' destructor"<<endl;
                    }

再次編譯運(yùn)行,就會(huì)得到如下正確結(jié)果:
    thinbox' destructor
    Box' destructor
    Thickbox' destructor
    Box' destructor



(3)什么時(shí)候使用虛析構(gòu)函數(shù)
使用繼承時(shí),并且類(lèi)中至少包含一個(gè)虛函數(shù)時(shí),我們應(yīng)該總是把基類(lèi)的析構(gòu)函數(shù)聲明
為虛析構(gòu)函數(shù),以保證析構(gòu)函數(shù)被正確調(diào)用,實(shí)現(xiàn)資源的正確釋放。



8. typeid操作符


(1)作用有兩個(gè)
    (1)確認(rèn)類(lèi)型
    (2)類(lèi)型統(tǒng)一

typeid()操作符相當(dāng)于java中的instanceof()操作符。

(2)typeid操作符詳解
    (1)使用格式
        typeid(expression)

    expression可以是:
    (1)類(lèi)型,
        (1)基本類(lèi)或者基本類(lèi)型相關(guān)的指針和引用類(lèi)型
        (2)組合類(lèi)型,數(shù)組,結(jié)構(gòu)體,聯(lián)合體,類(lèi)類(lèi)型,以及相關(guān)的指針引用

        比如:
        typeid(int),typeid(Student)
        

    (3)具體對(duì)象
        具體數(shù)值,結(jié)構(gòu)體/聯(lián)合體/變量,類(lèi)對(duì)象
        
        比如:
        int a;
        typeid(a);
        或者
        type(10);
        

(2)操作符返回類(lèi)型
    使用typeid操作符,返回的是type_info類(lèi)類(lèi)型的引用,

    (1)該類(lèi)類(lèi)型特點(diǎn)
        (1)實(shí)現(xiàn)了虛析構(gòu)函數(shù),可以作為基類(lèi)安全使用
        (2)它的構(gòu)造函數(shù),副本構(gòu)造函數(shù),=重載函數(shù)都被private限制因此不
             能直接使用該類(lèi)型實(shí)例化對(duì)象,不能初始化,不能進(jìn)行對(duì)象的賦值運(yùn)算。

        (3)使用typeid操作符是唯一產(chǎn)生type_info類(lèi)型對(duì)象的方法。


    (2)該類(lèi)型實(shí)現(xiàn)了的運(yùn)算符
        (1)t1==t2:類(lèi)型相同表達(dá)式為true,否者false
        (2)t1!=t2:類(lèi)型不相同表達(dá)式為true,否者false
        (3)t.name():返回類(lèi)型名稱(chēng)
        

    (4)例子
        int main(void)
        {
                int a = 10;
                int b = 10;
                long c = 120;

                if(typeid(a)==typeid(b)) {
                        cout<<"a 與 b的類(lèi)型相同\n"<<endl;
                }

                if(typeid(c)!=typeid(23.7)) {
                        cout<<"c 與 23.7的類(lèi)型相同\n"<<endl;
                }
    
                if(typeid(c)==typeid(long)) {
                        cout<<"c是long型\n"<<endl;
                }
    
                int *p = &a;
                cout <<"p的類(lèi)型"<<typeid(p).name() << endl<<endl;
                cout <<"*p的類(lèi)型"<<typeid(*p).name() << endl<<endl;
                cout <<"*p的類(lèi)型"<<typeid(*p).name() << endl<<endl;

                return 0;
        }

        以上測(cè)試方式對(duì)于結(jié)構(gòu)體,數(shù)組,聯(lián)合體等組合類(lèi)型同樣有效。    
    
(3)靜態(tài)類(lèi)型確認(rèn)
靜態(tài)類(lèi)型與sizeof同,但是sizeof不能提供==/!=等操作,靜態(tài)類(lèi)型的確認(rèn)是由編譯器
在編譯時(shí)進(jìn)行計(jì)算的。

(1)哪些情況是靜態(tài)類(lèi)型確認(rèn)  

    (1)對(duì)于非類(lèi)類(lèi)型的類(lèi)型確認(rèn),以及不包含虛函數(shù)的類(lèi)對(duì)象的類(lèi)型確認(rèn),
        都是靜態(tài)類(lèi)型確認(rèn)。

    (2)如果typeid參數(shù)是類(lèi)型的時(shí)候,比如
        typeid(int)
        或者
        typeid(Box)
        進(jìn)行的都是靜態(tài)類(lèi)型確認(rèn)



(3)動(dòng)態(tài)類(lèi)型確認(rèn)RTTI(run-time Type identification)

(1)什么時(shí)候進(jìn)行動(dòng)態(tài)類(lèi)型確認(rèn),什么是動(dòng)態(tài)類(lèi)型確認(rèn)

    對(duì)指向派生類(lèi)對(duì)象的包含有虛函數(shù)的基類(lèi)指針解引用和引用使用typeid進(jìn)行
    類(lèi)型確認(rèn)時(shí),得到的是派生類(lèi)的類(lèi)型,這就是動(dòng)態(tài)類(lèi)型確認(rèn)。

    注意:
    (1)這里強(qiáng)調(diào)的是基類(lèi)指針解釋引用(*p)和引用,如果直接對(duì)基類(lèi)類(lèi)型,指
        針或者對(duì)象進(jìn)行typeid時(shí),是靜態(tài)類(lèi)型確認(rèn)。

    (2)引用必須初始化,但是指針可以為NULL,如果動(dòng)態(tài)確認(rèn)時(shí),基類(lèi)指針為
        NULL,會(huì)導(dǎo)致typeid動(dòng)態(tài)類(lèi)型確認(rèn)失敗。 

    例子:
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        #include <iostream>
        #include <string>
        #include <cassert>
        #include <errno.h>
        #include <typeinfo>

        using namespace std;

        class Box
        {
        protected:
                int length;
                int width;
                int height;

        public:
                Box(int length, int width, int height)
                :length(length),width(width),height(height) { }

                virtual ~Box() { }

                /* 虛函數(shù) */
                virtual int volume() const {}
        };

        /* 薄箱子 */
        class Thinbox : public Box {
        public:
                Thinbox(int length, int width, int height):Box(length, width, height) { }

                /* 由于幾類(lèi)的volume函數(shù)是虛汗數(shù),就算這里不知定virtual,也是虛函數(shù) */
             virtual int volume() const {return length*width*height*(1-0.2);}
        };


        int main(void)
        {
                Thinbox thinbox(1,2,3);
                Box box1(1,2,3);

                Box &box2 = thinbox;
                Box *boxp = NULL;


                cout << "對(duì)類(lèi)型直接typeid是靜態(tài)類(lèi)型確認(rèn)" <<endl;
                cout << "typeid判斷基類(lèi)類(lèi)型Box的類(lèi)型為:" <<typeid(Box).name() << endl;
                cout << "typeid判斷派生類(lèi)類(lèi)型Thinbox的類(lèi)型為:" <<typeid(thinbox).name() << endl;

                cout << "\n對(duì)類(lèi)對(duì)象或者指針typeid也是靜態(tài)類(lèi)型確認(rèn)" <<endl;
                cout << "typeid判斷對(duì)象thinbox的類(lèi)型為:" <<typeid(thinbox).name() << endl;
                cout << "typeid判斷對(duì)象box1的類(lèi)型為:" <<typeid(box1).name() << endl;
                cout << "typeid判斷基類(lèi)指針boxp的類(lèi)型為:" <<typeid(boxp).name() << endl;

                cout << "\n對(duì)指向派生類(lèi)對(duì)象的包含有虛函數(shù)的基類(lèi)";
                cout <<"指針解引用和引用使用typeid時(shí)是動(dòng)態(tài)類(lèi)型確認(rèn)" <<endl;
                cout << "typeid判斷基類(lèi)引用box2的類(lèi)型為:" <<typeid(box2).name() << endl;
                cout << "typeid判斷基類(lèi)指針*boxp的類(lèi)型為:" <<typeid(*boxp).name() << endl;

                return 0;
        }

(2)動(dòng)態(tài)確認(rèn)的好處
    使用動(dòng)態(tài)類(lèi)型,便于在程序中根據(jù)類(lèi)型的不同進(jìn)行選擇不同的分支運(yùn)行。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>
    #include <typeinfo>

        using namespace std;
    /* 普通箱子 */
    class Box
    {
    protected:
            int length;
            int width;
            int height;

    public:
         Box(int length, int width, int height)
            :length(length),width(width),height(height) { }

            virtual ~Box() { }

            /* 虛函數(shù) */
            virtual int volume() const {}
    };

    /* 薄箱子 */
    class Thinbox : public Box {
    public:
            Thinbox(int length, int width, int height):Box(length, width, height) { }

            /* 由于幾類(lèi)的volume函數(shù)是虛汗數(shù),就算這里不知定virtual,也是虛函數(shù) */
            virtual int volume() const {return length*width*height*(1-0.2);}
    };

    /* 厚箱子 */
    class Thickbox : public Box {
    public:
            Thickbox(int length, int width, int height):Box(length, width, height) { }

            virtual int volume() const {return length*width*height*(1-0.5);}
    };

    //void using_box(const Box *box) {
    void using_box(const Box &box) {
            cout <<"箱子體積:"<<box.volume()<<endl;

            //if(typeid(*box)==typeid(Thinbox)) {//使用指針時(shí),需要解引用
            if(typeid(box)==typeid(Thinbox)) {
                    cout<<"薄箱子,不結(jié)實(shí),用來(lái)裝衣物!"<<endl;
            //} else if(typeid(*box)==typeid(Thickbox)) {
            } else if(typeid(box)==typeid(Thickbox)) {
                    cout<<"厚箱子,很結(jié)實(shí),用來(lái)裝健身用品!"<<endl;
            }
    }

    int main(void)
    {
            Thinbox thinbox(1,2,3);
            Thickbox thickbox(1,2,3);
            //using_box(&thinbox);
            using_box(thinbox);
            printf("\n");
            //using_box(&thickbox);
            using_box(thickbox);

            return 0;
    }
    
    運(yùn)行結(jié)果:
    箱子體積:4
    薄箱子,不結(jié)實(shí),用來(lái)裝衣物!

    箱子體積:3
    厚箱子,很結(jié)實(shí),用來(lái)裝健身用品!

    例子分析:
    在例子中的using_box函數(shù)中,
    
        void using_box(const Box &box) {
                cout <<"箱子體積:"<<box.volume()<<endl;

                if(typeid(box)==typeid(Thinbox)) {
                        cout<<"薄箱子,不結(jié)實(shí),用來(lái)裝衣物!"<<endl;
                } else if(typeid(box)==typeid(Thickbox)) {
                        cout<<"厚箱子,不結(jié)實(shí),用來(lái)裝健身用品!"<<endl;
                }
        }
        
        對(duì)類(lèi)型進(jìn)行了動(dòng)態(tài)確認(rèn),根據(jù)基類(lèi)引用box指向的不同的派生類(lèi)類(lèi)型
        ,通過(guò)分支語(yǔ)句選擇做不同的事情。
        
        從這里我們看出,使用基類(lèi)引用或者指針有一個(gè)好處就是可以用于
        統(tǒng)一各種不同的派生類(lèi)型對(duì)象的傳參。
        

(3)動(dòng)態(tài)類(lèi)型確認(rèn)帶來(lái)的問(wèn)題
    動(dòng)態(tài)類(lèi)型確認(rèn)有它一定的用途,但是動(dòng)態(tài)類(lèi)型確認(rèn)也帶了一個(gè)問(wèn)題,那就是
    不利于解耦。

    我們之所以使用多態(tài),是為了實(shí)現(xiàn)分層結(jié)構(gòu)的程序開(kāi)發(fā),層與層之間應(yīng)該盡
    可能的減少耦合。

    但是在上面使用動(dòng)態(tài)類(lèi)型確認(rèn)的例子中,using_box函數(shù)涉及的的耦合性太強(qiáng),
    因?yàn)樵摵瘮?shù)里面使用到了具體的派生類(lèi)型,如果派生類(lèi)型發(fā)生改變,using_box
    函數(shù)就必須相應(yīng)改動(dòng),顯然耦合性太強(qiáng)。

    在實(shí)際開(kāi)發(fā)中,應(yīng)該盡量利用多態(tài)的特點(diǎn),在上層使用基類(lèi)提供的統(tǒng)一接口調(diào)用
    下層派生類(lèi)中重寫(xiě)的函數(shù),降低層級(jí)間的耦合。

        
9. 使用多態(tài)降低耦合
例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>
    #include <typeinfo>

    using namespace std;

    class Plane {
    public:
            Plane(const string name): name(name) {}
            virtual ~Plane() {}
            virtual void turn_left(int agree) const=0;
            virtual void turn_right(int agree) const=0;
            virtual void go_forward(int speed) const=0;

    protected:
            string name;

    };

    class Byplane: public Plane {
    public:
            Byplane(const string name="by747"):Plane(name) {}
            void turn_left(int agree) const {
                    cout<<name<<" turn_left "<<agree<<"度"<<endl;
            }
            void turn_right(int agree) const {
                    cout<<name<<" turn_right "<<agree<<"度"<<endl;
            }
            void go_forward(int speed) const {
                    cout<<name<<" go forward "<<speed<<"英里"<<endl;
            }
    };

    class Airplane: public Plane {
    public:
            Airplane(const string name="air380"):Plane(name) {}
            virtual ~Airplane() {}
            void turn_left(int agree) const {
                    cout<<name<<" turn_left "<<agree<<"度"<<endl;
            }
            void turn_right(int agree) const {
                    cout<<name<<" turn_right "<<agree<<"度"<<endl;
            }
            void go_forward(int speed) const {
                    cout<<name<<" go forward "<<speed<<"英里"<<endl;
            }
    };
    void oprate_plane(const Plane &plane) {
            cout << "飛機(jī)操作選擇" << endl;
            cout << "1.左轉(zhuǎn)" << endl;
            cout << "2.右轉(zhuǎn)" << endl;
            cout << "3.前進(jìn)" << endl;

            int select = 0;
            cin>>select;

            int tmp = 0;
            if(1 == select) {
                    cout << "輸入轉(zhuǎn)向角度數(shù)" << endl;
                    cin>>tmp;
                    plane.turn_left(tmp);
            } else if(2 == select) {
                    cout << "輸入轉(zhuǎn)向角度數(shù)" << endl;
                    cin>>tmp;
                    plane.turn_right(tmp);
            } else if(3 == select) {
                    cout << "輸入前進(jìn)速度" << endl;
                    cin>>tmp;
                    plane.go_forward(tmp);
            }
    }

    int main(void)
    {
            Byplane byplane("by747");
            Airplane airplane("air380");

            oprate_plane(byplane);

            return 0;
    }

    例子分析:
    在operate_plane函數(shù)中,所有的操作只與基類(lèi)相關(guān),與派生類(lèi)沒(méi)有任何直接關(guān)系,
    就算是開(kāi)發(fā)出新的派生類(lèi),只要遵守plane基類(lèi)規(guī)定的接口,就可以直接使用
    operate_plane函數(shù)操作,無(wú)需任何更改,完全解除了operate_plane函數(shù)與派生類(lèi)
    之間的耦合。



10. 類(lèi)成員指針       

(1)通過(guò)指針訪(fǎng)問(wèn)類(lèi)對(duì)象數(shù)據(jù)成員
    (1)普通指針 
        直接看例子:
        #include <iostream>
        #include <stdio.h>
        #include <string>
        #include <typeinfo>
        #include <errno.h>

        using namespace std;

        class Rectangl {
        public:
                Rectangl(int length=1, int width=1):length(length), width(width) { } 
                int *get_length_ponter() { return &length; }


                int length;
        protected:
                int width;
        };

        int main(void)
        {
                Rectangl rect1(2, 3); 
                int *p = NULL;

                p = &rect1.length;
                printf("p=%p\n", p);
                printf("*p=%d\n", *p);

                p = rect1.get_length_ponter();
                printf("p=%p\n", p);
                printf("*p=%d\n", *p);

                return 0;
        }
    
        運(yùn)行結(jié)果:
            p=0xbff36394
            *p=2
            p=0xbff36394
            *p=2
        例子分析:
        例子中,直接使用普通指針存放對(duì)象數(shù)據(jù)成員地址,或者可以使用
        getter取得數(shù)據(jù)成員地址,從運(yùn)行結(jié)果來(lái)看,一般指針存放對(duì)象數(shù)
        據(jù)成員的地址是可行,這與使用一般指針存放結(jié)構(gòu)體地址是一樣的。

            
(2)類(lèi)成員指針
    (1)與普通指針的區(qū)別
        訪(fǎng)問(wèn)類(lèi)的數(shù)據(jù)成員,還可以使用類(lèi)成員指針,類(lèi)成員指針與普通指針區(qū)別在
        于,普通指針存放的是絕對(duì)地址,但是類(lèi)成員指針存放的確是相對(duì)于類(lèi)對(duì)象
        首字節(jié)地址的相對(duì)地址差,因此不能使用該地址直接訪(fǎng)問(wèn)數(shù)據(jù)成員,必須與
        對(duì)象綁定后才能訪(fǎng)問(wèn)指向的成員。

        
    (2)類(lèi)指針定義和使用格式
        成員類(lèi)型 類(lèi)名::*pointer_name;

        比如:
        int Student::*p = &Student::age; 
        Student stu1("zhangsan", 20);
        Student *stu2 = new Student("wangwu", 30);
        int a = stu1.*age;  
        int a = stu2->*age; 
        

    (3)使用舉例
        #include <iostream>
        #include <stdio.h>
        #include <string>
        #include <typeinfo>
        #include <errno.h>

        using namespace std;

        class Rectangl {
        public:
                Rectangl(int length=1, int width=1):length(length), width(width) { } 

                int length;
                int width;
        };

        int main(void)
        {
                Rectangl rect1(2, 3); 
                Rectangl *rect2 = new Rectangl(2, 3); 
        
                int Rectangl::*p = NULL;
                p = &Rectangl::width;

                printf("%p\n", p);
                printf("%d\n", rect1.*p);
                printf("%d\n", rect2->*p);


                return 0;
        }
        運(yùn)行結(jié)果:
        0x4
        3
        3
        
        例子分析:
        從打印出來(lái)的地址來(lái)看,并不是一個(gè)絕對(duì)地址,只是一個(gè)地址相
        對(duì)值,在通過(guò)類(lèi)成員指針訪(fǎng)問(wèn)類(lèi)數(shù)據(jù)成員時(shí),一定要注意與對(duì)象
        綁定后才能訪(fǎng)問(wèn)成員,而且要注意訪(fǎng)問(wèn)格式。

(2)通過(guò)指針訪(fǎng)問(wèn)對(duì)象成員函數(shù)

(1)與使用指針訪(fǎng)問(wèn)數(shù)據(jù)成員對(duì)比

    與前面的數(shù)據(jù)成員不同,對(duì)于成員函數(shù),如果希望通過(guò)函數(shù)指針訪(fǎng)問(wèn),只能通過(guò)類(lèi)
    成員函數(shù)指針訪(fǎng)問(wèn),不能通過(guò)普通函數(shù)指針訪(fǎng)問(wèn),同樣,訪(fǎng)問(wèn)時(shí)也需要和對(duì)象綁定。


(2)格式
    (1)定義格式
        返回值 (類(lèi)名::pointer_name)(形參類(lèi)型列表)
        
        樣子有點(diǎn)復(fù)復(fù)雜,可以使用typedef關(guān)鍵字進(jìn)行簡(jiǎn)化。         
        typedef 返回值 (類(lèi)名::pointer_name)(形參類(lèi)型列表)

        這個(gè)時(shí)候pointer_name就是個(gè)別名,使用這個(gè)別名就可以用于
        定義類(lèi)函數(shù)成員指針了。

        如果有成員函數(shù)有const的話(huà),需要加cosnt。
        
    (2)訪(fǎng)問(wèn)格式
        (對(duì)象名.*函數(shù)指針)(實(shí)參) 

        或者:

        (對(duì)象名->*函數(shù)指針)(實(shí)參)


        注意使用時(shí)要加括號(hào),否者函數(shù)指針將優(yōu)先與參數(shù)列表先結(jié)合,顯然是不對(duì)的。 
        

(3)使用例子
    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <typeinfo>
    #include <errno.h>

    using namespace std;

    class Rectangl {
    public:
            Rectangl(int length=1, int width=1):length(length), width(width) { }

            int get_length() const {
                    return length;
         }

    private:
            int length;
            int width;
    };  
        
    typedef int (Rectangl::*fun_type)() const;

    int main(void)
    {
            Rectangl rect1(2, 3);
            Rectangl *rect2 = new Rectangl(2, 3);

            fun_type get_funp;
            get_funp = &Rectangl::get_length;

            cout << get_funp <<endl;
            cout << (rect1.*get_funp)() <<endl;
            cout << (rect2->*get_funp)() <<endl;

            return 0;
    }
    
    運(yùn)行結(jié)果:
    1
    2
    2

    例子分析:
    與類(lèi)的數(shù)據(jù)成員指針的使用實(shí)際上是一樣的,使用時(shí)需要與對(duì)象綁定。     
    類(lèi)的函數(shù)成員指針變量存放的任然是一個(gè)地址偏移(相對(duì)地址差值)。
        
            
(4)類(lèi)成員作為傳參

    (1)普通函數(shù)的傳參
        #include <iostream>
        #include <stdio.h>
        #include <string>
        #include <typeinfo>
        #include <errno.h>

        using namespace std;

        class Rectangl {
        public:
                Rectangl(int length=1, int width=1):length(length), width(width) { } 

                int get_length() const {
                        return length;
                }   

                int length;
                int width;
        };

        typedef int (Rectangl::*fun_type)() const;

        void show(fun_type funp, int Rectangl::*datap) {
                Rectangl rect1(2, 3); 
                Rectangl *rect2 = &rect1;

                cout << "長(zhǎng):" << (rect1.*funp)() << endl;
                cout << "寬:" << rect2->*datap << endl;
        }


        int main(void)
        {
                fun_type get_funp;

                get_funp = &Rectangl::get_length;
                int Rectangl::*widthp = &Rectangl::width;

                show(get_funp, widthp);

                return 0;
        }


    (2)成員函數(shù)的傳參
        #include <iostream>
        #include <stdio.h>
        #include <string>
        #include <typeinfo>
        #include <errno.h>

        using namespace std;

        class Rectangl;//類(lèi)聲明
        typedef int (Rectangl::*fun_type)() const;

        class Rectangl {
        public:
                Rectangl(int length=1, int width=1):length(length), width(width) { } 

                int get_length() const {
                        return length;
                }   
                void show(fun_type funp, int Rectangl::*datap) {          
                        cout << "長(zhǎng):" << (this->*funp)() << endl;     
                        cout << "寬:" << this->*datap << endl;  
                }   

                int length;
                int width;
        };  

        int main(void)
        {
                Rectangl rect1(2, 3);
                fun_type get_funp;

                get_funp = &Rectangl::get_length;
                int Rectangl::*widthp = &Rectangl::width;

                rect1.show(get_funp, widthp);

                return 0;
        }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 清晨,當(dāng)我洗臉的時(shí)候,水在我指尖兒流。輕柔柔。我的臉,我的眼。鏡前霓燈,鏡中人影。霓燈的晶輝啊,你可否讓那影...
    靜鈴音閱讀 335評(píng)論 21 19
  • 佳節(jié),無(wú)人與還 取酒,弄弦歌起 秋來(lái)孤處恰佳節(jié), 舉酒無(wú)月何三人? 聞曲醉臥無(wú)紅襟, 伊人倩影映杯底。 恰似總角青...
    從何講閱讀 339評(píng)論 0 0

友情鏈接更多精彩內(nèi)容