C++學習之多型及過載(overload),覆蓋(override),隱藏(hide)的區別
阿新 • • 發佈:2019-01-02
C++程式語言是一款應用廣泛,支援多種程式設計的計算機程式語言。我們今天就會為大家詳細介紹其中C++多型性的一些基本知識,以方便大家在學習過程中對此能夠有一個充分的掌握。
多型性可以簡單地概括為“一個介面,多種方法”,程式在執行時才決定呼叫的函式,它是面向物件程式設計領域的核心概念。多型(polymorphisn),字面意思多種形狀。C++多型性是通過虛擬函式來實現的,虛擬函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(這裡我覺得要補充,重寫的話可以有兩種,直接重寫成員函式和重寫虛擬函式,只有重寫了虛擬函式的才能算作是體現了C++多型性)而過載則是允許有多個同名的函式,而這些函式的引數列表不同,允許引數個數不同,引數型別不同,或者兩者都不同。編譯器會根據這些函式的不同列表,將同名的函式的名稱做修飾,從而生成一些不同名稱的預處理函式,來實現同名函式呼叫時的過載問題。但這並沒有體現多型性。
多型與非多型的實質區別就是函式地址是早繫結還是晚繫結。如果函式的呼叫,在編譯器編譯期間就可以確定函式的呼叫地址,並生產程式碼,是靜態的,就是說地址是早繫結的。而如果函式呼叫的地址不能在編譯器期間確定,需要在執行時才確定,這就屬於晚繫結。
那麼多型的作用是什麼呢,封裝可以使得程式碼模組化,繼承可以擴充套件已存在的程式碼,他們的目的都是為了程式碼重用。而多型的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的物件,函式都能夠通過同一個介面呼叫到適應各自物件的實現方法。
最常見的用法就是宣告基類的指標,利用該指標指向任意一個子類物件,呼叫相應的虛擬函式,可以根據指向的子類的不同而實現不同的方法。如果沒有使用虛擬函式的話,即沒有利用C++多型性,則利用基類指標呼叫相應的函式的時候,將總被限制在基類函式本身,而無法呼叫到子類中被重寫過的函式。因為沒有多型性,函式呼叫的地址將是一定的,而固定的地址將始終呼叫到同一個函式,這就無法實現一個介面,多種方法的目的了。
筆試題目:
- #include<iostream>
- usingnamespace std;
- class A
- {
- public:
- void foo()
- {
- printf("1\n");
- }
- virtualvoid fun()
- {
- printf("2\n");
- }
- };
- class B : public A
- {
- public:
- void foo()
- {
- printf("3\n");
-
}
- void fun()
- {
- printf("4\n");
- }
- };
- int main(void)
- {
- A a;
- B b;
- A *p = &a;
- p->foo();
- p->fun();
- p = &b;
- p->foo();
- p->fun();
- return 0;
- }
第二個輸出結果就是1、4。p->foo()和p->fuu()則是基類指標指向子類物件,正式體現多型的用法,p->foo()由於指標是個基類指標,指向是一個固定偏移量的函式,因此此時指向的就只能是基類的foo()函式的程式碼了,因此輸出的結果還是1。而p->fun()指標是基類指標,指向的fun是一個虛擬函式,由於每個虛擬函式都有一個虛擬函式列表,此時p呼叫fun()並不是直接呼叫函式,而是通過虛擬函式列表找到相應的函式的地址,因此根據指向的物件不同,函式地址也將不同,這裡將找到對應的子類的fun()函式的地址,因此輸出的結果也會是子類的結果4。
筆試的題目中還有一個另類測試方法。即
B *ptr = (B *)&a; ptr->foo(); ptr->fun();
問這兩呼叫的輸出結果。這是一個用子類的指標去指向一個強制轉換為子類地址的基類物件。結果,這兩句呼叫的輸出結果是3,2。
並不是很理解這種用法,從原理上來解釋,由於B是子類指標,雖然被賦予了基類物件地址,但是ptr->foo()在呼叫的時候,由於地址偏移量固定,偏移量是子類物件的偏移量,於是即使在指向了一個基類物件的情況下,還是呼叫到了子類的函式,雖然可能從始到終都沒有子類物件的例項化出現。
而ptr->fun()的呼叫,可能還是因為C++多型性的原因,由於指向的是一個基類物件,通過虛擬函式列表的引用,找到了基類中fun()函式的地址,因此呼叫了基類的函式。由此可見多型性的強大,可以適應各種變化,不論指標是基類的還是子類的,都能找到正確的實現方法。
- //小結:1、有virtual才可能發生多型現象
- // 2、不發生多型(無virtual)呼叫就按原型別呼叫
- #include<iostream>
- usingnamespace std;
- class Base
- {
- public:
- virtualvoid f(float x)
- {
- cout<<"Base::f(float)"<< x <<endl;
- }
- void g(float x)
- {
- cout<<"Base::g(float)"<< x <<endl;
- }
- void h(float x)
- {
- cout<<"Base::h(float)"<< x <<endl;
- }
- };
- class Derived : public Base
- {
- public:
- virtualvoid f(float x)
- {
- cout<<"Derived::f(float)"<< x <<endl; //多型、覆蓋
- }
- void g(int x)
- {
- cout<<"Derived::g(int)"<< x <<endl; //隱藏
- }
- void h(float x)
- {
- cout<<"Derived::h(float)"<< x <<endl; //隱藏
- }
- };
- int main(void)
- {
- Derived d;
- Base *pb = &d;
- Derived *pd = &d;
- // Good : behavior depends solely on type of the object
- pb->f(3.14f); // Derived::f(float) 3.14
- pd->f(3.14f); // Derived::f(float) 3.14
- // Bad : behavior depends on type of the pointer
- pb->g(3.14f); // Base::g(float) 3.14
- pd->g(3.14f); // Derived::g(int) 3
- // Bad : behavior depends on type of the pointer
- pb->h(3.14f); // Base::h(float) 3.14
- pd->h(3.14f); // Derived::h(float) 3.14
- return 0;
- }
本來僅僅區別過載與覆蓋並不算困難,但是C++的隱藏規則使問題複雜性陡然增加。
這裡“隱藏”是指派生類的函式遮蔽了與其同名的基類函式,規則如下:
(1)如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無virtual
關鍵字,基類的函式將被隱藏(注意別與過載混淆)。
(2)如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有virtual
關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆)。
上面的程式中:
(1)函式Derived::f(float)覆蓋了Base::f(float)。
(2)函式Derived::g(int)隱藏了Base::g(float),而不是過載。
(3)函式Derived::h(float)隱藏了Base::h(float),而不是覆蓋。
me:過載是發生在同作用域中,見C++primer第四版,p500
-----------------------------------下面是一個覆蓋與隱藏的例子-------------------
#include<iostream>
class a{
public:
virtual void print(){
std::cout<<"NULL print in a"<<std::endl;
}
virtual void print(int){
std::cout<<"int print in a"<<std::endl;
}
};
class b:public a{
public:
virtual void print(){//can hide print ,print(int) in a
std::cout<<"NULL print in b"<<std::endl;
}
};
class c:public b{
public:
virtual void print(int){//can hide print in b,a
std::cout<<"int print in c"<<std::endl;
}
};
int main(){
b b1;
b1.print();
//b1.print(1);//compile error,hide
c c1;
c1.print(1);
//c1.print();//compile error,hide
//虛擬函式被隱藏,但是還是可以通過指標訪問到,如果不是虛擬函式,當然通過指標也不能
a*ap=new c;
ap->print(1);//int print in c
}
~
過載可以是函式的返回值型別不同,但是不能只是函式返回值型別不同;虛擬函式實現多型,子類中的虛擬函式的返回型別可以為父類中相應虛擬函式的返回型別可以不完全相同, 如返回引用或指標,子類的返回型別可以是父類的返回型別的子類,但函式引數型別必須完全相同,否則會出現上面程式碼情況。
#include <iostream>
class test{};
class test1:public test{};
test t;
test1 t1;
#include <iostream>
class test{};
class test1:public test{};
test t;
test1 t1;
class base{
public:
virtual test& print(test1*){
std::cout<<"print in base"<<std::endl;
return t;
}
};
class derived:public base{
public:
test1& print (test1*){
std::cout<<"print in derived"<<std::endl;
return t1;
}
};
int main(){
base*b=new(std::nothrow) derived;
b->print(new test1);//base :: print
delete b;
b=new (std::nothrow)base;
b->print(new test1);//derived::print
delete b;
}