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


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


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

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

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:

#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() выполняют следующие шаги. Сначала объект извлекается из стека _query_stack (напомним, что для класса stack, определенного в стандартной библиотеке, это требует двух операций: top() для получения элемента и pop() для удаления его из стека). Затем из хипа выделяется память для объекта класса AndQuery или OrQuery, и указатель на него передается объекту, извлеченному из стека. Каждая операция передает объекту AndQuery или OrQuery счетчики левых или правых скобок, необходимые ему для вывода своего содержимого. И наконец неполный оператор помещается в стек _current_op:

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 );

}

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

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 );

}

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

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 );

           }

}

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



inline void

UserQuery::

evalWord( const string &query )

{

           NameQuery *pq;

           loc       *ploc;

          

           if ( ! _word_map->count( query ))

                  pq = new NameQuery( query );

           else {

                  ploc = ( *_word_map )[ query ];

                  pq = new NameQuery( query, *ploc );     

           }

           if ( _current_op.size() <= _paren )

                  _query_stack.push( pq );

           else {

                  Query *pop = _current_op.top();

           _current_op.pop();

                  pop->add_op( pq );

                  _query_stack.push( pop );

           }

}

Упражнение 17.21

Напишите деструктор, копирующий конструктор и копирующий оператор присваивания для класса UserQuery.

Упражнение 17.22

Напишите функции print() для класса UserQuery. Обоснуйте свой выбор того, что она выводит.


Содержание раздела