(一三九)靜態聯編和動態聯編

2021-07-09 20:17:01 字數 4416 閱讀 4188

決定程式呼叫時,將使用哪個可執行**塊,是由編譯器負責的。

將源**中的函式呼叫解釋為執行特定的函式**塊被稱為 函式名聯編。

在c++

語言中,這個過程比在

c語言中更麻煩一些(因為

c++存在函式過載,編譯器要給函式重新命名),編譯器要檢視函式引數及函式名才能確定使用哪個函式(根據匹配的優先順序,一共

4級)。

c/c++編譯器可以在編譯過程中完成這種聯編,在編譯過程中進行聯編被稱為  

靜態聯編(static binding),又稱為早期聯編(early binding)。

然而虛函式使這項工作變得更困難(因為指標指向哪個類物件,這並不確定)。因此,編譯器必須生成能夠在程式執行時選擇正確的虛方法的**,這被稱為  動態聯編(dynamic binding),又稱為晚期聯編(late binding)。

指標和引用型別的相容性:

c++中,動態聯編與通過指標和引用呼叫方法相關(就是說,跟指標呼叫哪種方法有關係),從某種程度上來講,這是由繼承控制的。

公有繼承建立is-a

關係的一種方法是,如何處理指向物件的指標和引用。因為:

①一般情況下,乙個型別的指標或引用(如int*

或int&

)是不能指向另乙個型別的(如

double);

②但指向基類的指標和引用,可以指向派生類物件(不需要進行顯式型別轉換)。

將派生類指標、引用轉換為基類引用或指標,被稱為 向上強制轉換(upcasting)。

也就是說,將派生類指標、引用,賦給基類指標、引用之類的行為,就是向上強制轉換,是可以不顯式宣告的。

而將基類指標、引用轉換為派生類的指標、引用,被稱為 向下強制轉換(downcasting

)。向下強制轉換是需要顯示宣告的。

原因在於,基類的方法、資料成員,派生類都有,因此基類的指標、引用能做的事情,派生類也能做,不存在相容問題,因此向上強制轉換是安全的。

但相反,卻存在。

只有顯式的宣告向下強制轉換,才能告訴程式設計師,需要靠程式設計師來確保操作的安全。

隱式向上強制轉換使基類指標或引用,可以指向基類物件或派生類物件,因此需要動態聯編。c++

使用虛成員函式來滿足這種需求。

虛成員函式和動態聯編:

編譯器對非虛方法,使用靜態聯編;

編譯器對虛方法,使用動態聯編。

使用靜態聯編的好處:

①無需額外耗費開銷(如記憶體,處理器效能之類)來跟蹤究竟使用哪一種聯編;

②因此效率會更高。

一般來說,如果要在派生類中重新定義基類的方法,那麼最好把它設定為虛方法(這樣可以適用於指標),否則設定為非虛方法(因為虛方法的意義就在於多型)。

但事實上,設計的時候不一定知道會不會用。因此可能存在幾種情況:

①沒有設定虛方法,派生類也沒用(效率高);

②沒有設定虛方法,派生類用了(不支援多型,根據物件、指標、引用型別決定);

③設定虛方法,派生類沒用(應該是動態聯編,支援多型);

④設定虛方法,派生類用了(動態聯編,多型)。

虛方法的工作原理:

c++規定虛方法行為,而編譯器作者決定實現方法,程式設計師不需要知道虛方法的實現方法就能用。

編譯器處理虛函式的方法為:給每個物件新增乙個隱藏成員,而這個隱藏成員,包含乙個指向 函式位址 陣列的指標,這種陣列被稱為虛函式表(virtual funciton table, ftbl)。虛函式表中儲存了對類物件進行宣告的虛函式的位址。

大概意思就是(不保證完全準確,另外,不同編譯器上可能也有所區別):

①每個類物件,有乙個隱藏的成員,這個隱藏成員是乙個指標。

②這個指標幹嘛的呢,他指向乙個陣列(虛函式表)。

③這個陣列幹嘛的呢,他儲存了若干個函式的位址。

④這些位址哪來的呢,他是該類物件的虛方法的位址(注意,是涉及到虛方法才儲存,非虛方法是不儲存的,例如基類物件儲存了其所有虛方法的位址)。

⑤假如這個類物件的類是派生類,那麼首先他儲存了基類所有虛方法的位址(這是肯定的),但若基類的某個方法是虛方法,派生類重新定義了這個方法,那麼他就不儲存這個基類的方法的位址了,改儲存派生類方法的位址。

⑥如果基類指標指向這個派生類物件,那麼就在使用類方法的時候,就會呼叫這個物件的隱藏成員,然後找到虛函式表。然後根據虛函式表的位址,來找使用哪個虛方法。

例如,(6.1

)使用某個基類和派生類都有的虛方法,表中儲存的是派生類的,於是呼叫之;

(6.2

)如果基類有虛方法,派生類沒有重新定義,那麼表中儲存的是基類的,於是呼叫基類的;

(6.3

)如果基類沒有該方法,派生類有方法,由於基類指標只能使用基類的方法,因此無法使用。

(6.4

)如果基類有方法,但不是虛方法(此時表中沒有儲存該位址),那麼由於是基類指標並沒有儲存該方法的位址,因此自然也不會被派生類的方法替代。於是執行基類的方法。

⑦注意,虛方法面對的物件是指標和引用。因此,這個虛函式表其實也只對指標和引用起作用(物件是根據物件的型別決定呼叫哪個的)。

因此,使用虛函式,在記憶體和執行速度方面有一定的成本,包括:

①每個物件都增大,增大量為儲存位址的空間。

②對每個類,編譯器都建立乙個虛函式位址表(陣列);

③對每個函式呼叫,都將執行一項額外的操作,即到表中查詢位址。

雖然非虛函式的效率比虛函式稍高,但不具有動態聯編的功能。

有關虛函式的注意事項:

虛函式的一些特點:

①在基類方法的宣告中使用關鍵字virtual

,可使該方法在基類、以及所有的派生類、還有派生類的派生類中,是虛的;

②如果使用指向物件的引用或指標來呼叫虛方法,程式將根據其指向的物件(而不是指標、引用的型別)來決定呼叫的方法。

③如果定義的類被用作基類,那麼應將那些要被重新定義的類方法,定義為虛方法。

虛函式需要注意一些內容:

①建構函式不能是虛函式。

因為給基類建立物件時,宣告派生類的資料成員並沒有意義(比如基類沒有int a

,派生類有,呼叫派生類的物件則會生成

int a

,但是這對基類並沒有意義)

而給派生類建立物件時,自動會呼叫基類的建構函式,因此也不需要通過虛方法來呼叫(何況向下強制轉換並不好)。

②析構函式需要是虛函式。

例如基類指標new

分配乙個派生類的物件:

brass *q = new

brass_plus(one);

這個時候,指標指向的物件是派生類的。

如果delete

,假如是非虛析構函式,那麼則執行的是基類的析構函式(顯然是不對的)。我們如果想讓他執行派生類的析構函式,則需要是虛析構函式,才能呼叫派生類的析構函式。

③友元不能是虛函式。

因為只有成員函式才能是虛函式。友元不是類成員,所以不是虛函式。

基類的友元函式 不是 派生類的友元函式,只是使用友元函式時,將派生類轉換為了基類。

如果因為這個原因引起了設計問題,可以通過讓友元函式使用虛成員函式來解決。

——不懂,意思是讓友元函式呼叫虛成員函式麼?

④沒有重新定義

如果派生類沒有重新定義函式,那麼將使用該函式的基類版本。

如果派生類位於派生鏈中,則將使用最新的虛函式版本(基類a

——》派生

b——》派生

c,如果

c沒有定義,那麼使用

b定義的虛函式版本,不過前提應該是

a類指標指向

c類物件)。

例外的情況是基類版本是隱藏的(貌似指的是派生類同名函式隱藏了基類的同名函式)

⑤重新定義將隱藏方法

假如基類有虛方法void show();

派生類有虛方法void show();

那麼毫無疑問,派生類的虛方法將替代基類的虛方法。

但若派生類的虛方法變為void show(int a);

這並不會產生過載函式,而是隱藏掉基類同名的基類虛方法(無視特徵標),因此,也不會產生過載函式(無引數和有引數兩個版本)。

因此,產生兩個結論:

(1)如果重新定義繼承的方法,應確保與原來的原型完全一致(這是多型的意義),但如果返回值是基類引用或指標,則可以修改為返回指向派生類的引用或指標。

這種特性被稱為:返回型別協變,因為允許返回型別隨類型別的變化而變化。

但注意:只適合返回值,不適合引數(因為不同類作為引數對私有成員的訪問許可權不同)

(2)如果基類宣告被過載了,並且某乙個是虛函式,在派生類被定義了,那麼則應在派生類重新定義所有的基類版本(因為同名的過載都被隱藏了)。

不過如果沒特殊需求的話,可以定義需要定義的過載版本,其他版本可以使用基類的(假如需要顯示的和基類的一樣的話)。

使用方法有兩種:強制轉換為基類型別(對類物件使用),或者在派生類的函式定義中,呼叫基類的函式定義。

靜態聯編和動態聯編

聯編是指乙個電腦程式自身彼此關聯 使乙個 源程式經過編譯 連線,成為乙個可執行程式 的過程,在這個聯編過程中,需要確定程式中的操作呼叫 函式呼叫 與執行該操作 函式 的 段之間的對映關係,按照聯編所進行的階段不同,可分為靜態聯編和動態聯編。靜態聯編 呼叫函式和被調函式在程式編譯時,他們在記憶體中的位...

靜態聯編和動態聯編

聯編就是將模組或者函式合併在一起生成可執行 的處理過程,同時對每個模組或者函式呼叫分配記憶體位址,並且對外部訪問也分配正確的記憶體位址,它是電腦程式彼此關聯的過程。按照聯編所進行的階段不同,可分為兩種不同的聯編方法 靜態聯編和動態聯編。靜態聯編是指在編譯階段就將函式實現和函式呼叫關聯起來,因此靜態聯...

靜態聯編和動態聯編

聯編就是將模組或者函式合併在一起生成可執行 的處理過程,同時對每個模組或者函式呼叫分配記憶體位址,並且對外部訪問也分配正確的記憶體位址,它是電腦程式彼此關聯的過程。按照聯編所進行的階段不同,可分為兩種不同的聯編方法 靜態聯編和動態聯編。靜態聯編是指在編譯階段就將函式實現和函式呼叫關聯起來,因此靜態聯...