C++面向?qū)ο?多態(tài)

父類和子類指針

1 父類指針可以指向子類對(duì)象顿天,這是安全的犀忱,開(kāi)發(fā)中經(jīng)常用到拣展,繼承方式必須是public方式。
2 子類指針指向父類是不安全的梭灿,因?yàn)樽宇愔羔樋赡茉L問(wèn)到父類以外的數(shù)據(jù)画侣,而子類對(duì)象并沒(méi)有創(chuàng)建。

class Person {
public:
    int m_age;
};

class Student : public Person {
public:
    int m_score;
};

int main() {
    //父類指針指向子類對(duì)象堡妒,安全
    Person *p = (Person *) new Student();
    p->m_age = 10;

    //子類指針指向父類對(duì)象配乱,不安全
    Student *s = (Student *) new Person();
    //指向父類對(duì)象的子類指針訪問(wèn)了自己的成員變量,指針指向超出了范圍
    s->m_score = 10;
    return 0;
}

多態(tài)

多態(tài)是面向?qū)ο笠粋€(gè)非常重要的特性皮迟,同一操作作用于不同的對(duì)象搬泥,產(chǎn)生不同的執(zhí)行結(jié)果,在運(yùn)行時(shí)万栅,可以識(shí)別出真正的對(duì)象類型,調(diào)用對(duì)應(yīng)子類的函數(shù)西疤。產(chǎn)生多態(tài)的條件如下:子類重寫父類的成員函數(shù)烦粒,父類指針指向子類對(duì)象,利用父類指針調(diào)用重寫的成員函數(shù),這個(gè)成員函數(shù)必須是由Virtual修飾的成員函數(shù),父類只要聲明了Virtual代赁,子類自動(dòng)轉(zhuǎn)為Virtual函數(shù)

class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};

class Dog : public Animal {
public:
    void run() {
        cout << "Dog::run()" << endl;
    }
};

class ErHa : public Dog {
public:
    void run() {
        cout << "ErHa::run()" << endl;
    }
};

int main() {
    
    Dog *dog0 = new Dog();
    dog0->run(); //調(diào)用dog下的run函數(shù)
    
    Dog *dog1 = new ErHa();
    dog1->run();//調(diào)用ErHa下的run函數(shù)
    

    return 0;
}

以上就是虛函數(shù)實(shí)現(xiàn)的列子扰她,我們?cè)龠M(jìn)一步,為什么不加Virtual 就不能實(shí)現(xiàn)多態(tài)了芭碍,加了Virtual 就能實(shí)現(xiàn)多態(tài)呢徒役,多態(tài)的實(shí)現(xiàn)是靠虛表實(shí)現(xiàn)的。我們先看看這幾個(gè)類的大小

    //8
    cout << sizeof(Dog) << endl;
    //8
    cout << sizeof(ErHa) << endl;
    //8
    cout << sizeof(Animal) << endl;

這幾個(gè)類sizeof大小是8窖壕,我們這是64位的忧勿,其實(shí)這個(gè)新增加的大小就是虛表的地址杉女,指向虛表,而且這個(gè)虛表地址在類的最前面鸳吸,而虛表里面存著本類虛函數(shù)的地址熏挎,從而進(jìn)行真正的調(diào)用,每一個(gè)類只有一份虛表,多個(gè)對(duì)象共享一份虛表晌砾,無(wú)論這個(gè)對(duì)象是在堆還是棧還是全局對(duì)象坎拐。我們可以通過(guò)反匯編和內(nèi)存調(diào)試也能看出來(lái),這里我就不演示了养匈。當(dāng)父類實(shí)現(xiàn)了虛函數(shù)哼勇,而子類沒(méi)有實(shí)現(xiàn)該虛函數(shù)的時(shí)候,我們來(lái)看看這個(gè)情況:

class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
};

class Dog : public Animal {
public:
    int m_age; //dog的age
};

int main() {
    
    Animal *animal = new Animal();
    animal->run();會(huì)調(diào)用Animal::run()
    animal->speak();會(huì)調(diào)用Animal::speak()
    
    Dog *dog0 = new Dog();  
    dog0->run(); //會(huì)調(diào)用Animal::run()
    dog0->speak(); //會(huì)調(diào)用Animal::speak()

    return 0;
}

當(dāng)子類沒(méi)有重寫父類虛函數(shù)的時(shí)候呕乎,它也會(huì)調(diào)用父類的虛函數(shù)积担,其底層實(shí)現(xiàn)也是通過(guò)虛表查找到函數(shù)調(diào)用,也就是子類即時(shí)沒(méi)有虛函數(shù)楣嘁,也有自己的虛表磅轻,我反匯編看到此時(shí)父類子類的虛表地址一樣,可能不同的平臺(tái)有不同的處理逐虚,虛表沒(méi)有繼承一說(shuō)聋溜。如果子類想調(diào)用父類的虛函數(shù)方法的時(shí)候,應(yīng)該顯示調(diào)用叭爱,注意C++ 沒(méi)有super等類似關(guān)鍵字撮躁,正確調(diào)用如下:

class Animal {
public:
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
};

class Dog : public Animal {
public:
    int m_age; //dog的age
    
    void run() {
        Animal::run(); //直接用類::顯示調(diào)用
        cout << "Dog::run()" << endl;
    }
    void speak() {
        Animal::speak(); //直接用類::顯示調(diào)用
        cout << "Dog::speak()" << endl;
    }
};

int main() {
    Dog *dog0 = new Dog();
    dog0->run();
    dog0->speak();

    return 0;
}

含有虛虛函數(shù)實(shí)現(xiàn)的父類時(shí)候,父類的析構(gòu)函數(shù)也需要聲明為virtual函數(shù)买雾,此時(shí)析構(gòu)函數(shù)變成了虛析構(gòu)函數(shù)把曼,這樣才能夠保證銷毀對(duì)象的時(shí)候父類子類析構(gòu)函數(shù)調(diào)用,保證析構(gòu)的完整性,子類可不加(virtaul)漓穿。

純虛函數(shù)

沒(méi)有函數(shù)體嗤军,且初始化為0的虛函數(shù),用來(lái)定義接口規(guī)范

class Animal {
public:
    virtual void speak() = 0;
    virtual void run() = 0;
};

含有純虛函數(shù)的類是抽象類晃危,不可以實(shí)例化叙赚,不能創(chuàng)建對(duì)象,抽象類成也可以包含其它非純虛函數(shù),以及其他成員變量僚饭,抽象類的指針可以指向子類對(duì)象,如果父類是抽象類震叮,子類沒(méi)有完全實(shí)現(xiàn)純虛函數(shù),那么這個(gè)子類依然是抽象類

多繼承

C++允許一個(gè)類繼承多個(gè)類鳍鸵,可以擁有多個(gè)類的特性苇瓣,多繼承增加了設(shè)計(jì)的復(fù)雜性,不建議使用偿乖。

#include <iostream>
using namespace std;

class Student {
public:
    int m_score;
    Student(int score = 0) :m_score(score) { }
    void study() {
        cout << "Student::study() - score = " << m_score << endl;
    }
    ~Student() {
        cout << "~Student" << endl;
    }
};

class Worker {
public:
    int m_salary;
    Worker(int salary = 0) :m_salary(salary) { }
    void work() {
        cout << "Worker::work() - salary = " << m_salary << endl;
    }
    ~Worker() {
        cout << "~Worker" << endl;
    }
};

class Undergraduate : public Student, public Worker {
public:
    int m_grade;
    Undergraduate(
                  int score = 0,
                  int salary = 0,
                  int grade = 0) :Student(score), Worker(salary), m_grade(grade) {
        
    }
    void play() {
        cout << "Undergraduate::play() - grade = " << m_grade << endl;
    }
    ~Undergraduate() {
        cout << "~Undergraduate" << endl;
    }
};

int main() {
    {
        Undergraduate ug;
        ug.m_score = 100;
        ug.m_salary = 2000;
        ug.m_grade = 4;
        ug.study();
        ug.work();
        ug.play();
    }
    
    cout << sizeof(Undergraduate) << endl;
    
    return 0;
}

注意:這種多繼承击罪,父類的成員變量在子類前面哲嘲,先繼承誰(shuí),誰(shuí)的成員就在前面外邓。多繼承的構(gòu)造函數(shù)一樣需要使用初始化列表調(diào)用父類構(gòu)造函數(shù),

父類如果都含有虛函數(shù)撤蚊,子類多繼承多個(gè)父類后,子類對(duì)象會(huì)產(chǎn)生多個(gè)虛函數(shù)表损话,順序跟繼承順序有關(guān)侦啸。

#include <iostream>
using namespace std;

class Student {
public:
    virtual void study() {
        cout << "Student::study()" << endl;
    }
};

class Worker {
public:
    virtual void work() {
        cout << "Worker::work()" << endl;
    }
};

class Undergraduate : public Student, public Worker {
public:
    void study() {
        cout << "Undergraduate::study()" << endl;
    }
    void work() {
        cout << "Undergraduate::work()" << endl;
    }
    void play() {
        cout << "Undergraduate::play()" << endl;
    }
};

int main() {

    //含有16個(gè)字節(jié),因?yàn)橛?張?zhí)摫?    cout << sizeof(Undergraduate) << endl;
    
    Student *stu = new Undergraduate();
    stu->study();
    
    Worker *worker = new Undergraduate();
    worker->work();
    
    return 0;
}

同名成員

C++允許同名成員函數(shù)丧枪,同名成員變量光涂,子類不會(huì)覆蓋,訪問(wèn)的時(shí)候加上類名表示作用域拧烦,如下所示:

#include <iostream>
using namespace std;

class Student {
public:
    int m_age;
};

class Worker {
public:
    int m_age;
};

class Undergraduate : public Student, public Worker {
public:
    int m_age;
};

int main() {
    Undergraduate ug;
    ug.m_age = 10;
    ug.Student::m_age = 20;
    ug.Worker::m_age = 30;
    ug.Undergraduate::m_age = 40;

        //這里等于12
    cout << sizeof(Undergraduate) << endl;

    return 0;
}

虛繼承

虛繼承是為了解決菱形繼承帶來(lái)的成員變量冗余忘闻,重復(fù)。而且最底層子類因?yàn)槎x性無(wú)法訪問(wèn)基類的的成員變量恋博。我們先來(lái)看看菱形繼承:

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
};

class Student :  public Person {
public:
    int m_score;
};

class Worker :  public Person {
public:
    int m_salary;
};

class Undergraduate : public Student, public Worker {
public:
    int m_grade;
};

int main() {
     Undergraduate ug;
     ug.m_grade = 10;
     ug.m_score = 20;
     ug.Student::m_age = 20;
     ug.Worker::m_age = 30;
     cout << sizeof(Undergraduate) << endl; //20
     return 0;
}

這里我們看到Undergraduate的大小是20,因?yàn)檫@樣繼承Undergraduate的父類兩個(gè)類都有m_age成員變量,而且訪問(wèn)的時(shí)候我們需要通過(guò)作用域去訪問(wèn)齐佳,直接訪問(wèn)會(huì)報(bào)錯(cuò)。這種繼承方式债沮,基類的成員變量在最底層子類就冗余了炼吴,沒(méi)有必要,為了解決這種問(wèn)題疫衩,可以使用虛繼承硅蹦。加上virtual關(guān)鍵字:

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
};

class Student :virtual public Person {
public:
    int m_score;
};

class Worker :virtual public Person {
public:
    int m_salary;
};

class Undergraduate : public Student, public Worker {
public:
    int m_grade;
};


class Person1
{
    
};

int main() {
     Undergraduate ug;
     ug.m_grade = 10;
     ug.m_score = 20;
     ug.m_age = 30;

     return 0;
}

此時(shí)三個(gè)類比如:Student、Worker闷煤、Undergraduate都有了虛函數(shù)表指針童芹,此時(shí)成員變量m_age在最底層子類只有一份內(nèi)存。此時(shí)Person類被稱為虛基類鲤拿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末假褪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子近顷,更是在濱河造成了極大的恐慌生音,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幕庐,死亡現(xiàn)場(chǎng)離奇詭異久锥,居然都是意外死亡家淤,警方通過(guò)查閱死者的電腦和手機(jī)异剥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)絮重,“玉大人冤寿,你說(shuō)我怎么就攤上這事歹苦。” “怎么了督怜?”我有些...
    開(kāi)封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵殴瘦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我号杠,道長(zhǎng)蚪腋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任姨蟋,我火速辦了婚禮屉凯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘眼溶。我一直安慰自己悠砚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布堂飞。 她就那樣靜靜地躺著灌旧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绰筛。 梳的紋絲不亂的頭發(fā)上枢泰,一...
    開(kāi)封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音别智,去河邊找鬼宗苍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛薄榛,可吹牛的內(nèi)容都是我干的讳窟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼敞恋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼丽啡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起硬猫,我...
    開(kāi)封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤补箍,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后啸蜜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體坑雅,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年衬横,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裹粤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜂林,死狀恐怖遥诉,靈堂內(nèi)的尸體忽然破棺而出拇泣,到底是詐尸還是另有隱情,我是刑警寧澤矮锈,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布霉翔,位于F島的核電站,受9級(jí)特大地震影響苞笨,放射性物質(zhì)發(fā)生泄漏债朵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一瀑凝、第九天 我趴在偏房一處隱蔽的房頂上張望葱弟。 院中可真熱鬧,春花似錦猜丹、人聲如沸芝加。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)藏杖。三九已至,卻和暖如春脉顿,著一層夾襖步出監(jiān)牢的瞬間蝌麸,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工艾疟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留来吩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓蔽莱,卻偏偏與公主長(zhǎng)得像弟疆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盗冷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359