Типы данных в C++ и статическая типизация с нуля
Разбираем, зачем C++ требует объявлять тип каждой переменной, чем int отличается от double и почему компилятор ловит ошибки ещё до запуска программы.

Вы пишете первую программу на C++, объявляете int age = 25; и задаётесь вопросом: зачем вообще указывать этот int, если в Python или JavaScript можно просто написать age = 25? К концу этого разбора вы поймёте, что делает компилятор с этим int, почему он ругается на int x = "привет"; ещё до запуска и чем это отличается от языков, где такая строка спокойно доживёт до момента падения. Все примеры собираются любым современным компилятором со стандартом C++17 и новее (g++, clang, MSVC).
Что такое тип и почему компилятору это важно
Тип — это ответ на два вопроса: сколько байт памяти занимает значение и что с этими байтами разрешено делать. Число 42 и строка "42" лежат в памяти по-разному, и складывать их напрямую нельзя. В C++ вы обязаны сообщить тип каждой переменной при её объявлении, и это сообщение проверяется на этапе компиляции.
Возьмите строку int age = 25;. Компилятор читает её так: выделить участок памяти под целое число (обычно 4 байта), записать туда 25 и запомнить, что имя age теперь ссылается на целочисленное значение. Если дальше вы напишете age = "двадцать пять";, программа не соберётся — компилятор откажется класть строку в целочисленную ячейку.
Тип переменной в C++ — не подсказка и не аннотация. Это часть контракта, который компилятор проверяет за вас до того, как программа хоть раз запустится.
Статическая типизация: проверка до запуска
Статическая типизация означает, что тип каждой переменной известен и зафиксирован на этапе компиляции, а не во время работы программы. Компилятор проверяет, что вы не пытаетесь сделать с данными что-то бессмысленное, и если находит несостыковку — программа просто не соберётся.
Противоположность — динамическая типизация (Python, JavaScript): там тип определяется во время выполнения, и ошибка вида «прибавить строку к числу» вылезет только тогда, когда до этой строки дойдёт исполнение. В C++ такая ошибка ловится раньше.
#include <iostream>
int main() {
int count = 10; // целое число
double price = 99.5; // число с плавающей точкой
// Так работает: оба операнда — числа
double total = count * price;
std::cout << total << std::endl; // выведет 995
// А вот это не соберётся — компилятор укажет на ошибку типов:
// int wrong = "текст";
return 0;
}
Закомментированная строка — не гипотеза. Раскомментируйте её, и g++ выдаст ошибку вида error: invalid conversion from 'const char*' to 'int'. Программа не запустится ни разу.
Цена вопроса
За статическую типизацию вы платите многословием: тип нужно писать явно (или доверить выводу через auto, о котором ниже). Взамен получаете две вещи. Первая — часть ошибок ловится до релиза, а не в проде на данных пользователя. Вторая — компилятор точно знает размеры и может генерировать быстрый код без проверок типа во время работы.
Базовые встроенные типы
Стандарт C++ задаёт минимальные размеры типов, но точные размеры зависят от платформы и компилятора. Ниже — типичные значения для 64-битной системы. Проверить реальный размер на вашей машине можно оператором sizeof.
| Тип | Что хранит | Типичный размер | Пример |
|---|---|---|---|
int | целое число со знаком | 4 байта | int n = -5; |
long long | большое целое | 8 байт | long long big = 9000000000LL; |
double | дробное число | 8 байт | double pi = 3.14159; |
float | дробное, меньшая точность | 4 байта | float f = 1.5f; |
char | один символ / малое число | 1 байт | char c = 'A'; |
bool | истина или ложь | 1 байт | bool ok = true; |
Отдельно стоит std::string из заголовка <string> — это не встроенный тип, а класс из стандартной библиотеки для работы с текстом. Начинающему проще использовать его, а не «сырые» массивы символов.
#include <iostream>
#include <string>
int main() {
std::string name = "Мария";
int height = 168;
bool has_pet = true;
std::cout << "Размер int: " << sizeof(int) << " байт" << std::endl;
std::cout << "Размер double: " << sizeof(double) << " байт" << std::endl;
return 0;
}
Почему int + double даёт double
Когда в выражении встречаются разные числовые типы, компилятор приводит их к общему — обычно к «более широкому». В count * price из примера выше count (int) неявно превращается в double, потому что price — double. Результат тоже double.
Отсюда ловушка, на которой спотыкаются почти все новички:
int a = 7;
int b = 2;
double result = a / b; // result будет 3.0, а не 3.5
// Деление int на int даёт int: дробная часть отбрасывается ДО присваивания.
// Чтобы получить 3.5, приведите один операнд к double:
double correct = static_cast<double>(a) / b; // 3.5
Здесь 3.5 теряется не потому, что переменная result целая — она как раз double. Деление 7 / 2 выполняется целочисленно ещё до присваивания, даёт 3, и уже эта тройка превращается в 3.0. Приведение static_cast<double> делает один из операндов дробным, и деление идёт по правилам дробной арифметики.
auto: тип выводит компилятор
С C++11 можно не писать тип руками, а поручить это компилятору через ключевое слово auto. Важно: это не отказ от статической типизации. Тип по-прежнему фиксируется на этапе компиляции — просто компилятор вычисляет его из правой части выражения.
auto count = 10; // выведется int
auto price = 99.5; // выведется double
auto name = std::string("Анна"); // std::string
// count = "текст"; // всё равно не соберётся — тип уже int навсегда
auto удобен для длинных имён типов, но новичку не стоит применять его везде: явный тип в объявлении часто помогает читать код и понимать, что происходит.
Если не получилось: типичные ошибки компиляции
invalid conversion from 'const char*' to 'int'— вы пытаетесь положить строку в переменную числового типа. Проверьте, что тип слева совпадает с тем, что справа.'string' was not declared in this scope— забыли#include <string>или написалиstringвместоstd::string.narrowing conversion of ... inside { }— при инициализации через фигурные скобки (int x{3.5};) компилятор запрещает молчаливую потерю дробной части. Это защита, а не баг.- Результат деления получился «неправильным» (3 вместо 3.5) — вы столкнулись с целочисленным делением. Приведите операнд к
double. 'variable' may be used uninitialized— объявили переменную без значения (int x;) и прочитали её раньше, чем записали. В отличие от Python, в C++ неинициализированная локальная переменная содержит мусор.
Что делать дальше
Вы разобрались, зачем C++ требует тип и что именно проверяет компилятор. Следующие шаги, чтобы закрепить и двигаться дальше:
- Соберите примеры выше с флагами
-Wall -Wextra— компилятор начнёт предупреждать о подозрительных преобразованиях типов. - Поэкспериментируйте с
sizeofдля разных типов на вашей машине — увидите реальные размеры вместо табличных «типичных». - Изучите
static_castи другие явные приведения — это следующая тема после того, как типы стали понятны. - Загляните в раздел про фундаментальные типы на cppreference — там точные требования стандарта по диапазонам и размерам.
Тема неявных преобразований и переполнения целых чисел — отдельный большой разговор, здесь мы её только обозначили.
Проверка грамотности: Орфография, пунктуация, стиль — с объяснением каждой правки
Спросить за 10 ₽Источники: cppreference — Fundamental types, cppreference — Placeholder type specifiers (auto)
Частые вопросы
У меня другой компилятор — MSVC вместо g++. Примеры сработают?
Почему размеры типов в таблице названы «типичными», а не точными?
Зачем нужен тип, если есть auto, который всё выводит сам?
Я объявил int x; и программа выдаёт случайные числа. Это баг?
Почему 7 / 2 даёт 3, а не 3.5, если переменная объявлена как double?
Чем std::string отличается от char и почему его надо include-ить?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.