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

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

882

является объектом NameQuery. Часть nq, принадлежащая NameQuery, “усеченаперед инициализацией qobject, поскольку она не помещается в область памяти, отведенную под объект Query. Для поддержки этой парадигмы приходится использовать указатели и

void print ( Query object,

const Query *pointer, const Query &reference )

{

//до момента выполнения невозможно определить,

//какой экземпляр print() вызывается

pointer->print(); reference.print();

// всегда вызывается Query::print() object.print();

}

int main()

{

NameQuery firebird( "firebird" ); print( firebird, &firebird, firebird );

ссылки, но не сами объекты:

}

Вданном примере оба обращения через указатель pointer и ссылку reference разрешаются своим динамическим типом; в обоих случаях вызывается NameQuery::print(). Обращение же через объект object всегда приводит к вызову Query::print(). (Пример программы, в которой используется эффект усечения”, приведен в разделе 18.6.2.)

Вследующих подразделах мы продемонстрируем определение и использование виртуальных функций в разных обстоятельствах. Каждая такая функция-член будет иллюстрировать один из аспектов объектно-ориентированного проектирования.

17.5.1. Виртуальный ввод/вывод

Первая виртуальная операция, которую мы хотели реализовать, – это печать запроса на стандартный вывод либо в файл:

ostream& print( ostream &os = cout ) const;

Функцию print() следует объявить виртуальной, поскольку ее реализации зависят от типа, но нам нужно вызывать ее через указатель типа Query*. Например, для класса

ostream&

AndQuery::print( ostream &os ) const

{

_lop->print( os ); os << " && "; _rop->print( os );

AndQuery эта функция могла бы выглядеть так:

}

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

883

Необходимо объявить print() виртуальной функцией в абстрактном базовом Query, иначе мы не сможем вызвать ее для членов классов AndQury, OrQuery и NotQuery, являющихся указателями на операнды соответствующих запросов типа Query*. Однако для самого Query разумной реализации print() не существует. Поэтому мы определим

class Query { public:

virtual ostream& print( ostream &os=cout ) const {}

//...

еекак пустую функцию, а потом сделаем чисто виртуальной:

};

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

//ошибка: ключевое слово virtual может появляться

//только в определении класса

print() приведет к ошибке компиляции:

virtual ostream& Query::print( ostream& ) const { ... }

Правильный вариант не должен включать слово virtual.

Класс, в котором впервые появляется виртуальная функция, должен определить ее или объявить чисто виртуальной (напомним, что пока мы определили ее как пустую). В

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

Прежде чем приступать к рассмотрению реализаций print() для наших четырех производных классов, обратим внимание на употребление скобок в запросе. Например, с

помощью

fiery && bird || shyly

пользователь ищет вхождения пары слов

fiery bird

или одного слова

shyly

С другой стороны, запрос

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

884

fiery && ( bird || hair )

найдет все вхождения любой из пар

fiery bird

или

fiery hair

Если наши реализации print() не будут показывать скобки в исходном запросе, то для пользователя они окажутся почти бесполезными. Чтобы сохранить эту информацию, введем в наш абстрактный базовый класс Query два нестатических члена, а также функции доступа к ним (подобное расширение класса естественная часть эволюции

class Query { public:

//...

//установить _lparen и _rparen

void lparen( short lp ) { _lparen = lp; } void rparen( short rp ) { _rparen = rp; }

// получить значения_lparen и _rparen short lparen() { return _lparen; } short rparen() { return _rparen; }

// напечатать левую и правую скобки

void print_lparen( short cnt, ostream& os ) const; void print_rparen( short cnt, ostream& os ) const;

protected:

//счетчики левых и правых скобок short _lparen;

short _rparen;

//...

иерархии):

};

_lparen это количество левых, а _rparen правых скобок, которое должно быть выведено при распечатке объекта. (В разделе 17.7 мы покажем, как вычисляются такие величины и как происходит присваивание обоим членам.) Вот пример обработки запроса с большим числом скобок:

==> ( untamed || ( fiery || ( shyly ) ) )

evaluate word: untamed _lparen: 1

_rparen: 0 evaluate Or _lparen: 0 _rparen: 0

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

885

evaluate word: fiery _lparen: 1

_rparen: 0

evaluate 0r _lparen: 0 _rparen: 0

evaluate word: shyly _lparen: 1

_rparen: 0

evaluate right parens: _rparen: 3

( untamed ( 1 ) lines match ( fiery ( 1 ) lines match ( shyly ( 1 ) lines match

( fiery || (shyly ( 2 ) lines match3

( untamed || ( fiery || ( shyly ))) ( 3 ) lines match

Requested query: ( untamed || ( fiery || ( shyly ) ) )

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her, ( 4 ) magical but untamed. "Daddy, shush, there is no such thing,"

( 6 ) Shyly, she asks, "I mean, Daddy, is there?"

ostream&

NameQuery::

print( ostream &os ) const

{

if ( _lparen )

print_lparen( _lparen, os );

os << _name;

if ( _rparen )

print_rparen( _rparen, os );

return os;

Реализация print() для класса NameQuery:

}

class NameQuery : public Query { public:

virtual ostream& print( ostream &os ) const;

//...

Атак выглядит объявление:

};

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

3 Увы! Правые скобки не распознаются, пока OrQuery не выведет все ассоциированное с ним частичное решение.

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

886

быть одинаковыми за одним исключением: значение, возвращенное реализацией в производном классе, может принадлежать к типу класса, который открыто наследует классу значения, возвращаемого реализацией в базовом классе. Если бы реализация из базового класса возвращала значение типа Query*, то реализация из производного могла бы возвращать NameQuery*. (Позже при работе с функцией clone() мы покажем, зачем

class NotQuery : public Query { public:

virtual ostream& print( ostream &os ) const; // ...

это нужно.) Вот объявление и реализация print() в NotQuery:

ostream&

NotQuery::

print( ostream &os ) const

{

os << " ! ";

if ( _lparen )

print_lparen( _lparen, os );

_op->print( os );

if ( _rparen )

print_rparen( _rparen, os );

return os;

};

}

Разумеется, вызов print() через _op виртуальный.

Объявления и реализации этой функции в классах AndQuery и OrQuery практически

class AndQuery : public Query { public:

virtual ostream& print( ostream &os ) const; // ...

дублируют друг друга. Поэтому приведем их только для AndQuery:

};

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

887

ostream&

AndQuery::

print( ostream &os ) const

{

if ( _lparen )

print_lparen( _lparen, os );

_lop->print( os ); os << " && "; _rop->print( os );

if ( _rparen )

print_rparen( _rparen, os );

return os;

}

Такая реализация виртуальной функции print() позволяет вывести любой подтип Query

cout << "Был сформулирован запрос "; Query *pq = retrieveQuery();

в поток класса ostream или любого другого, производного от него: pq->print( cout );

Однако такой возможности недостаточно. Еще нужно уметь распечатывать любой производный от Query тип, который уже есть или может появиться в будущем, с

Query *pq = retrieveQuery(); cout << "В ответ на запрос "

<< *pq

помощью оператора вывода из библиотеки iostream:

<< " получены следующие результаты:\n";

Мы не можем непосредственно предоставить виртуальный оператор вывода, поскольку они являются членами класса ostream. Вместо этого мы должны написать косвенную

inline ostream&

operator<<( ostream &os, const Query &q )

{

// виртуальный вызов print() return q.print( os );

виртуальную функцию:

}

AndQuery query;

// сформулировать запрос ...

Строки