c++基類和派生類

派生類的繼承方式總結:


繼承方式 說明

public 基類的public和protected的成員被派生類繼承後,保持原來的狀態

private 基類的public和protected的成員被派生類繼承後,變成派生類的private成員

protected  基類的public和protected的成員被派生類繼承後,變成派生類的protected成員


注:無論何種繼承方式,基類的private成員都不能被派生類訪問。從上面的表中可以看出,聲明爲public的方法和屬性可以被隨意訪問;聲明爲protected的方法和屬性只能被類本身和其子類訪問;而聲明爲private的方法和屬性只能被當前類的對象訪問。  


1. 友元函數必須在類中進行聲明而在類外定義,聲明時須在函數返回類型前面加上關鍵字friend。友元函數雖不是類的成員函數,但它可以訪問類中的私有和保護類型數據成員。  


2. 虛函數在重新定義時參數的個數和類型必須和基類中的虛函數完全匹配,這一點和函數重載完全不同。  


3. #include <文件名>和#include "文件名"  


文件包含的兩種格式中,第一種格式用來包含那些由系統提供的並放在指定子目錄中的頭文件;而第二種格式用來包含那些由用戶自己定義的放在當前目錄或其他目錄下的頭文件或其它源文件。  


4. 數組也可以作爲函數的實參和形參,若數組元素作爲函數的實參,則其用法與變量相同。當數組名作爲函數的實參和形參時,傳遞的是數組的地址。當進行按值傳遞的時候,所進行的值傳送是單向的,即只能從實參傳向形參,而不能從形參傳回實參。形參的初值和實參相同,而形參的值發生改變後,實參並不變化,兩者的終值是不同的。而當用數組名作爲函數參數進行傳遞時,由於實際上實參和形參爲同一數組,因此當形參數組發生變化時,實參數組也隨之發生變化。  


注:實參數組與形參數組類型應一致,如不一致,結果將出錯;形參數組也可以不指定大小,在定義數組時數組名後面跟一個空的方括號,爲了在被調用函數中處理數組元素的需要,可以另設一個參數,傳遞數組元素的個數。如:int sum(int array[],int n);  


5. 重載、覆蓋和隱藏的區別?  


函數的重載是指C++允許多個同名的函數存在,但同名的各個函數的形參必須有區別:形參的個數不同,或者形參的個數相同,但參數類型有所不同。 


覆蓋(Override)是指派生類中存在重新定義的函數,其函數名、參數列、返回值類型必須同父類中的相對應被覆蓋的函數嚴格一致,覆蓋函數和被覆蓋函數只有函數體 (花括號中的部分)不同,當派生類對象調用子類中該同名函數時會自動調用子類中的覆蓋版本,而不是父類中的被覆蓋函數版本,這種機制就叫做覆蓋。


下面我們從成員函數的角度來講述重載和覆蓋的區別。


成員函數被重載的特徵有: 1) 相同的範圍(在同一個類中);2) 函數名字相同;3) 參數不同;4) virtual關鍵字可有可無。


覆蓋的特徵有: 1) 不同的範圍(分別位於派生類與基類);2) 函數名字相同;3) 參數相同;4) 基類函數必須有virtual關鍵字。


比如,在下面的程序中:


#include <iostream.h>

class Base

{

public:

void f(int x){ cout << "Base::f(int) " << x << endl; }

void f(float x){ cout << "Base::f(float) " << x << endl; }

virtual void g(void){ cout << "Base::g(void)" << endl;}

};


class Derived : public Base

{

public:

virtual void g(void){ cout << "Derived::g(void)" << endl;}

};


void main(void)

{

Derived d;

Base *pb = &d;

pb->f(42); // 運行結果: Base::f(int) 42

pb->f(3.14f); // 運行結果: Base::f(float) 3.14

pb->g(); // 運行結果: Derived::g(void)

}


函數Base::f(int)與Base::f(float)相互重載,而Base::g(void)被Derived::g(void)覆蓋。


隱藏是指派生類的函數屏蔽了與其同名的基類函數,規則如下: 1) 如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。2) 如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。


比如,在下面的程序中:


#include <iostream.h>


class Base

{

public:

virtual void 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:

virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }//被繼承之後,virtual 可有可無,但最好有。繼承後,還是虛函數。

void g(int x){ cout << "Derived::g(int) " << x << endl; }

void h(float x){ cout << "Derived::h(float) " << x << endl; }

using Base::g;//這句話是用來引用父類中被隱藏的部分的。

};


通過分析可得:


1) 函數Derived::f(float)覆蓋了Base::f(float)。


2) 函數Derived::g(int)隱藏了Base::g(float),注意,不是重載。


3) 函數Derived::h(float)隱藏了Base::h(float),而不是覆蓋。


看完前面的示例,可能大家還沒明白隱藏與覆蓋到底有什麼區別,因爲我們前面都是講的表面現象,怎樣的實現方式,屬於什麼情況。下面我們就要分析覆蓋與隱藏在應用中到底有什麼不同之處。在下面的程序中bp和dp指向同一地址,按理說運行結果應該是相同的,可事實並非如此。


void 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

}


請大家注意,f()函數屬於覆蓋,而g()與h()屬於隱藏。從上面的運行結果,我們可以注意到在覆蓋中,用基類指針和派生類指針調用函數f() 時,系統都是執行的派生類函數f(),而非基類的f(),這樣實際上就是完成的“接口”功能。而在隱藏方式中,用基類指針和派生類指針調用函數f()時,系統會進行區分,基類指針調用時,系統執行基類的f(),而派生類指針調用時,系統“隱藏”了基類的f(),執行派生類的f(),這也就是“隱藏”的由來。


重載(overload):這個好理解,在同個space域同名的。參數必須不同,有關virtual無關.


覆蓋(override):同名字,同參數,有virtual,覆蓋好理解比如show()函數,A派生了B,如果B中的show()覆蓋了A中的show(),但B中仍然有兩個show(),而不管是A類指針也好,B類對象調用也好,都只能調用B類自己的那個show();而從A類繼承過來的show()函數真的就被覆蓋了,沒有了嗎? 答案是不對的.這時可以在B類對象顯示的調用A類繼承過來的show();


程序代碼:


#include <iostream> 

using namespace std; 


class A 

public: 

virtual void show() 

cout << a << endl; 


int a; 

}; 


class B:public A 

public: 

void show() 

A::show(); 

//顯式地調用自己類中的 "由A類繼承過來的show()函數" ,像這種直接顯式指出某個類的某個函數時, 編譯器處理方式是這樣的: 首先在自己類中找有沒有A::show(),如果找到,調用.不在繼續在A類中找,如果找不到,則在顯式指出的那個類中(即A類)調用那個函數. 這裏當然是在B類中可以找到A::show() ,因爲基類中指出了這個函數是virtual函數. 

int b; 

};


int main() 

A a; 

a.a = 3; 

a.show(); 


B b; 

b.b = 4; 

b.show(); 

b.A::show(); //顯示的調用自己類中的 "由A類繼承過來的show()函數"


return 0; 

}


總結:


通俗的講B類還是有兩個show(),只是調用由A繼承過來的show()只能通過顯式的調用方法 [類名::virtual函數名] 而不管是基類A的指針 (B b; A *p = &b; p->show())或者派生類的對象(B b; b.show()),都只能調用B類的自己本身存在的show()函數


隱藏hide:


1:同名同參無virtual


2:同名不同參不管有無virtual


程序代碼:


class A 

public: 

void show() {}; //編號1 

void rose(int a) {} //編號2 

}; 


class B:public A 

public: 

void show() {}; //編號3 

void rose(int a, int b) {}; //編號4 

};


類B中的show()和rose()明顯是隱藏了類A的show()和rose() 隱藏的理解: B類中其實有兩個show(),兩個rose(); 但爲什麼不叫重載呢?你會這樣想,但我可以告訴你,因爲類B中的兩個show(),兩個rose(),不是都可以被B類的對象調用的.


編號1和編號2,在類B中哪怕存在,但只能通過類A的指針調用,而不能通過B類對象調用,如:


程序代碼:


A *p = new B; 

p->show(); 

p->rose(3); 

p->rose(3,5); //error


編號3和編程4,只能通過類B對象調用,而不能通過類A的指針調用,如:


程序代碼:


B b; 

b.show(); 

b.rose(3,5); 

b.rose(4); //error


6. 用參數列表可以區分重載函數,爲什麼返回值卻不能區分重載函數?


比如說有兩個函數:int fun(); double fun(); 如果寫fun();那麼編譯器就不知道該調用誰了。函數調用結束後才能確定返回值,但程序在調用函數時就需要明確知道調用哪一個重載的函數,這前後矛盾。


7. 派生類與基類之間的關係


派生類對象可以使用基類的方法,條件是方法在基類中沒有被聲明爲私有的。基類指針可以在不進行顯式類型轉換的情況下指向派生類對象,基類引用可以在不進行顯式類型轉換的情況下引用派生類對象。不過,基類指針或引用只能用於調用基類方法,不能調用派生類的方法。通常C++要求引用和指針類型與賦給的類型匹配,但這一規則對繼承來說是個例外。不過這種例外只是單向的,不可以將基類對象和地址賦給派生類引用和指針。


8. 靜態聯編和動態聯編


程序調用函數時,將使用哪個可執行代碼塊呢?編譯器負責回答這個問題。將源代碼中的函數調用解釋爲執行特定的函數代碼塊被稱爲函數名聯編。在C語言中,這非常簡單,因爲每個函數名都對應一個不同的函數。在C++中,由於函數重載的緣故,這項任務更復雜。編譯器必須查看函數參數以及函數名才能確定使用哪個函數。然而,C/C++編譯器可以在編譯過程完成這種聯編。在編譯過程中進行聯編被稱爲靜態聯編/綁定,又稱爲早期聯編/綁定。不過,虛函數使這項工作變得更困難。因爲使用哪個函數是不能在編譯時確定的,因爲編譯器不知道用戶將選擇哪種類型的對象。所以,編譯器必須生成能夠在程序運行時選擇正確的虛方法的代碼,這被稱爲動態聯編/綁定,又被稱爲晚期聯編/綁定。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章