Типы данных. Арифметические операции
Целые и вещественные типы, char и bool, арифметические операции, целочисленное деление и остаток, инкремент и декремент, приведение типов и формат вывода double.
Все программы предыдущего урока печатали строки. Как только появляются числа,
появляются и вопросы, на которых спотыкаются все: почему 6 / 4 равно единице,
почему cout печатает 2 вместо 2.0 и почему 0.1 + 0.2 не даёт 0.3.
Ни один из этих ответов нельзя угадать — их надо проверить, и примеры
на странице для этого и стоят.
Зачем переменной тип
Объявляя переменную, вы сообщаете компилятору две вещи: сколько памяти под неё отвести и как истолковать лежащие там биты. Одни и те же 32 бита — это целое число, вещественное число или четыре символа, в зависимости от типа. Отсюда правило, из которого потом вытекает половина урока: операция смотрит на типы операндов, а не на смысл задачи.
Целые типы
| Тип | Обычный размер | Диапазон |
|---|---|---|
char | 1 байт | −128…127 |
short | 2 байта | −32 768…32 767 |
int | 4 байта | −2 147 483 648…2 147 483 647 |
long long | 8 байт | ±9,2 · 1018 |
Приписка unsigned убирает знак и удваивает верхнюю границу:
unsigned int — это 0…4 294 967 295.
Размеры типов стандартом не заданы
Стандарт C++ фиксирует только минимальные размеры и соотношения между ними
(sizeof(short) <= sizeof(int) <= sizeof(long)). Конкретные значения зависят
от платформы и компилятора. Поэтому в вопросах вида «сколько байт занимает
long double» правильный ответ — «зависит», и проверять размеры надо
операцией sizeof на своей машине, а не по таблице из учебника.
Значение за границей диапазона не вызывает ошибку — оно переполняется,
то есть молча превращается в другое. Для int это неопределённое поведение,
и компилятор вправе сделать что угодно.
Вещественные типы
float — 4 байта, около 7 значащих десятичных цифр. double — 8 байт,
около 15 цифр. В курсе по умолчанию используется double: экономия четырёх
байт не стоит потерянной точности.
Главное свойство вещественных типов: они хранят значение приближённо.
Число 0.1 в двоичной системе — бесконечная периодическая дробь, и в память
попадает ближайшее представимое к ней значение. Отсюда знаменитый результат:
double x = 1234.56789;
cout << x << endl;
cout << setprecision(10) << x << endl;
cout << fixed << setprecision(3) << x << endl;
cout << scientific << x << endl;
cout << defaultfloat << setw(12) << setfill('.') << x << endl;
float f = 0.1f;
cout << defaultfloat << setprecision(6) << f + 0.2f << endl;
cout << 0.1 + 0.2 << endl;Две последние строки печатают 0.3 — и ни в одной из них сумма на самом деле
0.3 не равна. В последней складываются double, и результат равен
0.30000000000000004. В предпоследней — float, у которого точность ниже,
и расхождение начинается уже на восьмой значащей цифре: 0.30000001.
Видно этого не было только потому, что cout по умолчанию округляет
до шести значащих цифр. Увеличьте setprecision до 20 и запустите снова —
разница появится на экране.
Вещественные числа нельзя сравнивать на равенство
if (0.1 + 0.2 == 0.3) даёт ложь. Сравнивают не значения, а разность
с допуском: if (fabs(a - b) < 1e-9). Это правило действует во всех языках
и во всех задачах, где есть дробные числа.
Как cout печатает double
По умолчанию cout печатает не «как написано», а шесть значащих цифр,
убирая незначащие нули. Отсюда три результата, которые удивляют:
cout << 1.0 / 3;Шесть значащих цифр — 0.333333, дальше отброшено.
cout << 2.0;Печатается 2, а не 2.0: дробная часть нулевая, а нули незначащие. По выводу
cout нельзя понять, целое число или вещественное.
cout << 1e10;Когда для шести значащих цифр не хватает места, cout переходит
к экспоненциальной записи: 1e+10.
Формат управляется манипуляторами из <iomanip>:
cout << fixed << setprecision(2) << 1.0 / 3;fixed означает «фиксированная запись», и тогда setprecision(2) — это
два знака после запятой, а не два значащих. Без fixed тот же
setprecision(2) дал бы 0.33 из двух значащих цифр — для этого числа
совпало, а для 123.456 результаты разойдутся. Проверьте.
Символы и логический тип
char хранит один символ — а точнее, его код. Поэтому char полноправно
участвует в арифметике:
char c = 'A';
cout << c << endl; // символ
cout << (int)c << endl; // его код
cout << char(c + 1) << endl; // следующий символ
cout << 'z' - 'a' << endl; // расстояние между символамиОбратите внимание на четвёртую строку: 'z' - 'a' — это число, и cout
печатает число. Приведение char(...) в третьей строке обязательно: без него
c + 1 имеет тип int, и напечатался бы код, а не буква.
bool хранит true или false и занимает один байт. Печатается он
как 1 и 0:
bool ok = true;
cout << ok << endl;
cout << (3 > 5) << endl;
cout << boolalpha << (3 > 5) << endl;Манипулятор boolalpha переключает вывод на слова true/false.
Арифметические операции
Пять операций: +, -, *, / и % — остаток от деления. Приоритет
обычный: *, /, % выполняются раньше + и -, скобки сильнее всего.
Целочисленное деление — главная ловушка
Операция / смотрит на типы операндов. Если оба целые, деление целочисленное,
и дробная часть отбрасывается — не округляется, а именно отбрасывается.
int i = 4;
int x = 6;
double z;
z = x / i;
cout << "z=" << z << endl;Проверьте руками, прежде чем запускать. Напечатается z=1: 6 / 4 вычисляется
как деление целых — получается 1, — и только потом единица записывается
в double. Тип переменной слева на вычисление справа не влияет никак.
Чтобы деление стало вещественным, вещественным должен стать хотя бы один операнд:
int i = 4, x = 6;
cout << x / i << endl; // 1 — оба целые
cout << 1.0 * x / i << endl; // 1.5 — умножение на 1.0 сделало double
cout << (double)x / i << endl; // 1.5 — явное приведение
cout << x / (double)i << endl; // 1.5 — достаточно одного операндаОстаток от деления
cout << 11 % 3;Операция % определена только для целых. Её основное применение — проверка
делимости: a % b == 0 значит «a делится на b». Пара / и % вместе
разбирают число на цифры: n % 10 — последняя цифра, n / 10 — число
без последней цифры. На этом построена половина задач следующих уроков.
int n;
cin >> n;
cout << "последняя цифра: " << n % 10 << endl;
cout << "без последней: " << n / 10 << endl;
cout << "сумма цифр: " << n % 10 + n / 10 % 10 + n / 100 << endl;Инкремент и декремент
++ увеличивает переменную на единицу, -- уменьшает. У каждой операции
две формы, и разница между ними — не в том, что происходит с переменной,
а в том, что становится значением выражения:
- префиксная
++a— сначала увеличить, значением выражения будет новое; - постфиксная
a++— значением выражения будет старое, увеличение после.
int var1 = 20;
cout << var1--;
cout << ++var1;Разберите вывод 2020 по шагам. Первый cout получает старое значение 20
и печатает его, после чего var1 становится 19. Второй cout сначала
увеличивает var1 до 20 и печатает новое значение. Переменная дважды
изменилась, а напечаталось одно и то же.
int a = 5;
cout << "выражение a++ дало " << a++ << endl;
cout << "а сама a стала " << a << endl;
int b = 5;
cout << "выражение ++b дало " << ++b << endl;
cout << "а сама b стала " << b << endl;Каждое значение печатается отдельной командой, и это не случайно: смешивать
в одном cout изменение переменной и её же чтение не стоит — разобраться,
что получится, гораздо труднее, чем написать две строки.
Несколько изменений одной переменной в одном выражении
Выражения вида a = a++ + ++a в C++ — неопределённое поведение:
переменная изменяется дважды, а порядок ничем не задан, и программа
вправе напечатать что угодно. Такой код не «сложный», а неправильный;
g++ с флагом -Wall предупреждает о нём сам (-Wsequence-point).
Осторожнее с похожими, но законными записями: c = b+++--c компилятор
разбирает как c = (b++) + (--c) по правилу самого длинного токена,
и в C++17 это определено однозначно. Писать так всё равно не надо.
Составное присваивание
a += b означает a = a + b, и так же работают -=, *=, /=, %=.
Внутри действуют те же правила типов, что и в обычном выражении:
int i = 4, x = 6;
double z = 2;
z += x++ / i++;
cout << "z=" << z << endl;
cout << "x=" << x << ", i=" << i << endl;Здесь x++ / i++ — деление целых 6 / 4, то есть 1; постфиксные формы
дали делению старые значения, а увеличились переменные после. К z прибавилась
единица, а не 1.5.
Приведение типов
Неявное приведение компилятор делает сам, когда типы в выражении разные:
более «узкий» тип расширяется до более «широкого» (int → double).
Обратное преобразование тоже возможно, но теряет данные, и на нём компилятор
предупреждает.
Явное приведение записывается двумя способами:
double pi = 3.99;
cout << (int)pi << endl; // 3 — в стиле C
cout << static_cast<int>(pi) << endl; // 3 — в стиле C++
cout << (int)(pi + 0.5) << endl; // 4 — округление вручнуюПриведение double к int отбрасывает дробную часть, а не округляет:
3.99 становится 3. Чтобы округлить, прибавляют 0.5 или берут
round() из <cmath>.
Форма static_cast<int>(x) предпочтительнее: её видно в тексте, и по ней
легко искать все приведения в программе.
Математические функции
Заголовок <cmath> даёт корень, степень, модуль, округления
и тригонометрию. Пример — полная программа, потому что ей нужен
свой #include:
#include <cmath>
#include <iomanip>
#include <iostream>
using namespace std;
int main()
{
double a, b;
cin >> a >> b;
cout << sqrt(a * a + b * b) << endl;
cout << setprecision(4) << pow(a, 3) << " " << fabs(-a) << endl;
cout << floor(2.7) << " " << ceil(2.1) << " " << round(2.5) << endl;
return 0;
}Обратите внимание: модуль вещественного числа — это fabs, а не abs.
abs объявлен для целых, и при вызове с double может молча отбросить
дробную часть.
Задание к лабораторной
Вариант 1 (из рабочей программы). Даны два числа a и b. Выведите
гипотенузу треугольника с заданными катетами. В отчёте укажите, какого типа
должны быть переменные и что напечатает программа при вводе 3 4,
а что — при вводе 1 1.
Вариант 2 (из рабочей программы). Программа считывает целое число, после
чего выводит на экран следующее и предыдущее целое число с пояснительным
текстом. Решите задачу дважды: через + 1 / - 1 и через инкремент
с декрементом, и объясните, чем отличаются два решения.
Вариант 3. Программа считывает два целых числа и печатает четыре строки: целую часть их частного, остаток, вещественное частное с двумя знаками после запятой и то же частное в экспоненциальной записи. Проверьте поведение при нулевом делителе и опишите наблюдение в отчёте.
Как оценивается лабораторная
Работа сдаётся преподавателю очно: сайт её не принимает и не оценивает. Шкала и показатели ниже — из рабочей программы дисциплины, раздел 7.
Показатели оценки
- степень соответствия выполненного задания поставленным целям, задачам и требованиям;
- оформление, структурирование и комментирование лабораторной работы;
- уникальность выполнения работы (отличие от работ коллег);
- успешные ответы на контрольные вопросы.
Баллы
- 86–100отлично— оформление соответствует требованиям, критерии выдержаны, защита всего перечня контрольных вопросов.
- 66–85хорошо— оформление соответствует требованиям, критерии выдержаны, защита только до 85 % контрольных вопросов.
- 51–65удовлетворительно— оформление соответствует требованиям, критерии выдержаны, защита только до 51 % контрольных вопросов.
- 0–50неудовлетворительно— оформление не соответствует требованиям, критерии не выдержаны, защита менее 51 % контрольных вопросов.
Проверьте себя
Викторина по теме
Укажите размер в байтах переменных следующих типов в 32-битной системе (Windows, компилятор MSVC): тип int …..; тип long double ….; тип float ….; тип long ….
Ссылки на литературу
- Воробьев, Г. А. Основы программирования на C++ : учебно-методическое пособие. — Липецк : Липецкий ГПУ, 2022. — 89 с. — ISBN 978-5-907461-84-0. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
- Калитвин, В. А. Введение в программирование на C++ : учебное пособие. — Липецк : Липецкий ГПУ, 2023. — 84 с. — ISBN 978-5-907655-86-7. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
- Широбокова, С. Н., Кацупеев, А. А., Сулыз, А. В. Программирование на языке C++ для лабораторных занятий : учебное пособие. — Новочеркасск : ЮРГПУ, 2020. — 104 с. — ISBN 978-5-9997-0725-3. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей