物件導向編程--控制代碼類與繼承[續]
三、控制代碼的使用
使用Sales_item對象可以更容易地編寫書店應用程式。代碼將不必管理Item_base對象的指標,但仍然可以獲得通過Sales_item對象進行的調用的虛行為。
1、比較兩個Sales_item對象
在編寫Function Compute銷售總數之前,需要定義比較Sales_item對象的方法。要用Sales_item作為關聯容器的關鍵字,必須能夠比較它們。關聯容器預設使用關鍵字類型的小於操作符,但是如果給Sales_item定義小於操作符,將使其含義不明;
幸好,關聯容器使我們能夠指定一個函數[或函數對象]用作比較函數。而且,在定義容器物件時必須提供比較函數。
定義一個函數用於比較Sales_item對象:
inline boolcompare(const Sales_item &lsh,const Sales_item &rhs){ return lsh -> book() < rhs -> book();}
該函數使用Sales_item的->操作符,該操作符返回Item_base對象的指標,哪個指標用於擷取並運行成員的book操作,該成員返回ISBN。
2、使用帶比較子的關聯容器
對於比較函數,它必須作為容器的一部分而儲存,任何在容器中增加或尋找元素的操作都要使用比較函數。
要有效地工作,關聯容器需要對每個操作使用同一比較函數。然而,期望使用者每次記住比較函數是不合理的,尤其是,沒有辦法檢查每個調用使用同一比較函數。因此,容器記住比較函數是有意義的。通過將比較子儲存在容器物件中,可以保證比較元素的每個操作將一致地進行。
為了儲存比較子,它需要知道比較子類型。形參類型也不需要與 key_type完全符合,應該允許可以轉換為key_type的任意形參類型。
所以,要使用Sales_item的比較函數,在定義multiset時必須指定比較子類型。在我們的例子中,比較子類型是接受兩個constSales_item 引用並返回bool值的函數。
typedef bool (*Comp)(const Sales_item &,const Sales_item &);
將Comp定義為函數類型指標的同義字,該函數類型與我們希望用來比較 Sales_item對象的比較函數相匹配。
接著需要定義multiset,儲存Sales_item類型的對象並在它的比較函數中使用這個Comp類型。關聯容器的每個建構函式使我們能夠提供比較函數的名字。
可以這樣定義使用compare函數的空multiset:
std::multiset<Sales_item,Comp> items(compare);
這個定義是說,items是一個multiset,它儲存Sales_item對象並使用Comp類型的對象比較它們。multiset是空的—— 我們沒有提供任何元素,但我們的確提供了一個名為compare的比較函數。當在items中增加或尋找元素時,將用compare函數對multiset進行排序。
3、容器與控制代碼類
我們定義一個Basket類,用以跟蹤銷售並計算購買價格:
class Basket{ typedef bool (*Comp)(const Sales_item &,const Sales_item &);public: typedef multiset<Sales_item,Comp> set_type; typedef set_type::size_type size_type; typedef set_type::const_iterator const_iter; Basket():items(compare) {} void add_item(const Sales_item &item) { items.insert(item); } size_type size(const Sales_item &i) const { return items.count(i); } double total() const;private: multiset<Sales_item,Comp> items;};
該類定義了一個建構函式,即Basket預設建構函式。該類需要自己的預設建構函式,以便將compare傳遞給建立items成員的multiset建構函式。
4、使用控制代碼執行虛函數
Basket類中的total函數用於返回購物籃中所有物品的價格:
double Basket::total() const{ double sum = 0.0; for (set_type::iterator iter = items.begin(); iter != items.end(); iter = items.upper_bound(*iter)) { sum += (*iter) -> net_price(items.count(*iter)); } return sum;}
for迴圈中的“增量”運算式很有意思。與讀每個元素的一般迴圈不同,我們推進iter指向下一個鍵。調用upper_bound函數以跳過與當前鍵匹配的所有元素,upper_bound函數的調用返回一個迭代器,該迭代器指向與iter鍵相同的最後一個元素的下一元素,即,該迭代器指向集合的末尾或下一本書。測試iter的新值,如果與items.end()相等,則跳出for迴圈,否則,就處理下一本書。
我們使用multiset的 count成員確定multiset中的多少成員具有相同的鍵(即,相同的isbn),並且使用該數目作為實參調用net_price函數。
for迴圈的迴圈體調用net_price函數,閱讀這個調用需要一點技巧:
sum += (*iter) -> net_price(items.count(*iter));
對iter解引用獲得基礎Sales_item對象,對該對象應用Sales_item類重載的箭頭操作符,該操作符返回控制代碼所關聯的基礎Item_base對象的指標,用該Item_base對象指標調用net_price函數,傳遞具有相同isbn的圖書的count作為實參。net_price是虛函數,所以調用的定價函數的版本取決於基礎Item_base對象的類型。
//P511 習題15.35//1 in item.h#ifndef ITEM_H_INCLUDED#define ITEM_H_INCLUDED#include <string>#include <utility>class Item_base{public: Item_base(const std::string &book = "", double sales_price = 0.0): isbn(book),price(sales_price) {} std::string book() const { return isbn; } virtual double net_price(std::size_t n) const { return price * n; } virtual Item_base *clone() const { return new Item_base(*this); } virtual ~Item_base() {}private: std::string isbn;protected: double price;};class Disc_item : public Item_base{public: Disc_item(const std::string &book = "", double sales_price = 0.0, std::size_t qty = 0, double disc_rate = 0.0): Item_base(book,sales_price),quantity(qty),discount(disc_rate) {} virtual double net_price(std::size_t ) const = 0; std::pair<std::size_t,double> discount_policy() const { return std::make_pair(quantity,discount); }protected: std::size_t quantity; double discount;};class Bulk_item : public Disc_item{public: Bulk_item(const std::string &book = "", double sales_price = 0.0, std::size_t qty = 0, double disc_rate = 0.0): Disc_item(book,sales_price,qty,disc_rate) {} virtual double net_price(std::size_t n) const { if (n >= quantity) { return n * (1 - discount) * price; } else { return n * price; } } virtual Bulk_item *clone() const { return new Bulk_item(*this); }};class Lds_item : public Disc_item{public: Lds_item(const std::string &book = "", double sales_price = 0.0, std::size_t qty = 0, double disc_rate = 0.0): Disc_item(book,sales_price,qty,disc_rate) {} virtual double net_price(std::size_t n) const { if (n <= quantity) { return n * (1 - discount) * price; } else { return n * price - quantity * discount * price; } } virtual Lds_item *clone() const { return new Lds_item(*this); }};#endif // ITEM_H_INCLUDED
//2 in sales_item.h#ifndef SALES_ITEM_H_INCLUDED#define SALES_ITEM_H_INCLUDED#include "item.h"#include <stdexcept>class Sales_item{public: Sales_item():p(0),use(new std::size_t(1)) {} Sales_item(const Item_base &rhs): p(rhs.clone()),use(new std::size_t(1)) {} Sales_item(const Sales_item &rhs):p(rhs.p),use(rhs.use) { ++ *use; } ~Sales_item() { decr_use(); } Sales_item &operator=(const Sales_item &rhs); const Item_base *operator->() const { if (p) { return p; } else { throw std::logic_error("unbound Sales_item"); } } const Item_base &operator*() const { if (p) { return *p; } else { throw std::logic_error("unbound Sales_item"); } }private: Item_base *p; std::size_t *use; void decr_use() { if (-- *use) { delete p; delete use; } }};#endif // SALES_ITEM_H_INCLUDED
//3 in sales_item.cpp#include "sales_item.h"Sales_item &Sales_item::operator=(const Sales_item &rhs){ ++ * rhs.use; decr_use(); p = rhs.p; use = rhs.use; return *this;}
//4 in basket.h#ifndef BASKET_H_INCLUDED#define BASKET_H_INCLUDED#include "sales_item.h"#include <set>inline boolcompare(const Sales_item &lhs,const Sales_item &rhs){ return lhs -> book() < rhs -> book();}class Basket{ typedef bool (*Comp)(const Sales_item &,const Sales_item &);public: typedef std::multiset<Sales_item,Comp> set_type; typedef set_type::size_type size_type; typedef set_type::const_iterator const_iter; Basket():items(compare){} void add_item(const Sales_item &item) { items.insert(item); } size_type size(const Sales_item &item) const { return items.count(item); } double total() const;private: std::multiset<Sales_item,Comp> items;};#endif // BASKET_H_INCLUDED
//5 in basket.cpp#include "basket.h"double Basket::total() const{ double sum = 0.0; for (set_type::iterator iter = items.begin(); iter != items.end(); iter = items.upper_bound(*iter)) { sum += (*iter) -> net_price(items.count(*iter)); } return sum;}
//6 in main.cpp#include <iostream>#include "item.h"#include "basket.h"using namespace std;int main(){ Basket basket; Sales_item item1(Bulk_item("7-115-14554-7",99,20,0.2)); Sales_item item2(Item_base("7-115-14554-7",39)); Sales_item item3(Lds_item("7-115-14554-7",50,200,0.2)); Sales_item item4(Bulk_item("7-115-14554-7",99,20,0.2)); basket.add_item(item1); basket.add_item(item2); basket.add_item(item3); basket.add_item(item4); cout << basket.total() << endl;}