程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++多態分析(polymorphisn),多態polymorphisn

C++多態分析(polymorphisn),多態polymorphisn

編輯:C++入門知識

C++多態分析(polymorphisn),多態polymorphisn


前言

借著這次學校的生產實習來回顧下C++的多態,這裡討論下C++的多態以及實現原理。我都是在QT下使用C++的,就直接在QT下進行演示了

多態介紹

面向對象語言中最核心的幾個理念就是:封裝、繼承、多態,其中我感覺多態是真正的核心,第一第二個只是它的輔助。同時多態又是不容易懂的,所以在這就簡單的介紹下啦(雖然我也懂得不多,呵呵)

靜態聯編

第一個簡單的小程序(重載的多態)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <QCoreApplication>
#include <QDebug>
class Person {
public :
    int age;
    Person() : age(10){}
    void getAge() {
        qDebug() << "Person: " << age;
    }
};

class VellBibi : public Person {
public :
    int age;
    VellBibi() : age(21){}
    void getAge() {
        qDebug() << "VellBibi: " << age;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Person p;
    p.getAge();

    VellBibi v;
    v.getAge();

    Person *pPtr = &v;
    pPtr->getAge();

    return a.exec();
}

運行結果

說明

VellBibi類繼承了Person類,VellBibi裡重載了PersongetAge()方法,這樣就實現了靜態的多態,它的實現過程是在編譯期間的。這種多態存在硬傷,就是當使用父類指針指向子類對象時,訪問的是父類的東西,而不是子類的。這個算是C++的一個特性,在java裡面就沒有這個情況,因為java直接就是是動態聯編,java的多態裡面就不存在靜態聯編

動態聯編

第二個簡單的小程序(虛函數的多態)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <QCoreApplication>
#include <QDebug>
class Person {
public :
    int age;
    Person() : age(10){}
    virtual void getAge() {
        qDebug() << "Person: " << age;
    }
};

class VellBibi : public Person {
public :
    int age;
    VellBibi() : age(21){}
    virtual void getAge() {
        qDebug() << "VellBibi: " << age;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Person p;
    p.getAge();

    VellBibi v;
    v.getAge();

    Person *pPtr = &v;
    pPtr->getAge();

    return a.exec();
}

運行結果

說明

這個小程序和上一個一樣,唯一變得就是在PersongetAge()方法前加了一個virtual關鍵字,VellBibi可加可不加,但最好加上。在C++中virtual關鍵字就是用來聲明虛函數的,所謂虛函數就是虛的函數嘛,呵呵,就是這個函數不是在編譯期間就確定下來了,而是在運行期間動態指定的。這就是導致這個小程序和上個小程序的運行結果不同的原因,當使用父類指針指向子類對象時,調用父類虛函數時系統會自動尋找到子類對象的函數。

接下來介紹下C++是怎麼實現這個動態指定過程的

多態的實現

第三個小程序(靜態聯編時對象的大小)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <QCoreApplication>
#include <QDebug>
class Person {
public :
    int age;
    Person() : age(10){}
    void getAge() {
        qDebug() << "Person: " << age;
    }
};

class VellBibi : public Person {
public :
    int age;
    VellBibi() : age(21){}
    void getAge() {
        qDebug() << "VellBibi: " << age;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Person p;
    p.getAge();

    VellBibi v;
    v.getAge();

    Person *pPtr = &v;
    pPtr->getAge();

    qDebug() << "PersonSize: " << sizeof(p);
    qDebug() << "VellBibiSize: " << sizeof(v);

    return a.exec();
}

運行結果

說明

這個程序只是在第一個程序上加了sizeof(),看出來了神馬?Person是4個字節,也就是int age;的字節數;VellBibi是8個字節,其實就是Person的字節數加上VellBibiint age;的字節數。

第四個小程序(動態聯編時對象的大小)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <QCoreApplication>
#include <QDebug>
class Person {
public :
    int age;
    Person() : age(10){}
    virtual void getAge() {
        qDebug() << "Person: " << age;
    }
};

class VellBibi : public Person {
public :
    int age;
    VellBibi() : age(21){}
    virtual void getAge() {
        qDebug() << "VellBibi: " << age;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Person p;
    p.getAge();

    VellBibi v;
    v.getAge();

    Person *pPtr = &v;
    pPtr->getAge();

    qDebug() << "PersonSize: " << sizeof(p);
    qDebug() << "VellBibiSize: " << sizeof(v);

    return a.exec();
}

運行結果

說明

這個程序只是在第二個程序上加了sizeof(),這時會發現Person變8個字節了,為神馬呢?這是兩個int型的大小啊,why?好了不賣關子了,這是靜態Person的大小加上一個指針的大小,那哪來的指針呢?在Person裡面也沒有定義啊!呵呵,這是C++編譯器自動加上的,加上用來動態指定的,只要存在virtual關鍵字的類最上面都是有一個這樣的指針,指向一個vtable虛擬表,裡面記錄著這個類所有包含的虛函數地址。

動態聯編內存示例圖:

第五個小程序(動態聯編內存分析)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <QCoreApplication>
#include <QDebug>
class Person {
public :
    int age;
    Person() : age(10){}
    virtual void getAge() {
        qDebug() << "Person: " << age;
    }
};

class VellBibi : public Person {
public :
    int age;
    VellBibi() : age(21){}
    virtual void getAge() {
        qDebug() << "VellBibi: " << age;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Person p;
    VellBibi v;

    int *pfv = (int *)&p;
    int *vfv = (int *)&v;
    qDebug() << "PersonFirstValue: " << *pfv;
    qDebug() << "VellBibiFirstValue: " << *vfv;

    qDebug() << "PersonSecondValue: " << *(pfv + 1);
    qDebug() << "VellBibiSecondValue: " << *(vfv + 1);

    qDebug() << "PersonThirdValue: " << *(pfv + 2);
    qDebug() << "VellBibiSThirdValue: " << *(vfv + 2);

    int *pt = (int *)*pfv;
    int *vt = (int *)*vfv;
    qDebug() << "PersonVTableFirstValue: " << *pt;
    qDebug() << "VellBibiVTableFirstValue: " << *vt;

    qDebug() << "PersonVTableSecondValue: " << *(pt + 1);
    qDebug() << "VellBibiVTableSecondValue: " << *(vt + 1);
    return a.exec();
}

運行結果

說明

將對象第地址強制轉換成int型指針來探尋對象內部數據的情況。前兩行是對象的頭地址的值,很明顯這是一個指針;3、4行是第二個值,都是Personint age;變量; 5行是一個隨機值,說明Person對象裡面其實就只有一個int age;變量而已,其實在C++中類的實現也就是一個C語言的struct而已。再將頭地址指向的vtable裡的值取出來看看,7、8行就是各自vtable的第一個值,可以看出還是一個指針,指向的肯定是代碼段相對應的函數啦~這兩個指針指向了不同的函數,這也就是動態聯編啦~當然還需要一個程序來說明,等會再說;再看看9、10行,都是0,也就是NULL很好理解,是不是似曾相識啊,沒錯就是字符串裡的結束符啦!

第六個小程序(動態聯編的美妙)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <QCoreApplication>
#include <QDebug>
class Person {
public :
    int age;
    Person() : age(10){}
    virtual void getAge() {
        qDebug() << "Person: " << age;
    }
};

class VellBibi : public Person {
public :
    int age;
    VellBibi() : age(21){}
    virtual void getAge() {
        qDebug() << "VellBibi: " << age;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Person p;
    VellBibi v;
    Person *vp = &v;

    int *pfv = (int *)&p;
    int *vfv = (int *)vp;
    qDebug() << "PersonFirstValue: " << *pfv;
    qDebug() << "VellBibiFirstValue: " << *vfv;
    qDebug();

    qDebug() << "PersonSecondValue: " << *(pfv + 1);
    qDebug() << "VellBibiSecondValue: " << *(vfv + 1);
    qDebug();

    qDebug() << "PersonThirdValue: " << *(pfv + 2);
    qDebug() << "VellBibiSThirdValue: " << *(vfv + 2);
    qDebug();

    int *pt = (int *)*pfv;
    int *vt = (int *)*vfv;
    qDebug() << "PersonVTableFirstValue: " << *pt;
    qDebug() << "VellBibiVTableFirstValue: " << *vt;
    qDebug();

    qDebug() << "PersonVTableSecondValue: " << *(pt + 1);
    qDebug() << "VellBibiVTableSecondValue: " << *(vt + 1);

    return a.exec();
}

運行結果

說明

這個應該很好理解了,只是定義了一個Person的指針vp,指向了VellBibi的對象v,然後將&v換成了vp,其他的都沒變;也就是使用父類指針指向子類對象,結果可以看出和第五個程序是一樣的,說明了C++的動態聯編。

來道分析題

第七個小程序(問題代碼)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <QCoreApplication>
#include <QDebug>
class Person {
public :
    int age;
    Person() : age(10){}
    virtual void getAge() {
        qDebug() << "Person: " << age;
    }
};

class VellBibi : public Person {
public :
    int suiShu;
    VellBibi() : suiShu(21){}
    virtual void getAge() {
        qDebug() << "VellBibi: " << suiShu;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Person p;
    VellBibi v;

    Person *pptr = &p;

    int *pp = (int *)&p;
    int *vp = (int *)&v;

    *pp = *vp;

    pptr->getAge();

    return a.exec();
}

運行結果

問題分析

結果可以看出該打印的suiShu變隨機數了;我這裡使用了點小計倆,我先將對象的頭地址當做int看待,然後將VellBibi對象的頭地址賦值給了Person對象,最後使用Person對象的指針去調它自己的函數,會發現出錯了。上內存分析圖吧:

沒有改變Person對象頭指針

改變了Person對象頭指針後

繼續分析,Person對象的vtable指針指向了VellBibivtable,當使用指針去訪問時虛函數時就會做動態聯編,Person對象會找到VellBibivtable然後找到了VellBibigetAge(),而VellBibigetAge()回去調它自己的變量也就是suishu變量,這下就出問題了,Person對象哪來的suishu變量?所以就系統就在那個不是Person對象內容的地方取了個值,當然這是隨機的啦


結束語

寫這篇帖子是為了明天實習的每人一講啦~不過更是一種分享,我是從別人那得到的這份知識,想更好的讓更多的人去收獲這份知識,這就是分享的意義;當得知我做的努力給別人帶來了幫助,我會非常高興,這就是分享的樂趣。一起分享一起學習,Come on~~~

原文地址: http://vview.ml/2014/07/20/polymorphisn.html
written by Vell Bibi posted at VBlog


要怎理解PHP中的多態

如果對指針和內存分配有一定理解的話,多態我個人覺得是個非常簡單的問題。
 

什叫做類的繼承與多態,有什好處?

一個類最多只允許從一個類中派生;不允許從兩個或者更多的類中派生。
在C#中的繼承符合下列規則:
??繼承是可傳遞的。如果C從B中派生,B又從A中派生,那麼C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object類作為所有類的基類。
??派生類應當是對基類的擴展。派生類可以添加新的成員,但不能除去已經繼承的成員的定義。
??構造函數和析構函數不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承。基類中成員的訪問方式只能決定派生類能否訪問它們。
??派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋已繼承的成員。但這並不因為這派生類刪除了這些成員,只是不能再訪問這些成員。
??類可以定義虛方法、虛屬性以及虛索引指示器,它的派生類能夠重載這些成員,從而實現類可以展示出多態性。
1.覆蓋
在類的成員聲明中,可以聲明與繼承而來的成員同名的成員。
2.多態(Polymorphism)
在面向對象的系統中,多態性是一個非常重要的概念,它允許客戶對一個對象進行操作,由對象來完一系列的動作,具體實現哪個動作,如何實現由系統負責解釋。
在C#中,多態性是指同一操作作用於不同的類的實例,不同的類將進行不同的解釋,最後產生不同的執行結果。C#支持兩種類型的多態性:
??編譯時的多態性
編譯時的多態性是通過重載來實現的。對於非虛的成員來說,系統在編譯時,根據傳遞的參數,返回的類型等信息決定實現何種操作。
??運行時的多態性
運行時的多態性就是指直到系統運行時,才根據實際情況決定實現何種操作。C#中,運行時的多態性通過虛成員實現。
編譯時的多態提供了運行速度快的特點,而運行時的多態性則帶來了高度靈活和抽象的特點。
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved