C++中的動態型別與動態繫結、虛擬函式、執行時多型的實現
阿新 • • 發佈:2019-01-29
動態型別與靜態型別
靜態型別
是指不需要考慮表示式的執行期語義,僅分析程式文字而決定的表示式型別。靜態型別僅依賴於包含表示式的程式文字的形式,而在程式執行時不會改變。通俗的講,就是上下文無關,在編譯時就可以確定其型別。
動態型別
是指由一個左值表示式表示的左值所引用的最終派生物件的型別。例:如果一個靜態型別為“類 B ”的指標p 指向一個繼承於 B的類 D 的物件,則表示式 *p 的動態型別為“D”。引用按照相似規則處理。一般地講,基類的指標和基類引用有可能為動態型別,就是說在執行之前不能夠確定其真實型別。通常我們說,“基類指標指向的物件的實際/真正型別”或“基類引用所引用的物件的實際/真正型別”,就是它們的動態型別。很顯然,這個動態型別是 C++ 語言通過指標和引用實現執行時多型能力的核心概念。
動態繫結與靜態繫結
靜態繫結:編譯時繫結,通過物件呼叫 動態繫結:執行時繫結,通過地址實現 只有採用“指標->函式()”或“引用變數.函式()”的方式呼叫C++類中的虛擬函式才會執行動態繫結。對於C++中的非虛擬函式,因為其不具備動態繫結的特徵,所以不管採用什麼樣的方式呼叫,都不會執行動態繫結。 即所謂動態繫結,就是基類的指標或引用有可能指向不同的派生類物件,對於非虛擬函式,執行時實際呼叫該函式的物件型別即為該指標或引用的靜態型別(基類型別);而對於虛擬函式,執行時實際呼叫該函式的物件型別為該指標或引用所指物件的實際型別。比如下面程式碼:class Base { public: void func() { cout << "func() in Base." << endl; } virtual void test() { cout << "test() in Base." << endl; } }; class Derived : public Base { void func() { cout << "func() in Derived." << endl; } virtual void test() { cout << "test() in Derived." << endl; } }; int main() { Base* b; b = new Derived(); b->func(); b->test(); }
由執行結果可以看到,b是一個基類指標,它指向了一個派生類物件,基類Base裡面有兩個函式,其中test為虛擬函式,func為非虛擬函式。因此,對於test就表現為動態繫結,實際呼叫的是派生類物件中的test,而func為非虛擬函式,因此它表現為靜態繫結,也就是說指標型別是什麼,就會呼叫該型別相應的函式。
虛擬函式、動態繫結、執行時多型之間的關係
簡單地說,虛擬函式是動態繫結的基礎;動態繫結是實現執行時多型的基礎。 要觸發動態繫結,需滿足兩個條件:(1) 只有虛擬函式才能進行動態繫結,非虛擬函式不進行動態繫結。
(2) 必須通過基類型別的引用或指標進行函式呼叫。 通過基類指標或基類引用做形參,當實參傳入不同的派生類(或基類)的指標或引用,在函式內部觸發動態繫結
class Base {
public:
void Print() {
cout << "Print() from Base." << endl;
}
virtual void Display() {
cout << "Display() from Base." << endl;
}
};
class Derived1 : public Base {
public:
void Print() {
cout << "Print() from Derived1." << endl;
}
void Display() {
cout << "Display() from Derived2." << endl;
}
};
class Derived2 : public Base {
public:
void Print() {
cout << "Print() from Derived2." << endl;
}
void Display() {
cout << "Display() from Derived2." << endl;
}
};
class Derived3 : public Base {
public:
void Print() {
cout << "Print() from Derived3." << endl;
}
void Display() {
cout << "Display() from Derived3." << endl;
}
};
下面兩個全域性函式分別以基類指標和基類引用作形參來實現執行時多型:
//通過基類引用作形參實現多型
void Polymorphic1(Base& b) {
b.Print();
b.Display();
}
//通過基類指標作形參實現多型
void Polymorphic2(Base* b) {
b->Print();
b->Display();
}
下面是測試程式碼:
int main() {
Base b;
Derived1 d1;
Derived2 d2;
Derived3 d3;
vector<Base> base_vec;
base_vec.push_back(b);
base_vec.push_back(d1);
base_vec.push_back(d2);
base_vec.push_back(d3);
vector<Base*> base_ptr_vec;
base_ptr_vec.push_back(&b);
base_ptr_vec.push_back(&d1);
base_ptr_vec.push_back(&d2);
base_ptr_vec.push_back(&d3);
cout << endl << "對通過基類引用作形參實現多型進行測試" << endl;
//對通過基類引用作形參實現多型進行測試 (測試方式錯誤)
for (int i = 0; i != base_vec.size(); ++i) {
Polymorphic1(base_vec[i]);
}
cout << endl << "對通過基類指標作形參實現多型進行測試" << endl;
//對通過基類指標作形參實現多型進行測試
for (int i = 0; i != base_vec.size(); ++i) {
Polymorphic2(base_ptr_vec[i]);
}
cout << endl << "對通過基類引用作形參實現多型進行測試" << endl;
//對通過基類引用作形參實現多型進行測試
Polymorphic1(b);
Polymorphic1(d1);
Polymorphic1(d2);
Polymorphic1(d3);
return 0;
}
測試結果如下圖:
我們看到第一組想對通過基類引用作形參實現多型進行測試,需要將不同派生類的物件作實參傳過去。然而,把派生類放到基類的vector中儲存的過程中,派生類物件被自動轉換為基類物件了,因而實際儲存的均為基類物件,所以再從vector中取出物件元素做實參傳遞的時候,傳遞的均為基類物件,所以測試失敗。 而下面兩組我們分別把不同派生類的指標和物件作實參進行測試,結果顯示均實現了執行時多型:即傳入不同的物件,就會呼叫該物件相應的Display函式,因為在基類中,Display為虛擬函式,所以這裡它實現了物件的動態繫結,從而實現了執行時多型;與之做對比的Print函式在基類中為非虛構函式,因此對Print函式不會進行動態繫結,而是靜態繫結:即基類指標只能呼叫基類中的Print函式。