Преобразования типов и безопасность типов
Этот документ определяет распространенные проблемы преобразования типов и описывает, как их можно избежать в коде C++.
При написании программы C++ важно убедиться, что она является типобезопасной. Это означает, что каждое значение переменной, аргумента функции и возвращаемого функции сохраняет приемлемый тип данных, и что операции, включающие значения различных типов», «имеют смысл» и не вызывают потери данных, неправильной интерпретации битовых шаблонов или повреждения памяти. Программа, которая никогда явно или неявно преобразовывает значения из одного типа в другой является типобезопасной по определению. Однако иногда требуются преобразования типов, даже небезопасные преобразования. Например, может потребоваться сохранить результат операции с плавающей запятой в переменной типа int или передать значение в unsigned int функцию, которая принимает значение signed int . Оба примера иллюстрируют небезопасные преобразования, так как они могут привести к потере данных или повторной интерпретации значения.
Когда компилятор обнаруживает небезопасное преобразование, он выдает ошибку или предупреждение. Ошибка останавливает компиляцию; Предупреждение позволяет продолжить компиляцию, но указывает на возможную ошибку в коде. Однако даже если программа компилируется без предупреждений, она по-прежнему может содержать код, который приводит к неявным преобразованиям типов, которые создают неправильные результаты. Ошибки типа также могут быть представлены явными преобразованиями или приведениями в коде.
Неявные преобразования типов
Если выражение содержит операнды различных встроенных типов и нет явных приведения, компилятор использует встроенные стандартные преобразования для преобразования одного из операндов таким образом, чтобы типы соответствовали. Компилятор пытается выполнить преобразования в четко определенной последовательности, пока не завершится успешно. Если выбранное преобразование является повышением, компилятор не выдает предупреждения. Если преобразование является сужающим, компилятор выдает предупреждение о возможной потере данных. Указывает, происходит ли фактическая потеря данных, зависит от фактических значений, но рекомендуется рассматривать это предупреждение как ошибку. Если используется определяемый пользователем тип, компилятор пытается использовать преобразования, указанные в определении класса. Если не удается найти допустимое преобразование, компилятор выдает ошибку и не компилирует программу. Дополнительные сведения о правилах, которые управляют стандартными преобразованиями, см. в разделе «Стандартные преобразования». Дополнительные сведения о пользовательских преобразованиях см. в разделе «Определяемые пользователем преобразования» (C++/CLI).
Расширение преобразований (повышение уровня)
При расширении преобразования значение в меньшей переменной назначается большей переменной без потери данных. Так как расширяющие преобразования всегда безопасны, компилятор выполняет их автоматически и не выдает предупреждения. Следующие преобразования расширяются.
| С дт. | Кому |
|---|---|
| Любой signed или unsigned целочисленный тип, кроме long long или __int64 | double |
| bool или char | Любой другой встроенный тип |
| short или wchar_t | int , long , long long |
| int , long | long long |
| float | double |
Сужение преобразований (приведение)
Компилятор выполняет неявно сужающие преобразования, но предупреждает о потенциальной потере данных. Примите эти предупреждения очень серьезно. Если вы уверены, что потеря данных не будет происходить, так как значения в большей переменной всегда будут соответствовать меньшей переменной, добавьте явный приведение, чтобы компилятор больше не выдает предупреждение. Если вы не уверены, что преобразование безопасно, добавьте в код какой-то вид среды выполнения проверка для обработки возможной потери данных, чтобы она не приводила к неправильному результату программы.
Любое преобразование из типа с плавающей запятой в целочисленный тип является сужающим преобразованием, так как дробная часть значения с плавающей запятой не карта и потеряна.
В следующем примере кода показаны некоторые неявные сужающие преобразования и предупреждения о проблемах компилятора.
int i = INT_MAX + 1; //warning C4307:'+':integral constant overflow wchar_t wch = 'A'; //OK char c = wch; // warning C4244:'initializing':conversion from 'wchar_t' // to 'char', possible loss of data unsigned char c2 = 0xfffe; //warning C4305:'initializing':truncation from // 'int' to 'unsigned char' int j = 1.9f; // warning C4244:'initializing':conversion from 'float' to // 'int', possible loss of data int k = 7.7; // warning C4244:'initializing':conversion from 'double' to // 'int', possible loss of data
Подписанные — незаписанные преобразования
Подписанный целочисленный тип и его неподписанный аналог всегда одинаковый, но они отличаются в том, как битовый шаблон интерпретируется для преобразования значений. В следующем примере кода показано, что происходит, когда тот же битовый шаблон интерпретируется как подписанное значение и как неподписаемое значение. Битовый шаблон, хранящийся в обоих num и num2 никогда не изменяется от того, что показано на предыдущем рисунке.
using namespace std; unsigned short num = numeric_limits::max(); // #include short num2 = num; cout
Обратите внимание, что значения переосмыслены в обоих направлениях. Если программа создает нечетные результаты, в которых знак значения по-видимому, повернут из ожидаемого значения, найдите неявные преобразования между подписанными и неподписанными целочисленными типами. В следующем примере результат выражения (0 – 1) неявно преобразуется из int unsigned int того, когда он хранится. num Это приводит к повторному интерпретации битового шаблона.
unsigned int u3 = 0 - 1; cout
Компилятор не предупреждает о неявных преобразованиях между подписанными и неподписанными целочисленными типами. Поэтому рекомендуется избегать полностью подписанных преобразований в unsigned. Если вы не можете избежать их, добавьте среду выполнения проверка, чтобы определить, больше ли преобразованное значение больше или равно нулю и меньше или равно максимальному значению подписанного типа. Значения в этом диапазоне будут передаваться из входа в unsigned или из без знака на подписанный без повторного понимания.
Преобразования указателей
Во многих выражениях массив стилей C неявно преобразуется в указатель на первый элемент в массиве, а преобразования констант могут выполняться автоматически. Хотя это удобно, это также потенциально подвержено ошибкам. Например, следующий плохо разработанный пример кода кажется нечувствичным, но он компилирует и создает результат p. Во-первых, константный литерал строки help преобразуется char* в тот, который указывает на первый элемент массива. Затем указатель увеличивается тремя элементами, чтобы он указывал на последний элемент p.
char* s = "Help" + 3;
Явные преобразования (приведение)
С помощью операции приведения можно указать компилятору преобразовать значение одного типа в другой тип. Компилятор вызовет ошибку в некоторых случаях, если два типа полностью не связаны, но в других случаях это не приведет к возникновению ошибки, даже если операция не является типобезопасной. Используйте приведение с разреженным способом, так как любое преобразование из одного типа в другой является потенциальным источником ошибки программы. Однако литья иногда требуются, и не все литые одинаково опасны. Одно эффективное использование приведения заключается в том, что код выполняет сужение преобразования, и вы знаете, что преобразование не приводит к неправильному результату программы. В действительности это сообщает компилятору, что вы знаете, что вы делаете, и прекратить беспокоить вас с предупреждениями об этом. Другим способом является приведение от класса указателя на производный от указателя к базовому классу. Другое использование заключается в том, чтобы отбросить константу переменной, чтобы передать ее в функцию, требующую аргумента, отличного от константа. Большинство этих операций приведения включают некоторый риск.
В программировании в стиле C один и тот же оператор приведения в стиле C используется для всех видов приведения.
(int) x; // old-style cast, old-style syntax int(x); // old-style cast, functional syntax
Оператор приведения в стиле C идентичен оператору вызова () и поэтому неудобен в коде и легко упускать из виду. Оба плохо, потому что они трудно распознать на первый взгляд или найти, и они достаточно разрозненные, чтобы вызвать любое сочетание static , const и reinterpret_cast . Определение того, что на самом деле делает старый стиль, может быть трудным и подверженным ошибкам. По всем этим причинам, если требуется приведение, рекомендуется использовать один из следующих операторов приведения C++, которые в некоторых случаях значительно более типобезопасны, и которые выражают гораздо более явно намерение программирования:
-
static_cast , для приведения, которые проверка только во время компиляции. static_cast возвращает ошибку, если компилятор обнаруживает, что вы пытаетесь приведения между типами, которые полностью несовместимы. Вы также можете использовать его для приведения между указателем на базу и указатель на производный, но компилятор не всегда может определить, будут ли такие преобразования безопасными во время выполнения.
double d = 1.58947; int i = d; // warning C4244 possible loss of data int j = static_cast(d); // No warning. string s = static_cast(d); // Error C2440:cannot convert from // double to std:string // No error but not necessarily safe. Base* b = new Base(); Derived* d2 = static_cast(b);
Base* b = new Base(); // Run-time check to determine whether b is actually a Derived* Derived* d3 = dynamic_cast(b); // If b was originally a Derived*, then d3 is a valid pointer. if(d3) < // Safe to call Derived method. cout DoSomethingMore() else < // Run-time check failed. cout << "d3 is null" //Output: d3 is null;
void Func(double& d) < . >void ConstCast() < const double pi = 3.14; Func(const_cast(pi)); //No error. >
Примечание. Этот оператор приведения не используется так часто, как и другие, и он не гарантируется переносимым к другим компиляторам.
В следующем примере показано, как reinterpret_cast отличается от static_cast .
const char* str = "hello"; int i = static_cast(str);//error C2440: 'static_cast' : cannot // convert from 'const char *' to 'int' int j = (int)str; // C-style cast. Did the programmer really intend // to do this? int k = reinterpret_cast(str);// Programming intent is clear. // However, it is not 64-bit safe.
Как изменить тип переменной в c
Преобразование типов данных, это приведение одного типа к другому. Например, приведение целочисленной переменной к числу с плавающей точной, или преобразование числа в строку. В C# выделяют два варианта преобразования типов:
- Неявное преобразование типов. Это, так называемое безопасное преобразование типов в C#. Например, преобразование из типа float (более «маленький» тип) в тип double (более «большой» тип). При таком преобразовании никакая информация не «потеряется», что при обратном преобразовании вполне возможно.
- Явное преобразование типов. Такое преобразование выполняется программистом с прямым указанием типа, к которому нужно привести переменную. Для такого преобразования требуется наличие оператора преобразования.
А теперь, я покажу как можно использовать явное преобразование типов в C#:
using System; namespace TypesConvertion < class Program < static void Main(string[] args) < //Число с плавающей точкой double doubleData = 245.45; //Вывод этого числа Console.WriteLine(doubleData); //А теперь, мы сделаем на основе этого числа, цело число int intData = (int)doubleData; //Вывод целого числа Console.WriteLine(intData); //Чтобы окно не закрылось раньше времени Console.ReadKey(); >> >
Преобразование типа double в тип int выполнялось в выделенной строке (строке с номером 16). Если вы соберете данный пример и запустите его, то увидите, что преобразование типов прошло, т.е. из числа с плавающей точкой мы получили целое число.
Так же, для преобразования данных из одного типа в другой, есть целый класс с именем Convert, у которого имеются специальные методы. Ниже, в таблице, представлены основные методы этого класса.
| Название метода | Целевой тип |
|---|---|
| ToBoolean | bool |
| ToByte | byte |
| ToChar | char |
| ToDouble | double |
| ToSingle | float |
| ToInt32 | int |
| ToInt64 | long |
| ToString | string |
А теперь, пример использования класса Convert и его методов:
using System; namespace TypesConvertion < class Program < static void Main(string[] args) < //Число с плавающей точкой double doubleData = 245.45; //Вывод этого числа Console.WriteLine(doubleData); //А теперь, мы сделаем на основе этого числа, цело число int intData = Convert.ToInt32(doubleData); //Вывод целого числа Console.WriteLine(intData); //Чтобы окно не закрылось раньше времени Console.ReadKey(); >> >
Как видите, это практически тот же код, что был в первом примере, но с использованием другого подхода в преобразовании типов.
И обратите внимание на еще одни момент, если один тип нельзя преобразовать в другой, а вы это попытаетесь сделать, то получите ошибку в программе!
Пользоваться возможность преобразования типов вам придется довольно часто!
Как изменить тип переменной?
Maxim Siomin , C++ - это язык со статической типизацией. Тип ты можешь только привести при передаче значения. Сменить его нельзя.
Существуют конструкции с вариативным типом. Но они тебе, скорее всего, не подойдут.
Решения вопроса 2

Не выйдет. C++ статически типизирован, и это то, что позволяет ему работать быстро.
А зачем, собственно, это понадобилось? В конкретной ситуации можно что-нибудь придумать.
P.S. Если хочется "просто", стоит оставаться на Питоне 🙂
Как изменить тип переменной в c
Если в арифметических операциях участвуют значения разных типов, то компилятор неявно пытается привести их к одному типу. Кроме того, когда мы присваиваем переменной какое-либо значение, это значение всегда приводится к типу переменной. Например:
char c = 6; int d = c;
Переменной d, которая представляет тип int, присваивается значение типа char, поэтому компилятор выполняет приведение значения от типа char к типу int.
В то же время не всегда преобразования могут быть безопасными, поскольку разные типы имеют разное внутреннее представление. И просто так перейти от одного представления к другому без потери точности данных не всегда возможно.
Рассмотрим, какие преобразования применяет компилятор при арифметических операциях:
- Если один из операндов имеет тип long double , то второй операнд тоже будет преобразован в тип long double
- Если предыдущий пункт не выполняется и если один из операндов имеет тип double , то второй операнд тоже будет преобразован к типу double
- Если предыдущий пункт не выполняется и если один из операндов имеет тип float , то второй операнд тоже будет преобразован к типу float
- Если предыдущий пункт не выполняется и если один из операндов имеет тип unsigned long int , то второй операнд тоже будет преобразован к типу unsigned long int
- Если предыдущий пункт не выполняется и если один из операндов имеет тип long , то второй операнд тоже будет преобразован к типу long
- Если предыдущий пункт не выполняется и если один из операндов имеет тип unsigned , то второй операнд тоже будет преобразован к типу unsigned
- Если предыдущий пункт не выполняется то оба операнда приводятся к типу int
int a = 10; #include int main(void) < int number1 = 10; double number2 = 4; double result = number1 + number2; // 14.000000 printf("result = %f \n", result); // result = 14.000000 return 0; >
В выражении number1 + number2 число number2 представляет тип double , поэтому число number1 будет автоматически приводиться к числу double. И результат операции сложения также будет представлять тип double .
Операция преобразования
С помощью специальной операции преобразования мы можем явным образом привести данные к нужному типу. Например:
int a = 10; int b = 4; int c = a / b; // 2 double d = a / b; // 2.00000 double e = (double)a / (double)b; // 2.50000 printf("c = %d \n", c); printf("d = %f \n", d); printf("e = %f \n", e);
В выражении int c = a / b; результат деления будет целочисленный - 2, при котором дробная часть будет отброшена, так как оба операнда операции представляют целые числа.
В выражении double d = a / b; результат деления будет представлять вещественное число - 2.00000, но так как оба операнда являются целыми числами, то опять же результат операции будет представлять целое число 2, и только поле выполнения деления произойдет присвоение результата переменной d с приведением значения 2 от типа int к типу double.
В выражении double e = (double)a / (double)b применяется явное преобразование данных к типу double, поэтому и результат деления будет представлять вещественное число - 2.50000.
Для выполнения операции приведении в скобках указывается тот тип, к которому надо привести значение:
int number = 70; char symbol = (char) number; printf("symbol = %c \n", symbol); // F printf("symbol (int code) = %d \n", symbol); // 70
В ряде случаев преобразования сопровождаются потерей информации, например, когда числа большей разрядности (скажем размером 4 байт) получаем число меньшей разрядности (например, в 2 байта). Без потери информации проходят следующие цепочки преобразований:
char -> short -> int -> long
unsigned char -> unsigned short -> unsigned int -> unsigned long
float -> double -> long double
При всех остальных преобразованиях, которые не входят в эти цепочки, мы можем столкнуться с потерей точности данных. Так, в примере выше преобразование от int к char не является безопасным, поэтому к таким преобразованиям следует относиться с осторожностью. Например:
#include int main(void) < int number1 = 300; char code = number1; // потеря точности - число number1 усекается до 1 байта printf("code = %d \n", code); // code = 44 return 0; short number2 = 100000; // потеря точности - число 100000 усекается до 2 байт printf("number2 = %d \n", number2); // number2 = -31072 >
Здесь две ситуации небезопасных преобразований. В первом случае число типа int , которое равно 300, присваивается переменной типа char . В итоге переменная code будет равна 44. Почему? Число 300 в двоичной системе:
0000000100101100
Оставляем только первый младший байт:
00101100
И у нас получается число 44 в десятичной системе.
Во втором случае число 100000 (которое по умолчанию представляет тип int ), усекается до разрядности типа short - до двух байт.
short number = 100000;
В итоге число number в реальности будет равно -31072.