C++11(14):面向对象程序设计

来源:互联网 发布:网络用语lay了什么意思 编辑:程序博客网 时间:2024/06/01 08:46
面向对象程序设计基于三个基本概念:数据抽象、继承和动态绑定。
继承和动态绑定对程序的编写有两方面影响:一是我们可以更容易地定义与其他类似但不完全相同的新类:二是在使用这些彼此相类似的类编写程序时,我们可以在一定程度上忽略掉他们的区别。
使用数据抽象,我们可以将类的接口和实现分离,使用继承,可以定义相类似的类型并对其相类似的关系建模,使用动态绑定,可以再一定程度上忽略相似类型的区别,而以统一的方式使用它们。
派生类可以再需要重新定义的函数之前加上virtual,但不是必须的。
在c++中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。
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;
privete :
    std::string bookNo;
protected:
    double price = 0.0;
};
我们通常会定义一个虚析构函数,即使该函数不指向任何实际操作也是如此。


基类有两种成员函数:一种是基类希望其派生类进行覆盖(override)的函数;另一种是基类希望派生类直接继承而不要改变的函数。前者是虚函数。当我们用指针或引用调用时,该调用将被动态绑定。
任何构造函数之外的非静态函数都可以是虚函数。
virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。如果我们把以个函数声明成虚函数,则该函数的在派生类中隐式地也是虚函数。
成员函数如果么有被声明为虚函数,则其解析过程发生在编译时而非运行时。
派生类能访问基类的共有成员,而不能访问私有成员。受保护的成员允许派生类访问,同时禁止其他用户访问。

class Bulk_quote : public Quote {
public:
    Bulk_quote( ) = default;
    Bulk_quote(const std::string& , double , std::size_t ,double);
    double net_price(std::size_t) const override;
private:
    std::size_t min_qty = 0;
    double discount = 0.0;
};
如果一个派生类是共有的,则基类的公有成员也是派生类的组成部分。此外,我们将公有派生类型的对象绑定到基类的引用或指针上。
派生类可以再它覆盖的函数前使用virtual关键字, 但不是非得这么做。新标准,显示的注明覆盖,用override
一个派生类对象包含多个组成部分:自己定义的和继承来的一个或多个部分。在一个对象中,继承自基类的部分和派生类自定义的部分不一定是连续存储的。
因为派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当成基类对象来使用,而且我们也可以将基类的指针或引用绑定到派生类对象中的基类部分上。
编译器会隐式执行派生类到基类的转换。
这种隐式转换的特性意味着我们可以把派生类对象或派生类对象的引用用在需要基类引用的地方。同样的我们也可以吧派生类的指针要在需要基类指针的地方。

尽管派生类对象不直接初始化基类这些成员。但是必须使用基类的构造函数类初始化它的基类部分。首先初始化基类部分,然后按照声明顺序依次初始化派生类的成员。
Bulk_quote(const std::string& book, double p, std::size_t qty, double disc) : 
        Quote(book,p) , min_qty(qty) , discount(disc){ }
除非我们特别指出,否则派生类对象的基类部分会像数据成员一样进行默认初始化。如果想使用基类的其他构造函数就必须用参数类区别。

double Bulk_quote::net_price(size_t cnt) const
{
    if(cnt >= min_qty)
        return cnt * (1-discount) * price ; 
    else
        return cnt * price;
}
派生类可以访问基类的共有和受保护成员,派生类的作用域嵌套在基类的作用域之内。
尽管在语法上类说我们可以在派生类构造函数体内给它的共有或受保护的基类成员赋值,但是最好不要这么做。应该是用基类的接口。

如果基类定义了一个静态成员,在这个继承体系中只存在该成员的唯一定义。静态成员遵循通用的访问控制规则,如果基类中的成员是private的,则派生类无权访问它。假设某个静态成员可访问的,则我们既能通过基类使用它也能通过派生类使用它。

派生类的声明不用包含基类对象。

如果我们要将一个类用作基类,则该类必须已经定义而非仅仅声明。原因是,派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类当然要知道他们是什么。因此该规定还有一层隐含的意思,即一个类不能派生它本身。
一个类是基类,同时它也可以是一个派生类:直接基类,间接基类。
直接基类出现在派生列表中,而间接基类由派生类通过其直接基类继承而来。
每个类都会继承直接基类中的所有成员。最终的派生类将包含它的直接基类的子对象以及每个间接基类的子对象。

有时候我们会定义这样一种类,我们不希望其他类继承它,或者不想考虑它是否适合做一个基类。c++11提供了一种防止继承的方法:在类名后跟一个关键字final:
class NoDerived final{ /*...*/};

一般,如果我们想把引用或指针绑定到一个对象上,则引用或指针的类型应与对象的类型一致,或者对象的类型含有一个可接受的const类型转换规则。存在继承关系的类是一个重要的例外:我们可以将基类的指针或引用绑定到派生对象上。
可以将基类的对象或指针保定到派生对象上有一层极为重要的含义:当使用基类的引用(或指针)时,实际上我们并不清楚该引用(或指针)所绑定对象的真实类型。该对象可能是基类对象,也可能是派生类对象。
和基类一样,智能指针类也支持派生类向基类的类型转换。

表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型;动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知。
基类的指针或引用的静态类型可能与动态类型不一致,如果表达式既不是用的引用也不是指针,则它的动态类型永远和静态类型一致。
当基类的指针或引用调用虚函数时,将发生多态性。
因为一个基类的对可能是派生类对象的一部分,也可能不是,所以不存在从基类想派生的自动转换。
编译器在编译时无法确定某个特定的转换在运行时是否安全,这是因为编译器只能通过检查指针或引用的静态类型类判断该转换是否合法。如果在基类中含有一个或多个虚函数,我们可以使用dynamic_cast请求以个类型转换,该转换的安全检查在运行时执行。同样的,如果我们已知某个基类向派生类的转换是安全的,则我们可以使用static_cast类强制覆盖掉编译器的检查工作。
有个特别的:即使一个基类指针或引用绑定在一个派生类对象上,我们也不能从基类向派生类的转换;也就是说派生类绑定到基类,就完成了从派生类到基类的转换,没办法在转换回去。

在派生向基类的自动类型转换只对指针和引用类型有效,在派生类型和基类类型之间不存在这样的转换。
当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中基类部分会被拷贝、移动或赋值,它的派生类部分将被忽略掉
具有继承关系的类之间发生的转换,有三点非常重要:
    从派生类向基类的类型转换只对指针或引用类型有效。
    基类向派生类不存在隐式类型转换。
    和任何其他成员一样,派生类向基类的类型转换也可能会用于访问受限而变得不可行。

因为我们直到运行时才知道到底调用了哪个版本的虚函数,所以所有的虚函数都必须有定义。通常,如果我们不用某个函数,则无需为该函数提供定义。但是我们必须为每个虚函数都提供定义,而不管它是否被用到了,这是因为连编译器也无法知道到底会使用哪个虚函数。
当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数时与绑定到指针或引用上的对象的动态类型相匹配的那一个。
我们把具有继承关系的多个类型成为多态性,因为我们能使用这些类型的”多种形式“而无须在意他们的差异。
当我们通过引用或指针可以实现多态性。但是,对非虚函数的调用在编译时进行绑定。类似的,通过对象进行的函数(虚函数或非虚函数)调用也在编译时绑定。对象的类型是确定不变的,我们无论如何都不可能令对象的动态类型与静态类型不一致。因此,通过对象进行的函数调用将在编译时绑定到对象所属类中的函数模板上。

一旦某个函数被声明成虚函数,则所有派生类中它都是虚函数,因此virtual非必须。
一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被覆盖的基类函数完全一致。
同样,派生类中虚函数的返回类型与必须与基类函数匹配。该规则存在以个例外:当类的虚函数返回类型是类本身的指针或引用时,上述规则无效。就是说,如果D由B派生得到,则基类的虚函数可以返回B*而派生类的对应函数可以返回D*,只不过这样的返回类型要求从D到B的类转换可访问。

派生类如果定义了一个函数与基类中虚函数的名字相同但是形参列表不同,这仍然是合法的行为。编译器将认为新定义的这个函数与基类中原有的函数时相互独立的。这是,派生类函数并没有覆盖掉基类中的版本。这种做法往往意味着错误。但是非常难发现。
要想调试并发现错误非常困难。在c++11中我们可以使用override。如果该函数并没有覆盖已存在的虚函数,则编译器会报错。
我们还可以把投个函数指定为final,这之后任何尝试覆盖该函数的操作都将会引发错误。
final和override说明符在形参类类别(包括任何const和引用修饰符)以及位置返回类型之后。

如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。换句话说,如果我们通过基类的引用或指针调用,则使用基类中的默认实参,即使运行的是派生类中的函数版本也是如此。这样将产生于我们预期不符的结果。
如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定的版本。可以使用域运算符类实现。
double undiscounted = baseP->Quote::net_price(42);
通常,只有成员函数(或友元)中的代码才需要使用作用域运算符类回避虚函数机制。
如果一个派生类虚函数需要调用它的基类版本,但是没有用使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。

纯虚函数无序定义,我们通过在函数体的位置(即在声明语句的分号之前)书写=0就可以将一个虚函数说明为纯虚函数。其中=0只能出现在类内部的虚函数声明语句处:
class Disc_quote : public Quote{
public:
    Disc_quote( ) =default;
    Disc_quote( const std::string& book, double pirce, std::size_t qty, double disc) :
        Quote(book, price),quantity(qty), discount(disc) { }
    double net_price(std::size_t) const = 0;
protected:
    std::size_t quantity = 0;
    double discount = 0.0;
};
值得注意的是,我们也可以为虚函数提供定义,不个函数体必须定义在外部。也就是说,我们不能在类内的内部为一个 =0 的函数提供函数体

含有(或未经覆盖直接继承)纯虚函数的类是抽象基类。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。
我们不能(直接)创建以个抽象基类的对象。

派生类构造函数只初始化它的直接基类:
class Bulk_quote : public Disc_quote{
public:
    Bulk( ) = default;
    Bulk_quote (const std::string& book, duoble price, std::size_t qty, double disc):
        Disc_quote(book, price, qty, disc) { }
    double net_price(std::size_t) const override;
};
在Quote的继承体系中增加Disc_quote类是重构的一个典型示例。重构负责重新设计类的体系以便将操作和/或数据从一个类移动到另一个类中。不过一旦类被重构(或以其他方式被改变),就意味着我们必须重新编译含有这些类的代码了。

 每个类分别控制自己的成员初始化过程,与之类似,每个类还分别控制着其成员对于派生类来说是否可访问。
protected关键字来声明那些希望与派生类分析但是不想被其他公共访问使用的成员。
    和私有成员类似,受保护的成员对于类的用户来说是不可访问的。
    和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的。
    派生类成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于以个基类对象中的受保护成员没有任何访问特权。
规定,派生类成员和友元只能访问派生类对象中的基类部分的受保护成语;对于普通的基类对象中的成员不具有特殊的访问权限。
class Base{
protected:
    int prot_men;
};
class Sneaky : public Base{
    friend void clobber(Sneaky&);
    friend void clobber(Base&);
    int j;
};
void clobber(Sneaky& s)
{
    s.j = s.prot_mem = 0;
}//正确
void clobber(Base &b)
{
    b.prot_mem = 0;
}//错误,不可访问Base的protected成员

对于派生类前的说明符不影响派生类的访问权限。但对于派生类用户来说(包括派生类的派生类)来说对于基类成员的访问权限有影响。

派生类向基类的转换是否可访问有使用该专函的代码决定,同时派生类的访问说明符也会有影响,假定D继承B:
    只有当D公有地继承B时,用户代码才能使用派生类向基类的转换;如果D继承B的方式是受保护的或者私有的,则用户代码不能使用该转换
    不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和友元类说永远是可访问的。
    如果D继承B的方式是共有的或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;反之,如果是私有的,则不能使用。

对于代码中的某个节点来说,如果基类的公有成员是可访问的,则派生类向基类的类型转换也是可访问的;否则不行。

就像友元关系不能够被传递一样,友元关系同样也不能被继承。每个类负责控制各自成员的访问权限。

有时我们需要改变派生类继承的某个名字的访问基本,通过使用using声明可以达到这一目的,派生类只能为那些它可访问的成员提供using声明:
class Base{
public:
    std::size_t size( ) const {return n;}
protected:
    std::size_t n;
};
class Derived : private Base {
public:
    using Base::size;
protected:
    using Base::n;
};
通过在类内使用using声明,我们可以将该类的直接或间接基类中的任何可访问成员标记出来。using声明语句中名字的访问权限有该类using声明语句之前的访问符来决定;

默认情况,使用class关键字的定义的派生类是私有继承的,使用struct时共有继承的。
class  Base;
struct D1 : Base{ };   //共有继承
但是最好标注,清晰。

当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。如果以个名字在派生类中无法解析,则在外层基类作用域中寻找该名字。
以个对象、引用或指针的静态类型决定了该对象的哪些成员可见。即使静态类型与动态类型可能不一致,但是我们能使用哪些成员仍然是有静态类型决定的。就是说对于派生类中独有的成员,只能通过派生类及其对象、引用或指针访问。
派生类的成员将隐藏同名的基类成员。
我们可以通过作用域运算符类使用一个被隐藏的基类成员:
除了覆盖继承而来的虚函数之外,派生类最好不好重用其他定义在基类中的名字。

一如既往,名字查找先于类型检查。声明在内层作用域的函数并不会重载声明在外层作用域的函数。在派生关系中也是一样的。即使基类成员和派生类成员的形参列表不同。
对于为什么基类与派生类中的虚函数必须使用相同的形参列表,就是因为这个。 
无论函数是不是虚函数都会被覆盖。但是我们可以通过using声明来使用基类中的每一个重载版本。

继承关系对拷贝控制最直接的影响是基类通常应该定义一个虚析构函数。这样我们就能动态分配继承体系中的对象了。
和其他函数一样我能通过将析构函数定义成虚函数以确保执行正确的析构函数版本。其虚属性也会被继承。
如果基类的析构函数不是虚函数,则delete一个指向派生类的对象的基类指针将产生未定义。

之前我们曾介绍过一条经验准则,即如果以个类需要析构函数,那么它也同样需要拷贝和赋值操作。基类的析构函数并不遵循上述准则,它是以个重要例外。以个基类总是需要析构函数,而且他能将析构函数设定为虚函数。此时,该析构函数为成为虚函数而令内容为空,我们显然无法由此推断该基类还需要赋值运算符或拷贝构造函数。
基类需要一个虚析构函数这一事实还会对基类和派生类的定义产生另外一个间接的影响:如果一个类定义了析构函数,即使它通过=default的形式使用了和成版本,编译器也不会为这个类合成移动操作。(虚析构函数将阻止合成移动操作)

基类或派生类的合成拷贝控制行为与其他合成行为类似。就是负责本身和直接基类中对应的操作对一个对象的基类部分进行操作。
就向其他任何类的情况一样,基类或派生类也能出于同样的原因将其合成的默认构造函数或任何一个拷贝控制成员定义成删除的函数。此外,某些定义基类的方式也可能导致有的派生类成员成为被删除的函数:
    如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的或是不可访问的,则派生类中对应的成员将是被删除的,原因是编译器不能使用基类成员来执行派生类对象基类部分的构造、赋值或销毁操作。
    如果在基类中有一个不可访问或删除的析构函数,则派生类中合成的默认和拷贝构造函数将是被删除的,因为编译器无法销毁派生类对象的基类部分。
    和过去一样,编译器将不合成以个删除掉的移动操作。当我们使用default请求一个移动操作时,如果基类中的对应操作是删除的或不可访问的,那么派生类中该函数将是被删除的,原因是派生类对象的基类部分不可移动。同样,如果基类的析构函数删除的或不可访问的,则派生类的移动构造函数也将是被删除的。

在一派生类中,它必须考虑任何移动或拷贝其基类部分的成员。在实际编程中,如果在基类中没有默认、拷贝或移动构造函数,则一般情况下派生类也不会定义相应的操作。

因为基类中缺少移动操作会阻止派生类拥有自己的合成操作,所以当我们确实需要执行移动操作时应该首先在基类中定义。

和构造函数及赋值运算符不同的是,析构函数只负责销毁派生类自己分配的资源。析构函数是自动调用的
当派生类定义了拷贝或移动操作时,该操作赋值拷贝或移动包括基类部分成员在内的整个对象。

当为派生类定义拷贝或移动构造函数时,通常我们使用对应的基类构造函数初始化对象的基类部分:
在默认情况下,基类默认构造函数初始化派生类对象的基类部分。如果我们想拷贝(或移动)基类部分,则必须在派生类的构造函数初始值列表中显示的使用基类的构造函数。

与拷贝和移动构造函数一样,派生类的赋值运算符也必须显示地为其基类部分赋值:
D &D::operator = (const D &rhs)
{
    Base::operator = (rhs);
    return *this;
}

对象的销毁顺正好与其创建的顺序相反:派生类析构函数首先执行,然后是基类的析构函数,以此类推,沿着继承体系的反方向直到最后。

派生类对象的基类部分先被构造,而派生部分先被销毁。期间会参数派生部分成员未定义的状态。为了能个正确理解这种未定义的状态,编译器认为,当我们创建以个对象时,对象的类和析构函数的类是同一个类。也就是说,如果构造函数或析构函数调用了某个虚函数,则我们应该执行与构造函数或析构函数所属类型相对应的虚函数版本。

在新标准中,派生类能够重用其基类定义的构造函数。尽管人我们所知,这些构造函数并非以常规方式继承而来。一个类只初始化它的直接基类,出于同样的原因,一个类也只继承其直接基类的构造函数。类不能继承默认、拷贝和移动构造函数。如果派生类没有直接定义这些构造函数,则编译器将为派生类合成他们。
我们科以使用using声明类注明基类的构造函数。
通常,using声明语句只是令某个名字在当前作用域可将。但是作用于构造函数时,using声明语句将令编译器产生代码。对于基类的每个构造函数,编译器都在派生类中生成一个形参列表完全相同的构造函数。形如:
derived(parms) : base(args) { }

和普通成员的using声明不一样,一个构造函数的using声明不会改变该构造函数的访问级别。不管using声明出现在哪,基类的私有构造函数在派生类中还是一个私有构造函数;受保护和公有的也是。
而且,一个using声明语句不能指定explicit或constexpr。如果基类的构造函数时explicit或constexpr,则继承的构造函数也拥有相同的属性。
当一个基类构造函数含有默认实参时,这些实参并不会被继承。相反,派生类将获得多个继承的构造函数,其中每个构造函数分别省略掉一个含有默认实参的形参。如基类有一个接受两个形参的构造函数,其中第二个形参含有默认实参,在派生类将得到两个构造函数:一个构造函数接受两个形参(么有默认实参),另一个只接受一个形参,它对应于基类中最左侧的没有默认值的那个形参。
如果基类含有几个构造函数,则除了两个例外,大多数时候派生类都会继承所有的这些构造函数。第一个例外是派生类可以继承一部分构造函数,而为其他构造函数定义自己的版本。如果派生类定义的构造函数与基类的构造函数具有相同的参数列表,则该构造函数将不会被继承。定义在派生类中的构造函数将替换继承而来的构造函数。
第二个例外是默认、拷贝和移动构造函数不会被继承。这些构造函数按照正常规则被合成。继承的构造函数不会被作为用户定义的构造函数来使用,因此,如果一个类只含有继承的构造函数,则它也将拥有一个合成的默认构造函数。

当我们使用容器存放继承体系中的对象时,通常必须采取间接存储的方式。因为容器不允许在容器中保存不同类型的元素,所以我们不能把具体继承关系的多种类型对象直接 存在容器中。
当我们希望在容器中存放具有继承关系的对象时,我们实际上存放的通常是基类的指针(更好的选择是智能指针)
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("0-201-82470-1", 50));
basket.push_back(make_shared<Bulk_quote>("0-201-54848-8", 50, 10, .25));
//打印562.5,调用的是Buld_quote的版本。
cout<<basket.back( )->net_price(15) <<endl;
make_shared<Bulk_quote>返回一个shared_ptr<Bulk_quote>对象,当我们调用push_back时该对象被转换成shared_ptr<Quote>。因此尽管形式在上有所差别,但实际上basket的所有元素的类型是相同的。


因为指针会增加程序的复杂性,所以我们经常定义一些辅助的类来畜类这种复杂的情况。
class Basket{
public:
    void add_item(const std::shared_ptr<Quote> &sale)
        { item.insert(sale); }
    //打印每本书的总结和购物篮中所有书的总结
    double total_receipt(ste::ostream& ) const;
private:
    static bool compare(const std::shared_ptr<Quote> &lhs, const std::shared_ptr<Quote> &rhs)
        { return lhs->isbn() <rhs->isbn() ; }
    std::multiset<std::shared_ptr<Quote>, decltype(compate)*> items(compare);
};
double Basket::total_receipt(ostream &os)const
{
    double sum = 0.0;
    //upper_bound返回一个迭代器,该迭代器指向这批元素的尾后位置
    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;
}
但是对于上面的程序我们我们在添加元素时,不的不处理动态内存。
我们会重新定义add_item使他可以接受Quote对象。唯一的问题是add_item不知道要分配的类型。当add_item进行分配内存时,我们可能传递的是一个Bulk_quote对象,此时,该对象将被迫切掉一部分。为了解决这个问题。我们给Quote加一个虚函数,
class Quote{
public:
    virtual Quote* clone( ) const &{ return new Quote(*this); }
    virtual Quote* clone( ) && { return new Quote(std::move(*this)); }
};
class Buld_quote : public Quote{
public:
    Bulk_quote* clone( ) const & {return new Bulk_quote(* this); }
    Bulk_quote* clone( ) && { return new Bulk_quote(std::move(*this)); }
};
class Basket{
public:
    void add_item(const Quote& sale)
        { items.insert(std::shared_ptr<Quote>(sale.clone())); }
    void add_item(Quote && sale)
        { items.instert( std::shared_ptr<Quote>(std::move(sale).clone())); }
};

一个例子:文本查询(早起版本 在 动态内存中)
Query_base    
WordQuery    NotQuery    BinaryQuery
                                            AndQuery          OrQuery


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 &t)const { return q->eval(t); }
    std::string rep( ) const { return q->rep( ); }
private:
    Query(std::shared_ptr<Query_base> query) : q(query) { }
    std::shared_ptr<Query_base> q;
};
std::ostream & operator<<(std::ostream &os, const Query &query)
{
    return os<<query.rep( );
}
class WordQuery : public Query_base {
    frend class Query;
    WordQuery(const std::string &s) : query_word(s) { }
    QueryResult eval(const Text &t) const { return t.query(query_word)l }
    std::stirng rep( ) const { return query_word; }
    std::string query_word;
};
inline Query::Query( const std::string &s) : q(new WordQuery(s)) { }
class NotQuery : public Query_base {
    friend Query operator~( const Query &);
    NotQuery(const Query &q) : query(q) { }
    std::string rep( ) const { return "~(" + query.rep( ) + ")"; }
    QureyResult eval(const TextQuery&)const ;
    Query query;
};
inline Query operator~( const Query &operand)
{
    return std::shared_ptr<Query_base>(new NotQuery(operand));
}
class BinaryQuery : publicd 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 AndQuery : public BinaryQuery{
    friend Query operator& (const Query& , const Query&);
    AndQuery (const Query &left , const Query &right) : BinaryQuery(left, right, "&"){ }
    QueryResult eval(const TextQuery&) const;
};
inline Query operator& (const Query &lhs, const Query &rhs)
{
    return std::shared_ptr<Query_base>(new AndQuery(lhs,rhs)) ; 
}
class OrQuery : public BinaryQuery{
    friend Query operator| ( const Query& , Queryr& right);
    OrQuery(const Query &left ,const Query &right) : BinaryQuery(left, right, "|") { } 
    QueryResult eval( const TextQuery&) const;
};
inline Query operator | (const Query &rhs, const Query &rhs)
{
    return std::shared_ptr<Query_base>(new OrQuery(lhs , rhs));
}
OrQuery::eval(const TextQuery& text) const
{
    auto right = rhs.eval(text), left = lhs.eval(text);
    auto ret_lines = make_shared<set<line_no>>(left,begin( ) , left.end( ) ) ;
    ret_lines->insert(right.begin(),right.end());
    return QueryResult(rep(),ret_lines,left.get_file( ));
}
QueryResult AndQuery::eval(const TextQuery& text) const
{
    auto left = lhs.eval(text) , right = rhs.eval(text);
    auto ret_lines = make_shared<set<line_no>>();
    set_intersection(left.begin(),left.end(),right.begin(),right.end(),inserter(*ret_lines, ret_lines->begin()));
    return QueryResult(rep(),ret_lines,letf.get_file());
}
QueryResult NotQuery::eval(const TextQuery& text) const
{
    auto result = query.eval(text);
    auto ret_lines = make_shared<set<line_no>>();
    auto beg = result.begin( ),end = result.end();
    auto sz = result.get_file()->size();
    for(size_t n= 0 ;  n != sz ; ++n)
    {
        if(beg == end || *beg !=n)
            ret_lines->insert(n);
        else if(beg != end)
            ++beg;
    } 
    return QueryResult(rep( ), ret_lines, result.get_file( ));
}
0 0
原创粉丝点击