Простейший способ вычислить все это - создать единственный разделяемый всеми объектами вектор позиций, который содержит пары (строка, колонка) для каждого слова в тексте (полную реализацию мы рассмотрим в разделе 17.5, когда будем обсуждать функцию eval() класса NotQuery). Так или иначе, этот член мы объявим статическим для NotQuery.
Вот определение класса NotQuery (и снова рассмотрение конструкторов, деструктора и копирующего оператора присваивания отложено):
class NotQuery : public Query {
public:
// ...
// альтернативный синтаксис: явно употреблено ключевое слово virtual
// переопределение Query::eval()
virtual void eval();
// функция доступа для чтения
const Query *op() const { return _op; }
static const vector location * all_locs() {
return _all_locs; }
protected:
Query *_op;
static const vector location *_all_locs;
};
Классы AndQuery и OrQuery представляют бинарные операции, у которых есть левый и правый операнды. Оба операнда могут быть объектами любого из производных классов, поэтому мы определим соответствующие члены как указатели на тип Query. Кроме того, в каждом классе нужно переопределить виртуальную функцию eval(). Вот начальное определение OrQuery:
class OrQuery : public Query {
public:
// ...
virtual void eval();
const Query *rop() const { return _rop; }
const Query *lop() const { return _lop; }
protected:
Query *_lop;
Query *_rop;
};
Любой объект AndQuery должен иметь доступ к числу слов в каждой строке. В противном случае при обработке запроса AndQuery мы не сможем найти соседние слова, расположенные в двух смежных строках. Например, если есть запрос:
tell && her && magical
то нужная последовательность находится в третьей и четвертой строках:
like a fiery bird in flight. A beautiful fiery bird, he tells her,
magical but untamed. "Daddy, shush, there is no such thing, "
Векторы позиций, ассоциированные с каждым из трех слов, следующие:
her ((0,7),(1,5),(2,12),(4,11))
magical ((3,0))
tell ((2,11),(4,1),(4,10))
Если функция eval() класса AndQuery "не знает ", сколько слов содержится в строке (2), то она не сможет определить, что слова magical и her соседствуют. Мы создадим единственный экземпляр вектора, разделяемый всеми объектами класса, и объявим его статическим членом. (Реализацию eval() мы детально рассмотрим в разделе 17.5.) Итак, определим AndQuery:
class AndQuery : public Query {
public:
// конструкторы обсуждаются в разделе 17.4
virtual void eval();
const Query *rop() const { return _rop; }
const Query *lop() const { return _lop; }
static void max_col( const vector int *pcol )
{ if ( !_max_col ) _max_col = pcol; }
protected:
Query *_lop;
Query *_rop;
static const vectorint *_max_col;
};
17.2.3. Резюме
Открытый интерфейс каждого из четырех производных классов состоит из их открытых членов и унаследованных открытых членов Query. Когда мы пишем:
Query *pq = new NmaeQuery( "Monet" );
то получить доступ к открытому интерфейсу Query можно только через pq. А если пишем:
pq-eval();
то вызывается реализация виртуальной eval() из производного класса, на объект которого указывает pq, в данном случае - из класса NameQuery. Строкой
pq-display();
всегда вызывается невиртуальная функция display() из Query. Однако она выводит разрешающее множество строк объекта того производного класса, на который указывает pq. В этом случае мы не стали полагаться на механизм виртуализации, а вынесли разделяемую операцию и необходимые для нее данные в общий абстрактный базовый класс Query. display() - это пример полиморфного программирования, которое поддерживается не виртуальностью, а исключительно с помощью наследования. Вот ее реализация (это пока только промежуточное решение, как мы увидим в последнем разделе):
void
Query::
display()
{
if ( ! _solution-size() ) {
cout " \n\tИзвините, "
" подходящих строк в тексте не найдено.\n"
endl;
}
setshort::const_iterator
it = _solution-begin(),
end_it = _solution-end();
for ( ; it != end_it; ++it ) {
int line = *it;
// не будем пользоваться нумерацией строк с 0...
cout " (" line+1 " ) "
(*_text_file)[line] '\n';