Статическая типизация и преобразования типов


С++ является статически типизированным языком программирования. То есть если мы определили для переменной какой-то тип данных, то в последующем мы этот тип изменить не сможем. Соответственно переменная может получить значения только того типа, который она представляет. Однако нередко возникает необходимость присвоить переменной значения каких-то других типов. И в этом случае применяются преобразования типов.

Ряд преобразований компилятор может производить неявно, то есть автоматически. Например:

#include <iostream>

int main()
{
int code = 'g';
char letter = 103;
std::cout << letter << " in ASCII is " << code << "\n";
return 0;
}
В данном случае числовой переменной типа int присваивается символ 'g'. Этот символ будет автоматически преобразовываться в число. По факту переменная получит числовой код этого символа в таблице ASCII.

Переменной letter, наоборот, присваивается число, хотя эта переменная представляет тип char. Это число будет преобразовываться в символ, и в итоге переменная letter будет хранить символ, чей код в таблице ASCII равен 103, то есть символ 'g'.

Результатом этой программы будет следующий консольный вывод:

g in ASCII is 103
Как выполняются преобразования:

Переменной типа bool присваивается значение другого типа. В этом случае переменная получает false, если значение равно 0. Во всех остальных случаях переменная получает true.

bool a = 1; // true
bool b = 0; // false
bool c = 'g'; // true
bool d = 3.4; // true
Числовой или символьной переменной присваивается значение типа bool. В этом случае переменная получает 1, если значений равно true, либо получает 0, если присваиваемое значение равно false.

int c = true; // 1
double d = false; // 0
Целочисленной переменной присваивается дробное число. В этом случае дробная часть после запятой отбрасывается.

int a = 3.4; // 3
int b = 3.6; // 3
Переменной, которая представляет тип с плавающей точкой, присваивается целое число. В этом случае если целое число содержит больше битов, чем может вместить тип переменной, то часть информации усекается.

float a = 35005; // 35005
double b = 3500500000033; // 3.5005e+012
Переменной беззнакового типа (unsigned) присваивается значение не из его диапазона. В этом случае результатом будет остаток от деления по модулю. Например, тип unsigned char может хранить значения от 0 до 255. Если присвоить ему значение вне этого диапазона, то компилятор присвоит ему остаток от деления по модулю 256 (так как тип unsigned char может хранить 256 значений). Так, при присвоении значения -1 переменная типа unsigned char получит 256 - |-1/256| = 255

unsigned char a = -5; // 251
unsigned short b = -3500; // 62036 ()
unsigned int c = -50000000; // 4244967296
Переменной знакового типа (signed) присваивается значение не из его диапазона. В этом случае результат не определен. Программа может работать нормально, выдавая адекватный результат, а может работать некорректно.

Опасные и безопасные преобразования
Те преобразования, при которых не происходит потеря информации, являются безопасными. Как правило, это преобразования от типа с меньшей разрядностью к типу с большей разрядностью. В частности, это следующие цепочки преобразований:

bool -> char -> short -> int -> double -> long double

bool -> char -> short -> int -> long -> long long

unsigned char -> unsigned short -> unsigned int -> unsigned long

float -> double -> long double

Примеры безопасных преобразований:

short a = 'g'; // преобразование из char в short
int b = 10;
double c = b; // преобразование из int в double
float d = 3.4;
double e = d; // преобразование из float в double
double f = 35; // преобразование из int в double
Но также есть опасные преобразования. При подобных преобразованиях мы потенциально можем потерять точность данных. Как правило, это преобразования от типа с большей разрядностью к типу с меньшей разрядностью.

char letter = 295;
std::cout << letter;
В данном случае переменной letter присваивается значение, которое выходит за пределы диапазона допустимых значений для типа char, то есть больше 255.

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

И, как правило, в подобных случаях при компиляции присваиваемое значение усекается до допустимого. Например, в примере выше число 295 будет сокращено до 39. То есть следующие переменные будут содержать одно и то же значение:

char letter1 = 295;
char letter2 = 39;