С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 32
Текст из файла (страница 32)
Применение оператора static_castговорит и компилятору, и человеку, читающему программу, что программист знает обэтом.Кроме того, с помощью static_cast указатель void* можно преобразовать в указательопределенного типа, арифметическое значение – в значение перечисления (enum), абазовый класс – в производный. (О преобразованиях типов базовых и производныхклассов говорится в главе 19.)Эти изменения потенциально опасны, поскольку их правильность зависит от того, какоеконкретное значение имеет преобразуемое выражение в данный момент выполненияenum mumble { first = 1, second, third };extern int ival;программы:mumble mums_the_word = static_cast< mumble >( ival );Трансформация ival в mumble будет правильной только в том случае, если ival равен 1,2 или 3.reinterpret_cast работает с внутренними представлениями объектов (re-interpret –другая интерпретация того же внутреннего представления), причем правильность этойcomplex<double> *pcom;операции целиком зависит от программиста.
Например:char *pc = reinterpret_cast< char* >( pcom );Программист не должен забыть или упустить из виду, какой объект реально адресуетсяуказателем char* pc. Формально это указатель на строку встроенного типа, икомпилятор не будет препятствовать использованию pc для инициализации строки:string str( pc );177С++ для начинающиххотя скорее всего такая команда вызовет крах программы.Это хороший пример, показывающий, насколько опасны бывают явные преобразованиятипов. Мы можем присваивать указателям одного типа значения указателей совсемдругого типа, и это будет работать до тех пор, пока мы держим ситуацию под контролем.Однако, забыв о подразумеваемых деталях, легко допустить ошибку, о которойкомпилятор не сможет нас предупредить.Особенно трудно найти подобную ошибку, если явное преобразование типа делается водном файле, а используется измененное значение в другом.В некотором смысле это отражает фундаментальный парадокс языка С++: строгаяпроверка типов призвана не допустить подобных ошибок, в то же время наличиеоператоров явного преобразования позволяет “обмануть” компилятор и использоватьобъекты разных типов на свой страх и риск.
В нашем примере мы “отключили” проверкутипов при инициализации указателя 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Устаревшая форма явного преобразования имеет два вида:178С++ для начинающих179(type) expr;иможетприменятьсяreinterpret_cast.вместооператоровstatic_cast,const_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.21char cval; int ival;float fval; double dva1;Даны определения переменных:unsigned int ui;(a) cva1 = 'a' + 3;(b) fval = ui - ival *(c) dva1 = ui * fval;1.0;Какие неявные преобразования типов будут выполнены?(d) cva1 = ival + fvat + dva1;Упражнение 4.22void *pv;char *pc;int ival;double dval;Даны определения переменных:const string *ps;(a)(b)(c)pv = (void*)ps;ival = int( *pc );pv = &dva1;Перепишите следующие выражения, используя операторы явного преобразования типов:(d)pc = (char*) pv;С++ для начинающих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).}180С++ для начинающих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 valuecout << "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(): 10iStack 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(): 20iStack::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 30iStack::pop(): 30iStack::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 34iStack::push( 36 )iStack::push( 38 )iStack::push( 40 )( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34iStack::рор(): 40iStack::popQ: 38( 12 )( bot: 2 4 6 12 14 16 22 24 26 32 34iStack::push( 42 )iStack::push( 44 )( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34iStack::push( 46 )iStack::push( 48 ):top ):top )36 38 40 :top )36 :top )36 42 44 :top )С++ для начинающихiStack::push( 50 )( 17 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 46 48 50 :top )iStack::pop(): 50iStack::pop(): 48( 15 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 46 :top )Упражнение 4.23Иногда требуется операция peek(), которая возвращает значение элемента на вершинестека без извлечения самого элемента.