С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 31
Текст из файла (страница 31)
Неявное преобразование типовЯзык определяет набор стандартных преобразований между объектами встроенного типа,неявно выполняющихся компилятором в следующих случаях:•арифметическое выражение с операндами разных типов: все операндыприводятся к наибольшему типу из встретившихся. Это называетсяintival = 3;double dva1 = 3.14159;// ival преобразуется в double: 3.0арифметическим преобразованием. Например:ival + dva1;•присваивание значения выражения одного типа объекту другого типа. В этомслучае результирующим является тип объекта, которому значение присваивается.Так, в первом примере литерал 0 типа int присваивается указателю типа int*,// 0 преобразуется в нулевой указатель типа int*int *pi = 0;// dva1 преобразуется в int: 3значением которого будет 0. Во втором примере double преобразуется в int.ivat = dva1;•передача функции аргумента, тип которого отличаетсясоответствующего формального параметра.
Тип фактическогоextern double sqrt( double );// 2 преобразуется в double: 2.0приводится к типу параметра:cout << "Квадратный корень из 2: " << sqrt( 2 ) << endt;от типааргументаС++ для начинающих•возврат из функции значения, тип которого не совпадает с типомвозвращаемого результата, заданным в объявлении функции. Тип фактическиdouble difference( int ivati, int iva12 ){// результат преобразуется в doublereturn ivati - iva12;возвращаемого значения приводится к объявленному. Например:}4.14.2. Арифметические преобразования типовАрифметические преобразования приводят оба операнда бинарного арифметическоговыражения к одному типу, который и будет типом результата выражения.
Два общихправила таковы:•типы всегда приводятся к тому из типов, который способен обеспечитьнаибольший диапазон значений при наибольшей точности. Это помогаетуменьшить потери точности при преобразовании;•любое арифметическое выражение, включающее в себя целые операнды типов,меньших чем int, перед вычислением всегда преобразует их в int.•Мы рассмотрим иерархию правил преобразований, начиная с наибольшеготипа long double.Если один из операндов имеет тип long double, второй приводится к этому же типу влюбом случае.
Например, в следующем выражении символьная константа 'a'трансформируется в long double (значение 97 для представления ASCII) и затемприбавляется к литералу того же типа: 3.14159L + 'a'.Если в выражении нет операндов long double, но есть операнд double, всеintiva1;float fval;double dval;// fva1 и iva1 преобразуются к double перед сложениемпреобразуется к этому типу. Например:dval + fva1 + ival;В том случае, если нет операндов типа double и long double, но есть операнд float,char cvat;int iva1;float fva1;// iva1 и cval преобразуются к float перед сложениемтип остальных операндов меняется на float:173С++ для начинающих174cvat + 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.char cval;bool found;enum mumble { ml, m2, m3 }mval;unsigned long ulong;В следующем выраженииcval + ulong; ulong + found; mval + ulong;перед определением типа результата cval, found и mval преобразуются в int.После приведения к целому сравниваются получившиеся типы операндов.
Если один изних имеет тип unsigned long, то остальные будут того же типа. В нашем примере всетри объекта, прибавляемые к ulong, приводятся к типу unsigned long.Если в выражении нет объектов unsigned long, но есть объекты типа long, типchar cval;long lval;// cval и 1024 преобразуются в long перед сложениемостальных операндов меняется на long. Например:cval + 1024 + lval;Из этого правила есть одно исключение: преобразование unsigned int в longпроисходит только в том случае, если тип long способен вместить весь диапазонзначений unsigned int. (Обычно это не так в 32-битных системах, где и long, и intпредставляются одним машинным словом.) Если же тип long не способен представитьвесь диапазон unsigned int, оба операнда приводятся к unsigned long.С++ для начинающихВ случае отсутствия операндов типов unsigned long и long, используется типunsigned int. Если же нет операндов и этого типа, то к int.Может быть, данное объяснение преобразований типов несколько смутило вас.Запомните основную идею: арифметическое преобразование типов ставит своей цельюсохранить точность при вычислении.
Это достигается приведением типов всех операндовк типу, способному вместить любое значение любого из присутствующих в выраженииоперандов.4.14.3. Явное преобразование типовЯвное преобразование типов производится при помощи следующих операторов:static_cast, dynamic_cast, const_cast и reinterpret_cast. Заметим, что, хотяиногда явное преобразование необходимо, оно служит потенциальным источникомошибок, поскольку подавляет проверку типов, выполняемую компилятором. Давайтесначала посмотрим, зачем нужно такое преобразование.Указатель на объект любого неконстантного типа может быть присвоен указателю типаvoid*, который используется в тех случаях, когда действительный тип объекта либонеизвестен, либо может меняться в ходе выполнения программы.
Поэтому указательint iva1;int *pi = 0;char *pc = 0;void *pv;pv = pi; // правильно: неявное преобразованиеpv = pc; // правильно: неявное преобразованиеconst int *pci = &iva1;pv = pci; // ошибка: pv имеет тип, отличный от const void*;void* иногда называют универсальным указателем.
Например:const void *pcv = pci; // правильноОднако указатель void* не может быть разыменован непосредственно. Компилятор незнает типа объекта, адресуемого этим указателем. Но это известно программисту,который хочет преобразовать указатель void* в указатель определенного типа. С++ не#inc1ude <cstring>int ival = 1024;void *pv;int *pi = &iva1;const char *pc = "a casting call";void mumble() {pv = pi; // правильно: pv получает адрес ivalpc = pv; // ошибка: нет стандартного преобразованияchar *pstr = new char[ str1en( pc )+1 ];strcpy( pstr, pc );обеспечивает подобного автоматического преобразования:}175С++ для начинающихКомпилятор выдает сообщение об ошибке, так как в данном случае указатель pvсодержит адрес целого числа ival, и именно этот адрес пытаются присвоить указателюна строку.
Если бы такая программа была допущена до выполнения, то вызов функцииstrcpy(), которая ожидает на входе строку символов с нулем в конце, скорее всегопривел бы к краху, потому что вместо этого strcpy() получает указатель на целое число.Подобные ошибки довольно просто не заметить, именно поэтому С++ запрещает неявноепреобразование указателя на void в указатель на другой тип.
Однако такой тип можноvoid mumble 0 {// правильно: программа по-прежнему содержит ошибку,// но теперь она компилируется!// Прежде всего нужно проверить// явные преобразования типов...pc = static_cast< char* >( pv );char *pstr = new char[ str1en( pc )+1 ];// скорее всего приведет к крахуstrcpy( pstr, pc );изменить явно:}Другой причиной использования явного преобразования типов может служитьнеобходимость избежать стандартного преобразования или выполнить вместо негособственное. Например, в следующем выражении ival сначала преобразуется в double,double dval;int iva1;потом к нему прибавляется 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) – в неподвижный. Например:176С++ для начинающихextern char *string_copy( char* );const char *pc_str;char *pc = string_copy( const_cast< char* >( pc_str ));Любое иное использование const_cast вызывает ошибку компиляции, как и попыткаподобного приведения с помощью любого из трех других операторов.С применением static_cast осуществляются те преобразования, которые могут бытьdouble d = 97.0;сделаны неявно, на основе правил по умолчанию:char ch = static_cast< char >( d );Зачем использовать static_cast? Дело в том, что без него компилятор выдастпредупреждение о возможной потере точности.