Инструменты пользователя

Инструменты сайта


preobrazovanie_tipov

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

preobrazovanie_tipov [2018/09/05 06:53] (текущий)
Строка 1: Строка 1:
 +====== Преобразование типов в стиле С ======
 +{{htmlmetatags>​
 +metatag-description=(Синтаксис:​ приведение типов данных в C и C++)
 +}}
  
 +
 +Когда, мы что-либо делаем,​ нам, несомненно,​ важно знать каков будет результат. Вполне очевидно,​ что из тех ингредиентов из которых,​ скажем вариться суп харчо, вряд ли можно приготовить торт со взбитыми сливками. Следовательно,​ результат напрямую зависит,​ от составных частей. То же самое происходит с переменными. Если, скажем,​ складывается два числа типа int, вполне понятно,​ что результат так же будет иметь тип int. А вот как быть, если данные имеют разные типы? Именно об этом мы и поговорим в текущем разделе данного урока.
 +
 +Итак, прежде всего, давайте разберемся с тем, как типы данных взаимодействуют друг с другом. Существует так называемая иерархия типов, где все типы размещены по старшинству. Для того, что бы разбираться в преобразовании типов, необходимо всегда помнить порядок типов этой иерархии.
 +<​file>​
 +bool,​char,​short-int-unsigned int-long-unsigned long-float-double-long double
 +</​file>​
 +Несмотря на то, что некоторые типы имеют одинаковый размер,​ в них помещается разный диапазон значений,​ например,​ unsigned int в отличие от int может поместить в себя в два раза больше положительных значений,​ и потому является старше по иерархии,​ при этом оба типа имеют размер 4 байта. Кроме того, следует отметить,​ очень важную особенность,​ отраженную здесь, если в преобразовании типов участвуют такие типы, как bool,​char,​short,​ они автоматически преобразовываются к типу int.
 +
 +Теперь,​ давайте рассмотрим,​ различные классификации преобразований.
 +
 +Классификация по диапазону содержащихся значений.
 +Все преобразования можно разделить на две группы относительно местоположения в иерархии типов участвующих в преобразовании.
 +
 +  * **Сужающее преобразование** – при таком преобразовании - больший тип данных в иерархии преобразуется к меньшему типу, безусловно,​ в этом случае может произойти потеря данных,​ поэтому с сужающим преобразованием,​ следует быть осторожными. Например:​
 +<​file>​
 +int A=23.5;
 +cout<<​A;​ // на экране 23
 +</​file>​Как видите,​ такое преобразование от double к int, ведет к грубому обрезанию вещественного числа, без математического округления,​ результатом которого являлось бы число 24. 
 +
 +  * **Расширяющее преобразование**. Данный вид преобразования,​ ведет к так называемому расширению типа данных от меньшего диапазона значений к большему диапазону. В качестве примера предлагается такая ситуация.
 +<​file>​
 +unsigned int a=3000000000;​
 +cout<<​a;​ // на экране 3000000000
 +</​file>​
 +В данном случае 3000000000 - это литерал типа int, который благопоучно расширяется до unsigned int, что и позволяет нам увидеть на экране именно 3000000000, а не что-то другое. Тогда, как в обычный int такое число не поместиться.
 +
 +====== Классификация по способу осуществления преобразования ======
 +Вне зависимости от направления преобразования,​ оно может быть осуществлено одним из двух способов.
 +
 +  - **Неявное преобразование**. Все вышеописанные примеры относились к этому типу преобразования. Такой вид преобразования также называют автоматическим,​ т. к. оно происходит автоматически без вмешательства программиста,​ другими словами,​ мы ничего не делаем для того, что бы оно произошло.<​file>​
 +float A=23,5; - double стал ​ float без каких-либо дополнительных действий
 +</​file>​
 +  - **Явное преобразование**. (второе название – приведение типов). В данном случае,​ преобразование производится программистом,​ тогда, когда это необходимо. Давайте рассмотрим простой пример такого действия:<​file>​
 +double z=37.4;
 +float y=(int) z;
 +cout<<​z<<​”*** ”<<​y;​ // на экране 37.4 ***37
 +</​file>​
 +(int)z – есть явное сужающее преобразование от типа double к типу int. В этой же строке происходит расширяющее неявное преобразование от полученного типа int к типу float. Следует запомнить,​ что любое преобразование носит временный характер и действует только в пределах текущей строки. То есть переменная z как была double, так и останется на протяжении всей программы,​ а ее преобразование в int носило временный характер.
 +
 +====== Преобразование типов в выражении ======
 +И, вот, мы наконец-то подошли к тому, о чем говорили в самом начале данного раздела урока, как выяснить какого типа будет результат какого-то выражения. Давайте попробуем это вычислить,​ пользуясь полученными знаниями. Предположим у нас есть следующие переменные:<​file>​
 +int I=27;
 +short S=2;
 +float F=22.3;
 +bool B=false;
 +</​file>​
 +Пользуясь этими переменными,​ мы собираемся составить такое выражение:<​file>​
 +I-F+S*B
 +</​file>​
 +В переменную какого типа данных нам следует записать результат?​ Решить это просто,​ если представить выражение в виде типов данных:<​file>​
 +int-float+short*bool
 +</​file>​
 +Напоминаем,​ что short и bool сразу же примут тип int, так что выражение будет выглядеть так:
 +<​file>​
 +int-float+int*int, ​ при этом false станет 0
 +</​file>​
 +Умножение int на int даст, несомненно,​ результат типа int. А вот сложение float с int, даст на выходе float, так как, здесь вступает в игру новое правило:​
 +**Если в каком-либо выражении используются разные типы данных,​ то результат,​ приводится к большему из этих типов.**
 +Ну и, наконец – вычитание из int типа float, согласно только что упомянутому правилу снова даст float.
 +Таким образом,​ результат выражения будет иметь тип float. ​
 +<​file>​
 +float res= I-F+S*B; // 27-22.3+2*0
 +cout<<​res;​ // на экране число 4.7
 +</​file>​
 +Теперь,​ когда вы знакомы с правилом,​ вам нет необходимости,​ детально разбирать выражение,​ достаточно просто найти наибольший тип, именно он и будет результирующим.
 +
 +**Примечание:​ Будьте очень внимательны также и при сочетании переменных с одинаковыми типами данных. Например,​ помните,​ если целое делится на целое, то и получиться целое. То есть, int A=3; int B=2; cout<<​A/​B;​ // на экране 1, так как результат - int и дробная часть утеряна. cout<<​(float)A/​B;​ // на экране 1.5, так как результат - float.**
 +
 +===== Пример,​ использующий преобразование типов =====
 +Теперь давайте закрепим знания на практике. Создадим проект и напишем нижеследующий код.
 +<​file>​
 +# include <​iostream>​
 +using namespace std;
 +void main(){
 +
 + // объявление переменных и запрос на ввод данных
 + float digit;
 + cout<<"​Enter digit:";​
 + cin>>​digit;​
 +
 + /* Даже, если пользователь ввел число с вещественной частью,​
 + результат выражения запишется в int и вещественная часть будет утеряна,​
 + разделив число на 100 мы получим количество сотен в нем. */   
 + int res=digit/​100;​
 + cout<<​res<<"​ hundred in you digit!!!\n\n";​
 +}
 +</​file>​
 +Теперь,​ когда вы рассмотрели пример,​ вы, конечно же, убедились,​ что c помощью преобразования типов можно не просто организовать временный переход из одного типа в другой,​ но и решить простую логическую задачу. Следовательно,​ вы должны отнестись к этой теме с должным вниманием. Понимание преобразования в дальнейшем поможет вам не только решать интересные задачи,​ но избежать ненужных ошибок.
 +====== Преобразование типов в стиле С++ ======
 +Для начала напомним,​ что оператор приведения типов позволяет компилятору преобразовывать один тип данных в другой. И язык программирования С, и язык программирования С++ поддерживают такую форму записи приведения типа: ​
 +<​file>​
 +(тип) выражение
 +
 +Например,​
 +
 +double d;
 +d=(double) 10/3;
 +</​file>​
 +===== Операторы приведения типа в языке С++ =====
 +Кроме стандартной формы в стиле С, язык С++ поддерживает дополнительные операторы приведения типа. Вполне очевидны положительные стороны этих новых способов. Во-первых данные преобразования позволяют сделать то, чего стандарт языка С даже не предполагал:​ снять модификатор const, или радикально поменять тип данных. Во-вторых,​ стиль С++ увеличивает контроль над преобразованиями и позволяет выявить ошибки с ними связанные на ранних этапах. ​
 +  * const_cast<​тип>​ (объект)
 +  * dynamic_cast<​тип>​ (объект) проверяет законность выполнения заданной операции приведения типа
 +  * reinterpret_cast<​тип>​(объект) следует использовать для перевода типов указателей,​ которые несовместимы по своей природе
 +  * static_cast<​тип>​ (объект) Фактически,​ static_cast - это аналог стандартной операции преобразования в стиле С.
 +
 +const_cast используется для явного переопределения модификаторов const и/или volatile. Новый тип должен совпадать с исходным типом, за исключением изменения его атрибутов const или volatile. Чаще всего оператор const_cast используется для снятия атрибута const. Например: ​
 +
 +<​note>​Следует помнить,​ что только оператор const_cast может освободить от "​обета постоянства",​ т.е. ни один из остальных операторов этой группы не может "​снять"​ с объекта атрибут const.</​note> ​
 +<​file>​
 +#include <​iostream>​
 +using namespace std;
 +// указатель на объект является константным,​
 +// следовательно,​ через него изменить значение
 +// объекта нельзя
 +void test_pow(const int* v){
 + int*temp;
 + // снимаем модификатор const
 + // и теперь можем изменять объект
 + temp=const_cast<​int*>​(v);​
 + // изменение объекта
 + *temp= *v * *v;
 +}
 +void main(){
 + int x=10;
 + // на экране - 10
 + cout<<"​Before - "<<​x<<"​\n\n";​
 + test_pow(&​x);​
 + // на экране - 100
 + cout<<"​After - "<<​x<<"​\n\n";​
 +}
 +</​file>​
 +
 +Примечание:​ Кстати,​ стоит упомянуть о том, что такое volatile!!! Данный модификатор даёт компилятору понять,​ что значение переменной,​ может быть изменено неявным образом. Например,​ есть некая глобальная переменная,​ её адрес передаётся встроенному таймеру операционной системы. В дальнейшем эта конструкция может использоваться для отсчёта реального времени. Естественно,​ что значение этой переменной изменяется автоматически без участия операторов присваивания. Так вот такая переменная должна быть объявлена с использованием ключевого слова volatile. Это связано с тем, что многие компиляторы не обнаружив ни одного изменения переменной с помощью какого-либо присваивания,​ считают,​ что она не изменяется и оптимизируют выражения с ней, подставляя на её место конкретное значение. При этом компилятор переменную не перепроверяет. Действительно,​ зачем, если переменная для него имеет характер "​неизменяемой"?​! volatile - будет блокировать подобные действия компиляторов. ​
 +
 +**dynamic_cast** проверяет законность выполнения заданной операции приведения типа. Если такую операцию выполнить нельзя,​ то выражение устанавливается равным нулю. Этот оператор в основном используется для полиморфных типов. Например,​ если даны два полиморфных класса,​ B и D, причем класс D выведен из класса B, то оператор dynamic_cast всегда может преобразовать указатель D* в указатель B*. Оператор dynamic_cast может преобразовать указатель B* в указатель D* только в том случае,​ если адресуемым объектом является объект D. И вообще,​ оператор dynamic_cast будет успешно выполнен только при условии,​ что разрешено полиморфное приведение типов (т.е. если новый тип можно законно применять к типу объекта,​ который подвергается этой операции). Рассмотрим пример,​ демонстрирующий всё вышесказанное: ​
 +
 +<​file>​
 +#include <​iostream>​
 +using namespace std;
 +// базовый класс
 +class B{
 + public:
 + // виртуальная функция для ​
 + // последующего переопределения в потомке
 + virtual void Test(){
 + cout<<"​Test B\n\n";​
 + }
 +};
 +// класс-потомок
 +class D:public B{
 + public: ​
 + // переопределение виртуальной функции
 + void Test(){
 + cout<<"​Test D\n\n";​
 + }
 +};
 +void main(){
 + // указатель на класс-родитель
 + // и объект класса-родителя
 + B *ptr_b, obj_b;
 + // указатель на класс-потомок
 + // и объект класса-потомка
 + D *ptr_d, obj_d;
 +
 + // приводим адрес объекта (D*) к указателю типа D*
 + ptr_d= dynamic_cast<​D*>​ (&​obj_d);​
 + // если все прошло успешно - вернулся !0
 + // произошло приведение ​
 + if(ptr_d){
 + cout<<"​Good work - ";
 + // здесь вызов функции класса-потомка
 + // на экране - Test D
 + ptr_d->​Test(); ​
 + }
 + // если произошла ошибка - вернулся 0
 + else cout<<"​Error work!!!\n\n";​
 +
 + // приводим адрес объекта (D*) к указателю типа B*
 + ptr_b= dynamic_cast<​B*>​ (&​obj_d);​
 + // если все прошло успешно - вернулся !0
 + // произошло приведение ​
 + if(ptr_b){
 + cout<<"​Good work - ";
 + // здесь вызов функции класса-потомка
 + // на экране - Test D
 + ptr_b->​Test(); ​
 + }
 + // если произошла ошибка - вернулся 0
 + else cout<<"​Error work!!!\n\n";​
 +
 + // приводим адрес объекта (B*) к указателю типа B*
 + ptr_b= dynamic_cast<​B*>​(&​obj_b);​
 + // если все прошло успешно - вернулся !0
 + // произошло приведение ​
 + if(ptr_b){
 + cout<<"​Good work - ";
 + // здесь вызов функции класса-потомка
 + // на экране - Test B
 + ptr_b->​Test(); ​
 + }
 + // если произошла ошибка - вернулся 0
 + else cout<<"​Error work!!!\n\n";​
 +
 + // ВНИМАНИЕ!!! ЭТО НЕВОЗМОЖНО
 + // попытка привести адрес объекта (B*) к указателю типа D*
 + ptr_d= dynamic_cast<​D*>​ (&​obj_b);​
 + // если все прошло успешно - вернулся !0
 + // произошло приведение ​
 + if(ptr_d)
 + cout<<"​Good work - ";
 + // если произошла ошибка - вернулся 0
 + else cout<<"​Error work!!!\n\n";​
 +}
 +Результат работы программы:​
 +Good work - Test D
 +Good work - Test D
 +Good work - Test B
 +Error work!!!
 +</​file>​
 +
 + 
 +
 +static_cast выполняет неполиморфное приведение типов. Его можно использовать для любого стандартного преобразования. При этом никакие проверки во время работы программы не выполняются. Фактически,​ static_cast - это аналог стандартной операции преобразования в стиле С. Например,​ так: ​
 +
 +<​file>​
 +#include <​iostream>​
 +using namespace std;
 +void main(){
 + int i;
 + for(i=0;​i<​10;​i++)
 + // приведение переменной i к типу double
 + // результаты деления на экране,​ естественно
 + // вещественные
 + cout<<​static_cast<​double>​(i)/​3<<"​\t";​
 +}
 +</​file>​
 +reinterpret_cast переводит один тип в совершенно другой. Например,​ его можно использовать для перевода указателя в целый тип и наоборот. Оператор reinterpret_cast следует использовать для перевода типов указателей,​ которые несовместимы по своей природе. Рассмотрим пример: ​
 +
 +<​file>​
 +#include <​iostream>​
 +using namespace std;
 +void main(){
 + // целочисленная переменная
 + int x;
 + // строка (указатель типа char)
 + char*str="​This is string!!!";​
 + // демонстрируем строку на экран
 + cout<<​str<<"​\n\n";​ // на экране - This is string!!!
 + // преобразуем указатель типа char в число
 + x=reinterpret_cast<​int>​(str);​
 + // демонстрируем результат
 + cout<<​x<<"​\n\n";​ // на экране - 4286208
 +}
 +</​file>​
 +===== RTTI =====
 +  * [[wpru>​RTTI]]
 +В C++ для динамической идентификации типов применяются операторы dynamic_cast и typeid (определён в файле typeinfo.h),​ для использования которых информацию о типах во время выполнения обычно необходимо добавить через опции компилятора при компиляции модуля.
 +  * Оператор **dynamic_cast** пытается выполнить приведение к указанному типу с проверкой. Целевой тип операции должен быть типом указателя,​ ссылки или void*.
 +  * Оператор **typeid** возвращает ссылку на структуру type_info, которая содержит поля, позволяющие получить информацию о типе.
загрузка...
preobrazovanie_tipov.txt · Последние изменения: 2018/09/05 06:53 (внешнее изменение)