第15章 面向對象編程OOP(Object-oriented programming)
面向對象編程基於三個基本概念:
數據抽象、繼承和動態綁定。
在 C++ 中,用類進行數據抽象,用類派生從一個類繼承另外一個:派生類繼承基類的成員。動態綁定使編譯器可以在運行時決定是使用基類中定義的函數仍是派生類中定義
的函數。
- private 成員
• 經過類對象沒法訪問類的private成員。
• 在派生類中不能訪問基類的private成員。
• private 成員只能在當前類的做用域內訪問,類的友元也能夠訪問類的private 成員。例如,在成員函數中能夠訪問private 成員,在成員函數中還能夠經過本身類的對象來訪問類的private 成員。類的做用域包括:類定義的{}以內,類定義以外的成員函數的函數體,形參列表等。
- protected 成員
• 經過類對象沒法訪問protected 成員。
• protected 成員可被public派生類(包括派生類的派生類,向下傳遞)訪問,也就是說在派生類中可使用基類的protected 成員。
• 派生類只能經過派生類對象訪問其基類的 protected 成員,派生類沒法訪問其基類類型對象的 protected 成員。
- 派生類和虛函數
派生類通常會重定義所繼承的虛函數。若是派生類沒有重定義某個虛函數,則使用基類中定義的版本。
通常狀況下,派生類中虛函數的聲明必須與基類中的定義方式徹底匹配,例外:返回對基類型A的引用(或指針)的虛函數。派生類中的虛函數能夠返回類A的派生類的引用(或指針)。
一旦函數在基類中聲明爲虛函數,它就一直爲虛函數,派生類沒法改變該函數爲虛函數這一事實。派生類重定義虛函數時,可使用 virtual 保留字,也能夠不使用。
- 在運行時肯定 virtual 函數的調用
將基類類型的引用或指針綁定到派生類對象,那麼在調用虛函數時,若是調用非虛函數,則不管實際對象是什麼類型,都執行基類類型所定義的函數。若是調用虛函數,則直到運行時才能肯定調用哪一個函數,運行的虛函數是引用所綁定的或指針所指向的對象所屬類型定義的版本。只有經過引用或指針調用,虛函數纔在運行時肯定。只有在這些狀況下,直到運行時才知道對象的動態類型。
- 虛函數與默認實參
虛函數也能夠有默認實參。若是一個調用省略了具備默認值的實參,則所用的值由調用該函數的類型定義,與對象的動態類型無關。經過基類的引用或指針調用虛函數時,默認實參爲在基類虛函數聲明中指定的值,若是經過派生類的指針或引用調用虛函數,則默認實參是在派生類的版本中聲明的值。
- 友元關係與繼承
友元關係不能繼承:
(1)基類的友元對派生類的成員沒有特殊訪問權限。
(2)友元類的派生類不能訪問授予友元關係的類。
- 繼承與靜態成員
若是基類定義 static 成員,則整個繼承層次中只有一個這樣的成員。不管從基類派生出多少個派生類,每一個 static 成員只有一個實例。
static 成員遵循常規訪問控制:若是成員在基類中爲 private,則派生類不能訪問它。若是能夠訪問成員,則既能夠經過基類訪問 static 成員,也能夠經過派生類訪問 static 成員。通常而言,既可使用做用域操做符也可使用點或箭頭成員訪問操做符。
- 派生類到基類的轉換
指針或引用:(1)派生類型引用到基類類型引用,(2)派生類型指針到基類類型指針。反之是不行的。
對象:通常可使用派生類型的對象對基類類型的對象進行初始化或賦值,但沒有從派生類型對象到基類類型對象的直接轉換,編譯器不會自動將派生類型對象轉換爲基類類型
對象。
- 可使用派生類型對象對基類對象進行賦值或初始化。
將派生類型的對象傳給但願接受基類引用的函數,實際上實參是該對象的引用,對象自己未被複制。
將派生類對象傳給但願接受基類類型對象(而不是引用)的函數時,該派生類對象的基類部分被複制到形參。
- 派生類構造函數
構造函數和複製控制成員不能繼承,每一個類定義本身的構造函數和複製控制成員。像任何類同樣,若是類不定義本身的默認構造函數和複製控制成員,就將使用合成版本。
派生類的合成默認構造函數,除了初始化派生類的數據成員以外,它還初始化派生類對象的基類部分。基類部分由基類的默認構造函數初始化。
派生類構造函數的初始化列表只能初始化派生類的成員,不能直接初始化繼承成員。派生類構造函數只能經過將基類包含在構造函數初始化列表中來間接初始化繼承成員。
class Bulk_item : public Item_base {
public:
Bulk_item(const std::string& book, double sales_price,std::size_t qty = 0, double disc_rate = 0.0):
Item_base(book, sales_price), min_qty(qty), discount(disc_rate) { }
// as before
};
構造函數初始化列表爲類的基類和成員提供初始值,它並不指定初始化的執行次序。首先初始化基類,而後根據聲明次序初始化派生類的成員。
一個類只能初始化本身的直接基類(經過將基類包含在構造函數初始化列表中來間接初始化基類)。
- 複製控制和繼承
只包含類類型或內置類型數據成員、不含指針的類通常可使用合成操做,複製、賦值或撤銷這樣的成員不須要特殊控制。具備指針成員的類通常須要定義本身的複製控制來管理這些成員。
- 派生類的複製構造函數
若是派生類定義了本身的複製構造函數,該複製構造函數通常應顯式使用基類複製構造函數初始化對象的基類部分:
class Base { /* ... */ };
class Derived: public Base
{
public:
Derived(const Derived& d):Base(d) { /*... */ }
};
- 派生類賦值操做符
賦值操做符一般與複製構造函數相似:若是派生類定義了本身的賦值操做符,則該操做符必須對基類部分進行顯式賦值。
//Base::operator=(const Base&)
Derived &Derived::operator=(const Derived &rhs)
{
if (this != &rhs) //防止給本身賦值
{
Base::operator=(rhs); // 調用 Base 類的賦值操做符給基類部分賦值
……//爲派生類Derived 的成員賦值
}
return *this;
}
- 派生類析構函數
派生類析構函數不負責撤銷基類對象的成員。每一個析構函數只負責清除本身的成員。
class Derived: public Base
{
public:
~Derived() { /* do what it takes to clean up derived members
*/ }
};
- 虛析構函數
若是層次中基類的析構函數爲虛函數,則派生類析構函數也將是虛函數,不管派生類顯式定義析構函數仍是使用合成析構函數,派生類析構函數都是虛函數。
建議:即便析構函數沒有工做要作,繼承層次的根類也應該定義一個虛析構函數。
- 構造函數和賦值操做符不是虛函數
在複製控制成員中,只有析構函數應定義爲虛函數,構造函數不能定義爲虛函數。
- 名字衝突與繼承
與基類成員同名的派生類成員將屏蔽對基類成員的直接訪問。
可使用做用域操做符訪問被屏蔽的基類成員:
struct Derived : Base
{
int get_base_mem() { return Base::mem; }
};
設計派生類時,只要可能,最好避免與基類成員的名字衝突。
- 做用域與成員函數
在基類和派生類中使用同一名字的成員函數,其行爲與數據成員同樣:在派生類做用域中派生類成員將屏蔽基類成員。即便函數原型不一樣,基類成員也會被屏蔽。
- 重載函數
若是派生類重定義了重載成員,則經過派生類型只能訪問派生類中重定義的那些成員。
若是派生類想要經過自身類型來使用重載的版本,那麼派生類必須重定義全部的重載版本,但這樣會繁瑣,能夠經過給重載成員提供using 聲明來達到簡化的目的。
using Base::Func;//注意,將全部基類Base中的Func函數在本類中可見。
- 名字查找與繼承
(1)首先肯定進行函數調用的對象、引用或指針的靜態類型。
(2)在該類中查找函數,若是找不到,就在直接基類中查找,如此循着類的繼承鏈往上找,直到找到該函數或者查找完最後一個類。若是不能在類或其相關基類中找到該名字,則調用是錯誤的。
(3)一旦找到了該名字,就進行常規類型檢查,查看若是給定找到的定義,該函數調用是否合法。
(4)假定函數調用合法,編譯器就生成代碼。若是函數是虛函數且經過引用或指針調用,則編譯器生成代碼以肯定根據對象的動態類型運行哪一個函數版本,不然,編譯器生成代碼直接調用函數。
- 純虛函數
在函數形參表後面寫上 = 0 以指定純虛函數。該函數爲後代類型提供了能夠覆蓋的接口,可是這個類中的版本決不會調用。
含有(或繼承)一個或多個純虛函數的類是抽象基類。除了做爲抽象基類的派生類的對象的組成部分,不能建立抽象類型的對象。
第16章
模板和泛型編程
泛型編程就是以獨立於任何特定類型的方式編寫代碼。模板是泛型編程的基礎。在泛型編程中,咱們所編寫的類和函數可以多態地用於跨越編譯時不相關的類型。一個類或一個函數能夠用來操縱多種類型的對象。
- 定義函數模板
函數模板是一個獨立於類型的函數,可做爲一種方式,產生函數的特定類型版本。下面是 compare 的模板版本:
// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
模板定義以關鍵字 template 開始,後接模板形參表,模板形參表是用尖括號括住的一個或多個模板形參的列表,形參之間以逗號分隔。模板形參表不能爲空。
- inline 函數模板
函數模板能夠用與非模板函數同樣的方式聲明爲 inline。說明符放在模板形參表以後、返回類型以前,不能放在關鍵字 template 以前。
template <typename T> inline T min(const T&, const T&);// ok: inline specifier follows template parameter list
inline template <typename T> T min(const T&, const T&);// error: incorrect placement of inline specifier
- 類模版
類模版是一個類定義,用來定義一組特定類型的類,類模板用template關鍵字後接用尖括號<>括住,以逗號分隔的一個或多個模板形參的列表來定義。
template <class Type> class Queue {
public:
Queue (); // default constructor
Type &front (); // return element from head of Queue
const Type &front () const;
void push (const Type &); // add element to back of Queue
void pop(); // remove element from head of Queue
bool empty() const; // true if no elements in the Queue
private:
// ...
};
與調用函數模板造成對比,使用類模板時,必須爲模板形參顯式指定實參:
Queue<int> qi; // Queue that holds ints
Queue< vector<double> > qc; // Queue that holds vectors of doubles
Queue<string> qs; // Queue that holds strings
- 模板形參
模板形參做用域:模板形參的名字能夠在聲明爲模板形參以後直到模板聲明或定義的末尾處使用。
模板形參遵循常規名字屏蔽規則。與全局做用域中聲明的對象、函數或類型同名的模板形參會屏蔽全局名字。
- 限制
模板形參的名字不能在模板內部重用。
模板形參的名字只能在同一模板形參表中使用一次。
模板形參的名字能夠在不一樣模板中重用。
- 模板聲明
像其餘任意函數或類同樣,對於模板能夠只聲明而不定義。
同一模板的聲明和定義中,模板形參的名字能夠不相同。
- 模板類型形參
類型形參由關鍵字 class 或 typename 後接說明符構成。在模板形參表中,這兩個關鍵字具備相同的含義,都指出後面所接的名字表示一個類型。
它能夠用於指定返回類型或函數形參類型,以及在函數體中用於變量聲明或強制類型轉換。
- typename 與 class 的區別
在函數模板形參表中,關鍵字 typename 和 class 具備相同含義,能夠互換使用,兩個關鍵字均可以在同一模板形參表中使用。
關鍵字 typename 是做爲標準 C++ 的組成部分加入到 C++ 中的,所以舊的程序更有可能只用關鍵字 class。
- 在模板定義內部指定類型
經過在成員名前加上關鍵字 typename 做爲前綴,能夠告訴編譯器將成員看成類型。
template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
typename Parm::size_type * p; // ok: declares p to be a pointer
}
- 非類型模板形參
模板形參沒必要都是類型。
template <class T, size_t N>
void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
- 編寫泛型程序
在函數模板內部完成的操做限制了可用於實例化該函數的類型。因此,應該保證用做函數實參的類型實際上支持所用的任意操做,以及保證在模板使用哪些操做的環境中那些操做運行正常。
編寫模板代碼時,重要的原則是:實參類型的要求儘量少。
- 編寫泛型代碼的兩個重要原則(對實參類型的要求儘量少):
• 模板的形參是 const 引用。
• 函數體中的測試只用 < 比較。
- 類的實例化
類模板的每次實例化都會產生一個獨立的類類型。
想要使用類模板,就必須顯式指定模板實參。
- 函數模板實例化
使用函數模板時,編譯器一般會爲咱們推斷模板實參。
- 類型形參的實參的受限轉換,編譯器只會執行兩種轉換:
• const 轉換:接受 const 引用或 const 指針的函數能夠分別用非 const 對象的引用或指針來調用,無須產生新的實例化。
• 數組或函數到指針的轉換:若是模板形參不是引用類型,則對數組或函數類型的實參應用常規指針轉換。
- 類定義和函數聲明放在頭文件中,而函數定義和成員定義放在源文件中。
- 包含編譯模型
在包含編譯模型中,編譯器必須看到用到的全部模板的定義。通常而言,能夠經過在聲明函數模板或類模板的頭文件中添加一條 #include 指示使定義可用,該 #include 引入了包含相關定義的源文件。
- 分別編譯模型
在分別編譯模型中,編譯器會爲咱們跟蹤相關的模板定義。可是,咱們必須讓編譯器記住給定的模板定義,可使用 export 關鍵字來作這件事。
在一個程序中,一個模板只能定義爲導出一次。
(1)函數模板
在函數模板的定義中指明函數模板爲導出的,在關鍵字template 以前包含 export 關鍵字:
export template <typename Type>
Type sum(Type t1, Type t2) /* ...*/
這個函數模板的聲明像一般同樣應放在頭文件中,聲明沒必要指定 export。
(2)類模版
類聲明必須放在頭文件中,頭文件中的類定義體不該該使用關鍵字 export,應該在類的實現文件中使用 export。
template <class Type> class Queue { ... };
export template <class Type> class Queue;
#include "Queue.h"
- 一般,當使用類模板的名字的時候,必須指定模板形參。這一規則有個例外:在類自己的做用域內部,可使用類模板的非限定名。例如,在默認構造函數和複製構造函數的聲明中,名字 Queue 是 Queue<Type> 縮寫表示。
編譯器不會爲類中使用的其餘模板的模板形參進行這樣的推斷,所以,在聲明夥伴類 QueueItem 的指針時,必須指定類型形參: QueueItem<Type> *head; // pointer to first element in Queue
- 類模板成員函數
• 必須以關鍵字 template 開關,後接類的模板形參表。
• 必須指出它是哪一個類的成員。
• 類名必須包含其模板形參。例如:
template <class Type> void Queue<Type>::destroy()
{
while (!empty())
pop();
}
- 將類模板設爲友元
template <class Type> class Queue;
template <class Type> class QueueItem {
friend class Queue<Type>;
// ...
};
這個聲明創建了想要一對一映射,只將與 QueueItem 類用一樣類型實例化的
Queue 類設爲友元。
- 將函數模板設爲友元
template <class T>
std::ostream& operator<<(std::ostream&, const Queue<T>&);
template <class Type> class QueueItem {
friend class Queue<Type>;
// needs access to item and next
friend std::ostream&
operator<< <Type> (std::ostream&, const Queue<Type>&);
// ...
};
template <class Type> class Queue {
// needs access to head
friend std::ostream&
operator<< <Type> (std::ostream&, const Queue<Type>&);
};
- 成員模板
任意類(模板或非模板)能夠擁有自己爲類模板或函數模板的成員,這種成員稱爲成員模板,成員模板不能爲虛。
- 在類外部定義成員模板
當成員模板是類模板的成員時,它的定義必須包含類模板形參以及本身的模板形參。首先是類模板形參表,後面接着成員本身的模板形參表。
template <class T> template <class Iter> //第一個模板形參表 template<class T> 是類模板的,第二個模板形參表 template<class Iter> 是成員模板的。
void Queue<T>::assign(Iter beg, Iter end)
{
destroy(); // remove existing elements in this Queue
copy_elems(beg, end); // copy elements from the input range
}
- 類模板的 static 成員 類模板能夠像任意其餘類同樣聲明 static 成員