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

父類和子類指針

1 父類指針可以指向子類對象,這是安全的,開發(fā)中經(jīng)常用到,繼承方式必須是public方式。
2 子類指針指向父類是不安全的,因為子類指針可能訪問到父類以外的數(shù)據(jù),而子類對象并沒有創(chuàng)建。

class Person {
public:
    int m_age;
};

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

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

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

多態(tài)

多態(tài)是面向?qū)ο笠粋€非常重要的特性,同一操作作用于不同的對象,產(chǎn)生不同的執(zhí)行結(jié)果,在運行時,可以識別出真正的對象類型,調(diào)用對應(yīng)子類的函數(shù)。產(chǎn)生多態(tài)的條件如下:子類重寫父類的成員函數(shù),父類指針指向子類對象,利用父類指針調(diào)用重寫的成員函數(shù),這個成員函數(shù)必須是由Virtual修飾的成員函數(shù),父類只要聲明了Virtual,子類自動轉(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ù)實現(xiàn)的列子,我們再進(jìn)一步,為什么不加Virtual 就不能實現(xiàn)多態(tài)了,加了Virtual 就能實現(xiàn)多態(tài)呢,多態(tài)的實現(xiàn)是靠虛表實現(xiàn)的。我們先看看這幾個類的大小

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

這幾個類sizeof大小是8,我們這是64位的,其實這個新增加的大小就是虛表的地址,指向虛表,而且這個虛表地址在類的最前面,而虛表里面存著本類虛函數(shù)的地址,從而進(jìn)行真正的調(diào)用,每一個類只有一份虛表,多個對象共享一份虛表,無論這個對象是在堆還是棧還是全局對象。我們可以通過反匯編和內(nèi)存調(diào)試也能看出來,這里我就不演示了。當(dāng)父類實現(xiàn)了虛函數(shù),而子類沒有實現(xiàn)該虛函數(shù)的時候,我們來看看這個情況:

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();會調(diào)用Animal::run()
    animal->speak();會調(diào)用Animal::speak()
    
    Dog *dog0 = new Dog();  
    dog0->run(); //會調(diào)用Animal::run()
    dog0->speak(); //會調(diào)用Animal::speak()

    return 0;
}

當(dāng)子類沒有重寫父類虛函數(shù)的時候,它也會調(diào)用父類的虛函數(shù),其底層實現(xiàn)也是通過虛表查找到函數(shù)調(diào)用,也就是子類即時沒有虛函數(shù),也有自己的虛表,我反匯編看到此時父類子類的虛表地址一樣,可能不同的平臺有不同的處理,虛表沒有繼承一說。如果子類想調(diào)用父類的虛函數(shù)方法的時候,應(yīng)該顯示調(diào)用,注意C++ 沒有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ù)實現(xiàn)的父類時候,父類的析構(gòu)函數(shù)也需要聲明為virtual函數(shù),此時析構(gòu)函數(shù)變成了虛析構(gòu)函數(shù),這樣才能夠保證銷毀對象的時候父類子類析構(gòu)函數(shù)調(diào)用,保證析構(gòu)的完整性,子類可不加(virtaul)。

純虛函數(shù)

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

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

含有純虛函數(shù)的類是抽象類,不可以實例化,不能創(chuàng)建對象,抽象類成也可以包含其它非純虛函數(shù),以及其他成員變量,抽象類的指針可以指向子類對象,如果父類是抽象類,子類沒有完全實現(xiàn)純虛函數(shù),那么這個子類依然是抽象類

多繼承

C++允許一個類繼承多個類,可以擁有多個類的特性,多繼承增加了設(shè)計的復(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;
}

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

父類如果都含有虛函數(shù),子類多繼承多個父類后,子類對象會產(chǎn)生多個虛函數(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個字節(jié),因為有2張?zhí)摫?    cout << sizeof(Undergraduate) << endl;
    
    Student *stu = new Undergraduate();
    stu->study();
    
    Worker *worker = new Undergraduate();
    worker->work();
    
    return 0;
}

同名成員

C++允許同名成員函數(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;
}

虛繼承

虛繼承是為了解決菱形繼承帶來的成員變量冗余,重復(fù)。而且最底層子類因為二義性無法訪問基類的的成員變量。我們先來看看菱形繼承:

#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,因為這樣繼承Undergraduate的父類兩個類都有m_age成員變量,而且訪問的時候我們需要通過作用域去訪問,直接訪問會報錯。這種繼承方式,基類的成員變量在最底層子類就冗余了,沒有必要,為了解決這種問題,可以使用虛繼承。加上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;
}

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

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

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