Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

914

класс TextQuery, где производится обработка текста (подробно он рассматривался в разделе 16.4). Для него нет производных классов;

объектно-ориентированная иерархия Query для представления и обработки различных типов запросов;

класс UserQuery, с помощью которого представлен конечный автомат для построения иерархии Query.

До настоящего момента мы реализовали эти три компонента практически независимо друг от друга и без каких бы то ни было конфликтов. Но, к сожалению, иерархия классов Query не поддерживает требований к конструированию объектов, предъявляемых реализацией UserQuery:

классы AndQuery, OrQuery и NotQuery требуют, чтобы каждый операнд присутствовал в момент определения объекта. Однако принятая нами схема обработки подразумевает наличие неполных объектов;

наша схема предполагает отложенное добавление операнда к объектам AndQuery, OrQuery и NotQuery. Более того, такая операция должна быть виртуальной. Операнд приходится добавлять через указатель типа Query*, находящийся в стеке _current_op. Однако способ добавления операнда зависит от типа: для унарных (NotQuery) и бинарных (AndQuery и OrQuery) операций он различен. Наша иерархия классов Query подобные операции не поддерживает.

Оказалось, что анализ предметной области был неполон, в результате чего разработанный интерфейс не согласуется с конкретной реализацией проекта. Нельзя сказать, что анализ был неправильным, он просто неполон. Эта проблема связана с тем, что этапы анализа, проектирования и реализации отделены друг от друга и не допускают обратной связи и пересмотра. Но хотя мы не можем все продумать и все предвидеть, необходимо отличать неизбежные неправильные шаги от ошибок, обусловленных собственной невнимательностью или нехваткой времени.

В таком случае мы должны либо сами модифицировать иерархию классов Query, либо договориться, чтобы это сделали за нас. В данной ситуации мы, как авторы всей системы, сами изменим код, модифицировав конструкторы подтипов и включив виртуальную функцию-член add_op() для добавления операндов после определения оператора (мы покажем, как она применяется, чуть ниже, при рассмотрении функций evalRParen() и evalWord()).

17.7.1. Определение класса UserQuery

Объект класса UserQuery можно инициализировать указателем на вектор строк, представляющий запрос пользователя, или передать ему адрес этого вектора позже, с помощью функции-члена query(). Это позволяет использовать один объект для нескольких запросов. Фактическое построение иерархии классов Query выполняется функцией eval_query():

С++ для начинающих

915

// определить объект, не имея запроса пользователя

UserQuery user_query;

string text; vector<string> query_text;

// обработать запросы пользователя do {

while( cin >> text ) query_text.push_back( text );

//передать запрос объекту UserQuery user_query.query( &query_text );

//вычислить результат запроса и вернуть

//корень иерархии Query*

Query *query = user_query.eval_query();

}

while ( /* пользователь продолжает формулировать запросы */ );

Вот определение нашего класса UserQuery:

С++ для начинающих

916

#ifndef USER_QUERY_H #define USER_QUERY_H

#include <string> #include <vector> #include <map> #include <stack>

typedef

pair<short,short>

location;

typedef

vector<location,allocator>

loc;

#include "Query.h"

class UserQuery { public:

UserQuery( vector< string,allocator > *pquery = 0 )

:_query( pquery ), _eval( 0 ), _paren( 0 ) {}

Query *eval_query();

// строит иерархию

void

query( vector< string,allocator > *pq );

void

displayQuery();

 

static void word_map( map<string,loc*,less<string>,allocator> *pwm ) {

if ( !_word_map ) _word_map = pwm;

}

private:

enum QueryType { WORD = 1, AND, OR, NOT, RPAREN, LPAREN };

QueryType evalQueryString( const string &query );

void

evalWord( const string &query );

void

evalAnd();

void

evalOr();

void

evalNot();

void

evalRParen();

bool

integrity_check();

int

_paren;

Query

*_eval;

vector<string> *_query;

stack<Query*, vector<Query*> > _query_stack; stack<Query*, vector<Query*> > _current_op;

static short _lparenOn, _rparenOn;

static map<string,loc*,less<string>,allocator> *_word_map;

};

#endif

Обратите внимание, что два объявленных нами стека содержат указатели на объекты типа Query, а не сами объекты. Хотя правильное поведение обеспечивается обеими реализациями, хранение объектов значительно менее эффективно, поскольку каждый объект (и его операнды) должен быть почленно скопирован в стек (напомним, что операнды копируются виртуальной функцией clone()) только для того, чтобы вскоре быть уничтоженным. Если мы не собираемся модифицировать объекты, помещаемые в контейнер, то хранение указателей на них намного эффективнее.

Ниже показаны реализации различных встроенных операций eval. Операции evalAnd() и evalOr() выполняют следующие шаги. Сначала объект извлекается из стека

С++ для начинающих

917

_query_stack (напомним, что для класса stack, определенного в стандартной библиотеке, это требует двух операций: top() для получения элемента и pop() для удаления его из стека). Затем из хипа выделяется память для объекта класса AndQuery или OrQuery, и указатель на него передается объекту, извлеченному из стека. Каждая операция передает объекту AndQuery или OrQuery счетчики левых или правых скобок, необходимые ему для вывода своего содержимого. И наконец неполный оператор

inline void UserQuery:: evalAnd()

{

Query *pop = _query_stack.top(); _query_stack.pop(); AndQuery *pq = new AndQuery( pop );

if ( _lparenOn )

{pq->lparen( _lparenOn ); _lparenOn = 0; } if ( _rparenOn )

{pq->rparen( _rparenOn ); _rparenOn = 0; }

_current_op.push( pq );

}

inline void UserQuery:: evalOr()

{

Query *pop = _query_stack.top(); _query_stack.pop(); OrQuery *pq = new OrQuery( pop );

if ( _lparenOn )

{ pq->lparen( _lparenOn ); _lparenOn = 0; }

if ( _rparenOn )

{pq->rparen( _rparenOn ); _rparenOn = 0; } _current_op.push( pq );

помещается в стек _current_op:

}

Операция evalNot() работает следующим образом. В хипе создается новый объект класса NotQuery, которому передаются счетчики левых и правых скобок для правильного

inline void UserQuery:: evalNot()

{

NotQuery *pq = new NotQuery;

if ( _lparenOn )

{pq->lparen( _lparenOn ); _lparenOn = 0; } if ( _rparenOn )

{pq->rparen( _rparenOn ); _rparenOn = 0; }

_current_op.push( pq );

отображения содержимого. Затем неполный оператор помещается в стек _current_op:

}

С++ для начинающих

918

При обнаружении закрывающей скобки вызывается операция evalRParen(). Если число активных левых скобок больше числа элементов в стеке _current_op, то ничего не происходит. В противном случае выполняются следующие действия. Из стека _query_stack извлекается текущий еще не присоединенный к оператору операнд, а из стека _current_op текущий неполный оператор. Вызывается виртуальная функция add_op() класса Query, которая их объединяет. И наконец полный оператор помещается

inline void UserQuery:: evalRParen()

{

if ( _paren < _current_op.size() )

{

Query *poperand = _query_stack.top(); _query_stack.pop();

Query *pop = _current_op.top(); _current_op.pop(); pop->add_op( poperand ); _query_stack.push( pop );

}

в стек _query_stack:

}

Операция evalWord() выполняет следующие действия. Она ищет указанное слово в отображении _word_map взятых из файла слов на векторы позиций. Если слово найдено,

берется его вектор позиций и в хипе посредством конструктора с двумя параметрами создается новый объект NameQuery. В противном случае объект порождается с помощью конструктора с одним параметром. Если число элементов в стеке _current_op меньше либо равно числу встреченных ранее скобок, то нет неполного оператора, ожидающего операнда типа NameQuery, поэтому новый объект помещается в стек _query_stack. Иначе из стека _current_op извлекается неполный оператор, к которому с помощью виртуальной функции add_op() присоединяется операнд NameQuery, после чего ставший полным оператор помещается в стек _query_stack: