类编程实验
類編程實驗
- 基本知識
- 基類、派生類和繼承
- 虛函數與動態綁定
- 抽象基類與純虛函數
- 派生類的構造函數
- 指向基類或派生類的指針的轉換
- 關于示例程序
- 示例代碼
- Quote.h
- Quote.cpp
- Basket.h
- Basket.cpp
- Basket_main.cpp
- 運行結果
本文是本人大一期間在校學習C++課程時所撰寫的實驗報告的摘錄,由于剛上大學,剛接觸計算機編程方面的相關知識,故可能會有很多不足甚至錯誤的地方,還請各位看官指出及糾正。
本文所涉及到的“教材”指:
電子工業出版社《C++ Primary中文版(第5版)》
如需轉載或引用請標明出處。
基本知識
基類、派生類和繼承
類的概念體現了面向對象程序設計的其中一個核心思想——數據抽象。通過類的使用,我們可以系統、模塊化地解決具體問題,做到化繁為簡。而當我們的程序中存在著一些相互關聯但是有細微差別的概念時,這時候就要學會應用面向對象程序設計的另一個核心思想——繼承。基類和派生類就是繼承的體現。
通過繼承聯系在一起的類構成一種層次關系。通常在層次關系的根部有一個基類,其他類則直接或間接地從基類繼承而來,這些繼承得到的類稱為派生類。基類負責定義在層次關系中所有類共同擁有的成員,而每個派生類定義各自特有的成員。
假設有如下的類定義:
class Quote { public:string isbn() const;virtual double net_price(size_t n) const; protected: };其中,基類的訪問運算符除了private和public之外,還有一個protected,這種成員可以做到被派生類訪問,但其他用戶無權訪問。此外,基類的private成員也不能被它的派生類訪問。我們可以以上面定義的Quote為基類,定義它的一個或多個派生類。派生類必須通過使用類派生列表明確指明它是從哪個基或哪些類繼承而來。派生類列表的形式是:首先是一個冒號,后面緊跟以逗號分隔的基類列表,其中每個基類前面可以有訪問說明符。例如:
class Bulk_quote : public Quote { public:double net_price(size_t n) const override; };其中,因為Bulk_quote在它的派生列表中使用了public關鍵字,因此我們完全可以把Bulk_quote的對象當成Quote對象來使用。另外,如果我們想將某個類用作基類,則該類必須已經定義而非僅僅聲明。
虛函數與動態綁定
在上面定義的Quote類中,成員函數net_price的聲明前有一個virtual關鍵字,這說明該函數是一個虛函數。基類中的虛函數能被派生類各自重新定義,以此變成適合自身的版本。在派生類中重新定義基類的虛函數的行為被稱為覆蓋。當我們在派生類中覆蓋了某個虛函數時,覆蓋后的函數仍然是虛函數,可以再一次使用virtual關鍵字指出該性質,但這種再次聲明不是必須的。一個派生類的函數如果覆蓋了某個繼承而來的虛函數,則它的形參類型和數量及返回類型必須一致。
在覆蓋了某個基類的虛函數后,可以在其后加上override或final關鍵字,說明該函數的性質。前者顯式地說明了該函數是繼承而來,而后者則額外規定了該函數不能再被別的派生類重新定義——即覆蓋。
通過虛函數配合動態綁定,我們能用同一段代碼分別處理Quote和Bulk_quote的對象。例如,我們聲明如下函數:
double print_total(ostream &os, const Quote &items, size_t n) {double ret = items.net_price(n);return ret; }再進行兩次調用:
Quote basic; Bulk_quote bulk; print_total(cout, basic, 20); print_total(cout, bulk, 20);第一條語句將Quote對象傳入print_total,因此當print_total調用net_price時,執行的是Quote的版本;在第二條調用語句中,實參的類型是Bulk_quote,因此執行的是Bulk_quote版本的net_price。
這就實現了不同有微小差異的對象的統一操作。
抽象基類與純虛函數
當我們想聲明一個在當前基類中沒有實際意義、無須定義,只是用于給其他派生類覆蓋形成各自版本的這么一個函數時,我們可以通過在該函數聲明的分號之前加上“=0”即可。例如:
double net_price(std::size_t) const = 0;這種函數稱為純虛函數,擁有純虛函數的基類成為抽象基類。其他派生類必須給出該函數屬于自己的定義,否則它們仍是抽象函數。
另外,基類通常都應定義一個虛析構函數,即使該函數不執行任何實際操作也是如此。例子見代碼注釋部分。
派生類的構造函數
和其他創建基類對象的代碼一樣,派生類必須使用基類的構造函數來初始化它的基類部分。派生類對象的基類部分與派生類對象自己的成員都是在構造函數的初始化階段執行初始化操作的。類似于我們初始化成員的過程,派生類構造函數同樣是通過構造函數初始化列表來將實參傳遞給基類構造函數的。例如假設Quote有以下帶有參數的構造函數聲明,且min_qty與discount是Bulk_quote類自己的成員:
Quote(const std::string &book, double price);則Bulk_quote的帶有參數的構造函數應該如以下形式:
Bulk_quote(const std::string& book, double price, std::size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) { }該函數將它的前兩個參數傳遞給Quote的構造函數,讓它負責初始化Bulk_quote的基類部分,剩下的參數將初始化Bulk_quote自己獨有的成員。
指向基類或派生類的指針的轉換
在繼承關系中,我們可以將一個基類的指針或引用綁定到派生類對象上,這意味著,當使用基類的引用或指針時,實際上我們并不清楚該引用(或指針)所綁定的對象的真實類型。該對象可能時基類的對象,也可能是派生類的對象。
之所以存在派生類向基類的轉換,是因為每個派生類對象都包含一個基類部分,而基類的引用或指著可以綁定到該基類部分上。反過來,因為一個基類的對象可能是派生類對象的一部分,也可能不是,所以不存在從基類向派生類的自動類型轉換。此外,派生類向基類的自動轉換只對指針或引用類型有效,在類型對象之間不存在這樣的轉換。
關于示例程序
該程序演示了類與派生類的定義及使用方法。其中Quote類表示某書籍的銷售原價,其定義了名為Disc_quote、Bulk_quote和Lim_quote的幾個派生類。Disc_quote是另外兩個派生類的抽象接口,本身不能表示任何折扣策略;Bulk_quote表示的是購買超過一定數量書籍之后擁有相應折扣的報價;Lim_quote表示的是只有一定數量的書籍擁有折扣的報價。每個類都定義了自己折扣策略,但由于是繼承于同一個基類的派生類,故可以很方便地進行統一操作。
此外,還定義了一個Basket類起到相當于“購物車”的作用,用于保存購買的一種或多種不同折扣策略的書籍,并進行總價計算等操作。
具體各種操作詳見代碼注釋部分。
示例代碼
Quote.h
#ifndef QUOTE_H #define QUOTE_H#include <memory> #include <iostream> #include <string> #include <cstddef>//以原價售出商品,它的派生類會定義各種折扣策略 class Quote { friend std::istream& operator>>(std::istream&, Quote&); //友元聲明:重載的輸入運算符 friend std::ostream& operator<<(std::ostream&, const Quote&); //友元聲明:重載的輸出運算符 public:Quote(): price(0.0) { } //默認構造函數:初始化成員price為0.0//構造函數:接受一個string和double分別初始化成員bookNo、priceQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { }//如果一個指向派生類的基類指針被刪除了,則需要虛析構函數virtual ~Quote() { } //該虛構函數會進行動態綁定std::string isbn() const { return bookNo; } //成員函數:返回書號//根據購買的數量返回相應的總價格,且派生類會根據數量自動使用不同的折扣策略virtual double net_price(std::size_t n) const { return n * price; }//返回一個動態分配內存的自身副本的虛函數virtual Quote* clone() const { return new Quote(*this); } private: //private的成員不會被繼承 std::string bookNo; //指示書的編號 protected: //protected訪問運算符:只允許自己、友元和派生類訪問double price; //指示原價 };//用來保存折扣和折扣要求數量的抽象基類,而派生類將會使用這些數據實現不同的折扣策略 class Disc_quote : public Quote { public://其他成員和基類型Quote一樣//默認構造函數:初始化這兩個成員為0和0.0Disc_quote() : quantity(0), discount(0.0) { }//構造函數:接受一個string、double、size_t、double來初始化書號、原價、折扣要求數量及折扣Disc_quote(const std::string& book, double price, std::size_t qty, double disc) :Quote(book, price), quantity(qty), discount(disc) { }//因為該類自己用不到該函數,故說明為純虛函數double net_price(std::size_t) const = 0;//返回一個以折扣要求數量和折扣組成的pair,即返回一個折扣策略std::pair<size_t, double> discount_policy() const { return std::make_pair(quantity, discount); } protected: std::size_t quantity; //指示折扣要求數量:使用折扣時所需達到的購買數量double discount; //指示折扣:折扣率 };//當購買數量達到一定程度,就價格就會擁有折扣,即將原價乘上一個小數來降低價格 class Bulk_quote : public Disc_quote //Bulk_quote也從Quote繼承 { public://默認構造函數Bulk_quote() { }//構造函數:調用接受參數的Disc_quote的構造函數Bulk_quote(const std::string& book, double p, std::size_t qty, double disc) :Disc_quote(book, p, qty, disc) { }//覆蓋基礎版本以實施對應的折扣策略double net_price(std::size_t) const;//返回一個動態分配內存的自身副本的虛函數Bulk_quote* clone() const {return new Bulk_quote(*this);} };//僅對于指定數量的副本給予折扣,額外副本以原價出售 class Lim_quote : public Disc_quote { public://構造函數:調用接受參數的Disc_quote的構造函數,其中各個參數有默認實參Lim_quote(const std::string& book = "", double sales_price = 0.0, std::size_t qty = 0, double disc_rate = 0.0) :Disc_quote(book, sales_price, qty, disc_rate) { }//覆蓋基礎版本以實施對應的折扣策略double net_price(std::size_t) const;//返回一個動態分配內存的自身副本的虛函數Lim_quote* clone() const { return new Lim_quote(*this); } };//函數聲明:計算并打印銷售給定數量的某種書籍所得的費用 double print_total(std::ostream &, const Quote&, std::size_t);#endifQuote.cpp
#include "Quote.h" #include <algorithm> #include <cstddef> #include <iostream>using namespace std; //計算并打印銷售給定數量的某種書籍所得的,應用了相應折扣策略的總費用 double print_total(ostream &os, const Quote &item, size_t n) {//根據綁定到參數item的對象的類型,調用Quote::net_price或Bulk_quote::net_pricedouble ret = item.net_price(n); //ret指示總價格//item.isbn()調用的是Quote的成員函數isbnos << "ISBN: " << item.isbn() << " # sold: " << n << " total due: " << ret << endl;return ret; //返回總價格 }//如果購買數量達到了一定程度,則使用相應的折扣策略 double Bulk_quote::net_price(size_t cnt) const {if (cnt >= quantity) //如果購買數量達到了一定程度return cnt * (1 - discount) * price; //則打折elsereturn cnt * price; //否則以原價出售 }//對指定數量的商品打折,剩下的以原價出售 double Lim_quote::net_price(size_t cnt) const {//cnt指示購買的數量,quantity指示能打折的商品的最大數量size_t discounted = min(cnt, quantity); //找出兩者的最小值size_t undiscounted = cnt - discounted; //算出沒有折扣的商品數return discounted * (1 - discount) * price + undiscounted * price; //計算并返回總價 }Basket.h
#ifndef BASKET_H #define BASKET_H#include <iostream> #include <string> #include <set> #include <map> #include <utility> #include <cstddef> #include <stdexcept> #include <memory> #include "Quote.h"//Basket類用于保存正在購買的商品,且使用合成的拷貝控制成員 class Basket { public:Basket(): items(compare) { } //構造函數:使用成員函數compare對其中的元素進行排序void add_item(const std::shared_ptr<Quote> &sale) { items.insert(sale); } //使用指針向items中加入商品void add_item(const Quote& sale) { items.insert(std::shared_ptr<Quote>(sale.clone())); } //對給定的商品進行復制double total_receipt(std::ostream&) const; //輸出每種書的總價以及所有商品的總價void display (std::ostream&) const; //用于程序的debug private://items成員將會使用這個函數去比較指向對應Quote類對象的shared_ptrstatic bool compare(const std::shared_ptr<Quote> &lhs, const std::shared_ptr<Quote> &rhs){ return lhs->isbn() < rhs->isbn(); } //當lhs的書號“小于”rhs的書號時返回1typedef bool(*Comp)(const std::shared_ptr<Quote> &, const std::shared_ptr<Quote> &);//multiset類型的成員items保存多個Quote,使用成員函數compare排序std::multiset<std::shared_ptr<Quote>, Comp> items; }; #endifBasket.cpp
#include "Quote.h" #include "Basket.h" #include <cstddef> #include <set> #include <string> #include <iostream> #include <memory>using namespace std;//用于程序的debug:輸出當前Basket中的所有商品 void Basket::display(ostream &os) const {os << "Basket size: " << items.size() << endl;//輸出每種書的書號、購買數量及對應的價格。函數upper_bound將返回下一書號不同的書for (auto next_item = items.begin(); next_item != items.end(); next_item = items.upper_bound(*next_item)){//我們知道在當前的Basket中至少有一個元素os << (*next_item)->isbn() << " occurs " << items.count(*next_item) << " times" << " for a price of " << (*next_item)->net_price(items.count(*next_item)) << endl; } }double Basket::total_receipt(ostream &os) const {double sum = 0.0; //保存實時計算出的總價格//iter指向書號相同的一批元素中的第一個//upper_bound返回一個指向這批元素尾后位置的迭代器for (auto iter = items.begin(); iter != items.end(); iter = items.upper_bound(*iter)){//我們知道在當前的Basket中至少有一個該關鍵字的元素//打印該書籍對應的項目sum += print_total(os, **iter, items.count(*iter));} os << "Total Sale: " << sum << endl; //打印最終的總價格return sum; }Basket_main.cpp
#include <memory> #include "Basket.h" #include <iostream> using namespace std;int main(void) {Basket sale; //展示基類與不同派生類的區別及用法sale.add_item(shared_ptr<Quote>(new Quote("123", 45)));sale.add_item(shared_ptr<Quote>(new Quote("123", 45)));sale.add_item(shared_ptr<Quote>(new Quote("123", 45)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("345", 45, 3, .15)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25))); sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25)));sale.add_item(shared_ptr<Quote>(new Bulk_quote("678", 55, 5, .25)));sale.add_item(shared_ptr<Quote>(new Lim_quote("abc", 35, 2, .10)));sale.add_item(shared_ptr<Quote>(new Lim_quote("abc", 35, 2, .10)));sale.add_item(shared_ptr<Quote>(new Lim_quote("abc", 35, 2, .10)));sale.add_item(shared_ptr<Quote>(new Lim_quote("abc", 35, 2, .10)));sale.add_item(shared_ptr<Quote>(new Lim_quote("abc", 35, 2, .10)));sale.add_item(shared_ptr<Quote>(new Lim_quote("abc", 35, 2, .10)));sale.add_item(shared_ptr<Quote>(new Quote("def", 35)));sale.add_item(shared_ptr<Quote>(new Quote("def", 35)));sale.total_receipt(cout);Basket bsk; //同上,參數包括書號、原價、折扣要求數量及折扣bsk.add_item(shared_ptr<Quote>(new Bulk_quote("0-201-82470-1", 50, 5, .19)));bsk.add_item(shared_ptr<Quote>(new Bulk_quote("0-201-82470-1", 50, 5, .19)));bsk.add_item(shared_ptr<Quote>(new Bulk_quote("0-201-82470-1", 50, 5, .19)));bsk.add_item(shared_ptr<Quote>(new Bulk_quote("0-201-82470-1", 50, 5, .19)));bsk.add_item(shared_ptr<Quote>(new Bulk_quote("0-201-82470-1", 50, 5, .19)));bsk.add_item(shared_ptr<Quote>(new Lim_quote("0-201-54848-8", 35, 2, .10))); bsk.add_item(shared_ptr<Quote>(new Lim_quote("0-201-54848-8", 35, 2, .10)));bsk.add_item(shared_ptr<Quote>(new Lim_quote("0-201-54848-8", 35, 2, .10)));bsk.total_receipt(cout);system("pause");return 0; }運行結果
總結
- 上一篇: 类模板编程实验
- 下一篇: 拷贝构造函数编程实验