Alice Emma has long flowing red hair. Her Daddy says
а вот во второй строке слова расположены так, как нужно:
when the wind blows through her hair, it looks almost alive,
Для оставшихся двух вхождений слова her слово hair не является соседним. Таким образом, ответом на запрос является вторая строка текста: (1).
Если бы не операция AndQuery, нам не пришлось бы вычислять вектор позиций для каждой операции. Но, поскольку операндом AndQuery может быть результат любого запроса, то для каждого приходится вычислять и сохранять не только множество неповторяющихся строк, но и пары (строка, колонка). Рассмотрим следующие запросы:
fiery && ( hair || bird || potato )
fiery && ( ! burr )
NotQuery может быть операндом AndQuery, следовательно, мы должны создать не просто вектор, содержащий по одному элементу для каждой подходящей строки, но и вектор, в котором хранятся позиции. (Мы еще вернемся к этому при рассмотрении функции eval() для класса NotQuery в разделе 17.5.)
Таким образом, идентифицирован еще один необходимый член - вектор позиций, ассоциированный с вычислением каждой операции. У нас есть выбор: объявить его членом каждого производного класса или членом абстрактного базового класса Query, наследуемым всеми производными. Объем памяти для хранения этого члена в обоих случаях одинаков. Мы поместим его в базовый класс, локализовав поддержку инициализации и доступа к члену.
Решение о том, представлять ли множество неповторяющихся номеров строк (мы называем его разрешающим множеством) в виде члена класса или каждый раз вычислять его, принимает разработчик. Мы предпочли вычислять его по мере необходимости, а затем сохранять адрес для последующего доступа, объявляя этот адрес членом абстрактного базового класса Query.
Для вывода найденных строк нам необходимо как разрешающее множество, так и фактический текст, из которого взяты строки. Причем вектор позиций у каждой операции должен быть свой, а экземпляр текста нужен только один. Поэтому мы определим его статическим членом класса Query. (Реализация функции display() опирается только на эти два члена.)
Вот результат первой попытки создать абстрактный базовый класс Query (конструкторы, деструктор и копирующий оператор присваивания еще не объявлены: этим мы займемся в разделах 17.4 и 17.6):
#include vector
#include set
#include string
#include utility
typedef pair short, short location;
class Query {
public:
// конструкторы и деструктор обсуждаются в разделе 17.4
// копирующий конструктор и копирующий оператор присваивания
// обсуждаются в разделе 17.6
// операции для поддержки открытого интерфейса
virtual void eval() = 0;
virtual void display () const;
// функции доступа для чтения
const setshort *solution() const;
const vectorlocation *locations() const { return &_loc; }
static const vectorstring *text_file() {return _text_file;}
protected:
setshort* _vec2set( const vectorlocation* );
static vectorstring *_text_file;
setshort *_solution;
vectorlocation _loc;
};
inline const setshort
Query::
solution()
{
return _solution
? _solution
: _solution = _vec2set( &_loc );
}
Странный синтаксис
virtual void eval() = 0;
говорит о том, что для виртуальной функции eval() в абстрактном базовом классе Query нет определения: это чисто виртуальная функция, "удерживающая место" в открытом интерфейсе иерархии классов и не предназначенная для непосредственного вызова из программы. Вместо нее каждый производный класс должен предоставить настоящую реализацию. (Подробно виртуальные функции будут рассматриваться в разделе 17.5.)
17.2.2. Определение производных классов
Каждый производный класс наследует данные и функции-члены своего базового класса, и программировать приходится лишь те аспекты, которые изменяют или расширяют его поведение. К примеру, в классе NameQuery необходимо определить реализацию eval(). Кроме того, нужна поддержка для хранения слова-операнда, представленного объектом класса типа string.