熟悉C++的继承与多态 并完成自己定义的购物车
C++提供了比修改代码更好的方法来扩展和修改类。这种方法叫作类继承,它能够从已有的类派生出新的类,而派生类继承了原有类(称为基类)的特征,包括方法。
正如继承一笔财产要比自己白手起家容易一样,通过继承派生出的类通常比设计新类要容易得多。下面是可以通过继承完成的一些工作。
当一个类派生出另一个类,原始类称为基类,继承类成为派生类。
派生类对象将具有以下特征:
需要在继承属性中添加:
1、公用继承(public inheritance)
基类的公有成员和受保护成员在派生类中保持原有的访问属性,其私有成员仍为基类私有(基类的封装性)。
公有继承是一种is a关系,即派生类对象也是一个基类对象,可以对基类对象执行任何操作,也可以对派生类对象执行。
2、 私有继承(private inheritance)
基类的公有成员和受保护成员在派生类中成为了私有成员。其私有成员仍为基类私有。
3、受保护的继承(protected inheritance)
基类的公有成员和受保护成员在派生类中成为了受保护成员。其私有成员仍为基类私有。
受保护成员:不能被外界引用,但可以被派生类的成员引用。
派生类不能直接访问基类的私有成员,必须通过基类的方法进行访问。故派生类的构造函数必须使用基类的狗仔函数。
在创建派生类对象之前,程序首先先创造基类对象。
如果不显式的调用基类的构造函数(使用初始化器列表来指名要使用的基类构造函数),则默认使用基类的构造函数:BandAcount()
//写法一(写一种就好)
Bulk_item(const string &strName,double dPrice,int minNum,double dDis):Item_Base(strName,dPrice),m_nMinNum(minNum),m_dDis(dDis){}
//使用成员初始化列表 只有参数名而不包含参数类型=>这些参数是实参而不是形参
//写法二(写一种就好)
Bulk_item(const string &strName,double dPrice,int minNum,double dDis):Item_Base(strName,dPrice){
//函数体内部只对派生类新增的数据成员初始化
m_nMinNum=minNum;
m_dDis=dDis;
}
//写法一(写一种就好)
Bulk_item(const Bulk_item & in){
*this=in;
}
//写法二(写一种就好)
Bulk_item(const Bulk_item & in){
m_nMinNum=in.m_nMinNum;
m_dDis=in.m_dDis;
}
一个类不仅可以派生出一个新的类,派生类还可以继续派生,形成派生的层次结构。
释放对象的顺序与创建对象的顺序相反
对象过期时,程序将首先使用派生类析构函数,然后再(自动的)调用基类析构函数。
在派生时,派生类不能继承基类的析构函数,需要通过自己的析构函数去调用基类的析构函数。
(1)两个基类有同名成员
用基类限定名来限定
使用作用域解析运算符(::)来标识函数所属的类
(2)两个基类和派生类三者都有同名函数
基类的同名成员在派生类中被屏蔽(不可见),即派生类新增加的同名函数覆盖了基类中的同名成员。相当于重载。
注意:函数名与特征标相同时才为函数重载
特征标——函数的参数列表(特征标相同==参数数目和类型相同+参数的排列顺序相同(与变量名无关))
(3)如果类A和类B是从同一个基类派生的
通过类N的直接派生类名来指出要访问的时类N的哪一个派生类的基类成员。
(1)赋值
赋值兼容:不同类型数据之间的自动转换和赋值。
只能用派生类对象对基类对象赋值 ,不能用基类对象对派生类对象赋值 ,因为基类对象不包含派生类成员。同理,同一基类的不同派生类对象之间也不能赋值。
(2)派生类对象可以代替基类对象对基类的应用进行赋值和初始化
派生类对象可以代替基类对象对基类的应用进行赋值和初始化
如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象
(2)指针
指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。
可以使用static_cast
强制编译器进行转换。或者,可以用dynamic_cast
申请在运行时进行检查。
类的组合:在一个类中以另一个类的对象作为数据成员。
继承是”是“的关系,是纵向的,派生类是基类。
组合是”有“的关系,是横向的。
C++提供虚基类方法,在继承间接共同基类时只保留一份成员。
虚基类不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。
当派生类通过多条派生路径继承虚基类时,基类成员只保留一次(虚基类在派生类中只被继承一次)
注意:应该在该基类的所有派生类中直接声明为虚基类,否则仍会出现多次继承。
多态性(polymorphism):
向不同的对象发送同一条信息(调用函数),不同的对象在接收时回产生不同的行为、方法(执行不同的函数)
例如函数的重载、运算符重载都是多态。
静态多态性(编译时的多态):在程序编译时就知道调用函数的全部信息,系统就能决定要调用哪个函数。通过函数重载实现。
动态多态性(运行时的多态):在程序运行过程中动态地确定操作所针对的对象,通过虚函数(virtual function)实现。
C++编译器 对非虚方法使用静态联编(static binding) 对虚方法使用动态联编(dynamic binding)
虚函数:在基类声明函数时虚拟的,并不是实际存在的函数,然后再派生类中才正式定义此函数。
作用:允许派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问派生类中的同名函数。
若需要在派生类中重新定义基类的方法,通常应该将基类方法声明为虚的
虚函数的使用方法是:
(1)在基类中用virtual声明成员函数为虚函数。在类外定义虚函数时,不必再加virtual。
(2)在派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类的需要重新定义函数体。
当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加 virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。
如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
(3)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
(4)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
通过虚函数与指向基类对象的指针变量的配合使用,就能实现动态的多态性。
如果想调用同一类族中不同类的同名函数,只要先使用基类指针指向该类对象即可。
若在基类中定义的非虚函数再派生类中被重新定义。
基类指针 =>对象中基类部分的成员函数
派生类指针 =>派生类对象中的成员函数
这并不是多态性行为(使用的是不同类型的指针),没有用到虚函数的功能。
我们希望:通过基类的指针删除派生类对象时,正确的析构函数被调用,即程序将首先使用派生类析构函数,然后再(自动的)调用基类析构函数
。
若基类的析构函数不为虚析构函数时,用基类指针指向派生类对象的时候,只会调用基类析构函数。
当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统都会采用动态关联,调用相应类的析构函数,对该对象进行清理工作。
通常,给基类提供一个虚析构函数(即使它不需要虚构函数)
(1)构造函数
构造函数不能是虚函数(因为在执行构造函数时类对象还未完成建立过程)
(2)析构函数
析构函数应该是虚函数,除非类不用做基类。
(3)友元函数
友元不能是虚函数,因为友元不是类成员,只有成员才能是虚函数。
(可以让友元函数是使用虚成员函数)
(4)没有重新定义
如果派生类没有重新定义函数,则使用该函数的基类版本。
(5)重新定义
第一,如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返回类型协变(covariance of return type),因为允许返回类型随类类型的变化而变化:这种例外只适用于返回值,而不适用于参数。
第二,如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。
纯虚函数(pure virtual function)
抽象基类(abstract base class,ABC)
目的:用它作为基类去建立派生类。
包含纯虚函数的类都是抽象类(因为纯虚函数是不能被调用的,包含虚函数的类是无法建立对象的)
#ifndef SY
#define SY
#include
using namespace std;
class Bulk_item;
//不使用折扣策略的基类 Item_Base
class Item_Base{
private:
protected:
string m_strName; //商品名
double m_dPrice; //价格
public:
Item_Base(const string &strName="",double dPrice=10.0):m_strName(strName),m_dPrice(dPrice){}
Item_Base(const Item_Base & ib){
m_strName=ib.m_strName;
m_dPrice=ib.m_dPrice;
}
//返回商品名
string book() const{
return m_strName;
}
//基类不须要折扣策略
virtual double net_price(int n) const{
return n*m_dPrice;
}
virtual Item_Base *Clone() const{
return new Item_Base(*this);
}
//析构函数
virtual ~Item_Base(){}
//赋值操作符声明
Item_Base & operator=(const Item_Base &ib){
m_strName=ib.m_strName;
m_dPrice=ib.m_dPrice;
}
friend ostream & operator<<(ostream &out,const Item_Base &s){
out<<s.m_strName<<"\t"<<s.m_dPrice;
return out;
}
};
//批量购买折扣策略:大于一定的数量才有折扣
//派生类 Bulk_item (公有继承)
class Bulk_item:public Item_Base{
protected:
int m_nMinNum; //实现折扣策略的购买量
double m_dDis; //折扣率
public:
//构造函数
Bulk_item(const string &strName,double dPrice,int minNum,double dDis):Item_Base(strName,dPrice),m_nMinNum(minNum),m_dDis(dDis){}
Bulk_item(const Bulk_item & in){
m_nMinNum=in.m_nMinNum;
m_dDis=in.m_dDis;
*this=in;
}
virtual double net_price(int n) const{
return n>=m_nMinNum?n*m_dPrice*m_dDis:n*m_dPrice;
}
virtual Bulk_item *Clone() const{
return new Bulk_item(*this);
}
//析构函数
~Bulk_item(){
delete []this;
m_nMinNum=0;
m_dDis=0;
}
friend ostream & operator<<(ostream &out,const Bulk_item &s){
out<<s.m_strName<<"\t"<<s.m_dPrice<<"\t"<<s.m_nMinNum<<"\t"<<s.m_dDis;
return out;
}
};
#endif //SY2
#include
#include
#include
#include "sy2.h"
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
double total(Item_Base &ib){
cout<<"total="<<ib. net_price(10)<<endl;
}
void printT(Item_Base &bi){ // 形参是 Item_Base类对象的引用
//由于派生类对象与基类对象赋值兼容。派生类对象可以自动转换类型
cout<<"printT "<<bi.net_price(10)<<endl;
}
int main() {
cout<<"--------------------------------------------------"<<endl;
Item_Base base("《C++》",10); //定义 Item_Base类对象 base
Item_Base base1("《C++》",10); //定义 Item_Base类对象 base1
Bulk_item b1("《C++》",10,10,0.8); //定义 Bulk_item类对象 b1
cout<<base<<endl;
cout<<b1<<endl;
Item_Base base2(base); //定义 Item_Base类对象 base2
Bulk_item b2(b1); //定义 Bulk_item类对象 b2
cout<<base2<<endl;
cout<<b2<<endl;
cout<<base.book()<<"base.net_price(10):"<<base.net_price(10)<<endl;
cout<<b1.book()<<"bi.net_price(10):"<<b1.net_price(10)<<endl;
cout<<"--------------------------------------------------"<<endl;
Item_Base *pBase1=&base; //定义指向 Item_Base类对象的指针 pBase1,指向base
cout<<"pBase1->net_price(10):"<<pBase1->net_price(10)<<endl;
pBase1=&b1; //指针 pBase1,指向 Bulk_item类对象 b1
cout<<"函数重载 (不是多态) :"<<endl;
cout<<"*pBase1:"<<(*pBase1)<<endl; //运算符<<重载(不是多态
cout<<"b1:"<<b1<<endl;
cout<<"虚函数(多态): "<<endl;
cout<<"pBase1->net_price(10):"<<pBase1->net_price(10)<<endl;//net_price() 多态
cout<<"b1.net_price(10):"<<b1.net_price(10)<<endl;
Item_Base *pBase2=pBase1->Clone(); //定义指向 Item_Base类对象的指针 pBase2,指向 pBase1->Clone()
cout<<"*pBase2:"<<(*pBase2)<<endl;
Item_Base *pBase3=pBase1->Clone(); //定义指向 Item_Base类对象的指针 pBase3,指向 pBase1->Clone() 多态
cout<<"*pBase3:"<<pBase3->net_price(10)<<endl;
Item_Base *pBase4=b1.Clone(); //定义指向 Item_Base类对象的指针 pBase3,指向 b1.Clone()
cout<<"*pBase4:"<<pBase4->net_price(10)<<endl;
cout<<"--------------------------------------------------"<<endl;
//赋值转换
//b2=base2; //errorn 不能用基类对象对派生类对象赋值 (应为基类中不包含
base2=b2; //只能用派生类对象对基类对象赋值
cout<< base2.net_price(10)<<endl;
cout<< b2.net_price(10)<<endl;
//指针转换
Item_Base *pBase5=&base;
Bulk_item *pA=static_cast<Bulk_item*>(pBase5); //base
printT(*pA);
Bulk_item *pB=dynamic_cast<Bulk_item*>(pBase1);//b1
printT(*pB);
return 0;
}