linux程序设计知识点整理,笔试面试中C/C++重要知识点整理
4. ? 類與面向對象編程
4.1 類接口與實現的概念:
每個類都定義了一個接口(可以不是很確切的理解為類中訪問級別為public的函數為接口)和一個實現。接口由使用該類的代碼需要執行的操作組成。實現一般包括該類所需要的數據。實現還包括定義該類需要的但又不供一般性使用的函數。
定義類時,通常先要定義該類的接口,即該類所提供的操作。通過這些操作,可以決定該類完成其功能所需要的數據,以及是否需要定義一些函數來支持該類的實現。
public派生類繼承基類的接口,它具有與基類相同的接口。設計良好的類層次中,public派生類的對象可以用在任何需要基類對象的地方。
4.2 用struct關鍵字與class關鍵定義類以及繼承的區別
(1)定義類差別
struct關鍵字也可以實現類,用class和struct關鍵字定義類的唯一差別在于默認訪問級別:默認情況下,struct成員的訪問級別為public,而class成員的為private。語法使用也相同,直接將class改為struct即可。
(2)繼承差別
使用class保留字的派生類默認具有private繼承,而用struct保留字定義的類某人具有public繼承。其它則沒有任何區別。
class Base{ /*....*/};
struct ?D1: Base{ /*......*/} ; ? ?// 默認是public繼承
class ?D2: Base{/*.......*/}; ?// 默認是private繼承
4.3 類設計與protected成員
可以認為protected訪問標號是private和public的混合:
(1)像private成員一樣,protected成員不能被類的用戶訪問
(2)像public成員一樣,protected成員可以被該類的派生類訪問。
例如:
class Base
{
.........
protected:
int price;
};
class Item_Base :public Base
{
......................
};
Base ? b;
Item_Base d;
b.price; ?// error
d.price: // OK
小結(幫助理解為什么設置protected類型): 如果沒有繼承,類只有兩種用戶:類本身的成員以及該類的用戶,將類劃分為private和public訪問級別反映了用戶類型的這一分割:用戶只能訪問public接口,類成員和友元既能訪問public成員也能訪問private成員。
有了繼承,就有了第三種用戶:從派生類定義新類的程序員。派生類提供者通常(但不總是)需要訪問(類型為private的)基類實現(見4.1實現概念)。為了允許這種訪問而仍然禁止對實現的一般訪問。所以提供了附加的protected訪問標號。類的protected部分仍然不能被一般程序訪問,但可以被派生類訪問。
定義基類時,將成員設置為public的標準并沒有改變:仍然是接口函數應該為public而數據一般不應為public。被繼承的類必須決定實現那些部分為protected哪些部分為private。希望禁止派生類訪問的成員應該設為private,提供派生類實現所需操作或數據的成員設為protected。換句話說,提供給派生類的接口是protected成員和public成員的組合。
4.4 ?派生類與虛函數概述
(1)定義為virtual的函數是希望派生類重新定義。希望派生類繼承的函數不能定義為虛函數。如果派生類沒有重新定義某個虛函數,則在調用的時候會使用基類中定義的版本。
(2)派生類中函數的聲明必須與基類中定義的方式完全匹配,但有一個例外:返回對基類類型的引用(或指針)的虛函數。派生類中的虛函數可以返回基類函數所返回類型的派生類的引用(或指針)。比如:Item_base類可以定義返回Item_base*的函數。如果這樣,派生類Bulk_item類中定義的實例可以定義返回為Item_base*或者Bulk_item*
(3) 一旦函數在基類中聲明為虛函數,它就一直為虛函數,派生類無法改變該函數為虛函數這一事實。派生類重新定義虛函數時,可以使用virtual保留字,也可以省略。
4.5 virtual 函數詳解(待更新)
要觸發動態綁定,必須滿足兩個條件:第一:只有指定為虛函數的成員函數才能進行動態綁定。第二,必須通過基類類型的引用或者指針進行函數調用。下面重點講下第二個條件。
由于每個派生類都包含基類部分,所以可將基類對象引用或者指針綁定到派生類對象的基類部分(派生類對象本身不會改變)。如下:
double print_total(const Item_base&, size_t);
Item_base item;
print_total(item, 10); // OK
Bulk_item bulk;
print_total(bulk, 10); // OK ?引用bulk中Item_base的部分。
Item_base *item = &bulk; // OK , 指針指向bulk 的Item_base部分。
通過引用或者指針調用虛函數時,編譯器將生成代碼,在運行時確定調用哪個函數。比如:
假定print_total 為虛函數,在基類Item_base 和派生類Bulk_item中都有定義。
函數原型:void print_total(ostream& os, const Item_base &item, size_t n);
Item_base base;
Bulk_item derived;
print_total(count, base, 10); // 將調用基類Item_base中的print_total函數
print_total(count, derivede,10); // 將調用派生類中的print_total 函數。
在某些情況下,希望覆蓋虛函數的機制并強制函數使用虛函數的特定版本,這時可以使用作用域操作符。
Item_base *baseP = &derived;
double d = baseP->Item_base::net_price(42);
這段代碼將強制把net_price調用確定為Item_base中版本(在編譯時確定)。
小結:引用和指針的靜態類型與動態類型可以不同,這是C++支持多態性的基石。當通過基類引用或者指針滴哦啊用基類中定義的函數時,我們并不知道執行函數的對象的確切類型,執行函數的對象可能是基類類型的,也可能是派生類類型的。
如果調用非虛函數,則無論實際對象是什么類型,都執行基類中所定義的哦函數。如果調用虛函數,則直到運行時才能確定調用哪個函數。
4.5 派生類到基類的轉換: C++ primer 488 沒有想好怎么整理
4.6 基類與派生類中構造函數和復制控制:
構造函數和復制控制成員不能被繼承,每個類定義自己的構造函數和復制控制成員,如果不定義,則編譯器將合成一個。
繼承對基類中構造函數的唯一影響是,某些類需要只希望派生類使用的特殊構造函數,這樣的構造函數應該定義為protected。
4.6.1 派生類構造函數
派生類構造函數受繼承關系的影響,每個派生類構造函數除了初始化自己的數據成員之外,還要初始化基類。對于合成的派生類默認構造函數,先調用基類的默認構造函數初始化(問題,如果基類沒有定義默認構造函數咋整,要試驗下) 再默認初始化自己的對象成員。。。。。
具體語法參見P491 c++ primer
4.6.2 派生類析構函數
派生類析構函數不負責撤銷基類對象的成員。編譯器總是顯式調用派生類對象基類部分的析構函數。每個析構函數只負責清除自己的成員:
class Derived: public Base
{
// Base::~Base()函數會自動被調用
~Derived();
}
對象的撤銷順序與構造順序相反:首先運行派生類析構函數,然后按照繼承層次依次向上調用各基類的析構函數。
4.6.3 虛析構函數
當闡述指向動態分配對象的指針時,需要運行析構函數在釋放對象之前清除對象。如果把析構函數設置為虛函數,運行哪個析構函數將因指針所指向對象類型的不同而不同:
Item_base *itemP = new Item_base;
delete itemP; ?// ?基類的析構函數被調用
itemP = new Bulk_item;
delete itemP; // 派生類的析構函數被調用
如果不把析構函數定義為虛函數,則會一直調用基類的析構函數,從而引發程序異常。
像其他虛函數一樣,析構函數的虛函數性質將繼承,因此,如果層次中根類的析構函數為虛函數,則派生類析構函數也將是虛函數,無論派生類顯式定義析構函數還是使用合成析構函數,派生類析構函數都是虛函數。
構造函數不是虛函數: 構造函數實在對象完全構造之前運行的,在構造函數運行的時候,對象的動態類型還不夠完整(待理解),所以構造函數不是虛函數。
4.7 繼承情況下的類的作用域
繼承層次中函數調用遵循以下四個步驟:
(1)首先確定進行函數調用的對象,引用或者指針的靜態類型。
(2)在該類中查找函數,如果找不到,就直接在基類中查找,如此循環著類的繼承鏈往上找,直到找到該函數或者查找完最后一個類。如果不能再類或者相關基類中找到該名字,則調用是錯誤的。
(3)一旦找到了該名字,進行常規類型檢查(參數類型檢查等),查看該函數調用是否合法
(4) 假定函數調用合法,編譯器就生成代碼,如果函數是虛函數并且通過引用或者指針調用,則編譯器生成代碼以確定根據對象的動態類型運行哪個函數版本,否則,編譯器生成代碼直接調用函數。
舉例1:
Bulk_item bulk;
cout << bulk.book();
book 的使用將這樣確定:
(1) bulk 是Bulk_item 類對象,在Bulk類中查找,找不到名字book ?( 根據上面第一步,確定靜態類型為Bulk_item, 然后進入第二步)
(2)因為從Item_base派生Bulk_item, 所以接著在Item_base類中查找,找到book ,名字成功確定。
舉例2:
struct Base
{
int menfcn();
};
struct Derived: Base
{
int menfcn(int);
};
Derived d; Base b;
b.memfcn(); ? ? ?// 調用基類的函數
d.menfcn(10); ?// 調用派生類函數
d.menfcn(); ? ? ? // 錯誤:
d.Base::menfcn(); ? // 調用基類函數
第三個調用中出現錯誤,原因是,Derived中的么么fcn聲明隱藏了Base中的聲明。 原因是,根據上面規則,一旦找到了名字,編譯器就不會再繼續查找了。而是進行常規檢查,由于調用與Derived中的memfcn不匹配,該定義希望接受int實參,而這個函數調用沒有提供那樣的實參,所以錯誤
如果派生類重新定義了重載成員,則通過派生類行只能訪問派生類中重新定義的那些成員。
舉例3: 通過基類指針或者引用調用
假定print_total 為虛函數,在基類Item_base 和派生類Bulk_item中都有定義。
函數原型:void print_total(ostream& os, const Item_base &item, size_t n);
Item_base base;
Bulk_item derived;
print_total(count, base, 10); // 將調用基類Item_base中的print_total函數
print_total(count, derivede,10); // 將調用派生類中的print_total 函數。
如果print_total 不是虛函數,根據上面的步驟,將直接調用基類Item_base中的print_total 版本
由于print_total中 第二個參數的靜態類型為Item_base 所以根據規則(1),先Item_base 中查找print_total , 然后進行常規檢查,參數沒有錯,由于函數是虛函數之后根據規則(4),?print_total(count, base, 10);用基類Item_base中的print_total函數,print_total(count, derivede,10);,調用派生類中的print_total 函數。
現在可以理解為什么虛函數在基類和派生類中擁有同一原型了,如果沒有同一原型,比如基類與派生類中參數不同,根據規則3,確定基類中參數沒有問題時,如果根據規則4 實際調用的是派生類中的函數時由于參數不同就會出現錯誤。
4.8 細節知識點
4.8.1 explicit關鍵字
我們可以將構造函數聲明為explicit,來防止在需要隱式轉換的上下文中使用構造函數。例如
class Sales_item
{
public:
Sales_item(const string &book = ""):isbn(book),units_sold(0){}
bool same_isbn(const Sales_item &rhs) const;
};
每個構造函數都定義了一個隱式轉換。因此,在期待一個Sales_item類型對象的地方,可以使用一個string或者istream:如下
string null_book = "9-1111-1111";
item.same_isbn(null_book);
以上程序中,本來程序期待一個Sales_item對象作為實參,編譯器使用接受一個string的Sales_item構造函數從null_book生成一個新的Sales_item對象,新生成的臨時的Sales_item對象被傳遞給same_isbn。
如果我們不想要編譯器隱式的轉換,可以將構造函數聲明為explicit。 注意的是explicit關鍵字只能用于類內部的構造函數聲明上。在類定義外部所作的定義中不再重復它。 比如以下是錯誤的.
explicit Sales_item:: sales_item(istream& is) ? ?//錯誤,explicit只在類內構造函數的聲明上
{
。。。。。。
}
加上explicit關鍵字后,以下就不能編譯通過
item.same_isbn(null_book); // error:string constructor is explicit
當然我們可以顯式的使用構造函數來生成轉換,如下
item.same_isbn(Sales_item(null_book)); ?// OK
總結:通常,除非有明顯的理由需要隱式轉換,否則,構造函數應該為explicit。 將構造函數設置為explicit可以避免錯誤,并且當轉換有用時用戶可以顯式的構造對象
4. 9 虛函數與純虛函數區別
(1)虛函數在子類里面也可以不重載的;但純虛必須在子類去實現
(2)帶純虛函數的類叫虛基類,這種基類不能直接生成對象,而只有被繼承,并重寫其虛函數后,才能使用。這樣的類也叫抽象類。虛函數是為了繼承接口和默認行為
總結
以上是生活随笔為你收集整理的linux程序设计知识点整理,笔试面试中C/C++重要知识点整理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 细说php精要版 百度云,细说php精要
- 下一篇: hihocoder 1183 割点和割边