c++ primer第五版第十五章答案

15.01 什么是虚成员?

虚成员是基类希望派生类进行覆盖的函数,在其成员前加关键字virtual,使得该成员可以实现动态绑定。

15.02 protected访问说明符与private有何区别?

private成员:即使是基类的派生类也无法直接访问。

protected成员:基类的派生类可以访问,但禁止其它用户访问。

15.03 定义你自己的Quote类和print_total函数。

// Quote类
class Quote
{
public:
    Quote() = default;
    Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}
    std::string isbn() const { return bookNo; }
    // return the total sales of given number books.
    // the derived class need overwrite the method of calculate the discount.
    virtual double net_price(std::size_t n) const { return n * price; }
    virtual ~Quote() = default;
private:
    std::string bookNo;     // isbn number
protected:
    double price = 0.0;     // origin price
};
// print_total函数
double print_total(ostream& os, const Quote& item, size_t n)
{
    double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() << " # sold: " << n << " total due: " << ret << endl;
    return ret;
}

15.04 下面哪条声明语句是不正确的?请解释原因。

class Base { ... };
(a) class Derived : public Derived { ... };  // 类重复定义,不能自己继承自己
(b) class Derived : private Base { ... };    // 这是类的定义,并不是声明
(c) class Derived : public Base;             // 类的声明不包含类派生列表

15.05 定义你自己的Bulk_quote类。

class Bulk_quote : public Quote
{
public:
    Bulk_quote() = default;
    Bulk_quote(const std::string& book, double price, std::size_t qty, double disc) : Quote(book, price), min_qty(qty), discount(disc) {}
    double net_price(std::size_t) const override;
private:
    std::size_t min_qty = 0;    // the minimum purchase can use the discount.
    double discount;
};
double Bulk_quote::net_price(size_t cnt) const
{
    if (cnt >= min_qty) {
        return cnt * (1-discount) * price;
    }
    else {
        return cnt * price;
    }
}

15.06 将Quote和Bulk_quote的对象传给15.2.1节练习中的print_total函数,检查该函数是否正确。

double print_total(ostream& os, const Bulk_quote& item, size_t n)
{
    double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() << " # sold: " << n << " total due: " << ret << endl;
    return ret;
}

15.07 定义一个类使其实现一种数量首先的折扣策略,具体策略是:当购买书籍的数量不超过一个给定的限量时享受折扣,如果购买量一旦超过了限量,则超出的部分将以原价销售。

class Limit_quote : public Quote
{
public:
    Limit_quote() = default;
    Limit_quote(const std::string& book, double price, std::size_t qty, double disc) : Quote(book, price), max_qty(qty), discount(disc) {}
    double net_price(std::size_t) const override;
private:
    std::size_t max_qty = 0;
    double discount;
};
double Limit_quote::net_price(size_t cnt) const
{
    if (cnt <= max_qty) {
        return cnt * (1-discount) * price;
    }
    else {
        return (cnt-max_qty) * price + max_qty * (1-discount) * price;
    }
}

15.8 给出静态类型和动态类型的定义。

表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型。

动态类型则是变量或表达式表示的内存中的对象的类型,动态类型直到运行时才可知。

15.9 在什么情况下表达式的静态类型可能与动态类型不同?请给出三个静态类型与动态类型不同的例子。

基类的指针或引用的静态类型可能与其动态类型不一致,如果表达式既不是指针也不是引用,则它的动态类型永远与静态类型一致。

例子:如15.03中的函数print_total中,参数item的静态类型是其定义的类型Quote&,但如果我们传递一个Bulk_quote&进去,则它的动态类型是Bulk_quote&,此例中item的静态类型和动态类型不一致。

15.10 回忆我们在8.1节(279页)进行的讨论,解释284页中将ifstream传递给Sales_data的read函数的程序是如何工作的。

read本是std::istream下的函数,但因为ifstream继承自istream,因此也可以使用read函数。

15.11 为你的Quote类体系添加一个名为debug的虚函数,令其分别显示每个类的数据成员。

// Quote类
virtual void debug() const;
void Quote::debug() const
{
    cout << "This is Quote class." << endl;
    cout << "bookNo = " << bookNo << " price = " << price << endl;
}
// Bulk_quote类
void debug() const override;
void Bulk_quote::debug() const
{
    cout << "This is Bulk_quote class." << endl;
    cout << " price = " << price << endl;
    cout << "min_qty = " << min_qty << " discount = " << discount << endl;
}
// Limit_quote类
void debug() const override;
void Limit_quote::debug() const
{
    cout << "This is Limit_quote class." << endl;
    cout << " price = " << price << endl;
    cout << "max_qty = " << max_qty << " discount = " << discount << endl;
}

15.12 有必要将一个成员函数同时声明成override和final吗?为什么?

有必要,override意味着重载父类中的虚函数,final意味着禁止子类重载该虚函数。两个用法并不冲突。

15.13 给定下面的类,解释每个print函数的机理:

class base {
public:
    string name() { return basename; }
    virtual void print (ostream& os) { os << basename; }
private:
    string basename = "home";    
};

class derived : public base {
public: 
    void print(ostream& os) { print(os); os << " " << i; }
    // 这里意为重写base类的print函数,并且在其中调用base的print(os),但是由于没有加::范围限定符,导致
    // 其调用的还是derived的print函数,造成无限递归。现象是打印完一行home之后就卡住了。
    // 改为:void print(ostream& os) override{ base::print(os); os << " " << i; }
    // 加override说明是覆盖基类的虚函数。
private:
    int i = 2;
};

在上述代码中存在问题吗?如果有,你该如何修改它?

15.14 给定上一题中的类以及下面这些对象,说明在运行时调用哪个函数:

base bobj;
base *bp1 = &bobj;
base &br1 = bobj;

derived dobj;
base *bp2 = &dobj;
base &br2 = dobj;
(a)bobj.print();    // base::print()
(b)dobj.print();    // derived::print()
(c)bp1->name();     // base::name()
(d)bp2->name();     // base::name()
(e)br1.print();     // base::print()
(f)br2.print();     // derived::print()

15.15 定义你自己的Disc_quote和Bulk_quote。

class Disc_quote : public Quote
{
public:
    Disc_quote() = default;
    Disc_quote(const std::string& book, double sales_price, std::size_t qty, double disc) : Quote(book, sales_price), quantity(qty), discount(disc){}
    virtual double net_price(std::size_t n) const override = 0;
protected:
    std::size_t quantity;
    double discount;
};

class Bulk_quote : public Disc_quote
{
public:
    Bulk_quote() = default;
    Bulk_quote(const std::string& b, double p, std::size_t q, double disc) :
        Disc_quote(b, p, q, disc) {   }

    double net_price(std::size_t n) const override;
    void  debug() const override;
};

15.16 改写你在15.2.2节(第533页)练习中编写的数量受限的折扣策略,令其继承Disc_quote。

class Limit_quote : public Disc_quote
{
public:
    Limit_quote() = default;
    Limit_quote(const std::string& b, double p, std::size_t max, double disc):
        Disc_quote(b, p, max, disc)  {   }

    double net_price(std::size_t n) const override
    { return n * price * (n < quantity ? 1 - discount : 1 ); }
};

15.17 尝试定义一个Disc_quote的对象,看看编译器给出的错误信息是什么?

error: cannot declare variable 'dq' to be of abstract type 'Disc_quote'

15.18 假设给定了第543页和第544页的类,同时已知每个对象的类型如注释所示,判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许:

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,非法

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

void memfcn(Base &b) { b = *this; }

对于每个类,分别判断上面的函数是否合法。

  1. 无论D以什么方式继承B,其成员函数和友元都能使用派生类到基类的转换。因此,Pub_Derv, Pro_Derv和Priv_Derv类中都合法。
  2. 如果D继承B的方式是共有的或受保护的,则D的派生类成员和友元可以使用D向B的类型转换,反之,如果D继承B是私有的,则不能使用,因此,Derived_from_Public合法,Derived_from_Private和Derived_from_Protected都不合法。

15.20 编写代码检验你对前面两题的回答是否正确。

// .h文件
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; }
};

struct Pro_Derv : protected Base
{
    int f() { return prot_mem(); }
    void memfcn(Base &b) { b = *this; }
};

struct Priv_Derv : private Base
{
    int f1() { return prot_mem(); }
    void memfcn(Base &b) { b = *this; }
};

struct Derived_from_Public : public Pub_Derv
{
    int use_base() {return prot_mem();}
    void memfcn(Base &b) { b = *this; }
};

struct Derived_from_Pro : public Pro_Derv
{
 //   void memfcn(Base &b) { b = *this; }
};

struct Derived_from_Private : public Priv_Derv
{
 //   void memfcn(Base &b) { b = *this; }
};

//.cpp文件
int main()
{
    Pub_Derv d1;
    Priv_Derv d2;
    Pro_Derv d3;
    Derived_from_Public dd1;
    Derived_from_Pro dd2;
    Derived_from_Private dd3;

    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,非法
    return 0;
}

15.21 从下面这些一般性抽象概念中任选一个(或者选一个你自己的),将其对应的一组类型组织成一个继承体系:

​ (a) 图形文件格式(如gif、tiff、jpeg、bmp)

​ (b) 图形基元(如方格、圆、球、圆锥)

​ (c) C++语言中的类型(如类、函数、成员函数)

15.22 对于你在上一题中选择的类,为其添加合适的虚函数及共有成员和受保护的成员。

class Shape
{
public:
    typedef std::pair<std::string, std::string> coordinate;
    Shape() = default;
    virtual double area() const = 0;
    virtual double perimeter() const = 0;   //ÇóÖܳ¤

    virtual ~Shape() = default;
//    virtual ~Shape() throw(){};
};

class Circle : public Shape
{
public:
    Circle(coordinate c, double r) : center(c), radius(r) {}
    double area() const override
    {
        return 3.14*radius*radius;
    }
    double perimeter() const override
    {
        return 2*3.14*radius;
    }
    ~ Circle() throw(){};
private:
    coordinate center;
protected:
    double radius;
};

class Circle_Cone : public Circle
{
public:
    double volume () const
    {
        return 3.14*radius*radius*height;
    }
private:
    double height;
};

​ 15.23 假设第520页的D1类需要覆盖它继承而来的fcn函数,你应该如何对其进行修改?如果你修改了之后fcn匹配了Base中的定义,则该节的那些调用语句应如何解析?

#include <iostream>
using namespace std;

class Base
{
public:
    virtual int fcn() { cout << "Base::fcn()" << endl; }
};

class D1 : public Base
{
public:
    int fcn(int);
    virtual int fcn() override{ cout << "d1::fcn()" << endl; }
    virtual void f2(){ cout << "d1::f2()" << endl; }
};

class D2 : public D1
{
public:
    int fcn(int);
    int fcn() override { cout << "d2::fcn()" << endl; }
    void f2() override { cout << "d2::f2()" << endl; }
};

int main()
{
    Base bobj;
    D1 d1obj;
    D2 d2obj;

    Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;

    bp1->fcn();     // 虚调用,运行时调用Base::fcn
    bp2->fcn();     // 虚调用,运行时调用D1::fcn
    bp3->fcn();     // 虚调用,运行时调用D2::fcn

    D1 *d1p = &d1obj; D2 *d2p = &d2obj;
//    bp2->f2();        // 错误,Base中没有f2成员。
    d1p->f2();          // 虚调用,运行时调用D1::f2()
    d2p->f2();          // 虚调用,运行时调用D2::f2()
    return 0;
}

15.24 哪种类需要虚析构函数?虚析构函数必须执行什么样的操作?

15.25 我们为什么为Disc_quote定义一个默认构造函数?如果去除掉该构造函数的话会对Bulk_quote的行为产生什么影响?

15.26 定义Quote和Bulk_quote的拷贝控制成员,令其与合成的版本行为一致。为这些成员以及其他构造函数添加打印状态的语句,使得我们能够知道正在运行哪个程序。使用这些类编写程序,预测程序将创建和销毁哪些对象。重复实验,不断比较你的预测和实际输出结果是否相同,直到预测完全准确再结束。

。。。 未完待续

猜你喜欢

转载自www.cnblogs.com/songshuguiyu/p/10140619.html