前一小節《容器與繼承》提到過:
對於容器,如果定義為基類型別,那麼則不能通過容器訪問派生類新增的成員;如果定義為派生類型別,一般不能用它承載基類的物件,即使利用型別轉化強行承載,則基類物件可以訪問沒有意義的派生類成員,這樣做是很危險的。對這個問題的解決辦法,是使用容器儲存基類的指標。
在c++中,這類問題有一種通用的解決辦法,稱為控制代碼類。它大體上完成兩方面的工作:
1.管理指標。這與智慧型指標的功能類似
2.實現多型。利用動態繫結,是得指標既可以指向基類,也可以指向派生類。
控制代碼類的設計需要重點考慮兩個因素:
1.如何管理指標
2.是否遮蔽它所管理的基類和派生類的介面。這意味著,如果我們充分了解繼承成層次的介面,那麼就能直接使用它們;要麼我們將這些介面封裝起來,使用控制代碼類自身的介面。
下面通過乙個比較複雜的例子來說明這個問題:
這個例子的大體思路,是使用乙個容器(multiset)來模擬乙個購物車,裡面裝了許多書,有的書是原價銷售的,有的書是打折銷售的,並且打折銷售也分為兩種策略:買的多了才打折;買的少才打折,超出部分原價銷售。最後能夠方便的計算購買各種不同型別的書,在不同的打折條件下一共花了多少錢。
首先,是定義不同打折策略的書籍,它們時控制代碼類要管理的繼承層次:
//不使用折扣策略的基類
class item_base
//返回isbn號
std::string book()const
//基類不需要折扣策略
virtual double net_price(std::size_t n)const
//析構函式
virtual ~item_base(){};
virtual item_base* clone()const
private:
std::string isbn;
protected:
double price;
};//儲存折扣率和購買數量的類
//它有兩個派生類,實現兩種折扣模式
class disc_item:public item_base
//純虛函式:防止使用者建立這個類的物件
double net_price(std::size_t)const = 0;
//將買多少書與折扣率繫結起來
std::pairdiscount_policy()const
//受保護成員供派生類繼承
protected:
//實現折扣策略的購買量
std::size_t quantity;
//折扣率
double discount;
};//批量購買折扣策略:大於一定的數量才有折扣
class bulk_item:public disc_item
~bulk_item(){}
double net_price(std::size_t)const;
bulk_item* clone()const };
//批量購買折扣策略:小於一定數量才給折扣,大於的部分照原價處理
class lds_item:public disc_item
double net_price(std::size_t cnt)const
lds_item* clone()const
};
double bulk_item::net_price(std::size_t cnt)const
其中基類是不打折的。基類的直接派生類增加了兩個成員,分別是購買多少書才會打折的數量(或者是超過多少以後就不打折了的數量,這取決於它的派生類),以及折扣幅度。我們把這個類定義為了虛基類。通過將它的net_price定義為純虛函式來完成。定義為虛基類的目的是因為這個類並沒有實際的意義,我們不想建立它的物件,而它的派生類,則具體定義了兩種不同的打折策略。在基類和派生類中,都定義了clone函式來返回乙個自身的副本,在控制代碼類初始化時,會用得到它們。這裡有一點需要注意:一般情況下,虛函式在繼承體系中的宣告應該是相同的,但是有一種例外情況:基類中的虛函式返回的是指向某一基類(並不一定是這個基類)的指標或者引用,那麼派生類中的虛函式可以返回基類虛函式返回的那個基類的派生類(或者是它的指標或者引用)。
然後,我們定義乙個控制代碼類裡管理這個繼承層次中的基類或者派生類物件:
class sales_item
//接受item_base物件的建構函式
sales_item(const item_base &item):p(item.clone()),use(new std::size_t(1)){}
//複製控制函式:管理計數器和指標
sales_item(const sales_item &i):p(i.p),use(i.use)
//析構函式
~sales_item()
//賦值操作符宣告
sales_item& operator=(const sales_item&);
//過載成員訪問操作符
const item_base *operator->()const
//過載解引操符
const item_base &operator*()const
private:
//指向基類的指標,也可以用來指向派生類
item_base *p;
//指向引用計數
std::size_t *use;
//析構函式呼叫這個函式,用來刪除指標
void decr_use() }
};
sales_item& sales_item::operator=(const sales_item &rhs)
控制代碼類有兩個資料成員,分別是指向引用計數的指標和指向基類(或者是其派生類的指標)。還過載了解引操作符以及箭頭操作符用來訪問繼承層次中的物件。它的建構函式有3個:第乙個是預設建構函式,建立乙個引用計數為1,指標為空的物件;第三個是複製建構函式,讓指標指向實參指標所指向的物件,且引用計數+1;第二個建構函式的形參是乙個基類的物件的引用,但是實參有可能是基類物件也可能是派生類物件,怎麼確定呢?這裡通過基類和派生類中clone函式來確定:函式返回的是什麼型別,就是什麼型別。
有了前面的鋪墊,我們就可以編寫真正的購物車類了:
//關聯容器的物件必須定義《操作
inline bool compare(const sales_item &lhs,const sales_item &rhs)
class basket
//定義的操作:
//為容器新增乙個物件
void add_item(const sales_item &item)
//返回購物籃中返回isbn的記錄數
size_type size(const sales_item &i)const
//返回購物籃中所有物品的**
double total()const;
private:
//關聯容器來儲存每一筆交易,通過指向函式的指標comp指明容器元素的比較
std::multisetitems;
};
double basket::total()const
return sum;
}
購物車是使用multiset實現的,這意味著,相同isbn的書籍是連續存放的。
對於關聯容器,必須支援《操作,但是定義《操作並不好,因為我們的《是通過isbn序號判斷的,而「==」,也改用isbn判斷;可是按常理,只有isbn,**,折扣生效數目,以及折扣率都相等時,才能算作相等,所以這樣做很容易誤導類的使用者。這裡採取的辦法是定義乙個比較函式compare,把它定義成內聯函式,因為每次向容器插入元素時,都要用到它。而將這個比較函式與容器關聯起來的過程非常的「鬆散」,或者說,耦合度很低:
multisetitems;意味著我們建立乙個名為items的關聯容器,容器的型別是sales_item的。而且容器通過comp指向的函式來判斷容器元素的大小。這意味著,在容器的建構函式中,通過將指向函式的指標初始化給不同的函式,就能實現不同的判斷操作。
這個類定義了3個函式,分別用來向購物車中增加新的書籍以及返回某個isbn書的數量以及計算總的**。其中total函式值得仔細說明一下:
首先是迴圈的遍歷並不是使用iter++來完成的,而是使用iter = items.upper_bound(*iter)。對於multiset,upper_bound返回的是指向某乙個鍵的最後乙個元素的下乙個位置,這樣就可以一次處理同一本書。當然,這裡的有乙個前提,就是對於同一本書,它的折扣策略、折扣率以及達到折扣所滿足的數量是一致的。
其次,迴圈體中的函式寫的非常簡潔:iter解引獲得的是sales_item物件,利用定義的箭頭操作符可以訪問基類或者派生類的net_price函式,這個函式的派生類版本需要乙個表明有多少本書才打折的實參,這個實參通過呼叫關聯容器的count呼叫獲得。
理解控制代碼類
在 類的幫助下,我們已經可以實現在乙個容器裡儲存乙個類層次裡所有型別的物件,但是 有乙個很明顯的缺點,就是需要複製物件,當乙個物件非常大或者是一種不能輕易複製的資源的時候,這個實現遇到了很大的困難,於是我們有了控制代碼 handle 類這個技術。我們有這麼乙個類 class point point ...
Python類與繼承
2.1 類定義 class classname 1 2.2 類物件 類物件建立後,類命名空間中所有的命名都是有效屬性名。類物件有兩種操作 屬性引用 obj.name 和例項化 類定義了init 方法的話,類的例項化操作會自動呼叫init 方法。init 方法可以有引數,引數通過init 傳遞到類的例...
類與繼承1 2
2 宣告乙個時間類,時間類中有3個私有資料成員 hour,minute,second 和 兩個公有成員函式 settime和printtime settime根據傳遞的3個引數為物件設定時間 printtime負責將物件表示的時間顯示輸出,輸出格式為 hour minute second 1 在主函...