Урок 2.2

Типы данных и их модификаторы. Арифметические операторы и приведение типа

Второй урок по современному C++26: переменные, типы данных и их модификаторы, ввод через std::cin, арифметические операторы, инкремент и приведение типов.

Содержание29 разделов

В прошлой статье мы написали программу, которая выводит на экран строку «Hello, world!». Она ничего не запоминала и ничего не вычисляла: текст был записан прямо в коде.

Настоящие программы работают с данными: возрастом пользователя, ценой товара, координатами объекта на экране, ответом «да» или «нет». Чтобы хранить такие данные, в C++ используются переменные, а чтобы описать, что именно в них хранится, — типы данных.

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

После этой статьи Вы сможете:

  • объяснить, что такое переменная и из чего она состоит;
  • назвать основные типы данных C++ и их модификаторы: int, double, char, bool и другие;
  • сказать, сколько байт занимает тип и какие значения в нём можно хранить;
  • объявить переменную и дать ей понятное имя;
  • считывать данные с клавиатуры через std::cin и выводить их в терминал через std::cout;
  • выполнять арифметические операции и сокращать запись с помощью +=, ++ и подобных операторов;
  • преобразовывать значение одного типа в другой с помощью static_cast.

Что такое переменная?

Начнём с простой аналогии. Переменная - это некоторый ящик, который имеет название и хранит некоторое значение. Приведем несколько примеров.

Примеры переменных в C++: age со значением 23, pi со значением 3.1415 и name со значением "Marcus". Каждая переменная имеет имя и хранит своё значение.
Рисунок 1 — Примеры переменных на C++. У каждой переменной есть имя и хранимое значение.

На рисунке три переменные: age хранит число 23, pi — число 3.1415, а name — текст "Marcus". Значениями переменных можно оперировать: выводить их на экран, складывать, умножать, делить, увеличивать и уменьшать.

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

Основные типы данных C++

В C++ всего четыре базовых, или примитивных, типа данных. Из них строится всё многообразие переменных и контейнеров, с которыми мы будем работать дальше.

Четыре примитивных типа данных C++: целочисленный (15, -10, 0, 2'153'988), дробный (3.14, 2.71, -0.00007), символьный ('a', ' ', '!', '\n') и логический (true и false, которые соответствуют 1 и 0).
Рисунок 2 — Четыре примитивных типа данных C++ и примеры их значений
  • Целочисленный тип хранит только целые числа: 15, -10, 0, 2 153 988.
  • Дробный тип, который также называют вещественным или типом с плавающей запятой, хранит числа с дробной частью: 3.14, 2.71, -0.00007. Обратите внимание: в C++ дробную часть отделяют точкой, а не запятой.
  • Символьный тип хранит один символ: букву 'a', пробел ' ', восклицательный знак '!' или символ перевода строки '\n', с которым мы познакомились в прошлой статье.
  • Логический тип хранит одно из двух значений: true (истина) или false (ложь). При необходимости они превращаются в числа 1 и 0.

Похожий набор базовых типов есть в большинстве языков программирования, поэтому эти знания пригодятся Вам и за пределами C++.

Модификаторы типов данных

Чтобы объявить переменную определённого типа, в C++ используют ключевые слова. У одного типа данных может быть несколько вариантов записи, и в этом учебнике мы будем называть их модификаторами типа.

Строго говоря, модификаторами обычно называют только слова short, long, signed и unsigned, которые изменяют базовый тип int, а short int, int и long long int — это разные типы. Но на старте удобнее думать о них как о вариантах записи одного целочисленного типа.

Целочисленные типы: short int, int и long long int

У целочисленного типа три основных модификатора: short int, int и long long int. Чтобы объявить переменную, которая хранит целое число, нужно выбрать один из них.

Зачем три модификатора для одного типа данных? Каждый из них хранит свой диапазон значений и занимает разный объём памяти:

МодификаторДиапазон хранимых значенийОбъём памяти (байт)
short int-32'768 … 32'7672
int-2'147'483'648 … 2'147'483'6474
long long int-9'223'372'036'854'775'808 … 9'223'372'036'854'775'8078

Получается, что short int хранит числа примерно от −32 тысяч до 32 тысяч, int — примерно от −2 миллиардов до 2 миллиардов, а диапазон long long int огромен: примерно ±9,2 квинтиллиона, то есть 9,2 · 10¹⁸.

Возникает вопрос: зачем нужны short int и int, если можно всегда использовать long long int? В него ведь помещается больше всего чисел.

Дело в том, что за большой диапазон приходится платить памятью. Чем больше значений может хранить модификатор, тем больше места переменная занимает в оперативной памяти компьютера.

Сравнение размеров целочисленных типов: short int занимает 2 байта, int — 4 байта, long long int — 8 байт. В 1 ГБ памяти помещается примерно 537 миллионов short int, 268 миллионов int или 134 миллиона long long int.
Рисунок 3 — Чем больше диапазон модификатора, тем больше памяти занимает переменная

Например, в 1 ГБ оперативной памяти (1 073 741 824 байта) поместится примерно 268 миллионов переменных типа int, но лишь 134 миллиона переменных типа long long int — в два раза меньше. А по сравнению с short int переменных long long int поместится в четыре раза меньше. Поэтому модификатор стоит выбирать с умом.

Как выбрать модификатор

Как же выбрать подходящий модификатор? Подумайте о предметной области — о том, какие значения на самом деле будет хранить переменная:

  • возраст человека не может превышать 32 тысячи лет, поэтому для него подойдёт short int;
  • количество граммов картошки, которую человек купил на рынке, лучше хранить в int: больше 32 килограммов купить можно, а больше 2 тысяч тонн — вряд ли, если Вы не оптовик;
  • для населения Земли нужен long long int: больше 8 миллиардов человек в int уже не поместится.

Так Вы будете расходовать память компьютера максимально разумно.

Беззнаковые типы: unsigned

К каждому целочисленному модификатору можно добавить ключевое слово unsigned — «беззнаковый». Получаются unsigned short int, unsigned int и unsigned long long int.

Беззнаковые типы не хранят отрицательные числа: всё, что было в отрицательной части диапазона, «переезжает» в положительную.

МодификаторДиапазон хранимых значенийОбъём памяти (байт)
unsigned short int0 … 65'5352
unsigned int0 … 4'294'967'2954
unsigned long long int0 … 18'446'744'073'709'551'6158

Количество возможных значений при этом не меняется, поэтому беззнаковые типы занимают столько же памяти, сколько их обычные версии.

Беззнаковые типы полезны, когда значение по смыслу не может быть отрицательным. Например, это возраст или размер массива — количество элементов в нём. С массивами мы познакомимся позже: в массиве может быть от нуля до очень большого количества элементов, а отрицательный размер не имеет смысла.

Вещественные типы: float, double и long double

Дробные числа хранятся в переменных вещественного типа. У него три модификатора: float, double и long double.

МодификаторДиапазон хранимых значенийОбъём памяти (байт)
float1.17549e-38 … 3.40282e+384
double2.22507e-308 … 1.79769e+3088
long double2.22507e-308 … 1.79769e+3088

В таблице указаны наименьшее и наибольшее положительные значения в экспоненциальной записи: 3.40282e+38 означает 3,40282 · 10³⁸. Отрицательные числа хранятся в таком же диапазоне со знаком минус.

Главное отличие между модификаторами — точность, то есть сколько значащих цифр число хранит без искажений. float хранит примерно 6–7 значащих цифр, а double — примерно 15–16.

Если высокая точность не нужна, например Вы храните температуру воздуха, можно использовать float. Для точных вычислений, например при расчёте траектории полёта ракеты, нужен самый точный тип.

Обратите внимание, что в таблице у double и long double одинаковые диапазон и размер. Так устроено на macOS с процессорами Apple, где записывался видеоурок, и в компиляторе MSVC под Windows: там long double совпадает с double. Но это разные типы, и на других платформах они различаются. Например, в GCC на Linux с процессором x86-64 long double занимает 16 байт и точнее, чем double.

На практике чаще всего используют double: он достаточно точный и занимает 8 байт. Если не уверены, какой тип выбрать для дробного числа, — выбирайте double.

Символьный и логический типы: char и bool

С этими типами мы подробно поработаем через несколько уроков, а пока познакомимся с их характеристиками.

Тип данныхМодификаторДиапазон хранимых значенийОбъём памяти (байт)
Символьныйchar-128 … 1271
Логическийbooltrue, false1

Почему диапазон символьного типа записан числами? Дело в том, что компьютер хранит символы в виде чисел — кодов. Соответствие между символами и кодами задаёт таблица ASCII: например, у буквы 'a' код 97, а у пробела — 32.

Обычно char хранит значения от -128 до 127, но на некоторых платформах он беззнаковый и хранит значения от 0 до 255.

Тип bool хранит всего два значения, и для этого хватило бы одного бита. Тем не менее он занимает целый байт: в C++ не бывает переменных меньше одного байта, потому что байт — это минимальная единица памяти, к которой компьютер может обратиться напрямую.

Сводная таблица типов данных

Соберём все модификаторы в одну таблицу. Её можно сохранить и использовать как шпаргалку:

Сводная таблица типов данных C++: целочисленные модификаторы short int, int, long long int и их unsigned-версии; вещественные float, double, long double; символьный char и логический bool — с диапазонами значений и объёмом памяти в байтах.
Рисунок 4 — Примитивные типы данных C++ и их модификаторы

Запоминать всю таблицу не нужно. В большинстве задач Вам хватит четырёх модификаторов:

  • int — для целых чисел;
  • double — для дробных чисел;
  • char — для символов;
  • bool — для логических значений.

К остальным модификаторам Вы будете обращаться, когда почувствуете силы думать не только о решении задачи, но и о выборе самого подходящего типа.

Объявление и инициализация переменных

Посмотрим, как объявлять переменные в коде. Сначала указывается тип данных переменной (а точнее, модификатор), затем её имя. После имени можно сразу поставить точку с запятой или указать начальное значение:

main.cpp
C++
// <тип> <название>;
// <тип> <название> = <значение>;
// <тип> <название>{<значение>};
// <тип> <название>(<значение>);

Указание начального значения при объявлении называется инициализацией. Объявим переменные с тремя модификаторами целочисленного типа, которые мы уже изучили:

main.cpp
C++
#include <iostream>

int main() {
    short int age = 23;
    int money_amount = 1'000'000;
    int arbuz = 12345;
    long long int ananas = -9123;

    return 0;
}

Все четыре строки устроены одинаково: сначала тип, затем имя переменной, знак = и значение. Тип указывать обязательно — без него компилятор не поймёт, сколько памяти выделить под переменную и что с ней можно делать.

Переменные без начального значения

В одной строке можно объявить сразу несколько переменных одного типа. Их имена перечисляют через запятую:

main.cpp
C++
int a, b;
std::cout << a << " " << b;

Между значениями мы выводим пробел. Пробел — это один символ, поэтому его можно записать и в одинарных кавычках ' ', и в двойных " ".

Здесь мы не указали начальные значения, поэтому переменные остались неинициализированными. В них окажется «мусор» — случайные данные, которые до этого лежали в памяти компьютера. Если запустить программу, можно увидеть, например, такой результат:

terminal
Bash
-316981160 1

У Вас значения почти наверняка будут другими. CLion подсвечивает такие переменные и предупреждает, что они не инициализированы.

Исправим пример и сразу зададим переменным значения:

main.cpp
C++
int a{4}, b = 3;
std::cout << a << " " << b;
terminal
Bash
4 3

Обратите внимание: значение переменной a мы указали в фигурных скобках, а переменной b — через знак =. Для таких простых переменных оба способа дают одинаковый результат. Запись в круглых скобках, например int a(4);, тоже работает, но встречается реже.

Скотт Майерс, один из самых известных экспертов по C++, в книге «Эффективный и современный C++» подробно разбирает, чем отличаются эти способы. Но различия проявляются в довольно специфических ситуациях, поэтому новичку пока не стоит забивать ими голову.

Одно полезное отличие всё же стоит запомнить: фигурные скобки не позволяют незаметно потерять данные. Например, строка int x{3.7}; не скомпилируется, а строка int x = 3.7; скомпилируется и сохранит в x число 3 — в лучшем случае компилятор выдаст предупреждение.

В этом учебнике мы чаще будем писать через знак =: такая запись привычна и хорошо читается.

Как называть переменные

В именах переменных можно использовать:

  • латинские буквы — строчные и заглавные;
  • цифры;
  • символ нижнего подчёркивания _.
main.cpp
C++
short int age123 = 23;
short int AGE123 = 23;
int money___a__m__ount_ = 1'000'000;
int ar123b4u512z231 = 12345;

C++ — регистрозависимый язык. Переменные age123 и AGE123 различаются регистром букв, поэтому это две разные переменные.

При этом нужно соблюдать несколько правил:

  • имя не может начинаться с цифры: строка int 1a = 5; вызовет ошибку компиляции;
  • нижнее подчёркивание можно ставить в середине или в конце имени, но не в начале. Такие имена используются для макросов и внутренних деталей стандартной библиотеки, а часть из них зарезервирована языком;
  • имя не может совпадать с ключевым словом языка, например int или return.

Также не стоит называть переменные русскими буквами:

main.cpp
C++
long long int арбуз = -9123;

Некоторые компиляторы разрешают так писать, но полагаться на это не стоит: такой код ненадёжен, его неудобно набирать и поддерживать. Используйте латиницу и давайте переменным понятные имена: age, money_amount, student_count.

Разделители разрядов

Когда в числе много цифр, его трудно прочитать. Поэтому, начиная с C++14, разряды числа можно разделять апострофом ':

main.cpp
C++
int a = 1000000;
int b = 1'000'000;

Обе переменные хранят одно и то же число — миллион. Апострофы нужны только для удобства чтения и не влияют на значение.

Переменные других типов

Переменные остальных типов, с которыми мы познакомились выше, объявляются точно так же:

main.cpp
C++
double a;
float g = 12.3;

char sym = 'a';

bool flag = true;

Принцип всегда одинаковый: модификатор, имя переменной, а затем значение или точка с запятой.

Ввод и вывод: std::cin и std::cout

Теперь научимся получать данные от пользователя. Для ввода с клавиатуры используется std::cin. Его название удобно запомнить как console input — ввод с консоли. Как и std::cout, он объявлен в заголовке <iostream>.

main.cpp
C++
#include <iostream>

int main() {
    int a, b;
    std::cin >> a; // console input
    std::cin >> b; // console input
    std::cout << a + b;
    return 0;
}

Оператор >> передаёт введённое значение в переменную. Программа ждёт, пока пользователь введёт два числа, а затем выводит их сумму. Арифметические операторы мы ещё не проходили, но знак + здесь работает так же, как в математике.

Запустим программу и введём числа через пробел:

terminal
Bash
1 2
3

Числа можно ввести и на разных строках, нажимая Enter после каждого:

terminal
Bash
1
2
3

Более того, std::cin пропускает все пробелы и переводы строк перед числом. Даже если ввести много пробелов и несколько раз нажать Enter, числа всё равно считаются правильно:

terminal
Bash
    4


        7
11

Это может быть непривычно, если Вы раньше изучали Python: там функция input() читает строку целиком, и разбирать её на числа приходится отдельно.

Несколько переменных можно считать и одной строкой — для этого операторы >> записывают цепочкой:

main.cpp
C++
#include <iostream>

int main() {
    int a, b;
    std::cin >> a >> b; // console input
    std::cout << a + b;
    return 0;
}

Переменные в такой цепочке могут быть любого из изученных типов: int, double, float и других. Правила остаются прежними: значения можно разделять пробелами или переводами строк.

На первых уроках мы чаще всего будем считывать int и double, реже — char. А переменные типа bool с клавиатуры почти никогда не вводят.

Арифметические операторы

Переходим к следующей теме урока. В C++ есть пять арифметических операторов:

ОбозначениеНаименованиеПримерРезультат
+сложение5 + 27
-вычитание5 - 23
/деление5 / 22
/деление3.14 / 2.01.57
*умножение5 * 210
%извлечение остатка5 % 21

Сложение, вычитание и умножение работают так же, как в математике. А вот деление и оператор % разберём подробнее.

Целочисленное и вещественное деление

В C++ есть два вида деления. Если делить целое число на целое, то есть int на int, результат тоже будет целым:

main.cpp
C++
std::cout << 5 / 2; // 2

Математически 5 / 2 = 2,5, и по правилам округления получилось бы 3. Но C++ не округляет результат целочисленного деления, а отбрасывает дробную часть, поэтому мы получаем 2. Для отрицательных чисел дробная часть тоже просто отбрасывается: -5 / 2 даст -2.

Если делить дробные числа, результат будет дробным:

main.cpp
C++
std::cout << 3.14 / 2.0; // 1.57

Что будет, если разделить целое число на дробное, разберём в конце урока, когда дойдём до приведения типов.

Остаток от деления: оператор %

Оператор % вычисляет остаток от деления. Именно он обычно вызывает больше всего вопросов у новичков, поэтому разберём его на примере.

Представьте, что у Вас есть 5 новых телефонов, которые все хотят получить, и 2 лучших друга — назовём их Ваня 1 и Ваня 2. Вы любите и цените друзей одинаково и хотите раздать им телефоны так, чтобы каждый получил как можно больше и при этом поровну. Разделить телефон пополам, конечно, нельзя.

Иллюстрация операции 5 % 2: пять телефонов делят поровну между двумя друзьями, каждый получает по два телефона, и один телефон остаётся. Остаток равен 1.
Рисунок 5 — Извлечение остатка: 5 % 2 = 1

Каждый друг получит по 2 телефона, и у Вас останется 1 телефон. Это и есть результат операции 5 % 2: столько остаётся после того, как мы раздали поровну максимально возможное количество.

Немного практики в извлечении остатка

main.cpp
C++
7 % 12   // 7
6 % 3    // 0
0 % 10   // 0
  • 7 % 12 = 7. У Вас 7 телефонов и 12 друзей. Дать каждому хотя бы по одному телефону не получится, ведь тогда кого-то придётся обделить. Поэтому каждый получит 0 телефонов, и все 7 останутся у Вас. Хорошо иметь мало телефонов и много друзей!
  • 6 % 3 = 0. 6 телефонов и 3 друга: каждый получит по 2 телефона, и не останется ничего.
  • 0 % 10 = 0. Телефонов нет, поэтому каждый из 10 друзей получит 0 телефонов, и остаток тоже равен 0. Это не ошибка: мы не делим на ноль, а делим ноль на 10.

А вот делить на ноль и брать остаток от деления на ноль нельзя: выражения 10 / 0 и 10 % 0 — ошибка в программе.

В общем виде: если взять остаток от деления a на b, получится некоторое число r (от англ. remainder — остаток). Для неотрицательного a и положительного b оно всегда лежит на полуинтервале от 0 включительно до b не включительно:

формула
TXT
a % b = r,   0 ≤ r < b

С тем, что r может быть равен 0, мы уже разобрались. Но почему r всегда строго меньше b? Вспомните: b — это количество друзей. Если телефонов осталось столько же, сколько друзей, или больше, значит, Вы раздали не всё. Например, если осталось 7 телефонов, а друзей 5, каждому можно дать ещё по одному, и тогда останется 2.

С отрицательными числами оператор % тоже работает, но знак результата совпадает со знаком делимого: -7 % 3 равно -1. На первых уроках такие случаи нам не понадобятся.

Арифметические операторы на практике

Напишем программу, которая считывает два целых числа и выводит результаты всех арифметических операций:

main.cpp
C++
#include <iostream>

int main() {
    int a, b;
    std::cin >> a >> b;
    std::cout << a + b << std::endl;
    std::cout << a - b << std::endl;
    std::cout << a * b << std::endl;
    std::cout << a / b << std::endl;
    std::cout << a % b << std::endl;
    return 0;
}

std::endl переводит вывод на новую строку, как и символ '\n', с которым мы познакомились в прошлой статье. Поэтому каждый результат окажется на отдельной строке. Введём числа 5 и 2:

terminal
Bash
5 2
7
3
10
2
1

Деление здесь целочисленное, поэтому 5 / 2 дало 2, а остаток от деления 5 на 2 равен 1: 5 телефонов, 2 друга, остался 1 телефон.

Теперь заменим тип переменных на double. Для вещественных чисел оператор % запрещён: непонятно, как делить 2,5 телефона на 3,5 друга. Если оставить эту строку, программа не скомпилируется, поэтому закомментируем её:

main.cpp
C++
#include <iostream>

int main() {
    double a, b;
    std::cin >> a >> b;
    std::cout << a + b << std::endl;
    std::cout << a - b << std::endl;
    std::cout << a * b << std::endl;
    std::cout << a / b << std::endl;
    // std::cout << a % b << std::endl;
    return 0;
}
terminal
Bash
5 2
7
3
10
2.5

Мы снова ввели 5 и 2 — как будто целые числа. Но переменные имеют тип double, поэтому деление стало вещественным, и результат равен 2.5. Остальные операции работают так же, как с целыми числами.

Сокращённые операторы присваивания

Результат вычислений можно не только выводить на экран, но и сохранять в другую переменную:

main.cpp
C++
int result = a + b * 2;

Порядок действий здесь такой же, как в математике: сначала выполняется умножение, затем сложение. Если a = 5 и b = 2, в result окажется 9.

Часто нужно изменить значение переменной относительно её текущего значения, например прибавить к ней 2 или вычесть 2. Это можно записать полностью или в сокращённом виде:

main.cpp
C++
result = result + 2;
result += 2;

result = result - 2;
result -= 2;

Строки в каждой паре делают одно и то же. Сокращённые версии есть для всех арифметических операторов:

Сокращённая записьПолная запись
a += ba = a + b
a -= ba = a - b
a *= ba = a * b
a /= ba = a / b
a %= ba = a % b

Сокращённая запись короче и легче читается, поэтому берите её на вооружение.

Инкремент и декремент

Очень часто значение переменной нужно увеличить или уменьшить ровно на единицу. Для этого в C++ есть специальные операторы: инкремент (от англ. increase — увеличивать) и декремент (от англ. decrease — уменьшать). Особенно часто они встречаются в циклах и во всём, что мы будем изучать после них.

инкрементдекремент
постфиксныйa++a--
префиксный++a--a

Таблицу читают так: a++ стоит на пересечении строки «постфиксный» и столбца «инкремент», значит, это постфиксный инкремент. Он называется постфиксным, потому что плюсы стоят после имени переменной. В префиксной форме плюсы или минусы стоят перед именем: ++a и --a.

Любой инкремент — и постфиксный, и префиксный — увеличивает переменную ровно на 1, почти как a += 1 или a = a + 1. Любой декремент уменьшает переменную на 1. Но делают они это по-разному, и разница проявляется, когда инкремент стоит внутри выражения. Это, пожалуй, самая сложная часть урока, но на примере она станет понятной.

Постфиксный инкремент: c++

main.cpp
C++
#include <iostream>

int main() {
    int c = 5;
    int result = c++ / 2;
    std::cout << c << " " << result;
    return 0;
}
terminal
Bash
6 2

Постфиксный инкремент сначала использует текущее значение переменной в выражении и только потом увеличивает её. В момент вычисления c++ / 2 переменная c ещё равна 5, поэтому result получает значение 5 / 2 = 2. После этого c увеличивается и становится равна 6.

Префиксный инкремент: ++c

main.cpp
C++
#include <iostream>

int main() {
    int c = 5;
    int result = ++c / 2;
    std::cout << c << " " << result;
    return 0;
}
terminal
Bash
6 3

Префиксный инкремент сначала увеличивает переменную и только потом использует её новое значение. Переменная c становится равна 6, и уже после этого выполняется деление: result = 6 / 2 = 3.

Сравнение постфиксного и префиксного инкремента при c = 5. Для int result = c++ / 2 сначала используется c = 5, вычисляется 5 / 2 = 2, затем c становится 6. Для int result = ++c / 2 сначала c становится 6, затем вычисляется 6 / 2 = 3.
Рисунок 6 — Постфиксный и префиксный инкремент в выражении

В обоих примерах c в итоге равна 6, но результаты выражений разные.

С декрементом всё так же, только переменная уменьшается на 1: постфиксный c-- сначала использует значение, а потом уменьшает его, префиксный --c — сначала уменьшает, а потом использует.

Приведение типов

Последняя тема урока — приведение типов, то есть преобразование значения одного типа в другой. Ничего сложного здесь нет, просто это нужно знать. Приведение типов бывает двух видов: неявное и явное.

Схема видов приведения типов в C++: неявное приведение выполняет компилятор автоматически, например 5 / 2.0 даёт 2.5; явное приведение записывает программист — в стиле C, (int)(value), или в стиле C++, static_cast<int>(value).
Рисунок 7 — Виды приведения типов в C++

Неявное приведение типов

Начнём с неявного приведения — на нём проще понять, что вообще такое приведение типов.

main.cpp
C++
#include <iostream>

int main() {
    int a = 5;
    double b = 2.0;

    std::cout << a / b;

    return 0;
}

Переменную b мы записали как 2.0, хотя можно было написать просто 2: так сразу видно, что это дробное число, и код читается чище.

В выражении a / b операнды, то есть значения, над которыми выполняется операция, имеют разные типы: int и double. Однако арифметические операции в C++ выполняются над операндами одного типа: int с int, double с double и так далее. Если типы разные и программист не указал, к какому типу их привести, компилятор приведёт их к общему типу сам. Это и есть неявное приведение типов.

Главная идея: меньший тип приводится к большему. Операнд a имеет тип int и занимает 4 байта, а операнд b — тип double и 8 байт. Поэтому a приводится к double. Если бы, наоборот, больший тип приводился к меньшему, часть данных могла бы потеряться.

Неявное приведение типов: переменная a типа int (4 байта) на время деления приводится к double, переменная b уже имеет тип double (8 байт), поэтому 5.0 / 2.0 = 2.5, и результат имеет тип double.
Рисунок 8 — Неявное приведение: int приводится к double

Важно, что тип самой переменной a при этом не меняется. Только на момент деления её значение 5 используется как 5.0, поэтому и результат получается типа double:

terminal
Bash
2.5

Правило «меньший к большему» стоит немного уточнить. Компилятор учитывает не только размер типа, но и его вид:

  • если один операнд вещественный, а другой целый, целый всегда приводится к вещественному. Например, long long int и double занимают по 8 байт, но результат выражения с ними будет иметь тип double;
  • если оба операнда вещественные, меньший тип приводится к большему, например float к double;
  • если оба операнда целые, меньший тип приводится к большему, например int к long long int. При этом char, short int и bool перед вычислением всегда превращаются как минимум в int.

Смешивать в одном выражении знаковые и беззнаковые типы не стоит: правила для них сложнее и могут привести к неожиданным результатам.

Но даже когда правила известны, неявные преобразования делают код менее понятным. В программировании есть принцип: явное лучше неявного. Поэтому старайтесь не полагаться на неявное приведение типов и указывайте преобразование явно.

Явное приведение типов: C-style cast

При явном приведении программист сам говорит компилятору, к какому типу нужно привести значение. Такое приведение можно записать в двух стилях.

Первый стиль — C-style cast, приведение в стиле языка C. Он перешёл в C++ для обратной совместимости, чтобы код, написанный на C, работал и в C++:

main.cpp
C++
#include <iostream>

int main() {
    double value = 2.5;

    std::cout << (int)(value);

    return 0;
}
terminal
Bash
2

Тип, к которому приводим, записывают в круглых скобках перед значением. Запись (int)(value) означает: «используй value как int». При приведении вещественного числа к целому дробная часть отбрасывается, а не округляется, поэтому на экране мы увидим 2.

Современное приведение: static_cast

Второй стиль — C++-style cast. В современном C++ для таких преобразований используют static_cast:

main.cpp
C++
#include <iostream>

int main() {
    double value = 2.5;

    std::cout << static_cast<int>(value);

    return 0;
}
terminal
Bash
2

В угловых скобках указывают тип, к которому нужно привести значение, а в круглых — само значение. Результат такой же, как у C-style cast.

Почему же лучше использовать static_cast? C-style cast почти ничего не проверяет и может незаметно выполнить опасное преобразование. static_cast строже: если такое преобразование недопустимо, компилятор сообщит об ошибке. Кроме того, static_cast легко заметить и найти в коде.

Сейчас разница может казаться несущественной, но она станет важной, когда мы дойдём до указателей, наследования и полиморфизма. Поэтому привыкайте к static_cast с самого начала, даже если он выглядит более громоздко.

Частая ошибка при использовании static_cast

Пусть у нас есть две целые переменные, и мы хотим разделить их так, чтобы получить дробный результат. Многие новички пишут так:

main.cpp
C++
#include <iostream>

int main() {
    int a = 5, b = 2;
    std::cout << static_cast<double>(a / b);
    return 0;
}
terminal
Bash
2

Почему не 2.5? Сначала вычисляется выражение в круглых скобках. a / b — это деление int на int, поэтому дробная часть отбрасывается уже на этом шаге, и получается 2. Затем число 2 приводится к double, но это по-прежнему 2.

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

main.cpp
C++
#include <iostream>

int main() {
    int a = 5, b = 2;
    std::cout << static_cast<double>(a) / static_cast<double>(b);
    return 0;
}
terminal
Bash
2.5

Теперь на момент вычисления выражения a и b интерпретируются как double, и деление получается вещественным. Строго говоря, достаточно привести и один операнд — второй приведётся к double неявно. Но явное приведение обоих операндов делает код понятнее.

Подведём итоги

В этой статье Вы познакомились с переменными, типами данных и их модификаторами, научились вводить данные с клавиатуры, выполнять арифметические операции и приводить типы.

Главное, что нужно запомнить:

  • переменная имеет имя и тип и хранит значение;
  • в C++ четыре примитивных типа данных: целочисленный, вещественный, символьный и логический;
  • модификаторы одного типа различаются диапазоном значений и объёмом занимаемой памяти;
  • в большинстве задач достаточно модификаторов int, double, char и bool;
  • std::cin считывает данные с клавиатуры и пропускает лишние пробелы и переводы строк;
  • деление int на int отбрасывает дробную часть, а оператор % возвращает остаток от деления;
  • c++ сначала использует значение переменной, а потом увеличивает его, ++c — наоборот;
  • при неявном приведении меньший тип приводится к большему, а для явного приведения используйте static_cast.

Теория без практики быстро забывается, поэтому обязательно закрепите материал. Авторизуйтесь на сайте, отметьте статью как прочитанную и перейдите во вкладку «Решать задачи». Отправляйте решения на проверку, разбирайте результаты тестов и отслеживайте свой прогресс.

Использованная литература

  1. Рабочий проект стандарта C++ [Электронный ресурс]. — URL: https://eel.is/c++draft/ (дата обращения: 01.10.2026).
  2. Рабочий проект стандарта C++26 — N5054 [Электронный ресурс]. — URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/n5054.pdf (дата обращения: 01.10.2026).
  3. ISO/IEC 14882:2024 — язык программирования C++ [Электронный ресурс]. — URL: https://www.iso.org/standard/83626.html (дата обращения: 01.10.2026).
  4. Фундаментальные типы данных [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/types (дата обращения: 01.10.2026).
  5. Типы целых чисел фиксированной ширины [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/types/integer (дата обращения: 01.10.2026).
  6. Оператор sizeof [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/sizeof (дата обращения: 01.10.2026).
  7. std::numeric_limits [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/types/numeric_limits (дата обращения: 01.10.2026).
  8. Инициализация переменных [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/initialization (дата обращения: 01.10.2026).
  9. Стандартный поток ввода std::cin [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/io/cin (дата обращения: 01.10.2026).
  10. Арифметические операторы [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/operator_arithmetic (дата обращения: 01.10.2026).
  11. Операторы присваивания, включая сокращённые [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/operator_assignment (дата обращения: 01.10.2026).
  12. Операторы инкремента и декремента [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/operator_incdec (дата обращения: 01.10.2026).
  13. Функция std::fmod [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/numeric/math/fmod (дата обращения: 01.10.2026).
  14. Неявные преобразования типов [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/implicit_conversion (дата обращения: 01.10.2026).
  15. Оператор static_cast [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/static_cast (дата обращения: 01.10.2026).
  16. Явное приведение типов в стиле C [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/language/explicit_cast (дата обращения: 01.10.2026).
  17. Programming: Principles and Practice Using C++, 3-е издание [Электронный ресурс]. — URL: https://www.stroustrup.com/PPP3.html (дата обращения: 01.10.2026).
  18. A Tour of C++, 3-е издание [Электронный ресурс]. — URL: https://www.stroustrup.com/tour3.html (дата обращения: 01.10.2026).
  19. Скотт Майерс — Эффективный и современный C++ [Электронный ресурс]. — URL: https://www.oreilly.com/library/view/effective-modern-c/9781491908419/ (дата обращения: 01.10.2026).