Главная страница
Навигация по странице:

  • 4.14.1. Неявное преобразование типов

  • 4.14.2. Арифметические преобразования типов

  • 4.14.3. Явное преобразование типов

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

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница17 из 93
    1   ...   13   14   15   16   17   18   19   20   ...   93

    170

    побитовое НЕ
    expr
    ! логическое НЕ
    !expr
    - унарный минус
    -expr
    + унарный плюс
    +expr
    * разыменование
    *expr
    & адрес
    &expr
    () приведение типа
    (type)expr new выделение памяти new type new выделение памяти и инициализация new type(exprlist) new выделение памяти new
    (exprlist) type(exprlist) new выделение памяти под массив все формы delete освобождение памяти все формы delete освобождение памяти из-под массива все формы
    ->* доступ к члену классу по указателю pointer->
    *pointer_to_member
    .* доступ к члену класса по указателю object.*pointer_to_member
    * умножение expr * expr
    / деление expr / expr
    % деление по модулю expr % expr
    + сложение expr + expr
    - вычитание expr - expr
    << сдвиг влево expr << expr
    >> сдвиг вправо expr >> expr
    < меньше expr < expr
    <= меньше или равно expr <= expr
    > больше expr > expr
    >= больше или равно expr >= expr
    == равно expr == expr
    != не равно expr != expr
    & побитовое И expr & expr
    ^ побитовое
    ИСКЛЮЧАЮЩЕЕ expr ^ expr

    С++ для начинающих
    171
    ИЛИ
    | побитовое ИЛИ expr | expr
    && логическое И expr && expr
    || логическое ИЛИ expr || expr
    ?: условный оператор expr ? expr * expr
    = присваивание l- значение = expr
    =, *=, /=, %=,
    +=, -=, <<=, >>=,
    &=, |=, ^= составное присваивание l-значение += expr и т.д. throw возбуждение исключения throw expr
    , запятая expr, expr
    4.14.
    Преобразования типов
    Представим себе следующий оператор присваивания: ival = 3.541 + 3;
    В результате ival получит значение 6. Вот что происходит: мы складываем литералы разных типов – 3.541 типа double и 3 типа int. C++ не может непосредственно сложить подобные операнды, сначала ему нужно привести их к одному типу. Для этого существуют правила преобразования арифметических типов. Общий принцип таков: перейти от операнда меньшего типа к большему, чтобы не потерять точность вычислений.
    В нашем случае целое значение 3 трансформируется в тип double, и только после этого производится сложение. Такое преобразование выполняется независимо от желания программиста, поэтому оно получило название неявного преобразования типов.
    Результат сложения двух чисел типа double тоже имеет тип double. Значение равно
    6.541
    . Теперь его нужно присвоить переменной ival. Типы переменной и результата
    6.541
    не совпадают, следовательно, тип этого значения приводится к типу переменной слева от знака равенства. В нашем случае это int. Преобразование double в int производится автоматически, отбрасыванием дробной части (а не округлением). Таким образом, 6.541 превращается в 6, и этот результат присваивается переменной ival.
    Поскольку при таком преобразовании может быть потеряна точность, большинство компиляторов выдают предупреждение.
    Так как компилятор не округляет числа при преобразовании double в int, при необходимости мы должны позаботиться об этом сами. Например: int ival = 0;
    // обычно компилируется с предупреждением double dva1 = 8.6; int iva1 = 5;

    С++ для начинающих
    172
    ival += dva1 + 0.5; // преобразование с округлением
    При желании мы можем произвести явное преобразование типов: ival = static_cast< int >( 3.541 ) + 3;
    В этом примере мы явно даем указание компилятору привести величину 3.541 к типу int
    , а не следовать правилам по умолчанию.
    В этом разделе мы детально обсудим вопросы и неявного (как в первом примере), и явного преобразования типов (как во втором).
    4.14.1.
    Неявное преобразование типов
    Язык определяет набор стандартных преобразований между объектами встроенного типа, неявно выполняющихся компилятором в следующих случаях:

    арифметическое выражение с операндами разных типов: все операнды приводятся к наибольшему типу из встретившихся. Это называется арифметическим преобразованием. Например: ival + dva1;

    присваивание значения выражения одного типа объекту другого типа. В этом случае результирующим является тип объекта, которому значение присваивается.
    Так, в первом примере литерал 0 типа int присваивается указателю типа int*, значением которого будет 0. Во втором примере double преобразуется в int. ivat = dva1;

    передача функции аргумента, тип которого отличается от типа соответствующего формального параметра. Тип фактического аргумента приводится к типу параметра: cout << "
    Квадратный корень из 2: " << sqrt( 2 ) << endt;
    // инструкция компилятору привести double к int int ival = 3; double dva1 = 3.14159;
    // ival преобразуется в double: 3.0
    // 0 преобразуется в нулевой указатель типа int* int *pi = 0;
    // dva1 преобразуется в int: 3 extern double sqrt( double );
    // 2 преобразуется в double: 2.0

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

    возврат из функции значения, тип которого не совпадает с типом возвращаемого результата, заданным в объявлении функции. Тип фактически возвращаемого значения приводится к объявленному. Например:
    }
    4.14.2.
    Арифметические преобразования типов
    Арифметические преобразования приводят оба операнда бинарного арифметического выражения к одному типу, который и будет типом результата выражения. Два общих правила таковы:

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

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

    Мы рассмотрим иерархию правил преобразований, начиная с наибольшего типа long double.
    Если один из операндов имеет тип long double, второй приводится к этому же типу в любом случае. Например, в следующем выражении символьная константа 'a' трансформируется в long double (значение 97 для представления ASCII) и затем прибавляется к литералу того же типа: 3.14159L + 'a'.
    Если в выражении нет операндов long double, но есть операнд double, все преобразуется к этому типу. Например: dval + fva1 + ival;
    В том случае, если нет операндов типа double и long double, но есть операнд float, тип остальных операндов меняется на float: double difference( int ivati, int iva12 )
    {
    // результат преобразуется в double return ivati - iva12; int iva1; float fval; double dval;
    // fva1 и iva1 преобразуются к double перед сложением char cvat; int iva1; float fva1;
    // iva1 и cval преобразуются к float перед сложением

    С++ для начинающих
    174
    cvat + fva1 + iva1;
    Если у нас нет вещественных операндов , значит, все они представляют собой целые типы. Прежде чем определить тип результата, производится преобразование, называемое
    приведением к целому: все операнды с типом меньше, чем int, заменяются на int.
    При приведении к целому типы char, signed char, unsigned char и short int преобразуются в int. Тип unsigned short int трансформируется в int, если этот тип достаточен для представления всего диапазона значений unsigned short int (обычно это происходит в системах, отводящих полслова под short и целое слово под int), в противном случае unsigned short int заменяется на unsigned int.
    Тип wchar_t и перечисления приводятся к наименьшему целому типу, способному представить все их значения. Например, в перечислении enum status { bad, ok }; значения элементов равны 0 и 1. Оба эти значения могут быть представлены типом char, значит char и станет типом внутреннего представления данного перечисления.
    Приведение к целому преобразует char в int.
    В следующем выражении cval + ulong; ulong + found; mval + ulong; перед определением типа результата cval, found и mval преобразуются в int.
    После приведения к целому сравниваются получившиеся типы операндов. Если один из них имеет тип unsigned long, то остальные будут того же типа. В нашем примере все три объекта, прибавляемые к ulong, приводятся к типу unsigned long.
    Если в выражении нет объектов unsigned long, но есть объекты типа long, тип остальных операндов меняется на long. Например: cval + 1024 + lval;
    Из этого правила есть одно исключение: преобразование unsigned int в long происходит только в том случае, если тип long способен вместить весь диапазон значений unsigned int. (Обычно это не так в 32-битных системах, где и long, и int представляются одним машинным словом.) Если же тип long не способен представить весь диапазон unsigned int, оба операнда приводятся к unsigned long. char cval; bool found; enum mumble { ml, m2, m3 } mval; unsigned long ulong; char cval; long lval;
    // cval и 1024 преобразуются в long перед сложением

    С++ для начинающих
    175
    В случае отсутствия операндов типов unsigned long и long, используется тип unsigned int
    . Если же нет операндов и этого типа, то к int.
    Может быть, данное объяснение преобразований типов несколько смутило вас.
    Запомните основную идею: арифметическое преобразование типов ставит своей целью сохранить точность при вычислении. Это достигается приведением типов всех операндов к типу, способному вместить любое значение любого из присутствующих в выражении операндов.
    4.14.3.
    Явное преобразование типов
    Явное преобразование типов производится при помощи следующих операторов: static_cast
    , dynamic_cast, const_cast и reinterpret_cast. Заметим, что, хотя иногда явное преобразование необходимо, оно служит потенциальным источником ошибок, поскольку подавляет проверку типов, выполняемую компилятором. Давайте сначала посмотрим, зачем нужно такое преобразование.
    Указатель на объект любого неконстантного типа может быть присвоен указателю типа void*
    , который используется в тех случаях, когда действительный тип объекта либо неизвестен, либо может меняться в ходе выполнения программы. Поэтому указатель void*
    иногда называют универсальным указателем. Например: const void *pcv = pci; // правильно
    Однако указатель void* не может быть разыменован непосредственно. Компилятор не знает типа объекта, адресуемого этим указателем. Но это известно программисту, который хочет преобразовать указатель void* в указатель определенного типа. С++ не обеспечивает подобного автоматического преобразования:
    } int iva1; int *pi = 0; char *pc = 0; void *pv; pv = pi; // правильно: неявное преобразование pv = pc; // правильно: неявное преобразование const int *pci = &iva1; pv = pci; // ошибка: pv имеет тип, отличный от const void*;
    #inc1ude int ival = 1024; void *pv; int *pi = &iva1; const char *pc = "a casting call"; void mumble() { pv = pi; // правильно: pv получает адрес ival pc = pv; // ошибка: нет стандартного преобразования char *pstr = new char[ str1en( pc )+1 ]; strcpy( pstr, pc );

    С++ для начинающих
    176
    Компилятор выдает сообщение об ошибке, так как в данном случае указатель pv содержит адрес целого числа ival, и именно этот адрес пытаются присвоить указателю на строку. Если бы такая программа была допущена до выполнения, то вызов функции strcpy()
    , которая ожидает на входе строку символов с нулем в конце, скорее всего привел бы к краху, потому что вместо этого strcpy() получает указатель на целое число.
    Подобные ошибки довольно просто не заметить, именно поэтому С++ запрещает неявное преобразование указателя на void в указатель на другой тип. Однако такой тип можно изменить явно:
    }
    Другой причиной использования явного преобразования типов может служить необходимость избежать стандартного преобразования или выполнить вместо него собственное. Например, в следующем выражении ival сначала преобразуется в double, потом к нему прибавляется dval, и затем результат снова трансформируется в int. 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.
    Четыре вида явного преобразования введены для того, чтобы учесть все возможные формы приведения типов. Так const_cast служит для трансформации константного типа в неконстантный и подвижного (volatile) – в неподвижный. Например: void mumble 0 {
    // правильно: программа по-прежнему содержит ошибку,
    // но теперь она компилируется!
    //
    Прежде всего нужно проверить
    // явные преобразования типов... pc = static_cast< char* >( pv ); char *pstr = new char[ str1en( pc )+1 ];
    // скорее всего приведет к краху strcpy( pstr, pc ); double dval; int iva1;

    С++ для начинающих
    177
    char *pc = string_copy( const_cast< char* >( pc_str ));
    Любое иное использование const_cast вызывает ошибку компиляции, как и попытка подобного приведения с помощью любого из трех других операторов.
    С применением static_cast осуществляются те преобразования, которые могут быть сделаны неявно, на основе правил по умолчанию: char ch = static_cast< char >( d );
    Зачем использовать static_cast? Дело в том, что без него компилятор выдаст предупреждение о возможной потере точности. Применение оператора static_cast говорит и компилятору, и человеку, читающему программу, что программист знает об этом.
    Кроме того, с помощью static_cast указатель void* можно преобразовать в указатель определенного типа, арифметическое значение – в значение перечисления (enum), а базовый класс – в производный. (О преобразованиях типов базовых и производных классов говорится в главе 19.)
    Эти изменения потенциально опасны, поскольку их правильность зависит от того, какое конкретное значение имеет преобразуемое выражение в данный момент выполнения программы: mumble mums_the_word = static_cast< mumble >( ival );
    Трансформация ival в mumble будет правильной только в том случае, если ival равен 1,
    2
    или 3. reinterpret_cast работает с внутренними представлениями объектов (re-interpret – другая интерпретация того же внутреннего представления), причем правильность этой операции целиком зависит от программиста. Например: char *pc = reinterpret_cast< char* >( pcom );
    Программист не должен забыть или упустить из виду, какой объект реально адресуется указателем char* pc. Формально это указатель на строку встроенного типа, и компилятор не будет препятствовать использованию pc для инициализации строки: string str( pc ); extern char *string_copy( char* ); const char *pc_str; double d = 97.0; enum mumble { first = 1, second, third }; extern int ival; complex *pcom;

    С++ для начинающих
    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; и может применяться вместо операторов static_cast
    , const_cast и reinterpret_cast.
    Вот несколько примеров такого использования: int addr_va1ue = int( &iva1 );
    Эта форма сохранена в стандарте С++ только для обеспечения обратной совместимости с программами, написанными для С и предыдущих версий С++.
    1   ...   13   14   15   16   17   18   19   20   ...   93


    написать администратору сайта