《c++primer》第15章面对对象程序设计习题解答

1.什么是虚成员?
摘要由CSDN通过智能技术生成

练习15.1:什么是虚成员?

在类中声明为virtual的成员函数,基类希望这种成员函数在派生类中被重新定义。除了构造函数不能被定义为虚成员函数为,其他的任意非static成员函数可以定义为虚成员函数(也就是说析构函数可以定义为虚成员函数)

练习15.2:protected访问说明符与private有何区别?

protected是受保护的访问说明符,意味着被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吗?为什么?

有必要。
override:在C++11新标准中我们可以使用override关键字来说明派生类中的虚函数。这么做的好处是在使得我们的意图更加的清晰即明确的告诉编译器我们想要覆盖掉已存在的虚函数。如果我们定义了一个函数与基类中的名字相同但是形参列表不同,在不使用override关键字的时候这种函数定义是合法的,在使用了override关键字之后这种行为是非法的,编译器会提示出错。
final:如果我们将某个函数定义成final,则不允许后续的派生类来覆盖这个函数,否则会报错。
因此同时将一个成员函数声明成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。
注:我在Xcode下实际发现,当derived类中的print()函数调用base类中的print()函数时,应当显示注明,即
#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才能够赋值。这是因为:
只有当派生类公有地继承基类时,用户代码才能使用派生类向基类的转换;也就是说,如果派生类继承基类的方式是受保护的或者私有的,则用户代码不能使用该转换。
在上题中,只有d1和dd1类是公有地继承基类,故只有他们才能完成向基类的转换。

练习15.19:假设第543页和544页的每个类都有如下形式的成员函数:

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中的定义,则该节的那些调用语句将如何解析?

1.将D1类的fcn函数更改为 int fcn();
2.p2->fcn(42)  这一条调用语句将会出错。
测试代码如下:
#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的行为会产生什么影响?

因为Disc_quote的默认构造函数会运行Quote的默认构造函数,而Quote默认构造函数会完成成员的初始化工作。
如果去除掉该构造函数的话,Bulk_quote的默认构造函数而无法完成Disc_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;
	}

};


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值