C++_Primer15.oop

面向对象程序设计

核心思想:数据抽象、继承和动态绑定

概述

  • 数据抽象:将类的接口与实现分离
  • 继承:定义相似的类型并对其相似关系建模
  • 动态绑定:在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象
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;
private:
    std::string bookNo;
protected:
    double price = 0.0;
};

class Bulk_quote : public Quote {
public:
    Bulk_quote() = default;
    Bulk_quote(const std::stirng&, double, std::size_t, double);
    double net_price(std::size_t) const override;
private:
    std::size_t min_qty = 0;
    double discount = 0.0;
};

在调用net_price函数时,会根据所调用它的对象选择不同的函数版本,所以动态绑定有时也叫运行时绑定。

virtual 只能出现在类内部等声明语句之前,而不能用于类外的函数定义。
如果基类把一个函数声明为虚函数,则该函数在派生类中隐式地也是虚函数。
如果成员函数没有被声明为虚函数,则其解析过程发生在编译时而非运行时。

c++11允许派生类显式地注明它使用哪个成员函数覆盖了它继承的虚函数,即在形参列表后面、或者const成员函数的const关键字后面、或在引用成员函数的引用限定符后面添加关键字 override.

如果想将某个类用作基类,则该类必须已经定义而非只是声明。

基态类和派生类

基类通常都应该定义一个虚析构函数

定义派生类

类派生列表:冒号后面紧跟以逗号分割的基类列表,其中每个基类前可以有三种访问说明符中的一个:public,protected或private

派生列表访问说明符决定了派生类向基类转换的可访问性:(假设D继承自B)

  • 只有当D共有地继承B时,用户代码才能使用派生类向基类的转换;如果D继承B的方式是受保护的或私有的,则用户代码不能使用该转换
  • 不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的
  • 如果D继承B的方式是共有的或受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;反之,如果D继承B的方式是私有的,则不能使用

尽管派生类对象中含有从基类继承来等成员,但派生类并不直接初始化这些成员。派生类必须使用基类的构造函数来初始化它的基类部分:

Bulk_quote(const std::string& book, double p, std::size_t qty, double disc):
        Quote(book, p), min_qty(qty), discount(disc) {}

防止继承的发生

在类名后跟一个关键字 final:

class NoDerived final {};
class Base {};
class Last final : Base {};     // Last 是 final的,不能作为基类
class Bad: NoDerived {};        // 错误
class Bad2: Last {};            // 错误

类型转换与继承

可以将基类的指针或引用绑定到派生类对象上。比如用 Quote& 指向一个 Bulk_quote 对象,也可以把一个 Bulk_quote 对象的地址赋给一个 Quote*.

智能指针类也支持派生类向基类的类型转换

不存在从基类到派生类的隐式类型转换:

Bulk_quote bulk;
Quote* itemP = &bulk;           // 正确,动态类型是 Bulk_quote
Bulk_quote* bulkP = itemP;      // 错误

编译器在编译时不能确定某个特定的转换在运行时是否安全,因为编译器只能通过检查指针或引用的静态类型来推断该转换是否合法。如果我们已知某个基类向派生类的转换是安全的,则我们可以使用 static_cast 来强制覆盖掉编译器的检查工作。

动态类型只存在于引用或指针的情况,对象直接调用成员仅是一次常规函数调用

使用基类构造函数和拷贝赋值运算符时,会忽略掉派生类的数据,只保留基类变量:

Bulk_quote bulk;
Quote item(bulk);       // 使用 Quote::Quote(const Quote&) 构造函数
item = bulk;            // 调用 Quote::operator=(const Quote&)
  • 从派生类向基类的类型转换只对指针或引用类型有效
  • 基类向派生类不存在隐式类型转换
  • 派生类向基类的类型转换也可能会由于访问受限而变得不可能

通常能够将一个派生类对象拷贝、移动或赋值给一个基类对象。但这种操作只处理派生类对象的基类部分

虚函数

使用基类的引用或指针调用一个虚函数成员时会执行动态绑定,直到运行时才确定到底使用哪个版本的虚函数,所以通常所有虚函数都必须有定义。理论上如果我们不使用某个函数,则无需为该函数提供定义。
派生类中的虚函数可以用 virtual 修饰来指出该函数的性质,但并非必须。因为一旦某个函数被声明为虚函数,则所有派生类中都是虚函数。
派生类的虚函数返回类型必须与基类的相匹配。但如果返回类型是类本身的指针或引用,则可以不同。比如基类的虚函数返回基类类型的指针,则派生类可以返回派生类类型的指针。

当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同

默认实参

如果通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。

如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

回避虚函数的机制

如果希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。使用作用域运算符可以实现:

double undiscounted = baseP->Quote::net_price(42);

该调用属于静态绑定,会在编译时完成解析。

通常,基类的版本要完成继承层次中所有类型都要做的共同任务,而派生类中的版本需要执行一些派生类本身密切相关的操作。

抽象基类

类似 Java 中的接口 Interface

含有纯虚函数的类时抽象基类:

class Disc_quote: public Quote {
public:
    Disc_quote() = default;
    Disc_quote(const std::string& book, double price, 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 (类内声明)。

抽象基类只负责定义接口,而后续其他类可以覆盖该接口,不能直接创建一个抽象基类的对象。

基类应该将其接口声明为共有,并将其分为两组:
一组可供派生类访问,另一组只能由基类及其基类的友元访问。
前者应声明为受保护的,后者应声明为私有的

友元与继承

友元关系不能传递,所以不能继承。基类的友元在访问派生类成员时不具有特殊性,类似的,派生类的友元也不能随意访问基类的成员。(基类的友元可以访问派生类对象的基类部分)

// Sneaky 继承 Base,Pal 是 Base的友元,不是 Sneaky的友元;
// prot_mem 是 Base 的成员,j 是 Sneaky 的成员
class Base {
    friend class Pal;
};
class Pal {
public:
    int f(Base b) { return b.prot_mem; }        // 正确,Pal 是 Base 的友元
    int f2(Sneaky s) { return s.j; }            // 错误:Pal 不是 Sneaky 的友元
    int f3(Sneaky s) { return s.prot_mem; }     // 正确
};

class D2 : public Pal {
public:
    int mem(Base b) {
        return b.prot_mem;                      // 错误,友元关系不能继承
    }
};

改变成员的可访问性

派生类可以使用using改变可访问性,只能为那些能访问的名字提供 using 声明

class Base {
public:
    std::size_t size() const { return n; }
protected:
    std::size_t n;
};

// 注意,private继承,Base 的成员对于 Derived来说是private的
class Derived : private Base {
    // 改变了成员 size 和 n 的可访问性
public:
    using Base::size;
protected:
    using Base::n;
}

默认继承保护级别

class 定义的派生类是私有继承的;struct定义的时共有继承:

class Base {};
struct D1 : Base {};    // public继承
class D2 : Base {};     // private继承

继承中的类作用域

派生类的作用域嵌套在其基类的作用域之内,如果一个名字在派生类的作用域中无法正确解析,则编译器会继续在外层的基类作用域中寻找该名字的定义。(查找过程发生在编译期)

基类的引用和指针只能访问基类部分的成员,无法访问衍生类的成员:

class Disc_quote : public Quote {
public:
    // discount_policy 是衍生类 Disc_quote 独有的成员
    std::pair<size_t, double> discount_policy() const {
        return {quantity, discount};
    }
    // ...
};

Bulk_quote bulk;
Bulk_quote* bulkP = &bulk;      // 静态类型与动态类型一致
Quote* itemP = &bulk;           // 静态类型与动态类型不一致
bulkP->discount_policy();       // 正确
itemP->discount_policy();       // 错误,discount_policy 是衍生类独有的成员,基类无法访问

衍生类的成员将隐藏同名基类成员

struct Base {
    Base(): mem(0) {}
protected:
    int mem;
};
struct Derived : Base {
    Derived(int i): mem(i) {}       // 用i初始化Derived::mem; Base::mem进行默认初始化
    int get_mem() { return mem; }   // 返回 Derived::mem
protected:
    int mem;                        // 隐藏基类中的 mem
};

Derived d(42);
cout << d.get_mem() << endl;        // 42

同名函数的隐藏

派生类中的函数不会重载其基类中的成员,即衍生类会隐藏基类中的同名成员,不存在重载:

struct Base {
    int memfcn();
};
struct Derived : Base {
    int memfcn(int);    // 隐藏基类的 memfcn
};
Derived d; Base b;
b.memfcn();             // Base::memfcn
d.memfcn(10);           // Derived::memfcn
d.memfcn();             // 错误,参数列表为空的memfcn被隐藏了
d.Base::memfcn();       // 正确,调用 Base::memfcn

虚函数的覆盖

class Base {
public:
    virtual int fcn();
};
class D1 : public Base {
public:
    // 隐藏基类的fcn,这个fcn不是虚函数
    int fcn(int);           // 形参列表与Base中的fcn不一致
    virtual void f2();      // 新的虚函数
};
class D2 : public D1 {
public:
    int fcn(int);           // 常规函数,隐藏了 D1::fcn(int)
    int fcn();              // 覆盖了 Base 的 fcn
    void f2();              // 覆盖了 D1 的 f2
};

Base b;
D1 d1;
D2 d2;
Base* bp1 = &b, * bp2 = &d1, * bp3 = &d2;
bp1->fcn();     // 虚调用,运行时调用 Base::fcn
bp2->fcn();     // 虚调用,指针调用,运行时调用 Base::fcn
            // 对象直接调用 d1.fcn() 会报错
            // d1.Base::fcn() 正确
bp3->fcn();     // 虚调用,运行时调用 D2::fcn

bp1->fcn(42);   // 错误
bp2->fcn(42);   // 静态绑定,调用 D1::fcn(42)
bp3->fcn(42);   // 静态绑定,调用 D2::fcn(42)

D1* d1p = &d1;
D2* d2p = &d2;
bp2->f2();      // 错误,Base中没有 f2
d1p->f2();      // 虚调用,运行时调用 D1::f2()
d2p->f2();      // 虚调用,运行时调用 D2::f2()

隐藏和覆盖

隐藏(hiding):当派生类定义了一个与基类同名的函数时,派生类的函数会隐藏与之同名的基类函数,即使它们的参数列表不同。此时,如果通过基类指针或引用调用该函数,则实际上调用的是基类函数,而不是派生类函数。

覆盖(overriding):当派生类定义了一个与基类同名、参数列表相同的函数时,这个派生类函数就会覆盖基类函数。在这种情况下,如果通过基类指针或引用调用该函数,则实际上调用的是派生类函数,而不是基类函数。

可以说,隐藏是指基类函数被派生类同名函数覆盖但仍然存在,导致无法使用基类函数;而覆盖则是指基类函数被派生类同名函数完全覆盖,使得只能使用派生类函数。

总之,隐藏和覆盖都涉及到类的成员函数重载问题,在设计类继承时需要注意。

构造函数和拷贝控制

虚析构函数

当 delete 一个动态分配的对象的指针时将执行析构函数。如果该指针指向继承体系中的某个类型,则有可能出现指针的静态类型与被删除对象的动态类型不符的情况。例如 delete 一个 Quote* 类型的指针,而它实际指向一个 Bulk_quote 的析构函数,这种情况下,编译器就必须清楚它应该执行的是 Bulk_quote 的析构函数。我们通过在基类中将析构函数定义成虚函数以确保执行正确版本的析构函数版本:

class Quote {
public:
    virtual ~Quote() = default;
};

Quote* itemP = new Quote;
delete itemP;               // 调用 Quote 的析构函数
itemP = new Bulk_quote;     // 静态类型与动态类型不一致
delete itemP;               // 调用 Bulk_quote 的析构函数

如果基类定义了虚析构函数,即使通过=default的形式使用了合成的版本,编译器也不会为基类和派生类合成移动操作。

合成拷贝控制与继承

class Quote {
public:
    Quote() = default;
    Quote(const Quote&) = default;  // 拷贝
    Quote(Quote&&) = defualt;       // 移动
    Quote& operator=(const Quote&) = default;   // 拷贝赋值
    Quote& operator=(Quote&&) = default;        // 移动赋值
    virtual ~Quote() = default;
};

衍生类对象在拷贝和移动时,基类部分的成员由基类拷贝控制成员进行拷贝和移动,衍生类部分则依赖衍生类的拷贝控制成员。析构函数也是类似。

如果基类的拷贝控制成员是 delete 的,则衍生类的也是 delete 的。如果析构函数是delete的,则拷贝控制成员也是delete的:

class B {
public:
    B();
    B(const B&) = delete;   // 拷贝构造函数被删除
    // 其他成员,不含有移动构造函数
    ...
};
class D : public B {
    // 没有声明任何构造函数
};

D d;                    // 正确,D的合成默认构造函数使用B的默认构造函数
D d2(d);                // 错误,D的合成拷贝构造函数是被删除的
D d3(std::move(d));     // 错误,隐式地使用D的被删除的拷贝构造函数

拷贝或移动时,基类部分的成员不会自动拷贝和移动,衍生类需要显式调用他们:

class Base { ... };
class D : public Base {
public:
    D(const D& d): Base(d) { ... }
    D(D&& d): Base(std::move(d)) { ... }
};

而析构函数却是自动调用的:

class D: public Base {
public:
    // Base::~Base 被自动调用执行
    ~D() { ... }
}

对象销毁的顺序正好与其创建的顺序相反:派生类析构函数首先执行,然后是基类的析构函数,以此类推。

赋值运算符与拷贝控制函数一样,也必须显式地为基类部分赋值:

// Base::operator=(rhs) 不会自动调用
D& D::operator=(const D& rhs) {
    Base::operator=(rhs);
    // 衍生类部分的赋值
    ...
    return *this;
}

继承的构造函数

类不能继承默认、拷贝和移动构造函数,如果派生类没有直接定义这些构造函数,编译器会为派生类合成他们。
派生类继承基类构造函数的方式是提供一条注明了直接基类名的 using 声明语句:

class Bulk_quote : public Disc_quote {
public:
    using Disc_quote::Disc_quote;           // 继承Disc_quote的构造函数
    double net_price(std::size_t) const;
};

通常,using 声明语句只是令某个名字在当前作用域可见,而当作用域构造函数时,using 声明语句将令编译器产生代码。对于基类的每个构造函数,编译器都生成一个与之对应的派生类构造函数。

// 构造函数等价于:
Bulk_quote(const std::string& book, double price, std::size_t qty, double disc):
        Disc_quote(book, price, qty, disc) {}

继承的构造函数特点

  • 构造函数的using声明不会改变访问级别
  • using声明语句不能指定 explicit 或 constexpr
    • 如果基类构造函数是 explicit 或 constexpr,则继承的构造函数也拥有相同的属性
  • 对于含有默认实参的构造函数,这些实参不会被继承,相反,派生类会获得多个继承的构造函数,每个构造函数分别省略掉一个含有默认实参的形参
  • 一般派生类会继承所有基类的构造函数,但有两个例外:
    • 派生类定义了与基类相同的参数列表,则该构造函数将不会被继承
    • 默认、拷贝和移动构造函数不会被继承。他们会按照正常规则被合成
      • 继承的构造函数不会被作为用户定义的构造函数来使用,所以如果一个类只含有继承的构造函数,则它将拥有一个合成的默认构造函数

容器与继承

如果使用vector保存基类类型的对象,衍生类可以转换成基类,但只能保留基类部分的成员:

vector<Quote> basket;
basket.push_back(Quote("123456", 50));
// 正确,但只能把对象的 Quote 部分拷贝给 basket
basket.push_back(Bulk_quote("123456", 50, 10, .25));
// 调用 Quote 定义的版本,打印 750,即15×50
cout << basket.back().net_price(15) << endl;

存放(智能)指针而非对象:

vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("123456", 50));
basket.push_back(make_shared<Bulk_quote>("123456", 50, 10, .25));
// 调用的net_price的版本依赖于指针指向的动态类型
cout << basket.back()->net_price(15) << endl;

隐藏指针

定义虚拷贝函数,返回指针类型,加入容器时将其转换为智能指针:

class Quote {
    publi:
    virtual Quote* clone() const & { return new Quote(*this); }
    virtual Quote* clone() && { return new Quote(std::move(*this)); }
    ...
};
class Bulk_quote : public Quote {
    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.insert(std::shared_ptr<Quote>(std::move(sale).clone()));
    }
};

文本查询程序

功能描述:
从一个文本文件中查询指定的字符串(单词),查询条件支持以下形式:

  • 单词查询: magical
  • 逻辑非查询: ~(Alice)
  • 逻辑或查询: (hair | Alice)
  • 逻辑与查询: (hair & Alice)
  • 混合查询,比如: fiery & bird | wind

文本:

Alice Emma has long flowing red hair.
Her Daddy says when the wind blows
through her hair, it looks almost alive,
like a fiery bird in flight.
A beautiful fiery bird, he tells her,
magical but untamed.
"Daddy, shush, there is no such thing,"
she tells him, at the same time wanting
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"

输出格式如下:(打印所有行,按行排序)

Executing Query for: Daddy
Daddy occurs 3 times
(line 2) Her Daddy says when the wind blows
...

面向对象编程,继承体系:

graph TD q[Query_base] --> wq[WordQuery] q --> nq[NotQuery] q --> bq[BinaryQuery] bq --> aq[AndQuery] bq --> oq[OrQuery]

WordQuery 构造器接受一个string参数,NotQuery 接受一个 Query 对象,BinaryQuery 是二元查找,接受两个 Query 对象。

对于符合查询,可以用类似以下的方式组织结果:

Query q = Query("fiery") & Query("bird") | Query("wind");

定义 Query 对象的三个重载运算符以及一个接受 string 参数的 Query 构造函数:

  • &运算符生成一个绑定到新的 AndQuery 对象上的 Query 对象
  • |运算符生成一个绑定到新的 OrQuery 对象上的 Query 对象
  • ~运算符生成一个绑定到新的 NotQuery 对象上的 Query 对象
  • 接受 string 参数的 Query 构造函数生成一个新的 WordQuery 对象

Query 接口设计:

接口类和操作 操作说明
TextQuery 读入特定文件并构建一个查找图。这个类包含一个 query 操作,
接受一个 string 实参,返回一个 QueryResult 对象;
QueryResult 对象表示 string 出现的行
QueryResult 保存一个 query 操作的结果
Query 接口类,指向 Query_base 派生类的对象
Query q(s) 将 Query 对象 q 绑定到一个存放着 string s 的新 WordQuery 对象上
q1 & q2 返回一个 Query 对象,绑定到一个存放 q1 和 q2 的新 AndQuery 对象上
`q1 q2`
~q 返回一个 Query 对象,绑定到一个存放 q 的新 NotQuery 对象上

Query 实现类(派生类):

派生类 说明
Query_base 抽象基类
WordQuery 查找一个单词
NotQuery 查询 Query 运算对象没有出现的行的集合
BinaryQuery 抽象基类,表示二元查询
OrQuery BinaryQuery 的派生类,表示并集
AndQuery BinaryQuery 的派生类,表示交集

Query 类对外提供接口,同时隐藏了 Query_base 的继承体系。因为 Query 是 Query_base 的唯一入口,所以 Query 需要定义自己的 eval 和 rep 版本,在其中调用 Query_base 的 eval 和 rep.

// Query.h
#ifndef QUERY_H_
#define QUERY_H_

#include <string>
#include <iostream>
#include <vector>
#include <memory>
#include <map>
#include <set>
#include <fstream>
#include <sstream>

class QueryResult;
class Query;
class Query_base;

// 保存文本
// lines: 保存每行
// word_map: 保存单词字典
// query(): 从字典中查询单词
class TextQuery {
    friend class QueryResult;
public:
    TextQuery(std::ifstream&);
    QueryResult query(const std::string&) const;
    const std::shared_ptr<std::vector<std::string>>& get_lines() const { return lines; }
private:
    std::shared_ptr<std::vector<std::string>> lines;
    std::shared_ptr<std::map<std::string,
        std::shared_ptr<std::set<int>>>> word_map;
};

// 查询结果
// word: 查询单词
// line_no: 单词所在行
// print(): 打印每行
// get_line(): 从tp中获取每行文本
class QueryResult {
public:
    QueryResult(const std::string& w, const TextQuery* t)
            : word(w), tp(t) {}
    const std::string word;
    std::set<int> line_no;
    void print();
private:
    const std::string& get_line(int);
    const TextQuery* tp;
};

// 查询类的抽象基类
// eval(): 根据单词查找文本行
// rep(): 返回查询的单词
class Query_base {
    friend class Query;
protected:
    virtual ~Query_base() = default;
private:
    virtual QueryResult eval(const TextQuery&) const = 0;
    virtual std::string rep() const = 0;
};

// 查找接口,供外部访问
// 公共构造器接受一个单词,将q初始化为WordQuery对象
// 私有构造器供友元操作使用
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;
};

// 查询单词类
class WordQuery : public Query_base {
    friend class Query;
    WordQuery(const std::string& w): word(w) {}
    QueryResult eval(const TextQuery&) const;
    std::string rep() const;
    std::string word;
};

// 非查询
// 根据WordQuery的结果,取相反的行号
class NotQuery : public Query_base {
    friend Query operator~(const Query&);
    NotQuery(const Query& q): query(q) {}
    QueryResult eval(const TextQuery&) const;
    std::string rep() const {
        return "~(" + query.rep() + ")";
    }
    const Query query;
};

// 二元查询
class BinQuery : public Query_base {
protected:
    BinQuery(const Query& q1, const Query& q2, const std::string& s):
        query1(q1), query2(q2), symbol(s) {}
    std::string rep() const {
        return "(" + query1.rep() + " " + symbol + " " + query2.rep() + ")";
    }
    const Query query1;
    const Query query2;
    const std::string symbol;
};

// 或查询
// 将两个WordQuery的结果取并集
class OrQuery : public BinQuery {
    friend Query operator|(const Query&, const Query&);
    OrQuery(const Query& q1, const Query& q2, const std::string& s):
        BinQuery(q1, q2, s) {}
    QueryResult eval(const TextQuery&) const;
};

class AndQuery : public BinQuery {
    friend Query operator&(const Query&, const Query&);
    AndQuery(const Query& q1, const Query& q2, const std::string& s):
        BinQuery(q1, q2, "&") {}
    QueryResult eval(const TextQuery&) const;
};

Query operator~(const Query&);
Query operator|(const Query&, const Query&);
Query operator&(const Query&, const Query&);

#endif


// Query.cpp
#include "Query.h"


Query::Query(const std::string& word) {
    q = std::shared_ptr<WordQuery>(new WordQuery(word));
}

TextQuery::TextQuery(std::ifstream& is) {
    lines = std::make_shared<std::vector<std::string>>();
    word_map = std::make_shared<std::map<std::string, std::shared_ptr<std::set<int>>>>();
    std::string line;
    int idx = 0;
    while (getline(is, line)) {
        lines->push_back(line);
        std::istringstream iss(line);
        std::string word;
        while (iss >> word) {
            if (word_map->find(word) != word_map->end()) {
                (*word_map)[word]->insert(idx);
            } else {
                std::shared_ptr<std::set<int>> s(new std::set<int>{idx});
                word_map->insert(make_pair(word, s));
            }
        }
        ++idx;
    }
}

QueryResult TextQuery::query(const std::string& word) const {
    QueryResult ret(word, this);
    auto pair = word_map->find(word);
    if (pair != word_map->end()) {
        auto sp = pair->second;
        for (auto it = sp->begin(); it != sp->end(); ++it) {
            ret.line_no.insert(*it);
        }
    }
    return ret;
}

const std::string& QueryResult::get_line(int idx) {
    return (*(tp->lines))[idx];
}

void QueryResult::print() {
    std::cout << "Executing Query for: " << word << std::endl;
    std::cout << word << " occurs " << line_no.size() << " times" << std::endl;
    for (auto it = line_no.begin(); it != line_no.end(); ++it) {
        // 打印行号时从1开始
        std::cout << "(line " << (*it+1) << ") " <<
            (*(tp->lines))[*it] << std::endl;
    }
}

QueryResult WordQuery::eval(const TextQuery& t) const {
    return t.query(word);
}

std::string WordQuery::rep() const {
    return word;
}

QueryResult NotQuery::eval(const TextQuery& t) const {
    QueryResult ret(query.eval(t));
    QueryResult ret1(this->rep(), &t);
    int idx(0);
    auto lines = t.get_lines();
    for (auto it = lines->begin(); it != lines->end(); ++it) {
        if (ret.line_no.find(idx) == ret.line_no.end()) {
            ret1.line_no.insert(idx);
        }
        ++idx;
    }
    return ret1;
}

Query operator~(const Query& q) {
    return std::shared_ptr<Query_base>(new NotQuery(q));
}

QueryResult OrQuery::eval(const TextQuery& t) const {
    QueryResult ret1(query1.eval(t));
    QueryResult ret2(query2.eval(t));
    QueryResult ret3(this->rep(), &t);
    ret3.line_no = ret1.line_no;
    for (auto it = ret2.line_no.begin(); it != ret2.line_no.end(); ++it) {
        ret3.line_no.insert(*it);
    }
    return ret3;
}

Query operator|(const Query& q1, const Query& q2) {
    return std::shared_ptr<Query_base>(new OrQuery(q1, q2, "|"));
}

QueryResult AndQuery::eval(const TextQuery& t) const {
    QueryResult ret1(query1.eval(t));
    QueryResult ret2(query2.eval(t));
    QueryResult ret3(this->rep(), &t);
    for (auto it = ret2.line_no.begin(); it != ret2.line_no.end(); ++it) {
        if (ret1.line_no.find(*it) != ret1.line_no.end()) {
            ret3.line_no.insert(*it);
        }
    }
    return ret3;
}

Query operator&(const Query& q1, const Query& q2) {
    return std::shared_ptr<Query_base>(new AndQuery(q1, q2, "&"));
}

// main.cpp
#include <fstream>
#include "Query.h"

int main(int argc, char** argv) {
    std::ifstream is("text");
    TextQuery t(is);
    is.close();

    std::string s("the");
    if (argc == 2) {
        s = argv[1];
    }
    Query q(s);
    QueryResult ret = q.eval(t);
    ret.print();

    QueryResult ret1 = (~q).eval(t);
    ret1.print();

    Query q2 = q | Query("Alice");
    QueryResult ret2 = q2.eval(t);
    ret2.print();

    Query q3 = q & Query("wind");
    QueryResult ret3 = q3.eval(t);
    ret3.print();

    Query q4 = Query("fiery") & Query("bird") | Query("wind");
    QueryResult ret4 = q4.eval(t);
    ret4.print();

    return 0;
}

编译执行:

$ g++ --std=c++11 -c Query.cpp -I./ && g++ --std=c++11 -o main main.cpp Query.o -I./ && ./main the
Executing Query for: the
the occurs 2 times
(line 2) Her Daddy says when the wind blows
(line 8) she tells him, at the same time wanting
Executing Query for: ~(the)
~(the) occurs 9 times
(line 1) Alice Emma has long flowing red hair.
(line 3) through her hair, it looks almost alive,
(line 4) like a fiery bird in flight.
(line 5) A beautiful fiery bird, he tells her,
(line 6) magical but untamed.
(line 7) "Daddy, shush, there is no such thing,"
(line 9) him to tell her more.
(line 10) Shyly, she asks, "I mean, Daddy, is there?"
(line 11)
Executing Query for: (the | Alice)
(the | Alice) occurs 3 times
(line 1) Alice Emma has long flowing red hair.
(line 2) Her Daddy says when the wind blows
(line 8) she tells him, at the same time wanting
Executing Query for: (the & wind)
(the & wind) occurs 1 times
(line 2) Her Daddy says when the wind blows
Executing Query for: ((fiery & bird) | wind)
((fiery & bird) | wind) occurs 2 times
(line 2) Her Daddy says when the wind blows
(line 4) like a fiery bird in flight.

小结

C++ 中,动态绑定只作用与虚函数,并需要通过指针或引用调用。

动态绑定使得我们可以忽略继承时类型之间的差异,其机理时在运行时根据对象的动态类型来选择运行函数的哪个版本。
继承和动态绑定的结合使得我们能够编写具有特定类型行为但又独立于类型的程序。

当执行派生类的构造、拷贝、移动和赋值时,首先构造、拷贝、移动和赋值其中的基类部分,然后才轮到派生类部分。
析构函数的执行顺序则正好相反。
基类通常应该定义一个虚析构函数,即使基类根本不需要析构也最好这样做。
析构函数定义为虚函数的原因是为了确保当删除一个基类指针,而该指针实际指向一个派生类对象时,程序也能正确运行。

posted @ 2023-06-12 12:34  keep-minding  阅读(14)  评论(0)    收藏  举报