4.14. Преобразования типовПредставим себе следующий оператор присваивания: int ival = 0; // обычно компилируется с предупреждением ival = 3.541 + 3; В результате ival получит значение 6. Вот что происходит: мы складываем литералы
разных типов – 3.541 типа double и 3 типа int. C++ не может непосредственно
сложить подобные операнды, сначала ему нужно привести их к одному типу. Для
этого существуют правила преобразования арифметических типов. Общий принцип
таков: перейти от операнда меньшего типа к большему, чтобы не потерять точность
вычислений. double dva1 = 8.6; int iva1 = 5; ival += dva1 + 0.5; // преобразование с округлением При желании мы можем произвести явное преобразование типов: // инструкция компилятору привести double к int ival = static_cast< int >( 3.541 ) + 3; В этом примере мы явно даем указание компилятору привести величину 3.541 к
типу int, а не следовать правилам по умолчанию. 4.14.1. Неявное преобразование типовЯзык определяет набор стандартных преобразований между объектами встроенного типа, неявно выполняющихся компилятором в следующих случаях:
4.14.2. Арифметические преобразования типовАрифметические преобразования приводят оба операнда бинарного арифметического выражения к одному типу, который и будет типом результата выражения. Два общих правила таковы:
Если один из операндов имеет тип long double, второй приводится к этому же типу в любом случае. Например, в следующем выражении символьная константа 'a' трансформируется в long double (значение 97 для представления ASCII) и затем прибавляется к литералу того же типа: 3.14159L + 'a'. Если в выражении нет операндов long double, но есть операнд double, все преобразуется к этому типу. Например: int ival; float fval; double dval; // fval и ival преобразуются к double перед сложением В том случае, если нет операндов типа double и long double, но есть операнд float, тип остальных операндов меняется на float: char cvat; int ival; float fval; // iva1 и cval преобразуются к float перед сложением Если у нас нет вещественных операндов , значит, все они представляют собой
целые типы. Прежде чем определить тип результата, производится преобразование,
называемое приведением к целому: все операнды с типом меньше, чем int, заменяются
на int. enum status { bad, ok }; значения элементов равны 0 и 1. Оба эти значения могут быть представлены типом
char, значит char и станет типом внутреннего представления данного перечисления.
Приведение к целому преобразует char в int. char cval; bool found; enum mumble { ml, m2, m3 } mval; unsigned long ulong; cval + ulong; ulong + found; mval + ulong; перед определением типа результата cval, found и mval преобразуются в int. char cval; long lval; // cval и 1024 преобразуются в long перед сложением Из этого правила есть одно исключение: преобразование unsigned int в long происходит
только в том случае, если тип long способен вместить весь диапазон значений
unsigned int. (Обычно это не так в 32-битных системах, где и long, и int представляются
одним машинным словом.) Если же тип long не способен представить весь диапазон
unsigned int, оба операнда приводятся к unsigned long. 4.14.3. Явное преобразование типовЯвное преобразование типов производится при помощи следующих операторов: static_cast,
dynamic_cast, const_cast и reinterpret_cast. Заметим, что, хотя иногда явное
преобразование необходимо, оно служит потенциальным источником ошибок,
поскольку подавляет проверку типов, выполняемую компилятором. Давайте сначала
посмотрим, зачем нужно такое преобразование. int iva1; int *pi = 0; char *pc = 0; void *pv; pv = pi; // правильно: неявное преобразование pv = pc; // правильно: неявное преобразование const int *pci = &iva1; pv = pci; // ошибка: pv имеет тип, отличный от const void*; const void *pcv = pci; // правильно Однако указатель void* не может быть разыменован непосредственно. Компилятор не знает типа объекта, адресуемого этим указателем. Но это известно программисту, который хочет преобразовать указатель void* в указатель определенного типа. С++ не обеспечивает подобного автоматического преобразования: #include <cstring> int ival = 1024; void *pv; int *pi = &iva1; const char *pc = "a casting call"; void mumble() { Компилятор выдает сообщение об ошибке, так как в данном случае указатель pv содержит адрес целого числа ival, и именно этот адрес пытаются присвоить указателю на строку. Если бы такая программа была допущена до выполнения, то вызов функции strcpy(), которая ожидает на входе строку символов с нулем в конце, скорее всего привел бы к краху, потому что вместо этого strcpy() получает указатель на целое число. Подобные ошибки довольно просто не заметить, именно поэтому С++ запрещает неявное преобразование указателя на void в указатель на другой тип. Однако такой тип можно изменить явно: void mumble 0 { // правильно: программа по-прежнему содержит ошибку, // но теперь она компилируется! // Прежде всего нужно проверить // явные преобразования типов... pc = static_cast< char* >( pv ); char *pstr = new char[ str1en( pc )+1 ]; // скорее всего приведет к краху Другой причиной использования явного преобразования типов может служить необходимость избежать стандартного преобразования или выполнить вместо него собственное. Например, в следующем выражении ival сначала преобразуется в double, потом к нему прибавляется dval, и затем результат снова трансформируется в int. double dval; int iva1; ival += dval; Можно уйти от ненужного преобразования, явно заменив dval на int: ival += static_cast< int >( dval ); Третьей причиной является желание избежать неоднозначных ситуаций, в которых
возможно несколько вариантов применения правил преобразования по умолчанию.
(Мы рассмотрим этот случай в главе 9, когда будем говорить о перегруженных функциях.) cast-name< type >( expression ); Здесь cast-name – одно из ключевых слов static_cast, const_cast, dynamic_cast
или reinterpret_cast, а type – тип, к которому приводится выражение expression. extern char *string_copy( char* ); const char *pc_str; char *pc = string_copy( const_cast< char* >( pc_str )); Любое иное использование const_cast вызывает ошибку компиляции, как и попытка
подобного приведения с помощью любого из трех других операторов. double d = 97.0; char ch = static_cast< char >( d ); Зачем использовать static_cast? Дело в том, что без него компилятор выдаст
предупреждение о возможной потере точности. Применение оператора static_cast
говорит и компилятору, и человеку, читающему программу, что программист знает
об этом. enum mumble { first = 1, second, third }; extern int ival; Трансформация ival в mumble будет правильной только в том случае, если ival
равен 1, 2 или 3. complex<double> *pcom; char *pc = reinterpret_cast< char* >( pcom ); Программист не должен забыть или упустить из виду, какой объект реально адресуется указателем char* pc. Формально это указатель на строку встроенного типа, и компилятор не будет препятствовать использованию pc для инициализации строки: string str( pc ); хотя скорее всего такая команда вызовет крах программы. char *pc = (char*) pcom; Эта запись эквивалентна применению оператора reinterpret_cast, однако выглядит
не так заметно. Использование операторов xxx_cast позволяет четко указать те
места в программе, где содержатся потенциально опасные трансформации типов. 4.14.4. Устаревшая форма явного преобразованияОператоры явного преобразования типов, представленные в предыдущем разделе,
появились только в стандарте С++; раньше использовалась форма, теперь считающаяся
устаревшей. Хотя стандарт допускает и эту форму, мы настоятельно не рекомендуем
ею пользоваться. (Только если ваш компилятор не поддерживает новый вариант.) // появившийся в C++ вид type (expr); // вид, существовавший в C (type) expr; и может применяться вместо операторов static_cast, const_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;Назад Вперед Содержание |
Нет комментариев. Оставить комментарий: |