类编程实验


本文是本人大一期间在校学习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、price
	Quote(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.0
	Disc_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);

#endif

Quote.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_price
	double ret = item.net_price(n);		//ret指示总价格
	//item.isbn()调用的是Quote的成员函数isbn
    os << "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;	//则打折
    else
        return 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_ptr
	static bool compare(const std::shared_ptr<Quote> &lhs, const std::shared_ptr<Quote> &rhs)
	{ return lhs->isbn() < rhs->isbn(); }	//当lhs的书号“小于”rhs的书号时返回1
	typedef bool(*Comp)(const std::shared_ptr<Quote> &, const std::shared_ptr<Quote> &);
	//multiset类型的成员items保存多个Quote,使用成员函数compare排序
    std::multiset<std::shared_ptr<Quote>, Comp> items; 
};
#endif

Basket.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;
}

运行结果

结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值