Перейти к содержимому
Теория · Приведения и псевдонимы · урок 1 из 6

Неявные преобразования и static_cast

Сумма 3 и -5 равна -2, но эта программа печатает совсем другое число:

C++
#include <iostream>

int main() {
    int change = -5;
    unsigned stock = 3u;
    std::cout << stock + change << '\n';  // 4294967294
}

Перед сложением компилятор (compiler) привёл оба операнда (operands) к одному типу, и этим типом оказался unsigned. Отрицательных чисел в нём нет, поэтому и сумма получилась неотрицательной.

Неявные преобразования

Неявное преобразование (implicit conversion) — смена типа, которую компилятор выполняет сам, без указания в коде. Перед арифметикой и сравнением он приводит операнды к общему типу по двум правилам:

  • bool, char и short продвигаются (promotion) до int: true + true — это int со значением 2.
  • Если типы всё ещё разные, оба операнда приводятся к общему типу. Для int и double это double, для int и long long — long long, для int и unsigned — unsigned.

Вместе эти правила называют обычными арифметическими преобразованиями (usual arithmetic conversions). Отрицательное значение int при переходе в unsigned уходит по кругу, и результат определён стандартом. -1 становится наибольшим значением unsigned, а -5 — числом на четыре меньше. Так же работает сравнение: < с операндами int и unsigned сравнивает уже преобразованные значения.

Обратное направление опаснее. Значение double, записанное в int, теряет дробную часть, а long long может не поместиться в int. Это сужающие преобразования (narrowing): при = они компилируются без ошибки, а фигурные скобки, как показано в теме о переменных, их запрещают.

Приведение static_cast

Когда нужное преобразование компилятор сам не сделает, тип меняют явно. Такая запись называется приведением (cast): static_cast<double>(sum) даёт значение sum в виде double, а сама переменная остаётся int.

C++
#include <iostream>

int main() {
    int sum = 7;
    int count = 2;
    std::cout << static_cast<double>(sum) / count << '\n';  // 3.5
}

Делимое стало double, поэтому count тоже превращается в double, и деление сохраняет дробную часть.

Тот же приём защищает от переполнения (overflow). Наименьшее и наибольшее значения int хранят константы INT_MIN и INT_MAX из заголовка (header) <climits>:

C++
#include <climits>
#include <iostream>

int main() {
    int a = INT_MAX;
    int b = 1;
    long long sum = static_cast<long long>(a) + b;
    std::cout << sum << '\n';  // 2147483648
}

В типе int сумма a + b переполнилась бы, а это неопределённое поведение (undefined behavior). После приведения a к long long в long long переходит и b, и сложение выполняется в широком типе.

В языке C тип меняют записью (double)sum. В C++ она тоже работает, но умеет и снять const, и переинтерпретировать указатель (pointer). По коду не видно, что из этого имелось в виду. Именованное приведение вроде static_cast делает одну работу, и поиск по его имени находит каждое место в коде.

Свои преобразования задают и типы стандартной библиотеки: строковый литерал неявно превращается в std::string, а std::string — в std::string_view. Такие правила пишет автор типа, а компилятор применяет их так же неявно.

Частая ошибка

Приведение применили к результату деления, а не к операнду:

C++
#include <iostream>

int main() {
    int sum = 7;
    int count = 2;
    double avg = static_cast<double>(sum / count);  // ✗
    std::cout << avg << '\n';  // 3
}

Программа печатает 3. В скобках стоит целочисленное деление 7 / 2, и в double превращается уже его результат 3. Приводите операнд: static_cast<double>(sum) / count даёт 3.5.