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

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

178

хотя скорее всего такая команда вызовет крах программы.

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

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

В некотором смысле это отражает фундаментальный парадокс языка С++: строгая проверка типов призвана не допустить подобных ошибок, в то же время наличие операторов явного преобразования позволяет обманутькомпилятор и использовать объекты разных типов на свой страх и риск. В нашем примере мы отключилипроверку типов при инициализации указателя pc и присвоили ему адрес комплексного числа. При инициализации строки str такая проверка производится снова, но компилятор считает, что pc указывает на строку, хотя, на самом-то деле, это не так!

Четыре оператора явного преобразования типов были введены в стандарт С++ как наименьшее зло при невозможности полностью запретить такое приведение. Устаревшая, но до сих пор поддерживаемая стандартом С++ форма явного преобразования выглядит так:

char *pc = (char*) pcom;

Эта запись эквивалентна применению оператора reinterpret_cast, однако выглядит не так заметно. Использование операторов xxx_cast позволяет четко указать те места в программе, где содержатся потенциально опасные трансформации типов.

Если поведение программы становится ошибочным и непонятным, возможно, в этом виноваты явные видоизменения типов указателей. Использование операторов явного преобразования помогает легко обнаружить места в программе, где такие операции выполняются. (Другой причиной непредсказуемого поведения программы может стать нечаянное уничтожение объекта (delete), в то время как он еще должен использоваться в работе. Мы поговорим об этом в разделе 8.4, когда будем обсуждать динамическое выделение памяти.)

Оператор dynamic_cast применяется при идентификации типа во время выполнения (run-time type identification). Мы вернемся к этой проблеме лишь в разделе 19.1.

4.14.4. Устаревшая форма явного преобразования

Операторы явного преобразования типов, представленные в предыдущем разделе, появились только в стандарте С++; раньше использовалась форма, теперь считающаяся устаревшей. Хотя стандарт допускает и эту форму, мы настоятельно не рекомендуем ею пользоваться. (Только если ваш компилятор не поддерживает новый вариант.)

//появившийся в C++ вид type (expr);

//вид, существовавший в C

Устаревшая форма явного преобразования имеет два вида:

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

179

 

(type) expr;

const_cast и

 

 

 

и может применяться вместо операторов static_cast,

reinterpret_cast.

 

 

const char *pc = (const char*) pcom;

 

 

 

 

int ival = (int) 3.14159;

 

 

extern char *rewrite_str( char* );

 

 

char *pc2 = rewrite_str( (char*) pc );

 

Вот несколько примеров такого использования:

 

 

int addr_va1ue = int( &iva1 );

 

 

 

 

 

 

Эта форма сохранена в стандарте С++ только для обеспечения обратной совместимости с программами, написанными для С и предыдущих версий С++.

Упражнение 4.21

char cval; int ival; float fval; double dva1;

Даны определения переменных: unsigned int ui;

(a)cva1 = 'a' + 3;

(b)fval = ui - ival * 1.0;

(c)dva1 = ui * fval;

Какие неявные преобразования типов будут выполнены?

(d) cva1 = ival + fvat + dva1;

Упражнение 4.22

void

*pv;

int ival;

char

*pc;

double dval;

Даны определения переменных: const string *ps;

(a)pv = (void*)ps;

(b)ival = int( *pc );

(c)pv = &dva1;

Перепишите следующие выражения, используя операторы явного преобразования типов:

(d) pc = (char*) pv;

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

180

4.15. Пример: реализация класса Stack

Описывая операции инкремента и декремента, для иллюстрации применения их префиксной и постфиксной формы мы ввели понятие стека. Данная глава завершается примером реализации класса iStack стека, позволяющего хранить элементы типа int.

Как уже было сказано, с этой структурой возможны две основные операции поместить элемент (push) и извлечь (pop) его. Другие операции позволяют получить информацию о текущем состоянии стека пуст он (empty()) или полон (full()), сколько элементов в нем содержится (size()). Для начала наш стек будет предназначен лишь для элементов

#include <vector>

class iStack { public:

iStack( int capacity )

: _stack( capacity ), _top( 0 ) {}

bool pop( int &va1ue ); boot push( int value );

bool full(); bool empty(); void display();

int size();

private:

int _top;

vector< int > _stack;

типа int. Вот объявление нашего класса:

};

В данном случае мы используем вектор фиксированного размера: для иллюстрации

использования префиксных и постфиксных операций инкремента и декремента этого достаточно. (В главе 6 мы модифицируем наш стек, придав ему возможность динамически меняться.)

Элементы стека хранятся в векторе _stack. Переменная _top содержит индекс первой свободной ячейки стека. Этот индекс одновременно представляет количество заполненных ячеек. Отсюда реализация функции size(): она должна просто возвращать текущее значение _top.

inline int iStack::size() { return _top; };

empty() возвращает true, если _top равняется 0; full() возвращает true, если _top равен _stack.size()-1 (напомним, что индексация вектора начинается с 0, поэтому мы

inline bool iStack::empty() { return _top ? false : true; } inline bool iStack::full() {

return _top < _stack.size()-l ? false : true;

должны вычесть 1).

}

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

181

Вот реализация функций pop() и push(). Мы добавили операторы вывода в каждую из

 

bool iStack::pop( int &top_va1ue ) { if ( empty() )

return false;

top_value = _stack[ --_top ];

cout << "iStack::pop(): " << top_value << endl;

return true;

}

bool iStack::push( int value ) {

cout << "iStack::push( " << value << " )\n";

if ( full() ) return false;

_stack[ _top++ ] = value; return true;

них, чтобы следить за ходом выполнения:

}

Прежде чем протестировать наш стек на примере, добавим функцию display(), которая позволит напечатать его содержимое. Для пустого стека она выведет:

( 0 )

Для стека из четырех элементов – 0, 1, 2 и 3 – результатом функции display() будет:

( 4 )( bot: 0 1 2 3 :top )

void iStack::display() {

cout << "( " << size() << " )( bot: ";

for ( int ix = 0; ix < _top; ++ix ) cout << _stack[ ix ] <<" ";

cout << " :top )\n";

Вот реализация функции display():

}

А вот небольшая программа для проверки нашего стека. Цикл for выполняется 50 раз. Четное значение (2, 4, 6, 8 и т.д.) помещается в стек. На каждой итерации, кратной 5 (5, 10, 15...), распечатывается текущее содержимое стека. На итерациях, кратных 10 (10, 20, 30...), из стека извлекаются два элемента и его содержимое распечатывается еще раз.

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

182

 

 

#inc1ude <iostream>

 

 

 

 

 

 

#inc1ude "iStack.h"

 

 

 

int main() {

 

 

 

 

iStack stack( 32 ) ;

 

 

 

 

stack.display();

 

 

 

 

for ( int ix = 1; ix < 51; ++ix )

 

 

 

{

 

 

 

 

if ( ix%2 == 0 )

 

 

 

 

stack.push( ix );

 

 

 

 

if ( ix%5 == 0 )

 

 

 

 

stack.display();

 

 

 

 

if ( ix%10 == 0 ) {

 

 

 

 

int dummy;

 

 

 

 

stack.pop( dummy ); stack.pop( dummy );

 

 

 

 

stack.display();

 

 

 

}

 

 

 

}

 

 

 

 

 

 

 

 

 

Вот результат работы программы:

 

 

 

 

 

 

 

 

 

( 0 )( bot: :top )

 

 

 

 

iStack push( 2 )

 

 

 

 

iStack push( 4 )

 

 

 

 

( 2 )( bot: 2 4 :top )

 

 

 

 

iStack push( 6 )

 

 

 

 

iStack push( 8 )

 

 

 

 

iStack push ( 10 )

 

 

 

 

( 5 )( bot: 2 4 6 8 10 :top )

 

 

 

 

iStack pop(): 10

 

 

 

 

iStack pop(): 8

 

 

 

 

( 3 )( bot: 2 4 6 :top )

 

 

 

 

iStack push( 12 )

 

 

 

 

iStack push( 14 )

 

 

 

 

( 5 )( bot: 2 4 6 12 14 :top )

 

 

 

 

iStack::push( 16 )

 

 

 

 

iStack::push( 18 )

 

 

 

 

iStack::push( 20 )

 

 

 

 

( 8 )( bot: 2 4 6 12 14 16 18 20 :top )

 

 

 

 

iStack::pop(): 20

 

 

 

 

iStack::pop(): 18

 

 

 

 

( 6 )( bot: 2 4 6 12 14 16 :top )

 

 

 

 

iStack::push( 22 )

 

 

 

 

iStack::push( 24 )

 

 

 

 

( 8 )( bot: 2 4 6 12 14 16 22 24 :top )

 

 

 

 

iStack::push( 26 )

 

 

 

 

iStack::push( 28 )

 

 

 

 

iStack::push( 30 )

 

 

 

 

( 11 )( bot: 2 4 6 12 14 16 22 24 26 28 30 :top )

 

 

 

 

iStack::pop(): 30

 

 

 

 

iStack::pop(): 28

 

 

 

 

( 9 )( bot: 2 4 6 12 14 16 22 24 26 :top )

 

 

 

 

iStack::push( 32 )

 

 

 

 

iStack::push( 34 )

 

 

 

 

( 11 )( bot: 2 4 6 12 14 16 22 24 26 32 34 :top )

 

 

 

 

iStack::push( 36 )

 

 

 

 

iStack::push( 38 )

 

 

 

 

iStack::push( 40 )

 

 

 

 

( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 38 40 :top )

 

 

 

 

iStack::рор(): 40

 

 

 

 

iStack::popQ: 38

 

 

 

 

( 12 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 :top )

 

 

 

 

iStack::push( 42 )

 

 

 

 

iStack::push( 44 )

 

 

 

 

( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 :top )

 

 

 

 

iStack::push( 46 )

 

 

 

 

iStack::push( 48 )

 

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

 

 

 

 

 

 

183

 

 

 

 

 

 

 

 

 

 

 

iStack::push( 50

)

12 14 16

22

24 26 32 34

36

42 44 46

48 50 :top )

 

 

( 17 )( bot: 2 4

6

 

 

iStack::pop(): 50

 

 

 

 

 

 

 

 

iStack::pop(): 48

12 14 16

22

24 26 32 34

36

42 44 46

:top )

 

 

( 15 )( bot: 2 4

6

 

Упражнение 4.23

Иногда требуется операция peek(), которая возвращает значение элемента на вершине стека без извлечения самого элемента. Реализуйте функцию peek() и добавьте к программе main() проверку работоспособности этой функции.

Упражнение 4.24

В чем вы видите два основных недостатка реализации класса iStack? Как их можно исправить?