當前位置:首頁 » 編程軟體 » 多態性編譯器機制

多態性編譯器機制

發布時間: 2023-01-15 05:01:53

① 什麼叫做多態性,在c++中是如何實現多態的

C++中的多態(雖然多態不是C++所特有的,但是C++中的多態確實是很特殊的)分為靜多態和動多態(也就是靜態綁定和動態綁定兩種現象),靜動的區別主要在於這種綁定發生在編譯期還是運行期,發生在編譯期的是靜態綁定,也就是靜多態;發生在運行期的則是動態綁定,也就是動多態。

靜多態可以通過模板和函數重載來實現(之所說C++中的多態主要還是因為模板這個東西),下面舉兩個例子:
1)函數模板
template <typename T>
T max(const T& lsh, const T& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}
返回兩個任意類型對象的最大值(對象),前提是該類型能夠使用>運算符進行比較,並且返回值是bool類型。
使用:
int a = 3; int b = 4;
cout << max(a, b) << endl;
float c = 2.4; float d = 1.2;
cout << max(c, d) << endl;
輸出結果為:
4
2.4
這種綁定發生在編譯期,這是由於模板的實例化是發生在編譯期的,即在編譯時編譯器發現你調用max(a, b)時就自動生成一個函數
int max(const int& lsh, const int& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}
即將所有的T替換成int;
當你調用max(c, d)時就自動生成一個函數
float max(const float& lsh, const float& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}
之所以說開始的函數定義是函數模板,就是因為他就像個模子似的,你可以用鋁作為原料也可以用石膏或者銅。
2)函數重載:
int max (int a, int b)
{
return (a > b) ? a : b;
}
int max (int a, int b, int c)
{
return max(max(a, b), c);
}
兩個函數名稱一樣,參數類型或個數不完全相同,返回值一樣(這個不重要)。
使用:
int a = 3, b = 4, c = 5;
cout << max(a, b) << endl;
cout << max(a, b, c) << endl;
輸出結果為:
4
5
確定函數的過程也發生在編譯器,當你使用max(a, b),編譯器發現只有兩個參數,那麼就調用只有兩個參數的函數版本,當使用max(a, b, c)時,編譯器則使用有3個參數的版本。
通過上面的兩個例子,你還可以使用更為方便的模板函數重載:
template <typename T>
T max(const T& lsh, const T& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}

template <typename T>
T max(const T& a, const T& b, const T& c)
{
return max(max(a, b), c);
}
使用
float a = 3.6, b = 1.2, c = 7.8;
cout << max(a, b, c) << endl;
輸出:
7.8
通過參數個數和類型,編譯器自動生成和調用對應得函數版本!

動多態則是通過繼承、虛函數(virtual)、指針來實現。
class A {
public:
virtual void func() const {
coust << 「A::func()」 << endl;
}
}

class B : public A {
public:
virtual void func() const {
coust << 「B::func()」 << endl;
}
}
使用:
A a* = B();
a->func();
輸出:
B::func()
編譯期是不調用任何函數的,編譯器編譯到a->func()時只是檢查有沒有語法問題,經過檢查沒有。編譯器並不知道調用的是A版本的func()還是B版本的func(),由於a是一個指向B對象的指針,所以a只知道它指向的是一個A類型(或者能轉換成A類型)的對象。通常集成體系就說明了(由於是公有繼承)B是一種A。在運行期,a要調用a所指向對象的func()函數,就對它指向的對象下達調用func()的命令,結果a所指向的是一個B對象,這個對象就調用了自己版本(B版)的func()函數,所以輸出時B::func()

總結:
在編譯期決定你應該調用哪個函數的行為是靜態綁定(static-binding),這種現象就是靜多態。
在運行期決定應該調用哪中類型對象的函數的行為是動態綁定(dynamic-binding),這種現象就是動多態!

註:由於這是我花了有限的時間總結的,語言應用能力比較差,還有比如類模板(靜多態和動多態組合的情況)都沒有說,最近比較忙,請見諒!

如果還不是很懂,我建議你看C++Primer 4th Edition,講的比較清晰,但是比較零散!

② 一個關於C++多態性的問題

多態性(polymorphism)是面向對象編程的基本特徵之一。而在C++中,多態性通過虛函數(virtual function)來實現。我們來看一段簡單的代碼:

#include <iostream>
using namespace std;

class Base
{
int a;
public:
virtual void fun1() {cout<<"Base::fun1()"<<endl;}
virtual void fun2() {cout<<"Base::fun2()"<<endl;}
virtual void fun3() {cout<<"Base::fun3()"<<endl;}
};

class A:public Base
{
int a;
public:
void fun1() {cout<<"A::fun1()"<<endl;}
void fun2() {cout<<"A::fun2()"<<endl;}
};

void foo (Base& obj)
{
obj.fun1();
obj.fun2();
obj.fun3();
}

int main()
{
Base b;
A a;
foo(b);
foo(a);
}

運行結果為:
Base::fun1()
Base::fun2()
Base::fun3()
A::fun1()
A::fun2()
Base::fun3()

僅通過基類的介面,程序調用了正確的函數,它就好像知道我們輸入的對象的類型一樣!

那麼,編譯器是如何知道正確代碼的位置的呢?其實,編譯器在編譯時並不知道要調用的函數體的正確位置,但它插入了一段能找到正確的函數體的代碼。這稱之為晚捆綁(late binding)或運行時捆綁(runtime binding)技術。

通過virtual關鍵字創建虛函數能引發晚捆綁, 編譯器在幕後完成了實現晚捆綁的必要機制。它對每個包含虛函數的類創建一個表(稱為VTABLE),用於放置虛函數的地址。在每個包含虛函數的類中,編譯器秘密地放置了一個稱之為vpointer(縮寫為VPTR) 的指針,指向這個對象的VTABLE。所以無論這個對象包含一個或是多少虛函數,編譯器都只放置一個VPTR即可。VPTR由編譯器在構造函數中秘密地插入的代碼來完成初始化,指向相應的VTABLE,這樣對象就「知道」自己是什麼類型了。VPTR都在對象的相同位置,常常是對象的開頭。這樣,編譯器可以容易地找到對象的VTABLE並獲取函數體的地址。

如果我們用sizeof查看前面Base類的長度,我們就會發現,它的長度不僅僅是一個int的長度,而是增加了剛好是一個void指針的長度(在我的機器裡面,一個int佔4個位元組,一個void指針佔4個位元組,這樣正好類Base的長度為8個位元組)。

每當創建一個包含虛函數的類或從包含虛函數的類派生一個類時,編譯器就為這個類創建一個唯一的VTABLE。在VTABLE中,放置了這個類中或是它的基類中所有虛函數的地址,這些虛函數的順序都是一樣的,所以通過偏移量可以容易地找到所需的函數體的地址。假如在派生類中沒有對在基類中的某個虛函數進行重寫(overriding),那麼還使用基類的這個虛函數的地址(正如上面的程序結果所示)。

至今為止,一切順利。下面,我們的試驗開始了。

就目前得知的,我們可以試探著通過自己的代碼來調用虛函數,也就是說我們要找尋一下編譯器秘密地插入的那段能找到正確函數體的代碼的足跡。

如果我們有一個Base指針作為介面,它一定指向一個Base或由Base派生的對象,或者是A,或者是其它什麼。這無關緊要,因為VPTR的位置都一樣,一般都在對象的開頭。如果是這樣的話,那麼包含有虛函數的對象的指針,例如Base指針,指向的位置恰恰是另一個指針——VPTR。VPTR指向的 VTABLE其實就是一個函數指針的數組,現在,VPTR正指向它的第一個元素,那是一個函數指針。如果VPTR向後偏移一個Void指針長度的話,那麼它應該指向了VTABLE中的第二個函數指針了。

這看來就像是一個指針連成的鏈,我們得從當前指針獲取它指向的下一個指針,這樣我們才能「順藤摸瓜」。那麼,我來介紹一個函數:

void *getp (void* p)
{
return (void*)*(unsigned long*)p;
}

我們不考慮它漂亮與否,我們只是試驗。getp() 可以從當前指針獲取它指向的下一個指針。如果我們能找到函數體的地址,用什麼來存儲它呢?我想應該用一個函數指針:

typedef void (*fun)();

它與Base中的三個虛函數相似,為了簡單我們不要任何輸入和返回,我們只要知道它實際上被執行了即可。

然後,我們負責「摸瓜」的函數登場了:

fun getfun (Base* obj, unsigned long off)
{
void *vptr = getp(obj);

unsigned char *p = (unsigned char *)vptr;
p += sizeof(void*) * off;

return (fun)getp(p);
}

第一個參數是Base指針,我們可以輸入Base或是Base派生對象的指針。第二個參數是VTABLE偏移量,偏移量如果是0那麼對應fun1(),如果是1對應fun2()。getfun() 返回的是fun類型函數指針,我們上面定義的那個。可以看到,函數首先就對Base指針調用了一次getp(),這樣得到了vptr這個指針,然後用一個 unsigned char指針運算偏移量,得到的結果再次輸入getp(),這次得到的就應該是正確的函數體的位置了。

那麼它到底能不能正確工作呢?我們修改main() 來測試一下:

int main()
{
Base *p = new A;

fun f = getfun(p, 0);
(*f)();
f = getfun(p, 1);
(*f)();
f = getfun(p, 2);
(*f)();
delete p;
}

激動人心的時刻到來了,讓我們運行它!

運行結果為:
A::fun1()
A::fun2()
Base::fun3()

至此,我們真的成功了。通過我們的方法,我們獲取了對象的VPTR,在它的體外執行了它的虛函數。

③ 編譯時的多態性和運行時的多態性在實現方法上有何不同

我不知道你哪本書上看到的,但是,只要不是後綁定就不能稱為多態,前綁定只能稱為代碼重用,比如函數的重載、覆蓋以及一般的類繼承。
多態的關鍵特點就是:在運行時虛基類指針指向派生類對象地址,而將派生類對象地址賦值給基類指針,這就是所謂的後綁定,編譯時綁定稱為前綁定,因此多態另一個特點就是「動態「。換句話說,如果是後綁定,編譯器事先是不知道在運行時指針將指向哪一種派生類的對象,因此基類指針必須是「虛「的,虛基類中不能有任何實現只有定義,此時虛基類的作用就是一個類介面,這樣才能在編譯時「模糊」掉類型匹配原則,基類的作用只是個約定,定義了函數調用格式,而只在運行時才確定指針具體指向哪一個對象。
而所謂編譯時的多態性根本不存在,如果編譯器能確定基類指針指向哪一個派生類對象地址,就不是多態,哪怕你採用重載覆蓋或者繼承,這些編譯器已經可以預知的事情,一旦編譯完成就固定了,運行時無法更改的,比如你不能在不重新編譯的情況下增加一個重載,這就制約了程序運行時的靈活性以及可擴充性。而多態完全可以實現「熱「更新,更多的是便於程序的可擴充性。你完全可以將派生類編譯在DLL中,每當更新程序時,只要替換掉DLL而不用重新編譯全部代碼。

④ 怎麼理解java中的多態性

一、基本概念

多態性:發送消息給某個對象,讓該對象自行決定響應何種行為。
通過將子類對象引用賦值給超類對象引用變數來實現動態方法調用。

java 的這種機制遵循一個原則:當超類對象引用變數引用子類對象時,被引用對象的類型而不是引用變數的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。

1. 如果a是類A的一個引用,那麼,a可以指向類A的一個實例,或者說指向類A的一個子類。
2. 如果a是介面A的一個引用,那麼,a必須指向實現了介面A的一個類的實例。

二、Java多態性實現機制

SUN目前的JVM實現機制,類實例的引用就是指向一個句柄(handle)的指針,這個句柄是一對指針:
一個指針指向一張表格,實際上這個表格也有兩個指針(一個指針指向一個包含了對象的方法表,另外一個指向類對象,表明該對象所屬的類型);
另一個指針指向一塊從java堆中為分配出來內存空間。

The Java Virtual Machine does not require any particular internal structure for objects. In Sun 's current implementation of the Java Virtual Machine, a reference to a class instance is a pointer to a handle that is itself a pair of pointers: one to a table containing the methods of the object and a pointer to the Class object that represents the type of the object, and the other to the memory allocated from the Java heap for the object data. (jvm規范中關於對象內存布局的說明)

三、總結

1、通過將子類對象引用賦值給超類對象引用變數來實現動態方法調用。

DerivedC c2=new DerivedC();
BaseClass a1= c2; //BaseClass 基類,DerivedC是繼承自BaseClass的子類
a1.play(); //play()在BaseClass,DerivedC中均有定義,即子類覆寫了該方法

分析:
* 為什麼子類的類型的對象實例可以覆給超類引用?
自動實現向上轉型。通過該語句,編譯器自動將子類實例向上移動,成為通用類型BaseClass;
* a.play()將執行子類還是父類定義的方法?
子類的。在運行時期,將根據a這個對象引用實際的類型來獲取對應的方法。所以才有多態性。一個基類的對象引用,被賦予不同的子類對象引用,執行該方法時,將表現出不同的行為。

在a1=c2的時候,仍然是存在兩個句柄,a1和c2,但是a1和c2擁有同一塊數據內存塊和不同的函數表。

2、不能把父類對象引用賦給子類對象引用變數

BaseClass a2=new BaseClass();
DerivedC c1=a2;//出錯

在java裡面,向上轉型是自動進行的,但是向下轉型卻不是,需要我們自己定義強制進行。
c1=(DerivedC)a2; 進行強制轉化,也就是向下轉型.

3、記住一個很簡單又很復雜的規則,一個類型引用只能引用引用類型自身含有的方法和變數。
你可能說這個規則不對的,因為父類引用指向子類對象的時候,最後執行的是子類的方法的。
其實這並不矛盾,那是因為採用了後期綁定,動態運行的時候又根據型別去調用了子類的方法。而假若子類的這個方法在父類中並沒有定義,則會出錯。
例如,DerivedC類在繼承BaseClass中定義的函數外,還增加了幾個函數(例如 myFun())

分析:
當你使用父類引用指向子類的時候,其實jvm已經使用了編譯器產生的類型信息調整轉換了。
這里你可以這樣理解,相當於把不是父類中含有的函數從虛擬函數表中設置為不可見的。注意有可能虛擬函數表中有些函數地址由於在子類中已經被改寫了,所以對象虛擬函數表中虛擬函數項目地址已經被設置為子類中完成的方法體的地址了。

⑤ C++中多態是怎樣實現的

多態是一種不同的對象以單獨的方式作用於相同消息的能力,這個概念是從自然語言中引進的。例如,動詞「關閉」應用到不同的事務上其意思是不同的。關門,關閉銀行賬號或關閉一個程序的窗口都是不同的行為;其實際的意義取決於該動作所作用的對象。 大多數面向對象語言的多態特性都僅以虛擬函數的形式來實現,但C++除了一般的虛擬函數形式之外,還多了兩種靜態的(即編譯時的)多態機制: 2、模板:例如,當接受到相同的消息時,整型vector對象和串vector對象對消息反映是不同的,我們以關閉行為為例: vector < int > vi; vector < string > names; string name("VC知識庫"); vi.push_back( 5 ); // 在 vector 尾部添加整型 names.push_back (name); // 添加串和添加整型體現差別的潛在的操作 靜態的多態機制不會導致與虛擬函數相關的運行時開。此外,操作符重載和模板兩者是通用演算法最基本的東西,在STL中體現得尤為突出。 那麼接下來我們說說以虛函數形式多態: 通常都有以重載、覆蓋、隱藏來三中方式,三種方式的區別大家應該要很深入的了解,這里就不多說了。 許多開發人員往往將這種情況和C++的多態性搞混淆,下面我從兩方面為大家解說: 1、 編譯的角度 C++編譯器在編譯的時候,要確定每個對象調用的函數的地址,這稱為早期綁定(early binding)。2、 內存模型的角度為了確定對象調用的函數的地址,就要使用遲綁定(late binding)技術。當編譯器使用遲綁定時,就會在運行時再去確定對象的類型以及正確的調用函數。而要讓編譯器採用遲綁定,就要在基類中聲明函數時使用virtual關鍵字(注意,這是必須的,很多開發人員就是因為沒有使用虛函數而寫出很多錯誤的例子),這樣的函數我們稱為虛函數。一旦某個函數在基類中聲明為virtual,那麼在所有的派生類中該函數都是virtual,而不需要再顯式地聲明為virtual。 那麼如何定位虛表呢?編譯器另外還為每個類的對象提供了一個虛表指針(即vptr),這個指針指向了對象所屬類的虛表。在程序運行時,根據對象的類型去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在調用虛函數時,就能夠找到正確的函數。 正是由於每個對象調用的虛函數都是通過虛表指針來索引的,也就決定了虛表指針的正確初始化是非常重要的。換句話說,在虛表指針沒有正確初始化之前,我們不能夠去調用虛函數。那麼虛表指針在什麼時候,或者說在什麼地方初始化呢? 答案是在構造函數中進行虛表的創建和虛表指針的初始化。還記得構造函數的調用順序嗎,在構造子類對象時,要先調用父類的構造函數,此時編譯器只「看到了」父類,並不知道後面是否後還有繼承者,它初始化父類對象的虛表指針,該虛表指針指向父類的虛表。當執行子類的構造函數時,子類對象的虛表指針被初始化,指向自身的虛表。 要注意:對於虛函數調用來說,每一個對象內部都有一個虛表指針,該虛表指針被初始化為本類的虛表。所以在程序中,不管你的對象類型如何轉換,但該對象內部的虛表指針是固定的,所以呢,才能實現動態的對象函數調用,這就是C++多態性實現的原理。總結(基類有虛函數):1、 每一個類都有虛表。2、虛表可以繼承,如果子類沒有重寫虛函數,那麼子類虛表中仍然會有該函數的地址,只不過這個地址指向的是基類的虛函數實現。如果基類3個虛函數,那麼基類的虛表中就有三項(虛函數地址),派生類也會有虛表,至少有三項,如果重寫了相應的虛函數,那麼虛表中的地址就會改變,指向自身的虛函數實現。如果派生類有自己的虛函數,那麼虛表中就會添加該項。3、派生類的虛表中虛函數地址的排列順序和基類的虛表中虛函數地址排列順序相同。

⑥ c++ 的特點是什麼c++ 的特點是什麼談談它的多態性。

C++是以C語言為基礎,支持數據抽象和面向對象的程序設計語言。C++對C語言的擴充部分汲取了
許多著名語言中最優秀的特徵,如從Algo168中吸取了操作符重載機制等。由於C++語言具有與C語言一

樣的高執行效率,並容易被熟悉C語言的軟體人員接受,因而很快得以流行。但這種混合型面向對象的

程序設計語言是一種新的程序設計語言,人們對它許多潛在的性能(封裝、繼承、多態等)還沒有充分

地理解和應用,沒有充分發揮其優勢。多態性是面向對象系統的重要概念之一,它指的是同樣的消息

能被發送到父類的對象和它的子類的對象。本文重點討論多態性在程序設計中的應用。

1 多態性的實現形式

從廣義上說,多態性是指一段程序能夠處理多種類型對象的能力。在C++語言中,這種多態性可以

通過強制多態、重載多態、類型參數化多態、包含多態4種形式來實現。類型參數化多態和包含多態統

稱為一般多態性,用來系統地刻畫語義上相關的一組類型。重載多態和強制多態統稱為特殊多態性,

用來刻畫語義上無關聯的類型間的關系。
包含多態是指通過子類型化,1個程序段既能處理類型T的對象,也能夠處理類型T的子類型S的對

象,該程序段稱為多態程序段。公有繼承能夠實現子類型。在包含多態中,1個對象可以被看作屬於不

同的類,其間包含關系的存在意味著公共結構的存在。包含多態在不少語言中存在,如整數類型中的

子集構成1個子類型。每一個子類型中的對象可以被用在高一級的類型中,高一級類型中的所有操作可

用於下一級的對象。在C++中公有繼承關系是一種包含多態,每一個類可以直接公有繼承父類或多個父

類,如語句class D�public P1,public P2{……};表示類D分別是類P1和類P2的子類型。
類型參數化多態是指當1個函數(類)統一地對若干類型參數操作時,這些類型表現出某些公共的語

義特性,而該函數(類)就是用來描述該特性的。在類型參數化多態中,1個多態函數(類)必須至少帶有

1個類型參數,該類型參數確定函數(類)在每次執行時操作數的類型。這種函數(類)也稱類屬函數(類)

。類型參數化多態的應用較廣泛,被稱為最純的多態。
重載是指用同一個名字命名不同的函數或操作符。函數重載是C++對一般程序設計語言中操作符重

載機制的擴充,它可使具有相同或相近含義的函數用相同的名字,只要其參數的個數、次序或類型不

一樣即可。例如:
int min(int x,int y); //求2個整數的最小數
int min(int x,int y,int z); //求3個整數的最小數
int min(int n,int a〔〕); //求n個整數的最小數
當用戶要求增加比較2個字元串大小的功能時,只需增加:

char*min(char*,char*);

而原來如何使用這組函數的邏輯不需改變,min的功能擴充很容易,也就是說維護比較容易,同時也提

高了程序的可理解性,「min」表示求最小值的函數。
強制是指將一種類型的值轉換成另一種類型的值進行的語義操作,從而防止類型錯誤。類型轉換

可以是隱式的,在編譯時完成,如語句D=I把整型變數轉換為實型;也可以是顯式的,可在動態運行

時完成。
從總體上來說,一般多態性是真正的多態性;特殊多態性只是表面的多態性。因為重載只允許某

一個符號有多種類型,而它所代表的值分別具有不同的、不相兼容的類型。類似地,隱式類型轉換也

不是真正的多態,因為在操作開始前,各值必須轉換為要求的類型,而輸出類型也與輸入類型無關。

相比之下,子類與繼承卻是真正的多態。類型參數化多態也是一種純正的多態,同一對象或函數在不

同的類型上下文中統一地使用而不需採用隱式類型轉換、運行時檢測或其它各種限制。

2 多態性應用

2.1 包含多態
C++中採用虛擬函數實現包含多態,虛擬函數為C++提供了更為靈活的多態機制,這種多態性在程

序運行時才能確定,因此虛擬函數是多態性的精華,至少含有一個虛擬函數的類稱為多態類。包含多

態在程序設計中使用十分頻繁。
派生類繼承基類的所有操作,或者說,基類的操作能被用於操作派生類的對象,當基類的操作不

能適應派生類時,派生類需重載基類的操作,見下例中的void circle::showarea()。
#include <iostream.h>
class point //屏幕上的點類
� {int x,y;
public;
point(int x1,int y1)
{x=x1;y=y1;}
void showarea()
{cout<<〃Area of point is:〃<<0.0<<endl;}
};

class circle:public point//圓類
{int radius;
public:
circle(int x,int y,int r):point(x,y){ radius=r;}
void showarea(){cout<<〃Area of circle is:〃<<3.14
*radius*radius<<endl;}
};

void disparea(const point*p) //多態程序段
{p->showarea();}
void main()
{circle c1(1,1,1);disparea(&c1);

程序的運行結果為0.0(正確結果應為3.14),出錯的原因是:表達式p->showarea()中的函數調

用在編譯時被束定到函數體上,使得這個表達式中的函數調用執行point類的showarea()。為此,當程

序員在實現一個派生類而變動了基類中的操作實現時,C++提供的虛函數機制可將這種變動告訴編譯器

,即將關鍵字virtual放在類point中該函數的函數說明之前(virtual void showarea()),程序其它部

分保持不變(circle::showarea()自動地成為虛函數),編譯器就不會對函數調用p->showarea()進

行靜態束定(在編譯/連接時進行的束定)而產生有關的代碼,使函數調用與它所應執行的代碼的束定

工作在程序運行時進行,這樣上述程序的運行結果即為3.14。在程序運行時進行的束定被稱為動態束

定。
利用虛函數,可在基類和派生類中使用相同的函數名定義函數的不同實現,從而實現「一個介面

,多種方式」。當用基類指針或引用對虛函數進行訪問時,軟體系統將根據運行時指針或引用所指向

或引用的實際對象來確定調用對象所在類的虛函數版本。
C++語言還增加了純的虛函數機制用來更好地設計包含多態性。對於如圖1(a)所示結構的類層次,

假如每個類中都有一個函數「void display(void);」,那麼,怎樣對它們按多態性進行統一處理呢

?對這類問題應先設計一個抽象的類,使它成為所有類的祖先類,如圖1(b)所示。設置類A的目的是由

它說明統一使用該層次中的display()函數的方法(賦值兼容規則從語法上保證了A的子孫類可按A說明

的方式使用display()函數;多態性則從語義上保證了在執行時,根據實際的對象訪問相應對象類中的

display()函數)。

為了保證在類A中設置的display()函數是抽象動作,並能說明類A是一個抽象的類,在C++中,可用純

的虛函數語言機制在類A中聲明1個成員函數「virtual void display(void)=0;」。請注意,在類A

的子孫類中要麼給出display()的定義,要麼重新將該函數聲明為純的。
從上面的分析可以看出,類A的設計盡管是用繼承性語法表達的,但它的主要目的不是為代碼共享而設

計的,而是為了提高多態性而設計的,它是另一個維度的抽象。
2.2 類型參數化多態
參數化多態又稱非受限類屬多態,即將類型作為函數或類的參數,避免了為各種不同的數據類型

編寫不同的函數或類,減輕了設計者負擔,提高了程序設計的靈活性。
模板是C++實現參數化多態性的工具,分為函數模板和類模板二種。
類模板中的成員函數均為函數模板,因此函數模板是為類模板服務的。類模板在表示數組、表、

矩陣等類數據結構時,顯得特別重要,因為這些數據結構的表示和演算法的選擇不受其所包含的元素的

類型的影響。下面是一個通用數組類模板的定義。
template <class T,int N>
class array
{T elem〔N〕;
public:
array(){for(int j=0;j<N;j++)elem〔j〕=0;}
T& operator〔〕(int index){return elem〔index〕;}
void modi(int index,T value){elem〔index〕=value;}
};
其中,T是類型參數,N是常量參數。T和N的實際值是在生成具體類實例時指定的。類模板的< >

可以包括任意個類型參數或常量參數,但至少應有一個參數。在類模板定義中,可在程序中通常使用

類型指定的任何地方使用類型參數,可在通常使用特定類型常量表達式的任何地方使用常量參數。
成員函數模板可放在類模板中定義,也可放在類外定義,例如:
template <class T,int N>
T& array<T,N>::operator〔〕(int index){return elem〔index〕;}
當由類模板生成一個特定的類時,必須指定參數所代表的類型(值)。例如,1個元素類型為int、

長度為100的數組類使用類型表達式array<int,100>來表示,這個類型表達式被用於說明數組類對

象。例如:
array<int,100> a: //生成特定類的對象a
a.modi(1,34); //對象a訪問成員函數
類模板一旦生成了對象和指定了參數表中的類型,編譯器在以後訪問數據成員和調用成員函數時

完全強制為這些類型。
在C++中可以重載定義多個同名的函數模板,也可以將1個函數模板與1個同名函數進行重載定義。

例如:
template <class T> T min(T a,T b){return a<b?a:b;}
template <class T>
T min(T a,T b,T c){T x=min(a,b);return min(x,c);}
int min(int a,int b)〔return a<b?a:b;}
調用min(3,7),則調用第3個函數;調用min(3.8.5.9),編譯器將根據帶2個參數的模板生成新函

數min(double,double);調用min(4,90,76),則編譯器根據帶3個參數的模板生成新函數min(int,

int,int);而調用min(56.3,48,71),編譯將給出錯誤信息,說明無法從上面的模板中生成函數

min(double,double,double),因為編譯器在類型推導時,不存在類型強制。
模板描述了1組函數或1組類,它主要用於避免程序員進行重復的編碼工作,大大簡化、方便了面

向對象的程序設計。
2.3 重載多態
重載是多態性的最簡形式,而且把更大的靈活性和擴展性添加到程序設計語言中,它分成操作符

重載和函數重載。
C++允許為類重定義已有操作符的語義,使系統預定義的操作符可操作類對象。C++語言的一個非

常有說服力的例子是count對象的插入操作(<<)。由於其類中定義了對位左移操作符「<<」進行重

載的函數,使C++的輸出可按同一種方式進行,學習起來非常容易。並且,增加一個使其能輸出復數類

的功能(擴充)也很簡單,不必破壞原輸出邏輯。
C++規定將操作符重載為函數的形式,既可以重載為類的成員函數,也可以重載為類的友員函數。

用友員重載操作符的函數也稱操作符函數,它與用成員函數重載操作符的函數不同,後者本身是類中

成員函數,而它是類的友員函數,是獨立於類的一般函數。注意重載操作符時,不能改變它們的優先

級,不能改變這些操作符所需操作數的個數。
重定義已有的函數稱為函數重載。在C++中既允許重載一般函數,也允許重載類的成員函數。如對

構造函數進行重載定義,可使程序有幾種不同的途徑對類對象進行初始化。還允許派生類的成員函數

重載基類的成員函數,虛函數就屬於這種形式的重載,但它是一種動態的重載方式,即所謂的「動態

聯編(束定)」。
2.4 強制多態
強制也稱類型轉換。C++語言定義了基本數據類型之間的轉換規則,即:
char->short->int->unsigned->long->unsigned long->float->double->long

double
賦值操作是個特例,上述原則不再適用。當賦值操作符的右操作數的類型與左操作數的類型不同

時,右操作數的值被轉換為左操作數的類型的值,然後將轉換後的值賦值給左操作數。
程序員可以在表達式中使用3種強制類型轉換表達式:①static_cast<T>(E);②T(E);③(T)E

。其中任意一種都可改變編譯器所使用的規則,以便按自己的意願進行所需的類型強制。其中E 代表

一個運算表達式,T代表一個類型表達式。第三種表達形式是C語言中所使用的風格,在C++中,建議不

要再使用這種形式,應選擇使用第一種形式。例如,設對象f的類型為double,且其值為3.14。則表達

式static_cast<int>(f)的值為3,類型為int。
通過構造函數進行類類型與其它數據類型之間的轉換必須有一個前提,那就是此類一定要有一個

只帶1個非預設參數的構造函數,通過構造函數進行類類型的轉換只能從參數類型向類類型轉換,而想

將一個類類型向其它類型轉換是辦不到的。類類型轉換函數就是專門用來將類類型向其它本類型轉換

的,它是一種類似顯式類型轉換的機制。轉換函數的設計有以下幾點要特別注意:①轉換函數必須是

類的成員函數;②轉換函數不可以指定其返回值類型;③轉換函數其參數行不可以有任何參數。
強制使類型檢查復雜化,尤其在允許重載的情況下,導致無法消解的二義性,在程序設計時要注

意避免由於強制帶來的二義性。

⑦ c#多態的運行機理

描述一下C++的,應該差不多

1、編譯器發現一個類中有虛函數,編譯器會立即為此類生成虛擬函數表 vtable(後面有對vtable的分析)。虛擬函數表的各表項為指向對應虛擬函數的指針。

2、編譯器在此類中隱含插入一個指針vptr(對vc編譯器來說,它插在類的第一個位置上)。

有一個辦法可以讓你感知這個隱含指針的存在,雖然你不能在類中直接看到它,但你可以比較一下含有虛擬函數時的類的尺寸和沒有虛擬函數時的類的尺寸,你能夠發現,這個指針確實存在。

class cnovirtualfun
{
private:
long lmember;
public:
long getmembervalue();
} class chavevirtualfun
{
private:
long lmember;
public:
virtual long getmembervalue();
}

cnovirtualfun obj;
sizeof(obj) -> == 4;
chavevirtualfun obj;
sizeof(obj) -> == 8;

3、在調用此類的構造函數時,在類的構造函數中,編譯器會隱含執行vptr與vtable的關聯代碼,將vptr指向對應的vtable。這就將類與此類的vtable聯系了起來。

4、在調用類的構造函數時,指向基礎類的指針此時已經變成指向具體的類的this指針,這樣依靠此this指針即可得到正確的vtable,從而實現了多態性。在此時才能真正與函數體進行連接,這就是動態聯編。

⑧ c++中什麼是多態!

在C++中介面的多種不同的實現方式就是多態。

多態性是允許你將父對象設置成為一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。允許將子類類型的指針賦值給父類類型的指針。多態性在ObjectPascal和C++中都是通過虛函數實現的。

賦值之後,父類型的引用就可以根據當前賦值給它的子對象的特性以不同的方式運作。也就是說,父親的行為像兒子,而不是兒子的行為像父親。

(8)多態性編譯器機制擴展閱讀

多態的使用

#include<iostream>

usingnamespacestd;

classA{

public:

virtualvoidPrint(){cout<<"A::Print"<<endl;}

};

classB:publicA{

public:

voidPrint(){cout<<"B::Print"<<endl;}

};

voidPrintlnfo(A&r){//傳參相當於:r=a(指向父類A的Print()虛函數);或者r=b(指向子類B的Print()虛函數);

r.Print();//多態,調用哪個Print,取決於r引用了哪個類的對象

}

intmain()

{

Aa;Bb;

Printlnfo(a);//輸出A::Print

Printlnfo(b);//輸出B::Print

return0;

}

⑨ c++和java的多態性的區別

多態是指用父指針指向不同子類對象時,調用其共有的函數,不同的子類會有不同的行為。雖然C++和Java都具有多態機制,但是他們的實現不同,使用時的效果也會略有不同

在C++中

  • 普通函數調用:具體調用哪個方法在編譯時就可以決定(通過查找編譯器的符號表),同時在使用標准過程調用機制基礎上增加一個表示對象身份的指針(this指針)。

  • 虛函數調用:函數調用依賴於對象的實際類型,一般地說,對象的實際類型只能在運行時間才能確定。實現機制是使用virtual table(vtbls)和virtual table pointers(vptrs)。

  • vtbl 是由函數指針構成的數組或鏈表,程序中每一個class凡聲明(或繼承)虛函數者,都有一個自己的vtbl,其中的條目就是該class的各個虛函數實現的指針。因此必須為每一個class消耗一個vtbl空間,其大小視虛函數的個數確定。

  • 凡聲明有虛函數的class,其對象都有一個隱藏的data member,用來指向class的vtbl。

  • 當多態發生時,編譯器首先根據對象vptr找出其vtbl,然後找出vtbl內對應的函數指針,最後調用函數指針指向的函數。從而實現多態。

  • 在Java中

    1.C++中VTable和vptr是在編譯階段由編譯器自動生成的,也就是說,在C++程序載入內存以前,在.obj(.o)文件中已經有這些結構的信

    息;Java中的方法表是由JVM生成的,因此,使用javac命令編譯後生成的.class文件中並沒有方法表的信息。只有等JVM把.class文件
    載入到內存中時,才會為該.class文件動態生成一個與之關聯的方法表,放置在JVM的方法區中。
    2.
    C++中某個方法在VTable的索引號是在編譯階段已經明確知道的,並不需要在運行過程中動態獲知;Java中的方法初始時都只是一個符號,並不是
    一個明確的地址,只有等到該方法被第一次調用時,才會被解析成一個方法表中的偏移量,也就是說,只有在這個時候,實例方法才明確知道自己在方發表中的偏移
    量了,在這之前必須經歷一個解析的過程。

  • 因此在構造函數是Java會發生多態,即使子類此時還沒有構造完全(一個極難發現的bug)。而C++則不會發生多態,待父類構造完全,在構造子類。
熱點內容
java返回this 發布:2025-10-20 08:28:16 瀏覽:645
製作腳本網站 發布:2025-10-20 08:17:34 瀏覽:936
python中的init方法 發布:2025-10-20 08:17:33 瀏覽:632
圖案密碼什麼意思 發布:2025-10-20 08:16:56 瀏覽:821
怎麼清理微信視頻緩存 發布:2025-10-20 08:12:37 瀏覽:731
c語言編譯器怎麼看執行過程 發布:2025-10-20 08:00:32 瀏覽:1066
郵箱如何填寫發信伺服器 發布:2025-10-20 07:45:27 瀏覽:299
shell腳本入門案例 發布:2025-10-20 07:44:45 瀏覽:160
怎麼上傳照片瀏覽上傳 發布:2025-10-20 07:44:03 瀏覽:852
python股票數據獲取 發布:2025-10-20 07:39:44 瀏覽:763