Технологии и методы программирования — на главную

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

Целые и вещественные типы, char и bool, арифметические операции, целочисленное деление и остаток, инкремент и декремент, приведение типов и формат вывода double.

лекции 2 ч
лаб. 2 ч
СРС 8 ч

Все программы предыдущего урока печатали строки. Как только появляются числа, появляются и вопросы, на которых спотыкаются все: почему 6 / 4 равно единице, почему cout печатает 2 вместо 2.0 и почему 0.1 + 0.2 не даёт 0.3. Ни один из этих ответов нельзя угадать — их надо проверить, и примеры на странице для этого и стоят.

Зачем переменной тип

Объявляя переменную, вы сообщаете компилятору две вещи: сколько памяти под неё отвести и как истолковать лежащие там биты. Одни и те же 32 бита — это целое число, вещественное число или четыре символа, в зависимости от типа. Отсюда правило, из которого потом вытекает половина урока: операция смотрит на типы операндов, а не на смысл задачи.

Целые типы

ТипОбычный размерДиапазон
char1 байт−128…127
short2 байта−32 768…32 767
int4 байта−2 147 483 648…2 147 483 647
long long8 байт±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.

Показатели оценки

  1. степень соответствия выполненного задания поставленным целям, задачам и требованиям;
  2. оформление, структурирование и комментирование лабораторной работы;
  3. уникальность выполнения работы (отличие от работ коллег);
  4. успешные ответы на контрольные вопросы.

Баллы

  • 86–100
    отлично— оформление соответствует требованиям, критерии выдержаны, защита всего перечня контрольных вопросов.
  • 66–85
    хорошо— оформление соответствует требованиям, критерии выдержаны, защита только до 85 % контрольных вопросов.
  • 51–65
    удовлетворительно— оформление соответствует требованиям, критерии выдержаны, защита только до 51 % контрольных вопросов.
  • 0–50
    неудовлетворительно— оформление не соответствует требованиям, критерии не выдержаны, защита менее 51 % контрольных вопросов.

Проверьте себя

Викторина по теме

Вопрос 1 из 10
короткий ответ

Укажите размер в байтах переменных следующих типов в 32-битной системе (Windows, компилятор MSVC): тип int …..; тип long double ….; тип float ….; тип long ….

Ссылки на литературу

  1. Воробьев, Г. А. Основы программирования на C++ : учебно-методическое пособие. — Липецк : Липецкий ГПУ, 2022. — 89 с. — ISBN 978-5-907461-84-0. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
  2. Калитвин, В. А. Введение в программирование на C++ : учебное пособие. — Липецк : Липецкий ГПУ, 2023. — 84 с. — ISBN 978-5-907655-86-7. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
  3. Широбокова, С. Н., Кацупеев, А. А., Сулыз, А. В. Программирование на языке C++ для лабораторных занятий : учебное пособие. — Новочеркасск : ЮРГПУ, 2020. — 104 с. — ISBN 978-5-9997-0725-3. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
Мой прогресс

Содержание