练习15.1:什么是虚成员?
练习15.2:protected访问说明符与private有何区别?
练习15.3:定义你自己的Quote类和print_total函数。
class Quote
{
public:
Quote()=default;
Quote(const std::string &book,double sales_price):
bookNo(book),price(sales_price){}
std::string isbn() const {return bookNo;}
virtual double net_price(std::size_t n)const {return n*price;} //返回给定数量的书籍的销售总额,派生类改写并使用不同的折扣计算方法
virtual ~Quote()=default; //对析构函数进行动态绑定
private:
std::string bookNo; //书籍的ISBN编号
protected:
double price=0.0; //代表普通状态下不打折的价格
};
注:此类为书上定义的Quote类。
练习15.4:下面哪条声明语句是不正确的?请说明原因。
class Base { … };
(a) class Derived : public Derived { … };
(b) class Derived : private Base { … };
(c) class Derived : public Base;
(a)不正确,一个类本身不能从自己派生而来
(b)正确
(c)不正确,声明类的时候,不可以包含派生列表,只有在定义类的时候才派生列表。
练习15.5:定义你自己的Bulk_quote类
class Bulk_quote : public Quote
{
public:
double net_price( size_t cnt ) const override
{
if ( cnt >=min_qty )
return cnt * ( 1- discount ) * price;
else
return cnt * price;
}
private:
size_t min_qty;
double discount;
};
练习15.6:将Quote类和Bulk_quote的对象传给15.2.1节(第529页)练习中的print_total函数,检查该函数是否正确。
练习15.7:定义一个类使其实现一种数量受限的折扣策略,具体策略是:当购买书籍的数量不超过一个给定的限量时享受折扣,如果购买量一旦超过了限量,则超出的部分将以原价销售。
class Limited_quote : public Quote
{
public:
double net_price( size_t cnt ) const override
{
if ( cnt <=min_qty )
return cnt * ( 1- discount ) * price;
else
return min_qty*(1-discount)*price+(cnt-min_qty) * price;
}
private:
size_t min_qty;
double discount;
};
练习15.8:给出静态类型和动态类型的定义。
Quote *pQuote =new Bulk_quote;
指针pQuote的静态类型是Quote,在编译时就已经确定了。但是它的动态类型是Bulk_quote,直到运行时才能知道它指向的是基类还是派生类。
练习15.9:在什么情况下表达式的静态类型可能与动态类型不同?请给出三个静态类型与动态类型不同的例子。
Bulk_quote bulk;
Quote *pQuote=&bulk;
Quote &rQuote=bulk;
只知道两个,第三个不知道,o(╯□╰)o
练习15.10:回忆我们在8.1节(第279页)进行的讨论,解释第284页中将ifstream传递给Sales_data的read函数的程序是如何工作的。
练习15.11:为你的Quote类体系添加一个名为debug的虚函数,令其分别显示每个类的数据成员。
#include <iostream>
using namespace std;
class Quote
{
public:
Quote()=default;
Quote(const std::string &book,double sales_price):
bookNo(book),price(sales_price){}
std::string isbn() const {return bookNo;}
virtual double net_price(std::size_t n)const {return n*price;} //返回给定数量的书籍的销售总额,派生类改写并使用不同的折扣计算方法
virtual void debug()
{
cout<<"bookNo="<<bookNo<<" price="<<price<<endl;
}
virtual ~Quote()=default; //对析构函数进行动态绑定
private:
std::string bookNo; //书籍的ISBN编号
protected:
double price=0.0; //代表普通状态下不打折的价格
};
class Bulk_quote : public Quote
{
public:
Bulk_quote(const string &book="",double sales_price=0,size_t qty=0,double disc_rate=0):
Quote(book,sales_price),min_qty(qty),discount(disc_rate) { }
double net_price( size_t cnt ) const
{
if ( cnt > min_qty )
return cnt * ( 1- discount ) * price;
else
return cnt * price;
}
virtual void debug()
{
Quote::debug(); //bookNo变量为private,所以我们不能直接访问bookNo,只能调用基类的debug()函数来显示
cout<<"min_qty="<<min_qty<<" discount="<<discount<<endl;
}
private:
size_t min_qty;
double discount;
};
int main(int argc, const char * argv[])
{
Quote base("0-201-82470-1",50);
Bulk_quote derived("0-201-82470-1",50,5,0.19);
base.debug();
derived.debug();
return 0;
}
输出结果为:
bookNo=0-201-82470-1 price=50
bookNo=0-201-82470-1 price=50
min_qty=5 discount=0.19
练习15.12有必要将一个成员函数同时声明成override和final吗?为什么?
练习15.13给定下面的类,解释每个print函数的机理:
class base
{
public:
string name() { return basename; }
virtual void print( ostream &os ) { os << basename; }
private:
string basename;
};
class derived:public base
{
public:
virtual void print(ostream &os)
{
print(os);
os<<" "<<i;
}
private:
int i;
};
base中的print函数打印出自己的base name,而derived类则调用base类的print()函数打印出private变量base name之后,再打印一个空格和自己的private变量i。
#include <iostream>
using namespace std;
class base
{
public:
base(string name):basename(name){}
string name() { return basename; }
virtual void print( ostream &os ) { os << basename; }
private:
string basename;
};
class derived:public base
{
public:
derived(string name,int val):base(name),i(val){}
virtual void print(ostream &os)
{
base::print(os); //显示注明
os<<" "<<i;
}
private:
int i;
};
int main()
{
base b1("base");
derived d1("derived",5);
b1.print(cout);
d1.print(cout);
return 0;
}
运行结果如图所示:
basederived 5
练习15.14给定上一题中的类以及下面这些对象,说明在运行时调用哪个函数:
base bobj; base *bp1 = &bobj; base&br1 = bobj;
derived dobj; base *bp2 = &dobj; base&br2 = dobj;
(a) bobj.print();
(b) dobj.print();
(c) bp1->name();
(d) bb2->name();
(b) br1.print();
(f) br2.print();
(a) bobj.print(); 用的是基类的print函数
(b) dobj.print(); 用的是派生类的print函数
(c) bp1->name(); 用的是基类的name函数
(d) bp2->name(); 用的是基类的name函数
(b) br1.print(); 用的是基类的print函数
(f) br2.print(); 用的是派生类的print函数
练习15.15定义你自己的Disc_quote和Bulk_quote。
class Disc_quote:public Quote
{
public:
Disc_quote(const string &book="",double sales_price=0.0,size_t qty=0,double disc=0.0):Quote(book,sales_price),quantity(qty),discount(disc){}
double net_price(size_t cnt) const=0;
protected:
size_t quantity;
double discount;
};
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote(const string &book="",double sales_price=0,size_t qty=0,double disc_rate=0):
Disc_quote(book,sales_price,qty,disc_rate) { }
double net_price( size_t cnt ) const
{
if ( cnt > quantity )
return cnt * ( 1- discount ) * price;
else
return cnt * price;
}
};
练习15.16改写你在15.2.2节(第533页)练习中编写的数量受限的折扣策略,令其继承Disc_quote。
class Limited_quote : public Disc_quote
{
public:
Limited_quote(const string &book="",double sales_price=0.0,size_t qty=0,double disc_rate=0.0):
Disc_quote(book,sales_price,qty,disc_rate) { }
double net_price( size_t cnt ) const override
{
if ( cnt <=quantity )
return cnt * ( 1- discount ) * price;
else
return quantity*(1-discount)*price+(cnt-quantity) * price;
}
};
练习15.17:尝试定义一个Disc_quote的对象,看看编译器给出的错误信息是什么。
IDE为Xcode,给出的错误信息是:Variable type 'Disc_quote' is an abstract class.
练习15.18:假设给定了第543页和第544页的类,同时已知每个对象的类型如注释所示,判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许:
Base *p=new Base;
p=&d1; //d1的类型是Pub_Derv
p=&d2; //d2的类型是Priv_Derv
p=&d3; //d3的类型是Prot_Derv
p=&dd1; //dd1的类型是Derived_from_Public
p=&dd2; //dd2的类型是Derived_from_Private
p=&dd3; //dd3的类型是Derived_from_Protected
答:只有d1和dd1才能够赋值。这是因为:
练习15.19:假设第543页和544页的每个类都有如下形式的成员函数:
void memfcn(Base &b)
{
b=*this;
}
对于每个类,分别判断上面的函数是否合法。
void memfcn(Base &b)
{
b=*this;
}
Derived_from_Private: private Priv_Derv这个类的函数不合法。
原因如下:
1.无论派生类以什么方式继承基类,派生类的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和函数来说永远是可访问的。
2.如果派生类继承基类的方式是公有地或者受保护的,则派生类的成员和友元可以使用派生类向基类的类型转换;反之,如果派生类继承基类的方式是私有的,则不能使用。
练习15.20:编写代码检验你对前面两题的回答是否正确。
#include <iostream>
using namespace std;
class Base
{
public:
void pub_mem();
protected:
int prot_mem;
private:
char priv_mem;
};
struct Pub_Derv:public Base
{
int f(){return prot_mem;}
void memfcn(Base &b)
{
b=*this;
cout<<"Pub_Derv"<<endl;
}
};
struct Priv_Derv:private Base
{
int f1() const {return prot_mem;}
void memfcn(Base &b)
{
b=*this;
cout<<"Priv_Derv"<<endl;
}
};
struct Prot_Derv:protected Base
{
int f2(){return prot_mem;}
void memfcn(Base &b)
{
b=*this;
cout<<"Prot_Derv"<<endl;
}
};
struct Derived_from_Public:public Pub_Derv
{
int use_base(){return prot_mem;}
void memfcn(Base &b)
{
b=*this;
cout<<"Derived_from_Public"<<endl;
}
};
struct Derived_from_Protected:protected Prot_Derv
{
int use_base()
{
return prot_mem;
}
void memfcn(Base &b)
{
b=*this;
cout<<"Derived_from_Protected"<<endl;
}
};
/*struct Derived_from_Private: private Priv_Derv
{
int use_base()
{
return prot_mem;
}
void memfcn(Base &b)
{
b=*this;
}
};*/
int main(int argc, const char * argv[])
{
Pub_Derv d1;
Priv_Derv d2;
Prot_Derv d3;
Derived_from_Public dd1;
// Derived_from_Private dd2;
Derived_from_Protected dd3;
Base base;
Base *p=new Base;
p=&d1; //d1的类型是Pub_Derv
//p=&d2; //d2的类型是Priv_Derv
//p=&d3; //d3的类型是Prot_Derv
p=&dd1; //dd1的类型是Derived_from_Public
//p=&dd2; //dd2的类型是Derived_from_Private
//p=&dd3; //dd3的类型是Derived_from_Protected
d1.memfcn(base);
d2.memfcn(base);
d3.memfcn(base);
dd1.memfcn(base);
//dd2.memfcn(base);
dd3.memfcn(base);
return 0;
}
代码运行结果:
Pub_Derv
Priv_Derv
Prot_Derv
Derived_from_Public
Derived_from_Protected
练习15.21:从下面这些一般性抽象概念中任选一个(或者选一个你自己的),将其对应的一组类型组织成一个继承体系:
(a) 图像文件格式(如 gif, tiff, jpeg, bmp )
(b) 几何图元(如矩形,圆,球形,锥形)
(c) C++语言的类型(如类,函数,成员函数)
对(b)中的几何图元组织成一个继承层次,
基类Figure,
矩形Rectangle, 圆cicle, 球形sphere,锥形 Cone继承自Figure类。
练习15.22:对于你在上一题中选择的类,为其添加合适的虚函数及公有成员和受保护成员。
虚函数:
比如计算图形面积的函数 virtual double area();
计算体积的函数virtual double cubage();
求周长的函数 virtualdouble perimeter();
Figuer类的public成员可能有两个图元的尺寸:xSize, ySize,其他类的protected成员可能有cone类和球形的zSize即Z轴尺寸练习15.23:假设第550页的D1类需要覆盖它继承而来的fcn函数,你应该如何对其进行修改?如果你修改之后fcn匹配了Base中的定义,则该节的那些调用语句将如何解析?
#include <iostream>
using namespace std;
class Base
{
public:
virtual int fcn()
{
cout<<"int fcn from Base"<<endl;
return 0;
}
};
class D1:public Base
{
public:
int fcn()
{
cout<<"int fcn from D1"<<endl;
return 0;
}
virtual void f2()
{
cout<<"void f2() from D1"<<endl;
}
};
class D2:public D1
{
public:
int fcn(int )
{
cout<<"int fcn(int ) from D2"<<endl;
return 0;
}
int fcn()
{
cout<<"int fcn from D2"<<endl;
return 0;
}
void f2()
{
cout<<"f2 from D2"<<endl;
}
};
int main(int argc, const char * argv[])
{
Base bobj;D1 d1obj;D2 d2obj;
Base *bp1=&bobj,*bp2=&d1obj,*bp3=&d2obj;
bp1->fcn();
bp2->fcn();
bp3->fcn();
cout<<endl;
D1 *d1p=&d1obj,*d2p=&d2obj;
bp2->f2(); //错误,base类里面没有f2()的成员函数
d1p->f2();
d2p->f2();
cout<<endl;
Base *p1=&d2obj;
D1 *p2=&d2obj;
D2 *p3=&d2obj;
p1->fcn(42); //错误,Base类中没有接受int实参的fcn
p2->fcn(42); //错误,D1类中没有接受int实参的fcn
p3->fcn(43);
std::cout << "Hello, World!\n";
return 0;
}
将错误代码注释后运行得到结果如下:
int fcn from Base
int fcn from D1
int fcn from D2
void f2() from D1
f2 from D2
int fcn(int ) from D2
练习15.24:哪种类需要虚析构函数?虚析构函数必须执行什么样的操作?
1.
作为基类使用的类应该具有虚析构函数,以保证在删除指向动态分配对象的基类指针时,根据指针实际指向的对象所属的类型运行适当的析构函数。
2.
虚析构函数可以为空,即不执行任何操作,而当类中有指针类成员时,则需要自己定义虚析构函数,以对指针成员进行适当的清除。
练习15.25:我们为什么为Disc_quote定义一个默认构造函数?如果去除掉该构造函数的话会对Bulk_quote的行为会产生什么影响?
练习15.26:定义Quote和Bulk_quote的拷贝控制成员,令其与合成的版本行为一致。为这些成员以及其他构造函数添加打印状态的语句,使得我们能够知道正在运行哪个程序。使用这些类编写程序,预测程序将创建和销毁哪些对象。重复实验,不断比较你的预测和实际输出结果是否相同,直到预测完全准确再结束。
#include <iostream>
#include <string>
#include <ostream>
using namespace std;
class Quote
{
public:
Quote()=default;
Quote(const std::string &book="",double sales_price=0.0):
bookNo(book),price(sales_price)
{
cout<<"Quote constructor is running"<<endl;
}
std::string isbn() const
{
return bookNo;
}
virtual double net_price(std::size_t n)const //返回给定数量的书籍的销售总额,派生类改写并使用不同的折扣计算方法
{
return n*price;
}
virtual void debug()
{
cout<<"bookNo="<<bookNo<<" price="<<price<<endl;
}
virtual ~Quote()
{
cout<<"Quote destructor is running"<<endl;
}
friend ostream &operator <<(ostream&,Quote&);
private:
std::string bookNo; //书籍的ISBN编号
protected:
double price=0.0; //代表普通状态下不打折的价格
};
ostream & operator <<(ostream&os,Quote "e)
{
os<<"\tUsing operator <<(ostream &,Quote &);"<<endl;
return os;
};
class Bulk_quote:public Quote
{
public:
Bulk_quote(const string &book="",double sales_price=0.0,size_t qty=0,double disc=0.0):
Quote(book,sales_price),min_qty(qty),discount(disc)
{
cout<<"Bulk_constructor is running"<<endl;
}
double net_price(size_t cnt)const
{
if ( cnt > min_qty )
return cnt * ( 1- discount ) * price;
else
return cnt * price;
}
~Bulk_quote()
{
cout<<"Bulk_quote destructor is running"<<endl;
}
private:
size_t min_qty;
double discount;
};
ostream &operator<<(ostream &os,Bulk_quote& bq)
{
os<<"\tUsing operator <<(ostream&,Bulk_quote &)"<<endl;
return os;
}
int main(int argc, const char * argv[])
{
Quote base("C++ Primer",128.0);
Bulk_quote bulk("Core Python Programming",89,5,0.19);
cout<<base<<endl;
cout<<bulk<<endl;
return 0;
}
运行结果如下:
Quote constructor is running
Quote constructor is running
Bulk_constructor is running
Using operator <<(ostream &,Quote &);
Using operator <<(ostream&,Bulk_quote &)
Bulk_quote destructor is running
Quote destructor is running
Quote destructor is running
练习15.27:重新定义你的Bulk_quote类,令其继承构造函数。
class Disc_quote:public Quote
{
public:
Disc_quote(const string &book="",double sales_price=0.0,size_t qty=0,double disc=0.0):Quote(book,sales_price),quantity(qty),discount(disc){}
double net_price(size_t cnt) const=0;
protected:
size_t quantity;
double discount;
};
class Bulk_quote : public Disc_quote
{
public:
using Disc_quote::Disc_quote;
double net_price( size_t cnt ) const
{
if ( cnt > quantity )
return cnt * ( 1- discount ) * price;
else
return cnt * price;
}
};