C++Primer习题第十五章


练习15.1:什么是虚成员?

答:

用关键字virtual声明类内的成员函数,基类希望其派生类进行覆盖的函数。

除了构造函数之外的任何非static成员函数都可以是虚函数。


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

protected说明符允许该类成员、友元及其派生类的成员(非友元)访问,而不可以被该类型的用户访问。而private允许该类的成员及其友元访问,但是派生类或用户代码都不可以访问。


练习15.3:定义你自己的 Quote类和print_total函数。

class Quote{
public:
    //构造函数 
    Quote() = default;
    Quote( std::string &bkNo, double pr ):
        bookNo( bkNo ), price( pr ) { }
    //基类都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此
    virtual ~Quote() = default;
    //接口    
    std::string isbn() const { return bookNo; }
    
    virtual double net_price( std::size_t n ) const { return n * price; }
private:
    std::string bookNo;
protected:
    double price = 0.0;
};

//计算并打印销售给定数量的某种书籍所得的费用
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.4:下面哪条声明语句是不正确的?请解释原因。

class Base{ ... };

(a)

class Derived : public Derived { ... };

不正确。如果我们想将某个类用作基类,那该类必须已经定义而非仅仅声明。

很明显,Derived还未定义。具体到本题也就是不能用类本身作为类的基类。

(b)class Derived : private Base { ... };

正确。Derived类私有继承Base类。 

(c)class Derived : public Base;

不正确。

派生类的声明不能含有派生列表。


练习15.5:定义你自己的Bulk_quote类。

class Bulk_quote : public Quote {
public:
    //构造函数
    Bulk_quote() = default;
    Bulk_quote( const std::string &bkNo, double pr, std::size_t qty, double disc ):
        Quote( bkNo, pr ), min_qty( qty ), discount( disc ) { }
    
    double net_price( std::size_t ) const override;    
private:
    std::size_t min_qty = 0;
    double discount = 0.0;
};

double Bulk_quote::net_price( std::size_t n ) const {
    if( n >= min_qty )
        return n * ( 1 - discount ) * price;
    else
        return n * price;
}

练习15.6:将Quote和Bulk_quote的对象传给15.2.1节练习中的print_total函数。

#include
#include"Quote.h"

using namespace std;
class Quote;
class Bulk_quote;
double print_total( ostream &os, const Quote &item, size_t n );


int main()
{
    Quote b1( "978-7-121-15535-2", 128.0 );
    Bulk_quote b2( "978-7-121-25229-7", 89.0, 10, 0.2 );

    print_total( cout, b1, 2 );
    print_total( cout, b2, 5 );
    print_total( cout, b2, 10 );
    return 0;
}

//计算并打印销售给定数量的某种书籍所得的费用
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.7:定义一个类使其实现一种数量受限的折扣策略,具体策略是:当购买书籍的数量不超过一个给定的限量时享受折扣,如果购买量一旦超过了限量,则超出的部分将以原价销售。

class Limited_quote: public Quote{
public:
    Limited_quote() = default;
    Limited_quote( const std::string &bkNo, double pr, std::size_t qty, double disc ):
        Quote( bkNo, pr ), max_qty( qty ), discount( disc ) { }

    double net_price( std::size_t ) const override;
private:
    std::size_t max_qty = 0;
    double discount = 0.0;
};

double Limited_quote::net_price( std::size_t n ){
    if( n <= max_qty )
        return n * ( 1 - discount ) * price;
    else
        return max_qty * ( 1 - discount) * price + ( n - max_qty ) * price;
}

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

(标准)

答:

静态类型在编译时已经确定了,它是变量声明时的类型或表达式生成的类型;而动态类型则是变量或者表达式表示的内存中的对象的类型,动态类型直到运行时才能知道。

基类的指针或引用的静态类型可能与动态类型不一致。

若一个变量非指针非引用,那么它的静态类型和动态类型永远一致。


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

略了。


练习15.10:回忆我们在8.1节进行的讨论,解释第284页中将ifstream传递给Sales_data的read函数的程序是如何工作 的?

ifstream类继承自istream类。

read函数接受istream类的引用,所以发生了派生类向基类的类型转换。


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

#include
#include

class Quote{
public:
    //构造函数
    Quote() = default;
    Quote( const std::string &bkNo, double pr ):
        bookNo( bkNo ), price( pr ) { }
    //基类都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此
    virtual ~Quote() = default;
    //接口
    std::string isbn() const { return bookNo; }

    virtual double net_price( std::size_t n ) const { return n * price; }
    
    virtual void debug() const;
private:
    std::string bookNo;
protected:
    double price = 0.0;
};

void Quote::debug() const{
    std::cout << "bookNo=" << bookNo 
        << " price=" << price << std::endl;
}



class Bulk_quote : public Quote {
public:
    //构造函数
    Bulk_quote() = default;
    Bulk_quote( const std::string &bkNo, double pr, std::size_t qty, double disc ):
        Quote( bkNo, pr ), min_qty( qty ), discount( disc ) { }

    double net_price( std::size_t ) const override;
    
    void debug() const override;
private:
    std::size_t min_qty = 0;
    double discount = 0.0;
};

double Bulk_quote::net_price( std::size_t n ) const {
    if( n >= min_qty )
        return n * ( 1 - discount ) * price;
    else
        return n * price;
}
void Bulk_quote::debug() const{
    Quote::debug();
    
    std::cout << "min_qty=" << min_qty
        << " discount =" << discount << std::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;

};


class derived: public base{

public:

void print( ostream &os ) { print(os); os << " " << i; }

private:

int i;

};

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

答:

有问题。派生类derived中的print函数会发生递归调用无限循环。

该处的代码修改为:

void print( ostream &os ) { Base::print(os); os << " " << i; }


练习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 &bk, double pr, std::size_t qty, double disc ):
        Quote( bk, pr ), quantity( qty ), discount( disc ) { }
        
    virtual double net_price( std::size_t ) const = 0;
protected:
    std::size_t quantity = 0;
    double discount = 0.0;
};


class Bulk_quote: public Disc_quote{
public:
    Bulk_quote() = default;
    Bulk_quote( const std::string &bk, double pr, std::size_t qty, double disc ):
        Disc_quote( bk, pr, qty, disc ) { }
        
    double net_price( std::size_t n ) const override{
        if( n >= quantity )
            return n * ( 1 - discount ) * price;
        else
            return n * price;
    }    
};

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

class Limited_quote: public Disc_quote{
public:
    Limited_quote() = default;
    Limited_quote( const std::string &bk, double pr, std::size_t qty, double disc ):
        Disc_quote( bk, pr, qty, disc ) { }
    
    double net_price( std::size_t n ) const override{
        if( n <= quantity )
            return n * ( 1 - discount ) * price;
        else
            return quantity * ( 1 - discount ) * price + ( n - quantity ) * price;
    }
};

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

答:略了。


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


Base *p = &d1; //d1的类型是Pub_Derv

合法。


p = &d2;

不合法。只有当派生类公有地继承基类的时候,用户代码才能实现派生类向基类的转换。


p = &d3;

同上。


p = &dd1;

合法。


p = &dd2;

不合法。只有当派生类公有地继承基类的时候,用户代码才能实现派生类向基类的转换。


p = &dd3;

不合法。同上。


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

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

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

(1)对于Base类,合法。

(2)对于Pub_Derv类,合法。

(3)对于Priv_Derv类,合法。无论派生类是什么方式继承基类,派生类的成员和友元都能够实现派生类向基类的转换。

(4)对于Derived_from_public类,合法。它是继承自Pub_Derv类的,而Pub_Derv类是公有继承的,所以该类成员可以使用派生类的派生类向基类的转换。

(5)对于Derived_from_private类,不合法。它是继承自Priv_Derv类的,而Priv_Derv类是私有继承的,所以该类成员不可以使用派生类的派生类向基类的转换。


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

略了。


练习15.21:从下面这些一般性抽象概念中任选一个,将其对应的一组类型组织成一个继承体系:

(a)图形形式格式( 如:gif、tiff、jpeg、bmp )

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

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

。。。显然(b)选项的方向比较明确易做,就选择(b)了。

#ifndef FIGURE_H_INCLUDED
#define FIGURE_H_INCLUDED

class Figure{
public:
    Figure( double x, double y ):
        xSize( x ), ySize( y ) { }

    virtual ~Figure() { }
protected:
    double xSize = 0.0, ySize = 0.0;
};

class Figure_2D: public Figure{
public:
    Figure_2D( double x, double y ):
        Figure( x, y ) { }
    virtual double perimeter() const = 0;
    virtual double area() const = 0;
};

class Figure_3D: public Figure{
public:
    Figure_3D( double x, double y, double z ):
        Figure( x, y ), zSize( z ) { }
    virtual double superficial_area() const = 0;
    virtual double volume() const = 0;

protected:
    double zSize = 0.0;
};

class Rectangle: public Figure_2D{
public:
    Rectangle( double x, double y ):
        Figure_2D( x, y ) { }

    double perimeter() const override { return 2 * ( xSize + ySize ); }
    double area() const override { return xSize * ySize;}
};

class Circle: public Figure_2D{
public:
    //半径传递给xSize.
    Circle( double radius ):
        Figure_2D( radius, 0.0 ) { }
    double perimeter() const override { return 2 * 3.14 * xSize; }
    double area() const override { return 3.14 * xSize * xSize; }
};

class Cuboid: public Figure_3D{
public:
    Cuboid( double x, double y, double z ):
        Figure_3D( x, y, z ) { }
    double superficial_area() const override
        { return 2*(xSize*ySize + ySize*zSize + xSize*zSize);  }
    double volume() const override
        { return xSize * ySize * zSize; }
};

class Globe: public Figure_3D{
public:
    Globe( double radius ):
        Figure_3D( radius, 0.0, 0.0 ) { }
    double superficial_area() const override { return 4 * 3.14 * xSize * xSize; }
    double volume() const override { return 4 / 3 * 3.14 * xSize * xSize * xSize; }
};

#endif // FIGURE_H_INCLUDED

#include
#include"Figure.h"

using namespace std;

int main()
{
    Circle c1( 4 );
    Rectangle r1( 3, 4 );
    cout << "圆周长: " << c1.perimeter() << endl;
    cout << "圆面积: " << c1.area() << endl;
    cout << "矩形周长: " << r1.perimeter() << endl;
    cout << "矩形面积: " << r1.area() << endl;

    cout << endl;

    Globe g1( 4 );
    Cuboid cu1( 3, 4, 5 );
    cout << "球体表面积: " << g1.superficial_area() << endl;
    cout << "球体体积: " << g1.volume() << endl;
    cout << "长方体表面积: " << cu1.superficial_area() << endl;
    cout << "长方体体积: " << cu1.volume() << endl;
    return 0;
}



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

见上题代码。



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

D1类中的

 int fcn( int );改成 int fcn() override;

bp2->fcn();

这条调用语句在修改之后,调用的将是D1::fcn()

p2->fcn(42);

这条调用不合法。因为D1类及其基类都无法找到匹配该参数的成员。


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

答:

作为一个继承体系的基类,通常应该定义一个虚析构函数。以保证在删除指向动态分配对象的基类指针时根据指针实际指向的对象所属的类型运行适当的析构函数。

虚析构函数可以为空,即不执行任何操作。一般而言,析构函数的主要作用是清除本类中定义的数据成员。如果该类没有定义指针类成员,则使用合成版本即可;如果该类定义了指针成员,则一般需要自定义析构函数以对指针成员进行适当的清除。因此,如果有虚析构函数必须执行的操作,则就是清除本类中定义的数据成员的操作。


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

答:

因为Disc_quote的默认构造函数需要调用Quote的默认构造函数。如果没有Disc_quote的默认构造函数,则无法对Bulk_quote进行默认初始化。


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

#include
#include

class Quote{
public:
    //构造函数
    Quote() = default;
    Quote( const std::string &bkNo, double pr ):
        bookNo( bkNo ), price( pr ) { std::cout << "Quote's constructor" << std::endl; }
    //拷贝构造函数
    Quote( const Quote &qt ):
        bookNo( qt.bookNo ), price( qt.price ) { std::cout << "Quote's copy constructor" << std::endl; }
    //拷贝赋值运算符
    Quote& operator=( const Quote &qt ){
        std::cout << "Quote's copy operator=" << std::endl;
        bookNo = qt.bookNo;
        price = qt.price;
        return *this;
    }

    //基类都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此
    virtual ~Quote() { std::cout << "Quote's destructor" << std::endl; };
    //接口
    std::string isbn() const { return bookNo; }

    virtual double net_price( std::size_t n ) const { return n * price; }

    virtual void debug() const;
private:
    std::string bookNo;
protected:
    double price = 0.0;
};

void Quote::debug() const{
    std::cout << "bookNo=" << bookNo
        << " price=" << price << std::endl;
}

//抽象基类
class Disc_quote: public Quote{
public:
    Disc_quote() = default;
    Disc_quote( const std::string &bk, double pr, std::size_t qty, double disc ):
        Quote( bk, pr ), quantity( qty ), discount( disc ) { std::cout << "Disc_quote's constructor" << std::endl; }

    Disc_quote( const Disc_quote &dq ):
        Quote( dq ), quantity( dq.quantity ), discount( dq.discount ) { std::cout << "Disc_quote's copy constructor" << std::endl;}
    Disc_quote& operator=( const Disc_quote &dq ){
        std::cout << "Disc_quote's copy operator=" << std::endl;
        Quote::operator=( dq );
        quantity = dq.quantity;
        discount = dq.discount;
        return *this;
    }

    virtual double net_price( std::size_t ) const = 0;
protected:
    std::size_t quantity = 0;
    double discount = 0.0;
};


class Bulk_quote: public Disc_quote{
public:
    Bulk_quote() = default;
    Bulk_quote( const std::string &bk, double pr, std::size_t qty, double disc ):
        Disc_quote( bk, pr, qty, disc ) { std::cout << "Bulk_quote's constructor" << std::endl; }

    //拷贝构造函数
    Bulk_quote( const Bulk_quote &bq ):
        Disc_quote( bq ) { std::cout << "Bulk_quote's copy constructor" << std::endl; }
    //拷贝赋值运算符
    Bulk_quote& operator=( const Bulk_quote &bq ){
        std::cout << "Bulk_quote's copy operator=" << std::endl;
        Disc_quote::operator=( bq );
    }
    ~Bulk_quote() { std::cout << "Bulk_quote's destructor" << std::endl; }

    double net_price( std::size_t n ) const override{
        if( n >= quantity )
            return n * ( 1 - discount ) * price;
        else
            return n * price;
    }
};

测试程序:

#include
#include"Quote.h"

using namespace std;

int main()
{

    Bulk_quote bq( "978-7-121-25229-7", 89.0, 10, 0.2 );
    Bulk_quote bq1( "978-7-121-15535-2", 128.0, 7, 0.9 );
    Bulk_quote bq2 = bq;
    bq2 = bq1;

    return 0;
}

结果:


结果符合预期。(值得注意的是 我没有定义Disc_quote的析构函数,所以运行它的析构函数时也就没有打印语句了。。。)


练习15.27:重新定义你的Bulk_quote类,令其继承构造函数。

可能编译器的版本不够新,修改了好几次都是报错。先略了。


练习15.28:定义一个存放Quote对象的vector,将Bulk_quote对象传入其中。计算vector中所有元素总的net_price。

Bulk_quote虽然可以传入其中,但是发生了类型转换,派生类部分会被切掉。


练习15.29:再一次运行你的程序,这次传入Quote对象的shared_ptr。如果这次计算出的总额与之前的程序不一致,解释为什么;如果一致,也请说明原因。

#include
#include
#include
#include"Quote.h"

using namespace std;

int main()
{
    vector> basket;
    double sum = 0.0;
    for( size_t i = 0; i != 10; ++i )
        basket.push_back( make_shared( "C++Primer", 6, 5, 0.5 ) );
    for( const auto &bq: basket )
        sum += bq->net_price( 10 );

    cout << sum << endl;
    return 0;
}

程序结果会产生差异。因为当通过Quote类型的对象调用虚函数net_price时,不实行动态绑定,调用的是Quote类中定义的版本。而通过Quote类的指针调用虚函数net_price时,实行动态绑定,而该指针实际指向的是Bulk_quote类定义的版本。


练习15.30:编写你自己的Basket类,用它计算上一个练习中交易记录的总价格。

我的编译器似乎不支持引用限定符,所以 移动版本的成员 我就省略了。

#ifndef BASKET_H_INCLUDED
#define BASKET_H_INCLUDED

#include
#include
#include
#include"Quote.h"

using namespace std;

class Basket{
public:
    void add_item( const Quote& );
    void add_item( Quote&& );

    double total_receipt( ostream& ) const;
private:
    static bool compare( const shared_ptr &lhs , const shared_ptr &rhs )
        { return lhs->isbn() < rhs->isbn(); }
    multiset, decltype(compare)*> items{ compare };
};


void Basket::add_item( const Quote &sale ){
    items.insert( shared_ptr ( sale.clone() ) );
}
/*
void Basket::add_item( Quote &&sale ){
    items.insert( shared_ptr ( std::move( sale ).clone() ) );
}
*/
double Basket::total_receipt( ostream &os ) const{
    double sum = 0.0;
    for( auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound( *iter ) )
        sum += print_total( os, **iter, items.count( *iter ) );
    os << "Total sale: " << sum << endl;

    return sum;
}
#endif // BASKET_H_INCLUDED

#include
#include"Basket.h"

using namespace std;

int main()
{
    Basket bsk;
    Quote b1( "978-7-121-15535-2", 128.0 );
    Bulk_quote b2( "978-7-121-25229-7", 89.0, 2, 0.2 );
    Bulk_quote b3( "978-7-121-25229-7", 89.0, 2, 0.2 );

    bsk.add_item( b1 );
    bsk.add_item( b2 );
    bsk.add_item( b3 );

    bsk.total_receipt( cout );

    return 0;
}



练习15.31:已知s1、s2、s3和s4都是string,判断下面的表达式分别创建了什么样的对象:

(a)

Query(s1) | Query( s2 ) & ~Query( s3 )

一个NotQuery对象

一个AndQuery对象

一个OrQuery对象。

3个WordQuery对象

以及相应的6个Query对象。

逻辑关系(简写):

s1 | ( s2 & (~s3) )

(b)同上。

(c)

两个AndQuery对象

一个OrQuery对象

四个WordQuery对象。

以及相应的7个Query对象。


练习15.32:当一个Query类的对象被拷贝、移动、赋值或销毁时,将分别发生什么?

Query类并没有定义任何拷贝控制成员,所以编译器会为其合成对应的拷贝控制成员。

当一个对象被拷贝时,Query类的数据成员 智能指针q被拷贝,引用计数加1。

当一个对象被移动时,调用shared_ptr的移动构造函数完成移动操作。

当一个对象被赋值时,Query的数据成员 智能指针q被赋予新值,旧对象的引用计数减1,如果就对象的引用计数减为0,将会调用类对象的析构函数销毁对象。而新对象的引用计数会加1。

当一个对象被销毁时,成员的智能指针指向对象的引用计数减1,如果引用计数减为0,那么将调用相应类对象的析构函数销毁对象。


练习15.33:当一个Query_base类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么?

Query_base是一个抽象基类,不允许直接声明其对象。


练习15.34:针对图15.3构建的表达式:

(a)列举出在处理表达式的过程中执行的所有构造函数。

Query( const string& );

WordQuery( const string& );


Query( const string& );

WordQuery( const string& );


BinaryQuery( const Query&, const Query&, const string&)

AndQuery( const Query&, const Query& );

Query( shared_ptr );


Query( const string& );

WordQuery( const string& );


BinaryQuery( const Query&, const Query&, const string& );

OrQuery( const Query&, const Query& );

Query( shared_ptr );


(b)列举出cout << q所调用的rep。

太繁琐了,先略了。

(c)列举出q.eval()所调用的eval

实际上,调用过程中,会用到很多个Query,可能用画图的方式比较容易表达。

上题同理,不过rep更繁琐。


练习15.35:实现Query类和Query_base类,其中需要定义rep而无须定义eval。

我直接上全部代码了吧。

#ifndef QUERY_H_INCLUDED
#define QUERY_H_INCLUDED

#include
#include
#include
#include

#include"TextQuery.h"

//抽象基类
class Query_base{
    friend class Query;
protected:
    using line_no = TextQuery::line_no;
    virtual ~Query_base() = default;
private:
    virtual QueryResult eval( const TextQuery& ) const = 0;
    virtual std::string rep() const = 0;
};


//接口类。
class Query{
    friend Query operator~( const Query& );
    friend Query operator&( const Query&, const Query& );
    friend Query operator|( const Query&, const Query& );
public:
    Query( const std::string& );

    QueryResult eval( const TextQuery &tq ) const
        { return spq->eval( tq ); }
    std::string rep() const
        { return spq->rep(); }
private:
    Query( std::shared_ptr query ): spq( query ) { }
    std::shared_ptr spq;
};

ostream& operator<<( ostream &os, const Query &q ){
    os << q.rep();
}

//抽象基类
class BinaryQuery: public Query_base{
protected:
    BinaryQuery( const Query &l, const Query &r, std::string s ):
        lhs( l ), rhs( r ), opSym( s ) { }
    std::string rep() const
        { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; }

    Query lhs,rhs;
    std::string opSym;
};



class WordQuery: public Query_base{
    friend class Query;
private:
    WordQuery( const std::string &s ): query_word( s ) { }
    //接口成员
    QueryResult eval( const TextQuery &tq ) const override
        { return tq.query( query_word ); }
    std::string rep() const override { return query_word; }
    //数据成员
    std::string query_word;
};

inline
Query::Query( const std::string &s ):
    spq( new WordQuery( s ) ) { }

class NotQuery: public Query_base{
    friend Query operator~( const Query& );
private:
    NotQuery( const Query &q ):
        query( q ) { }
    std::string rep() const { return "~(" + query.rep() + ")"; }
    QueryResult eval( const TextQuery &tq ) const override;
    Query query;
};


inline Query operator~( const Query &operand ){
    return std::shared_ptr ( new NotQuery( operand ) );
}

QueryResult NotQuery::eval( const TextQuery &tq ) const{
    //先获得含有该关键字的行号set的QueryResult
    auto result = query.eval( tq );

    //not_lines用来保存单词没出现的行号,也就是出现行号的补集,
    //初始时为空。
    auto not_lines = make_shared> ();

    //获得指向result对象里面保存行号的set的首迭代器和尾后迭代器。
    auto beg = result.begin(), end = result.end();
    //获得文本的最大行号
    auto sz = result.get_file()->size();

    for( line_no i = 1; i < sz; ++i ){
        if( beg == end || *beg != i )
            not_lines->insert( i );
        else if( *beg == i )
            ++beg;
    }
    return QueryResult( rep(), not_lines, result.get_file() );
}

class AndQuery: public BinaryQuery{
    friend Query operator&( const Query&, const Query& );
private:
    AndQuery( const Query &left, const Query &right ):
        BinaryQuery( left, right, "&" ) { }
    //rep()继承了BinaryQuery
    QueryResult eval( const TextQuery &tq ) const override;
};

inline Query operator&( const Query &lhs, const Query &rhs ){
    return std::shared_ptr ( new AndQuery( lhs, rhs ) );
}

QueryResult AndQuery::eval( const TextQuery &tq ) const{
    //分别获得 左操作数 和 右操作数 的QueryResult;
    auto left = lhs.eval( tq );
    auto right = rhs.eval( tq );

    //ret_lines用来保存行号的交集,初始为空
    auto ret_lines = make_shared> ();
    //使用标准库合并两个保存行号的set,只留下都两个单词都出现的行号。
    set_intersection( left.begin(), left.end(),
                      right.begin(), right.end(), inserter( *ret_lines, ret_lines->begin()) );

    return QueryResult( rep(), ret_lines, left.get_file() );
}

class OrQuery: public BinaryQuery{
    friend Query operator|( const Query&, const Query& );
private:
    OrQuery( const Query &left, const Query &right ):
        BinaryQuery( left, right, "|" ) { }

    QueryResult eval( const TextQuery& ) const override;
};

inline Query operator|( const Query &lhs, const Query &rhs )
{
    return std::shared_ptr ( new OrQuery( lhs, rhs ) );
}

QueryResult OrQuery::eval( const TextQuery &tq ) const{
    //获得 左操作数 和 右操作数 的QueryResult
    auto left = lhs.eval( tq );
    auto right = rhs.eval( tq );
    //获得 左操作数对应的行号,顺便也用来返回。
    auto ret_lines = make_shared> ( left.begin(), left.end() );

    ret_lines->insert( right.begin(), right.end() );

    return QueryResult( rep(), ret_lines, left.get_file() );
}


#endif // QUERY_H_INCLUDED



练习15.36:在构造函数和rep成员中添加打印语句,运行你的代码以检验你对本第一个练习的(a) (b)两小题的回答是否正确。

略了。


练习15.37:如果在派生类中含有shared_ptr类型的成员而非Query类型的成员,则你的类需要做出什么样的改变。(标准)

书中的实现方式是用Query类封装了Query_base指针,管理实际查询处理用到的不同Query类型对象。如果不使用Query类,则涉及使用Query类型的地方,都要改成Query_base指针。如创建单个词查询时,就必须创建WordQuery类而不是Query对象。几个重载的布尔运算符也不能再针对Query对象,而需针对Query_base指针,从而复杂的查询请求无法写成目前的简单形式,而需逐个运算完成,将结果赋予Query_base指针,然后再进行下一步运算。资源管理方面也需要重新设计。因此,当前的设计仍是最佳方式。


练习15.38:下面的声明合法吗?如果不合法,请解释原因;如果合法,请指出该声明的含义。

BinaryQuery a = Query( "fiery" ) & Query( "bird" );

不合法。BinaryQuery是一个抽象基类。无法定义一个抽象基类。


AndQuery b = Query( "fiery" ) & Query( "bird" );

不合法。无法将Query类型拷贝初始化AndQuery类型对象。(无法实现该类型转换)


OrQuery c = Query( "fiery" ) & Query( "bird" );

不合法。无法将Query类型拷贝初始化OrQuery类型对象。


练习15.39:实现Query类和Query_base类,求图15.3中表达式的值并打印相关信息,验证你的程序是否正确。

Query类和Query_base类代码的实现见 练习15.35

#include
#include
#include"Query.h"
#include"TextQuery.h"

using namespace std;

int main()
{
    ifstream fin;
    fin.open( "T15_39.txt" );
    if( !fin ){
        cerr << "cant open file!";
        return -1;
    }
    TextQuery text( fin );
    auto q = Query( "fiery" ) & Query( "bird" ) | Query( "wind" );
    auto result = q.eval( text );

    print( cout, result );

    return 0;
}




练习15.40:OrQuery的eval函数中,如果rhs成员返回的是空集将发生什么?如果lhs是空集呢?如果lhs和rhs都是空集又将发生什么?

OrQuery的原理是:

先分别获取左 右关键字分别的QueryResult,并得到他们各自的行号的集合set。

然后合并两个set:具体实现是往左关键字对应行号的set插入右关键字中set的行号。所以,它们的set就算各为空,插入和被插入都能正确进行。


练习15.41:重新实现你的类,这次使用指向Query_base的内置指针而非shared_ptr。请注意,做出上述改动后,你的类将不能再使用合成的拷贝控制成员。

这题先搁置,回头再做。


练习15.42:从下面的改进中选择一种,设计并实现它。

(a)按句子查询并打印单词,而不再是按行打印。

(b)引入一个历史系统,用户可以按编号查询之前的某个查询,并可以在其中增加内容或者将其与其他查询组合。

(c)允许用户对结果做出限制,比如从给定范围的行中挑出匹配的进行显示。

先搁置。






你可能感兴趣的:(C++primer习题)