Иногда может возникнуть необходимость преобразовать один тип в другой. Это называется преобразованием типов или приведением типов.
В этой статье мы обсудим различные преобразования типов, поддерживаемые в C++.
Преобразования типов
C++ поддерживает два типа преобразования типов:
- Неявное преобразование типов: Неявное преобразование типов выполняется автоматически. Пользователь не вмешивается в этот тип преобразования, и компилятор непосредственно выполняет преобразование сам. Преобразование обычно выполняется, когда в выражении присутствует более одного типа данных. Но, как правило, при таком типе преобразования существует вероятность потери данных, потери знаков или переполнения данных.
- Явное преобразование типов: Явное преобразование типов определяется пользователем и обычно называется «приведением типов». Здесь пользователь приводит или преобразует значение одного типа данных в другой в зависимости от требований. Такие преобразования более безопасны.
Теперь мы подробно рассмотрим оба типа преобразования типов.
Неявное преобразование
При неявном преобразовании компилятор выполняет преобразования из одного типа данных в другой всякий раз, когда выражение имеет более одного типа данных. Чтобы предотвратить потерю данных, все переменные других типов данных преобразуются в самый большой тип данных. Это называется продвижением.
Давайте разберемся с неявным преобразованием на примере кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> using namespace std; int main() { int num = 10; char ch = 'A'; cout<<"10 + 'A' = "<<num + ch<<endl; float val = num + 'a'; cout<<"float val(10 + 'a') = "<<val<<endl; short var = 1000; int var_int = var; cout<<"var_int = "<<var_int; return 0; } |
Вывод данных:
1 2 3 |
10 + ‘A’ = 75 float val(10 + ‘a’) = 107 var_int = 1000 |
Приведенный выше пример кода демонстрирует неявное преобразование. Мы объявили целочисленную и символьную переменную со значениями 10 и ‘A’ соответственно. Когда мы складываем эти две переменные, происходит неявное преобразование.
Поскольку целое число является более крупным типом в этом выражении, значение символьной переменной ‘A’ преобразуется в его целочисленный эквивалент, т.е. значение 65 (значение ASCII). Таким образом, результат выражения равен 75.
В следующем выражении мы добавляем целое число и символ (‘a’ -> 97), а затем присваиваем результату значение float. Таким образом, результат выражения неявно преобразуется компилятором в число с плавающей запятой.
В третьем выражении переменная типа short int неявно преобразуется в целое число.
Кстати, в случае неявных преобразований, если компилятор обнаружит возможную потерю данных, он может выдать соответствующее предупреждение.
Явное преобразование
Явное преобразование также известно как «приведение типов», поскольку мы «приводим» один тип данных к другому типу данных. Здесь пользователи явно определяют приведение, в отличие от неявного преобразования, когда компилятор выполняет внутреннее преобразование.
Мы можем выполнить явное преобразование двумя способами:
1) Использование оператора присваивания
Явное преобразование или приведение типов с использованием оператора присваивания выполняется принудительно. Здесь мы приводим или преобразуем один тип данных в другой тип данных с помощью оператора присваивания.
Общий синтаксис:
1 |
(data type) expression; |
Следующий пример объясняет это:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> #include <string> using namespace std; int main() { int sum; double salary = 4563.75; sum = (int)salary + 1000; cout<<"Sum = "<<sum<<endl; double comp = (double)sum + 0.20; cout<<"Comp = "<<comp; return 0; } |
Вывод данных:
1 2 |
Sum = 5563 Comp = 5563.2 |
В приведенном выше примере мы показали явное приведение типов с использованием оператора присваивания. Во-первых, мы приводим переменную salary типа double к целочисленному типу. Затем мы приводим целочисленную переменную sum к типу double.
Как показано в выводе, тип, к которому мы приводим, указывает окончательный тип результата выражения.
Это выгодно, поскольку пользователь может изменить тип выражения в соответствии с требованиями.
2) Использование оператора приведения
В этом типе приведения мы используем «оператор приведения», который является унарным оператором для перехода от одного типа к другому.
Типы приведения
У нас есть следующие типы приведения в зависимости от используемого оператора приведения:
1) Статическое приведение
Статическое приведение — самое простое из всех приведения типов с использованием оператора приведения. Статическое приведение может выполнять все преобразования, которые выполняются неявно. Оно также выполняет преобразования между указателями классов, связанных друг с другом (upcast -> от производного к базовому или downcast -> от базового к производному).
Помимо перечисленных выше преобразований, статическое приведение также может преобразовать любой указатель в void*.
Статическое приведение — это скомпилированное приведение по времени. Это означает, что во время выполнения не выполняется проверка, является ли выполненное приведение действительным или нет. Таким образом, на программисте остается ответственность за обеспечение того, чтобы преобразование было безопасным и действительным.
Другими словами, пользователь должен убедиться, что преобразованный объект был полным по отношению к целевому типу данных.
Мы указываем статическое приведение следующим образом:
1 |
static_cast <new_type> (expression) |
Давайте разберемся со статическим приведением на примере:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> using namespace std; int main() { double df = 3.5 * 3.5 * 3.5; cout<<"Before casting: df = "<<df<<endl; int total = static_cast<int>(df); cout <<"After static_cast:total = "<<total; } |
Вывод данных:
До приведения: df = 42,875
После static_cast: total = 42
Таким образом, в приведенном выше примере мы вычислили значение типа double. Затем мы применяем static_cast к этому значению, чтобы преобразовать его в целочисленный тип. В выводе мы видим, что значение усекается после приведения его к типу int.
Теперь давайте изменим приведенный выше код следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> using namespace std; int main() { double df = 3.5 * 3.5 * 3.5; cout<<"Before casting :df = "<<df<<endl; int total = static_cast<int>(df); cout <<"After static_cast:total = "<<total; char c = 'A'; int* pq = static_cast<int*>(&c); cout<<*pq; } |
В приведенном выше примере мы немного изменили код, чтобы включить символьную переменную со значением «A». Затем мы объявляем целочисленный указатель и применяем статическое приведение для преобразования символа в целочисленный указатель.
Когда мы скомпилируем эту программу, мы получим следующий вывод данных:
1 2 |
In function ‘int main()’: 10:35: error: invalid static_cast from type ‘char*’ to type ‘int*’ |
Программа выдает ошибку при выполнении статического приведения, так как оно недействительно. Таким образом, статическое приведение допускает только действительное приведение или преобразование типов и выдает ошибку, когда мы пытаемся выполнить нежелательное приведение типов.
2) Динамическое приведение
Динамическое приведение — это приведение во время выполнения, выполняемое для проверки достоверности приведения. Динамическое приведение выполняется только для указателей и ссылок на классы. Выражение возвращает значение NULL, если приведение не удается.
Динамическое приведение использует механизм, известный как RTTI (идентификация типа во время выполнения). RTTI делает всю информацию о типе данных объекта доступной во время выполнения и доступна только для классов, которые имеют хотя бы одну виртуальную функцию (полиморфный тип). RTTI позволяет определить тип объекта во время выполнения или во время выполнения.
Давайте посмотрим один пример, чтобы понять динамическое приведение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> #include <string> using namespace std; class base {public: virtual void print(){}}; class derived:public base{}; int main() { base* b = new derived; derived* d = dynamic_cast<derived*>(b); if(d != NULL) cout<<"Dynamic_cast done successfully"; else cout<<"Dynamic_cast not successful"; } |
В этой программе мы определили два класса: базовый с виртуальной функцией и производный от базового класса base.
В основной функции мы создаем объект производного класса, на который указывает указатель базового класса. Затем мы выполняем dynamic_cast для базового указателя, указывающего на производный класс, чтобы привести его к указателю производного класса.
Как и в базовом классе, база полиморфна (содержит виртуальную функцию), dynamic_cast выполнена успешно.
Кстати, если мы удалим виртуальную функцию из вышеуказанного класса, то dynamic_cast завершится ошибкой, так как информация RTTI для объектов будет недоступна.
3) Reinterpret_cast
Этот тип приведения наиболее опасен в использовании, поскольку он работает с любым типом объектов, при этом классы не связаны друг с другом.
Reintepret_cast работает с любыми указателями и преобразует указатель любого типа в любой другой тип, независимо от того, связаны ли указатели друг с другом или нет. Он не проверяет, совпадают ли указатель или данные, на которые указывает указатель, или нет.
Оператор приведения <reinterpret_cast> принимает только один параметр — указатель источника для преобразования и не возвращает никакого значения. Он просто преобразует тип указателя.
Мы не должны использовать <reinterpret_cast> без необходимости. Обычно мы приводим исходный указатель к исходному типу.
Мы используем <reinterpret_cast> в основном для работы с битами. Когда <reinterpret_cast> используется для логических значений, логические значения преобразуются в целочисленные значения, т. е. 1 для истинного и 0 для ложного.
Давайте посмотрим на пример приведения Reinterpret:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> using namespace std; int main() { int* ptr = new int(97); char* ch = reinterpret_cast<char*>(ptr); cout << ptr << endl; cout << ch << endl; cout << *ptr << endl; cout << *ch << endl; return 0; } |
Вывод данных:
1 2 3 4 |
0x3ef3090 а 97 а |
В приведенном выше примере мы объявили целочисленный указатель ptr, указывающий на значение 97. Затем мы объявляем указатель символа ch и приводим к нему ptr с помощью <reinterpret_cast>.
Далее мы печатаем различные значения. Сначала мы печатаем ptr, указывающий на целочисленное местоположение. Следовательно, он печатает адрес.
Следующее значение ch содержит значение 97 и, таким образом, печатает ‘a’, что является ASCII-эквивалентом 97. Следующее значение «*ptr» содержит значение 97, а «*ch» содержит ASCII-эквивалент 97, т.е. ‘a’, поскольку оно приведено с использованием reinterpret_cast.
4) Const Cast
Оператор приведения <const_cast> используется для изменения или управления константностью исходного указателя. Под манипулированием мы подразумеваем, что можно либо установить константу в неконстантный указатель, либо удалить константность из константного указателя.
Условием успешного приведения оператора <const_cast> является то, что указатель и источник, который приводится, должны быть одного типа.
Давайте посмотрим еще один пример для понимания этого:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> using namespace std; int printVal(int* ptr) { return(*ptr*10); } int main(void) { const int value = 10; const int *ptr = &value; int *ptr_cast = const_cast <int *>(ptr); cout <<"printVal returned = "<< printVal(ptr_cast); return 0; } |
В этом примере мы видим, что функция printVal принимает неконстантный указатель. В основной функции у нас есть константная переменная ‘value’, присвоенная константному указателю ptr.
Чтобы передать этот константный указатель функции printVal, мы приводим его, применяя <const_cast> для удаления константности. Затем мы передаем указатель ptr_cast в функцию, чтобы получить желаемые результаты.
Итог
На этом мы завершим тему преобразования типов в C++. Мы рассказали вам все о неявных и явных преобразованиях, которые используются в C++. Однако следует помнить, что для предотвращения потери данных и других подобных трудностей преобразования или приведение типов следует применять с умом, и только в том случае, если ситуация требует их использования.
В следующей статье мы поговорим о пространствах имен в C++.
С Уважением, МониторБанк