Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

#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 set<short> *solution() const;

const vector<location> *locations() const { return &_loc; }

static const vector<string> *text_file() {return _text_file;}

protected:

set<short>* _vec2set( const vector<location>* );

static vector<string> *_text_file;

set<short> *_solution; vector<location> _loc;

};

inline const set<short> Query::

solution()

{

return _solution ? _solution

: _solution = _vec2set( &_loc );

}

Странный синтаксис

virtual void eval() = 0;

говорит о том, что для виртуальной функции eval() в абстрактном базовом классе Query нет определения: это чисто виртуальная функция, “удерживающая место” в открытом интерфейсе иерархии классов и не предназначенная для непосредственного вызова из программы. Вместо нее каждый производный класс должен предоставить настоящую реализацию. (Подробно виртуальные функции будут рассматриваться в разделе 17.5.)

17.2.2. Определение производных классов

Каждый производный класс наследует данные и функции-члены своего базового класса, и программировать приходится лишь те аспекты, которые изменяют или расширяют его поведение. К примеру, в классе NameQuery необходимо определить реализацию eval().

Кроме того, нужна поддержка для хранения слова-операнда, представленного объектом класса типа string.

Наконец, для получения ассоциированного вектора позиций должно быть доступно отображение слов на векторы. Поскольку один такой объект разделяется всеми объектами класса NameQuery, мы объявляем его статическим членом. Первая попытка определения NameQuery (рассмотрение конструкторов, деструктора и копирующего

typedef vector<location> loc;

class NameQuery : public Query { public:

//...

//переопределяет виртуальную функцию Query::eval()2 virtual void eval();

//функция чтения

string name() const { return _name; }

static const map<string,loc*> *word_map() { return _word_map; }

protected:

string _name;

static map<string,loc*> *_word_map;

оператора присваивания мы снова отложим) выглядит так:

};

Класс NotQuery в дополнение к предоставлению реализации виртуальной функции eval() должен обеспечить поддержку своего единственного операнда. Поскольку им может быть объект любого из производных классов, определим его как указатель на тип Query. Результат запроса NotQuery, напомним, обязан содержать не только строки текста, где нет указанного слова, но также и номера колонок внутри каждой строки. Например, если есть запрос:

! daddy

то операнд запроса NotQuery включает следующий вектор позиций:

daddy ((0,8),(3,3),(5,5))

Вектор позиций, возвращаемый в ответ на исходный запрос, должен включать все номера колонок в строках (1,2,4). Кроме того, он должен включать все номера колонок в строке (0), кроме колонки (8), все номера колонок в строке (3), кроме колонки (3), и все номера колонок в строке (5), кроме колонки (5).

Простейший способ вычислить все это – создать единственный разделяемый всеми объектами вектор позиций, который содержит пары (строка, колонка) для каждого слова в тексте (полную реализацию мы рассмотрим в разделе 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(). Вот начальное

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;

определение OrQuery:

};

Любой объект 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.) Итак,

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 vector< int > *_max_col;

определим AndQuery:

};

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;

}

set<short>::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';

}

cout << endl;

разделе):

}

В этом разделе мы попытались определить иерархию классов Query. Однако вопрос о том, как же построить с ее помощью структуру данных, описывающую запрос пользователя, остался без ответа. Когда мы приступим к реализации, это определение придется пересмотреть и расширить. Но прежде нам предстоит более детально изучить механизм наследования в языке C++.

Упражнение 17.3

Рассмотрите приведенные члены иерархии классов для поддержки библиотеки из упражнения 17.1 (раздел 17.1). Выявите возможные кандидаты на роль виртуальных функций, а также те члены, которые являются общими для всех предметов, выдаваемых библиотекой, и, следовательно, могут быть представлены в базовом классе. (Примечание: LibMember – это абстракция человека, которому разрешено брать из библиотеки различные предметы; Date – класс, представляющий календарную дату.)