Загружаем лабораторные…
// C++ · Лекция 01

Основы языка

Первая программа C++20: сохранение, сборка и запуск, типы, вычисления и проверка ввода.

Функция main: точка входа в программу

main.cpp
#include <iostream>

int main() {
    std::cout << "Hello, C++!\n";
    return 0;
}
На экране появляется только Hello, C++! Код завершения 0 получает среда запуска, он не является частью напечатанной строки.
РезультатHello, C++!

Сохранить, собрать и запустить

  1. Создайте папку и сохраните пример как main.cpp, а не main.cpp.txt.
  2. Откройте терминал в этой папке. В GCC или Clang выберите C++20 и предупреждения.
  3. После успешной сборки запускайте именно полученный файл. При ошибке сборки исправьте первое сообщение и не запускайте старую программу.

Windows / PowerShell:

PowerShell
> g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o app.exe
> .\app.exe

Linux / macOS: замените имя результата на app и запускайте ./app. В IDE выберите C++20 в настройках проекта. #include <iostream> предоставляет ввод и вывод; std:: уточняет имена стандартной библиотеки.

Точка входа (entry point) — Место, в которое среда выполнения передаёт управление для начала основной работы программы. В обычном приложении C++, запускаемом операционной системой, эту роль выполняет глобальная функция main.

main — специальное имя, о котором договорились язык и среда запуска. Мы не пишем вызов main() сами: стартовый код вызывает её после необходимой подготовки. Компилятор не выбирает первую функцию в файле и не ищет функцию с «подходящим» смыслом имени. Если переименовать main в start, обычная сборка приложения не получит требуемую точку входа.

У main есть тело — инструкции внутри фигурных скобок. При входе управление переходит к первой инструкции тела, затем идёт дальше по правилам языка: условия выбирают ветки, циклы повторяют действия, вызовы временно передают управление другим функциям.

Часть записиСмысл
int
Тип результата. Возвращаемое целое используется как код завершения приложения.
main
Специальное имя основной функции. Для такой программы она определяется в глобальной области.
()
В этой форме у функции нет параметров.
{ ... }
Тело функции с инструкциями, которые выполняются после входа в неё.
return 0;
Завершает main и сообщает об успешном завершении. Этот ноль не печатается на экран.

Почему действия начинаются с main

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

main_and_call.cpp
#include <iostream>

void greet() {
    std::cout << "greet: hello\n";
}

int main() {
    std::cout << "main: before call\n";
    greet();
    std::cout << "main: after call\n";
    return 0;
}
Порядок печати задают вызовы и инструкции, а не положение определения greet выше main. После завершения greet управление возвращается в main.
Результатmain: before call greet: hello main: after call

В примере функция greet записана выше main, но первой печатается строка main: before call. Затем greet(); передаёт управление в её тело. После возврата выполнение продолжается со следующей инструкции в main. Это и есть вызов функции (временно перейти к описанным действиям и вернуться к месту продолжения).

Что ещё важно знать о main

Для стандартной формы нужен возвращаемый тип int: запись void main() не является стандартным вариантом. Если выполнение дошло до закрывающей скобки main без return, это равнозначно return 0;. У обычных функций, возвращающих значение, такого общего правила нет.

Есть и форма int main(int argc, char* argv[]) для аргументов командной строки: argc сообщает их количество, argv даёт доступ к строкам аргументов. Для первых примеров достаточно int main(). В библиотеках и отдельных средах вроде микроконтроллеров правила запуска могут отличаться; здесь рассматривается обычное приложение под операционную систему.

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

Типизация

Тип можно воспринимать как набор правил для значения: какие данные в нём представимы, какие операции разрешены и что получится при вычислении. Компилятор проверяет эти правила ещё до запуска. Поэтому он отличает число от строки не по тому, что пользователь ввёл сегодня, а по объявлениям и выражениям в исходном коде. Значения меняются во время работы программы, а тип уже объявленной переменной остаётся прежним.

Тип переменной определяет, как компилятор интерпретирует данные и какие операции над ними допустимы. Изменение содержимого x не меняет его тип; запись x = 3.14 преобразует значение к int, а не превращает переменную в double.

Статическая типизация — тип каждой переменной известен при компиляции и не меняется на протяжении существования этой переменной.

Static · компиляция

Компилятор заранее устанавливает тип выражения и переменной.

Dynamic · выполнение

Динамические действия происходят в runtime, когда программа уже запущена.

Runtime (время выполнения) — Этап, на котором программа уже исполняется. int x = 5; связывает имя с типом при компиляции; значение x изменяется во время выполнения.

Разберите присваивание int count = 3;, а затем count = 4.9;: переменная не становится дробной, в неё попадает целое значение 4. При этом компилятор может предупредить о потере дробной части. Если дробь нужна для дальнейшей работы, следует изначально выбрать подходящий тип, например double, а не надеяться, что переменная поменяет его сама.

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

Тип постоянен, значение изменяется
Компиляциятип = int
→
Выполнениезначение = 5
Компилятор определяет тип переменной: int.
types.cpp
int x = 5;         // int
x = 10;            // всё ещё int
x = 3.14;          // станет 3 (преобразование)

Не путать изменение значения с изменением типа. Если int x = 5;, то даже присваивание дробного числа не превращает x в double. Имя x в иной области видимости может относиться к другой переменной Shadowing · Во вложенной области видимости можно объявить новую переменную с тем же именем. Это другой объект, а не изменение типа старого..

Преобразование типов (conversion / implicit conversion) — При несовпадении ожидаемого и фактического типов компилятор иногда вставляет преобразование. Оно может быть безопасным (char → int) или с потерей информации (double → int).

Тип, введённое значение и хранимый результат

ТипЗаписаноХранится
int
4.9
4: дробная часть отброшена
double
4.9
Приближение к 4.9

Предскажите результат 7 / 2, 7.0 / 2 и присваивания int count = 4.9;. Меняется значение, а не тип.

Целые числа

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

Деление и остаток

7 / 2 равно 3: оба операнда целые. 7.0 / 2 равно 3.5. 7 % 2 равно 1 — остаток целочисленного деления. Например, 250 см дают 250 / 100 полных метров и 250 % 100 оставшихся сантиметров. Делитель не должен быть нулевым. Знаковое переполнение недопустимо: выбирайте диапазон до вычисления.

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

ТипЧастый размерНазначение / смысл
char
1 байт
Символ / небольшой целочисленный код

Таблица ASCII

Базовая 7-битная таблица: 128 кодов от 0 до 127. Символ 'A' имеет код 65 (0x41), 'a' — 97 (0x61).

Показано 128 из 128
DECHEXСимволЗначение
0000x00NULНулевой символ
0010x01SOHНачало заголовка
0020x02STXНачало текста
0030x03ETXКонец текста
0040x04EOTКонец передачи
0050x05ENQЗапрос
0060x06ACKПодтверждение
0070x07BELЗвуковой сигнал
0080x08BSВозврат на позицию
0090x09HTГоризонтальная табуляция
0100x0ALFПеревод строки
0110x0BVTВертикальная табуляция
0120x0CFFПеревод страницы
0130x0DCRВозврат каретки
0140x0ESOShift Out
0150x0FSIShift In
0160x10DLEData Link Escape
0170x11DC1Управление устройством 1
0180x12DC2Управление устройством 2
0190x13DC3Управление устройством 3
0200x14DC4Управление устройством 4
0210x15NAKОтрицательное подтверждение
0220x16SYNСинхронизация
0230x17ETBКонец блока передачи
0240x18CANОтмена
0250x19EMКонец носителя
0260x1ASUBЗамена
0270x1BESCEscape
0280x1CFSРазделитель файлов
0290x1DGSРазделитель групп
0300x1ERSРазделитель записей
0310x1FUSРазделитель единиц
0320x20␠Пробел (SPACE)
0330x21!Знак препинания / спецсимвол
0340x22"Знак препинания / спецсимвол
0350x23#Знак препинания / спецсимвол
0360x24$Знак препинания / спецсимвол
0370x25%Знак препинания / спецсимвол
0380x26&Знак препинания / спецсимвол
0390x27'Знак препинания / спецсимвол
0400x28(Знак препинания / спецсимвол
0410x29)Знак препинания / спецсимвол
0420x2A*Знак препинания / спецсимвол
0430x2B+Знак препинания / спецсимвол
0440x2C,Знак препинания / спецсимвол
0450x2D-Знак препинания / спецсимвол
0460x2E.Знак препинания / спецсимвол
0470x2F/Знак препинания / спецсимвол
0480x300Цифра
0490x311Цифра
0500x322Цифра
0510x333Цифра
0520x344Цифра
0530x355Цифра
0540x366Цифра
0550x377Цифра
0560x388Цифра
0570x399Цифра
0580x3A:Знак препинания / спецсимвол
0590x3B;Знак препинания / спецсимвол
0600x3C<Знак препинания / спецсимвол
0610x3D=Знак препинания / спецсимвол
0620x3E>Знак препинания / спецсимвол
0630x3F?Знак препинания / спецсимвол
0640x40@Знак препинания / спецсимвол
0650x41AЛатинская заглавная буква
0660x42BЛатинская заглавная буква
0670x43CЛатинская заглавная буква
0680x44DЛатинская заглавная буква
0690x45EЛатинская заглавная буква
0700x46FЛатинская заглавная буква
0710x47GЛатинская заглавная буква
0720x48HЛатинская заглавная буква
0730x49IЛатинская заглавная буква
0740x4AJЛатинская заглавная буква
0750x4BKЛатинская заглавная буква
0760x4CLЛатинская заглавная буква
0770x4DMЛатинская заглавная буква
0780x4ENЛатинская заглавная буква
0790x4FOЛатинская заглавная буква
0800x50PЛатинская заглавная буква
0810x51QЛатинская заглавная буква
0820x52RЛатинская заглавная буква
0830x53SЛатинская заглавная буква
0840x54TЛатинская заглавная буква
0850x55UЛатинская заглавная буква
0860x56VЛатинская заглавная буква
0870x57WЛатинская заглавная буква
0880x58XЛатинская заглавная буква
0890x59YЛатинская заглавная буква
0900x5AZЛатинская заглавная буква
0910x5B[Знак препинания / спецсимвол
0920x5C\Знак препинания / спецсимвол
0930x5D]Знак препинания / спецсимвол
0940x5E^Знак препинания / спецсимвол
0950x5F_Знак препинания / спецсимвол
0960x60`Знак препинания / спецсимвол
0970x61aЛатинская строчная буква
0980x62bЛатинская строчная буква
0990x63cЛатинская строчная буква
1000x64dЛатинская строчная буква
1010x65eЛатинская строчная буква
1020x66fЛатинская строчная буква
1030x67gЛатинская строчная буква
1040x68hЛатинская строчная буква
1050x69iЛатинская строчная буква
1060x6AjЛатинская строчная буква
1070x6BkЛатинская строчная буква
1080x6ClЛатинская строчная буква
1090x6DmЛатинская строчная буква
1100x6EnЛатинская строчная буква
1110x6FoЛатинская строчная буква
1120x70pЛатинская строчная буква
1130x71qЛатинская строчная буква
1140x72rЛатинская строчная буква
1150x73sЛатинская строчная буква
1160x74tЛатинская строчная буква
1170x75uЛатинская строчная буква
1180x76vЛатинская строчная буква
1190x77wЛатинская строчная буква
1200x78xЛатинская строчная буква
1210x79yЛатинская строчная буква
1220x7AzЛатинская строчная буква
1230x7B{Знак препинания / спецсимвол
1240x7C|Знак препинания / спецсимвол
1250x7D}Знак препинания / спецсимвол
1260x7E~Знак препинания / спецсимвол
1270x7FDELУдаление (Delete)

Важно: ASCII — 7-битная кодировка. Отдельный char хранит кодовую единицу; в C++ фактическая кодировка символов зависит от среды, а Unicode не ограничивается таблицей ASCII.

short
2 байта
Целое небольшого диапазона
int
4 байта
Основной целочисленный тип
long
4 или 8 байт
Размер зависит от ABI и платформы
long long
8 байт
Большие целые значения
bool
часто 1 байт
true / false

Допустим, два входных числа помещаются в int, а их произведение — уже нет. Если оба операнда имеют тип int, одно лишь присваивание результата в long long не делает предшествующее умножение безопасным. В выражении 1LL * a * b более широкий тип участвует с самого начала. И всё равно нужно проверить, что ответ помещается уже в выбранный long long.

Логика хранения

Откуда берётся диапазон целого типа?

Один бит хранит одно из двух значений: 0 или 1. Поэтому n бит дают 2n комбинаций.

signed, n бит−2n−1 … 2n−1 − 1
unsigned, n бит0 … 2n − 1

Выбери разрядность в модели справа и посмотри, как меняются границы. Почему есть минус? У знакового числа часть комбинаций кодирует отрицательные значения. Для обычного двоичного дополнительного кода диапазон асимметричен на единицу.

Не стоит запоминать границы int по одной платформе: программа может узнать их через numeric_limits. Для беззнаковых чисел переход через границу определён правилами языка; у знаковых целых переполнение имеет другие последствия.

Модель целочисленного диапазонаТипичная платформа x86-64
32бит
4байт
2^32комбинаций
0
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
Диапазон: −2^31 … 2^31 − 1

На схеме — распространённые размеры. Стандарт C++ не фиксирует количество байтов для всех базовых целочисленных типов.

Пример: 32-битный int−2³¹ … 2³¹ − 1
−2 147 483 648минимум
────
0нейтральное
────
2 147 483 647максимум

Если по условию задачи могут появиться числа больше ≈ 2,1 млрд, проверь, достаточно ли int. Для диапазона порядка 10¹⁸ обычно используют long long.

signed, unsigned и char

Знак числа

unsigned хранит только неотрицательные целые. Для signed часть диапазона используется под отрицательные значения.

signed char (8 бит)−128 … 127
unsigned char (8 бит)0 … 255

Одинаковое количество бит, но разные границы. При этом char не обязан быть знаковым.

basic_types.cpp
int a = 42;
long long big = 9'000'000'000LL;
unsigned int n = 10u;
bool flag = (a > 0);
char letter = 'A';
Особенность char

Символ — тоже целочисленный код

'A'→65

Таблица ASCII

Базовая 7-битная таблица: 128 кодов от 0 до 127. Символ 'A' имеет код 65 (0x41), 'a' — 97 (0x61).

Показано 128 из 128
DECHEXСимволЗначение
0000x00NULНулевой символ
0010x01SOHНачало заголовка
0020x02STXНачало текста
0030x03ETXКонец текста
0040x04EOTКонец передачи
0050x05ENQЗапрос
0060x06ACKПодтверждение
0070x07BELЗвуковой сигнал
0080x08BSВозврат на позицию
0090x09HTГоризонтальная табуляция
0100x0ALFПеревод строки
0110x0BVTВертикальная табуляция
0120x0CFFПеревод страницы
0130x0DCRВозврат каретки
0140x0ESOShift Out
0150x0FSIShift In
0160x10DLEData Link Escape
0170x11DC1Управление устройством 1
0180x12DC2Управление устройством 2
0190x13DC3Управление устройством 3
0200x14DC4Управление устройством 4
0210x15NAKОтрицательное подтверждение
0220x16SYNСинхронизация
0230x17ETBКонец блока передачи
0240x18CANОтмена
0250x19EMКонец носителя
0260x1ASUBЗамена
0270x1BESCEscape
0280x1CFSРазделитель файлов
0290x1DGSРазделитель групп
0300x1ERSРазделитель записей
0310x1FUSРазделитель единиц
0320x20␠Пробел (SPACE)
0330x21!Знак препинания / спецсимвол
0340x22"Знак препинания / спецсимвол
0350x23#Знак препинания / спецсимвол
0360x24$Знак препинания / спецсимвол
0370x25%Знак препинания / спецсимвол
0380x26&Знак препинания / спецсимвол
0390x27'Знак препинания / спецсимвол
0400x28(Знак препинания / спецсимвол
0410x29)Знак препинания / спецсимвол
0420x2A*Знак препинания / спецсимвол
0430x2B+Знак препинания / спецсимвол
0440x2C,Знак препинания / спецсимвол
0450x2D-Знак препинания / спецсимвол
0460x2E.Знак препинания / спецсимвол
0470x2F/Знак препинания / спецсимвол
0480x300Цифра
0490x311Цифра
0500x322Цифра
0510x333Цифра
0520x344Цифра
0530x355Цифра
0540x366Цифра
0550x377Цифра
0560x388Цифра
0570x399Цифра
0580x3A:Знак препинания / спецсимвол
0590x3B;Знак препинания / спецсимвол
0600x3C<Знак препинания / спецсимвол
0610x3D=Знак препинания / спецсимвол
0620x3E>Знак препинания / спецсимвол
0630x3F?Знак препинания / спецсимвол
0640x40@Знак препинания / спецсимвол
0650x41AЛатинская заглавная буква
0660x42BЛатинская заглавная буква
0670x43CЛатинская заглавная буква
0680x44DЛатинская заглавная буква
0690x45EЛатинская заглавная буква
0700x46FЛатинская заглавная буква
0710x47GЛатинская заглавная буква
0720x48HЛатинская заглавная буква
0730x49IЛатинская заглавная буква
0740x4AJЛатинская заглавная буква
0750x4BKЛатинская заглавная буква
0760x4CLЛатинская заглавная буква
0770x4DMЛатинская заглавная буква
0780x4ENЛатинская заглавная буква
0790x4FOЛатинская заглавная буква
0800x50PЛатинская заглавная буква
0810x51QЛатинская заглавная буква
0820x52RЛатинская заглавная буква
0830x53SЛатинская заглавная буква
0840x54TЛатинская заглавная буква
0850x55UЛатинская заглавная буква
0860x56VЛатинская заглавная буква
0870x57WЛатинская заглавная буква
0880x58XЛатинская заглавная буква
0890x59YЛатинская заглавная буква
0900x5AZЛатинская заглавная буква
0910x5B[Знак препинания / спецсимвол
0920x5C\Знак препинания / спецсимвол
0930x5D]Знак препинания / спецсимвол
0940x5E^Знак препинания / спецсимвол
0950x5F_Знак препинания / спецсимвол
0960x60`Знак препинания / спецсимвол
0970x61aЛатинская строчная буква
0980x62bЛатинская строчная буква
0990x63cЛатинская строчная буква
1000x64dЛатинская строчная буква
1010x65eЛатинская строчная буква
1020x66fЛатинская строчная буква
1030x67gЛатинская строчная буква
1040x68hЛатинская строчная буква
1050x69iЛатинская строчная буква
1060x6AjЛатинская строчная буква
1070x6BkЛатинская строчная буква
1080x6ClЛатинская строчная буква
1090x6DmЛатинская строчная буква
1100x6EnЛатинская строчная буква
1110x6FoЛатинская строчная буква
1120x70pЛатинская строчная буква
1130x71qЛатинская строчная буква
1140x72rЛатинская строчная буква
1150x73sЛатинская строчная буква
1160x74tЛатинская строчная буква
1170x75uЛатинская строчная буква
1180x76vЛатинская строчная буква
1190x77wЛатинская строчная буква
1200x78xЛатинская строчная буква
1210x79yЛатинская строчная буква
1220x7AzЛатинская строчная буква
1230x7B{Знак препинания / спецсимвол
1240x7C|Знак препинания / спецсимвол
1250x7D}Знак препинания / спецсимвол
1260x7E~Знак препинания / спецсимвол
1270x7FDELУдаление (Delete)

Важно: ASCII — 7-битная кодировка. Отдельный char хранит кодовую единицу; в C++ фактическая кодировка символов зависит от среды, а Unicode не ограничивается таблицей ASCII.

При сложении двух char обычно срабатывает целочисленное продвижение: результат имеет тип int.

char.cpp
char a = 'A';
char b = 'B';
int sum = a + b; // char → int при арифметике

Логический тип bool

При преобразовании числа к bool: 0 → false, любое ненулевое значение → true. При преобразовании bool в целое: false → 0, true → 1.

Литерал (literal) — Запись конкретного значения прямо в программе: 5, 3.14, 'A', "abc". Вид записи влияет на тип значения.

bool.cpp
bool a = 0;       // false
bool b = -25;     // true
int x = true;     // 1

Важно: переполнение знакового целого типа может приводить к неопределённому поведению. Для беззнаковых целых результат вычисляется по модулю 2N. Это одна из причин внимательно следить за типом выражений.

Целочисленные литералы и суффиксы

42 — целочисленный литерал; 42u — беззнаковый, 42LL — long long. Запись 0xFF использует шестнадцатеричную систему, 0b1010 — двоичную (с C++14), а 1'000'000 разделяет разряды для чтения.

literals.cpp
int a = 42;
unsigned int b = 42u;
long long c = 9'000'000'000LL;
int hex = 0xFF;    // 255
int binary = 0b1010; // 10

Вещественные числа

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

float, double, long double представляют вещественные числа с ограниченной точностью. Основная идея хранения: знак + значащие биты + порядок.

ТипЧастый размерОсобенность
float
4 байта
Меньше точность, меньше память
double
8 байт
Частый выбор для вещественных вычислений
long double
8 / 12 / 16 байт
Зависит от платформы и реализации
Идея хранения числа
Знакплюс / минус
×
Мантиссазначащие цифры
×
2порядокмасштаб

Напоминает научную запись 1.64 × 10⁻³, но в двоичной системе основание степени — 2.

Мантисса и экспонента (significand · exponent) — Значащая часть хранит точные цифры, а показатель степени определяет масштаб. Поэтому расстояние между представимыми числами растёт с увеличением модуля числа.

Число 0.5 можно точно представить как половину, а для 0.1 в обычном двоичном формате требуется округление. Поэтому результат 0.1 + 0.2 не стоит бездумно проверять на точное равенство 0.3. Когда задача допускает погрешность, сравнивают близость значений с выбранным допуском (насколько большое расхождение ещё приемлемо). Допуск должен соответствовать масштабу и смыслу задачи.

У числа в памяти и его записи на экране разные задачи. Форматирование меняет только вывод; проверка близости чисел относится к самим вычислениям. Во вкладках показаны эти два случая отдельно.

Модель double (IEEE 754)64 бита
S
E
E
E
E
E
E
E
E
E
E
E
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
M
1 бит — знак11 бит — порядок52 бита — дробная часть мантиссы (показана частично)
2^0масштаб числа
2.22e-16шаг около 2^0
Чем больше число, тем дальше друг от друга соседние представимые значения.
float.cpp
float  a = 0.25f;
double b = 0.25;
long double c = 0.25L;
// Не все десятичные дроби кодируются точно в binary64

Потеря точности: не всякое целое представимо в double точно. Для типичного binary64 гарантированно точны все целые до 253 по модулю, но не все большие long long. Уточнение к устной речи · В лекции звучит слишком широкое утверждение о точном преобразовании больших целых в double. В конспекте указана корректная граница для обычной IEEE 754 binary64.

Денежный пример с double показывает только форматирование и округление. Если задача требует точного количества копеек, храните их целым числом. Допуск при сравнении результатов измерений выбирают по масштабу задачи; формат с двумя знаками не делает вычисление точным.

Ввод и вывод

Поток помогает отделить источник данных от правил их чтения. Числа могут приходить с клавиатуры, из файла или из строки в памяти, а извлечение через >> во многих случаях выглядит одинаково. При этом поток не знает, что пользователь «наверняка хотел ввести число»: он разбирает фактические символы. Успешность чтения — отдельный результат, который программа должна проверить.

Что такое поток

Поток — объект, через который данные последовательно читаются или записываются. std::cin — объект типа std::istream, std::cout — объект типа std::ostream. Оба объявлены в заголовке <iostream>.

Ввод >>
Входные данные
→
cin
→
int x

Оператор пытается извлечь значение из потока и записать его в переменную.

Вывод <<
x
→
cout
→
Выход

Оператор вставляет данные в поток вывода, где они форматируются как текст.

Что возвращают операторы?

Оба оператора возвращают ссылку на сам поток, а не прочитанное число и не число записанных символов. Это позволяет выстраивать цепочки:

cin >> a→ std::istream&
cout << a→ std::ostream&

Поэтому cin >> a >> b — это (cin >> a) >> b, а cout << a << b — это (cout << a) << b.

Цепочка ввода
01cin >> aвозвращает cin
↓
02cin >> bвозвращает cin
↓
→std::istream&результат выражения
streams.cpp
#include <iostream>

int main() {
    int a = 0, b = 0;
    std::cin >> a >> b;            // std::istream&
    std::cout << a << " " << b << '\n'; // std::ostream&
}

Состояние потока (stream state) — Поток хранит флаги ошибок. После неудачного чтения устанавливается failbit; при достижении конца ввода — eofbit; при серьёзной ошибке — badbit. Проверка потока в if истинна, когда не установлены failbit и badbit.

Как проверить корректность ввода

Основной приём

if (std::cin >> x) проверяет, удалось ли прочитать значение нужного типа. Если формат неверный или ввод закончился до получения значения, условие ложно. Так же работает while (std::cin >> x), чтобы читать числа до конца ввода или ошибки. Тонкость проверки · При вводе 12abc извлечение целого может успешно прочитать 12, оставив abc в потоке. Если требуется проверить всю строку целиком, сначала считай её через std::getline, затем разбери через std::istringstream и убедись, что не осталось лишних символов.

Минимальная проверка: if (!(std::cin >> a >> b)) означает «если прочитать оба числа не удалось». Внутри фигурных скобок выводим сообщение и выполняем return 1;: вычисление завершается. Знак ! меняет истину на ложь и наоборот. Подробные ветвления изучаются в следующей теме маршрута. Не используйте значения после отказа чтения.

check_input.cpp
#include <iostream>

int main() {
    int a = 0, b = 0;
    if (!(std::cin >> a >> b)) {
        std::cerr << "Ошибка ввода\n";
        return 1;
    }
    std::cout << a + b << '\n';
}

Проверять нужно всю цепочку

if (cin >> a >> b) выполнится только если оба извлечения прошли успешно. При первой ошибке дальнейший ввод пропускается, пока не сбросить флаги состояния.

Неверно: отдельно написать cin >> x; и сразу использовать x, не проверяя успешность ввода.

Представьте, что программа ждёт возраст, а получает слово. Ошибка здесь состоит не только в отсутствии подходящего числа: поток также переходит в состояние неудачного чтения. Повторять ту же операцию без изменения ситуации бесполезно. Восстановление состоит из двух разных действий: снять флаги ошибки и убрать мешающие символы (подготовить поток к следующей попытке). Решение повторять ввод или завершить программу зависит уже от вашей задачи.

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

Модель: cin >> a >> bВыбери ситуацию
ВХОД12 8
Чтение 1cin >> a
Источник: 12
Успешно
Чтение 2cin >> b
Источник: 8
Успешно
if (cin >> a >> b) → truegoodbit · ошибок нет

Условие if (cin >> a >> b) истинно, сумма равна 20.

Если ввод оказался неправильным

После ошибки обычная повторная операция чтения не поможет: сначала нужно вызвать std::cin.clear(), а затем убрать проблемный остаток строки с помощью ignore(...).

recover.cpp
#include <iostream>
#include <limits>

int main() {
    int x = 0;
    if (!(std::cin >> x)) {
        std::cin.clear();
        std::cin.ignore(
            std::numeric_limits<std::streamsize>::max(), '\n'
        );
        return 1;
    }
}
ПроверкаЧто сообщает
if (cin)
Нет failbit и badbit
cin.fail()
Ошибка форматированного чтения или более серьёзная ошибка
cin.eof()
Встречен конец входного потока
cin.bad()
Критическая ошибка чтения или записи
cin.clear()
Сбрасывает флаги состояния, не удаляя сам ввод

Нюанс: cin.good() — более строгая проверка: она ложна даже при одном eofbit. Обычно для обработки прочитанного значения удобнее именно if (cin >> x).

Вывод тоже имеет состояние. std::cout << x возвращает std::ostream&, и при необходимости можно проверять if (!(std::cout << x)). Обычно это актуальнее для файловых потоков. '\n' переводит строку, а std::endl дополнительно сбрасывает буфер вывода.

Другие потоки: std::ifstream / std::ofstream работают с файлами; std::istringstream / std::ostringstream — со строками. Строковые потоки · std::istringstream in("10 20"); int a, b; in >> a >> b; использует те же правила чтения, что и cin, но берёт данные из строки.

Дополнение к конспекту: подробная проверка состояния потоков приведена для практики; в исходной лекции кратко объясняются сами типы потоков и операторы.

Три небольших задания

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

Сначала достаточно прекратить работу при ошибке. Восстановление потока через clear/ignore и строгий разбор всей строки — следующий уровень.

Сущности языка

Первые программы удобнее читать не как набор непонятных символов, а как описание объектов и действий над ними. В строке int count = 3; слово int задаёт тип, count становится именем переменной, а 3 — начальным значением. Эти части отвечают на разные вопросы: какие данные допустимы, как к ним обратиться и с чего начать вычисления.

Основная идея

Программа на C++ состоит из объявлений сущностей; тела функций содержат инструкции, которые при выполнении изменяют состояние объектов, вычисляют выражения и управляют ходом программы.

1 · Сущноститипы · переменные · функции · пространства имён
→
2 · Выражениязначения · операнды · операторы · вызовы
→
3 · Инструкцииприсваивания · условия · циклы

Грамматически это разные понятия: тип определяет, какие значения и операции допустимы; переменная — имя некоторой сущности; оператор участвует в построении выражения; инструкция описывает действие внутри функции.

Элемент языкаЧто означаетКороткий пример
Тип (type)
Множество значений и допустимые операции
int, double
Объект (object)
Область хранения с типом и временем жизни
объект x типа int
Переменная (variable)
Именованная переменная-сущность, обычно объект или ссылка
int x = 5;
Идентификатор (identifier)
Имя, которым обозначается сущность
x, main
Литерал (literal)
Значение, записанное прямо в коде
42, "hi"
Выражение (expression)
Конструкция, которая вычисляется; имеет тип и категорию значения
x + 5
Оператор (operator)
Синтаксическая операция над операндами
+, =, <<
Инструкция (statement)
Грамматическая единица, например выражение-инструкция или цикл
x += 1;, if(...)
Функция (function)
Именованный вызываемый блок поведения
int main() {}
Пространство имён
Группирует объявления и отделяет их имена
namespace n { ... }

Объявление (declaration) — Вводит имя и сообщает компилятору, что за сущность обозначается этим именем. void f(int); объявляет функцию, но не задаёт её тело.

Определение (definition) — Даёт определение сущности. void f(int x) {} одновременно объявляет и определяет функцию. Всякое определение — объявление, но не наоборот.

Одно имя — три разных действия
Объявить
int x; — имя x появляется; для обычной переменной это также определение.
Создать
При определении переменной появляется объект; если это локальный int x;, он не получает заданного начального значения.
Инициализировать
int x = 5; — начальное значение задано при создании. Позднее x = 8; — уже присваивание.

Прослеживая программу, каждый раз различайте создание переменной и последующее изменение её значения. Сначала int count = 3; создаёт объект с заданным начальным состоянием. Затем count = 8; записывает в тот же объект новое значение. Объявление функции работает иначе: запись void print(int); сообщает, как функцию можно вызвать, но её действия станут понятны только из определения с телом (кода внутри фигурных скобок).

entities.cpp
#include <iostream>

int add(int a, int b);    // объявление функции

int main() {               // определение функции
    int x = 5;             // определение и инициализация
    x = add(x, 2);         // выражение + присваивание
    std::cout << x << '\n';
}

int add(int a, int b) { return a + b; }

Важно: int x; внутри функции не означает «значение равно нулю». Чтение такого неинициализированного скалярного объекта недопустимо. Глобальные объекты проходят нулевую инициализацию.

Что компилятор делает с программой?

Исходный код→Препроцессор→Компилятор→Компоновщик→Программа
Компиляция / выполнение

Compile time — когда исходный код анализируется и переводится в машинные инструкции; runtime — когда уже запущенная программа выполняет вычисления. Некоторые ошибки компилятор ловит заранее, другие выявляются только во время работы.

Раздел расширяет базовые понятия, используемые в первой лекции; строгое различие объявления и определения подробно изучается в лекции 03.

Глобальная область, подключения и runtime

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

Глобальная область (global namespace scope) — Область вне тел функций, классов и явно объявленных пространств имён. Здесь можно объявлять переменные, функции, типы и пространства имён. Имя, объявленное здесь, доступно в допустимых местах после объявления, если его не скрывает другое имя.

Что разрешено писать вне функций

Запись int score = 10; объявляет и определяет глобальную переменную. Определение int twice(int value) { return value * 2; } описывает действия функции, но ещё не вызывает её. А отдельная инструкция std::cout << score; прямо в глобальной области недопустима: там ожидаются объявления, а не последовательность команд для выполнения.

global_scope.cpp
#include <iostream>

int score = 10; // определение глобальной переменной

int twice(int value) { // определение глобальной функции
    return value * 2;
}

// std::cout << score; // ошибка: отдельная инструкция вне функции

int main() {
    std::cout << twice(score) << '\n'; // вызов и вывод — внутри функции
    return 0;
}
Определения score и twice допустимы в глобальной области. Вызов twice происходит при выполнении инструкции внутри main; закомментированная инструкция вне функции была бы ошибкой компиляции.
Результат20

Глобальная область и время выполнения — разные понятия. Первая отвечает на вопрос «где объявлено имя и откуда оно видно?», второе — «когда происходят действия?». Обычная вспомогательная функция может быть определена глобально, а исполниться позже, когда её вызовет main или другая функция.

Зачем нужен #include

#include <iostream> — директива препроцессора: она подключает содержимое заголовка, чтобы компилятор увидел необходимые объявления. Например, <iostream> даёт доступ к потокам std::cin и std::cout. Это подготовка исходного текста при сборке; подключение заголовка не означает вызов всех описанных в нём функций.

Системные заголовки стандартной библиотеки обычно подключают через угловые скобки: #include <vector>. Свои заголовки — через кавычки: #include "helpers.hpp". Подключай заголовок, который объявляет используемое средство: случайное косвенное подключение через другой файл может исчезнуть.

ПодключениеЧто предоставляетПример использования
<iostream>
Потоки ввода и вывода.
std::cin, std::cout.
<string>
Строки с управляемой памятью.
std::string text = "hello";
<vector>
Динамический массив.
std::vector<int> values{1, 2, 3};
<algorithm>
Алгоритмы над диапазонами.
std::sort(values.begin(), values.end());
<cmath>
Математические функции.
std::sqrt(9.0), std::abs(-2.5).
<iomanip>
Настройки форматирования потоков.
std::setprecision(2) вместе с std::fixed.
<limits>
Границы и свойства числовых типов.
std::numeric_limits<int>::max().
<cstddef>
Базовые типы для размеров и смещений.
std::size_t для индексов и размеров.

Префикс std:: указывает на пространство имён стандартной библиотеки. Запись using namespace std; влияет на поиск имён, но не подключает библиотеку и не заменяет #include. В C++20 есть отдельный механизм модулей с import; в этих начальных примерах используется привычное подключение заголовков.

Когда начинается runtime

Время выполнения (runtime) — Период, когда уже собранная программа запущена и выполняет действия: читает данные, вызывает функции, вычисляет значения и выводит результат. Compile time — время сборки, когда компилятор проверяет код и переводит его в исполняемую форму.

После запуска сначала работает стартовый код среды выполнения: он подготавливает программу и передаёт управление в main. Инициализация глобальных объектов тоже может включать вызовы функций и происходить до входа в main. Поэтому утверждение «в глобальной области вообще ничего не выполняется» слишком сильное: нельзя размещать там произвольные инструкции, но можно определить объект с вычисляемым начальным значением.

global_initialization.cpp
#include <iostream>

int initial_score() {
    std::cout << "global initialization\n";
    return 10;
}

int score = initial_score(); // вызов нужен для инициализации объекта

int main() {
    std::cout << "main: " << score << '\n';
    return 0;
}
Вызов initial_score является частью определения score. Здесь глобальная инициализация выполняет код раньше тела main: глобальная область не означает запрет на вычисления в инициализаторах.
Результатglobal initialization main: 10

В этом примере вызов initial_score() задаёт начальное значение глобального score. Строка с "global initialization" появится раньше строки из main. Такой порядок не стоит обобщать на все глобальные объекты большого проекта: для объектов из разных файлов действуют дополнительные правила. На старте проще держать основную последовательность действий внутри функций и явно вызывать их из main.

C++ и версии языка

Когда мы пишем программу, мы составляем инструкции для обработки данных: получить исходные значения, выполнить вычисления и сообщить результат. C++ позволяет достаточно подробно управлять тем, как хранятся эти данные и какие операции над ними выполняются. За такую свободу приходится отвечать внимательностью к типам, границам и времени жизни объектов (то есть понимать, с какими данными программа сейчас имеет право работать).

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

Схема эволюции
Cпроцедуры
указатели
⟶
C++классы · шаблоны
STL · ООП · и многое другое

Это схема развития идей, а не строгое включение всех корректных программ C в C++.

Зачем изучать C++?

⚙ Производительность

Контроль ресурсов и эффективная работа с памятью.

◇ Разные парадигмы

Процедурное, объектно-ориентированное и обобщённое программирование.

▣ Системное применение

Движки, инфраструктура, производительные программы и библиотеки.

Поколения стандарта

Версии языка регулируются стандартом ISO. Начиная с C++11 произошёл особенно крупный скачок возможностей.

98
C++98
первый ISO
03
C++03
уточнения
11
C++11
большой скачок
14
C++14
развитие
17
C++17
новые средства
20
C++20
большой релиз
23
C++23
контекст записи

Компилятор может поддерживать стандарт не полностью. Версия языка ≠ версия компилятора ≠ версия стандартной библиотеки.

Контекст лекции: запись относится к 2023 году. В этом материале C++20.

Обозначение C++20 в примере — это договор о доступных правилах языка, а не название отдельной программы. Один компилятор может поддерживать несколько режимов: например, флаг -std=c++20 выбирает нужный стандарт при сборке в GCC. Если пример не собирается, сначала проверьте выбранный режим и версию инструментов: иногда проблема в неподдерживаемой возможности, а не в самой идее решения.

+Дополнительно

Специальные типы

ТипДля чего нужен
std::int32_t / std::uint32_t
Целые ровно 32 бита, когда такие типы предоставлены реализацией (<cstdint>).
std::size_t
Беззнаковый тип, предназначенный для размеров объектов (<cstddef>).
std::byte
Представляет байт памяти без обычной числовой арифметики (C++17).
signed char / unsigned char
Чётко заданная знаковость; обычный char может иметь любую из них.

В конспекте запоминать необязательно: достаточно знать, что они существуют и где искать их описание.

Преобразования и потеря точности

conversions.cpp
int a = 5;
double b = a;  // 5.0
int c = 3.9;     // 3 — потеря дробной части
unsigned u = 0;
// u - 1 даёт UINT_MAX, а не отрицательное значение

При смешивании int и unsigned возможны неожиданные результаты. Это не «общее правило выбирать больший диапазон»: работают стандартные правила обычных арифметических преобразований.

Как проверять код

Кнопка «Диагностика» в примерах показывает лишь учебные эвристики. Для реальной проверки нужны g++ или clang++ с предупреждениями.

build.sh
# g++ -std=c++20 -Wall -Wextra -Wconversion main.cpp -o main
# ./main

Термины

Undefined behavior · неопределённое поведение

Стандарт C++ не предъявляет требований к последствиям выполнения такой программы. Это не обязательная ошибка компиляции или падение.

Чтение v[100] при v.size() == 3

Примеры кода

Вычисление с проверкой ввода

rectangle-cases.cpp
#include <iostream>

int main() {
    double width = 0.0;
    double height = 0.0;
    if (!(std::cin >> width >> height) || width <= 0.0 || height <= 0.0) {
        std::cerr << "Invalid sides\n";
        return 1;
    }
    std::cout << width * height << '\n';
    return 0;
}

Два вызова одной функции. Ввод и печать остаются в main.

code-guided-function.cpp
#include <iostream>

double area(double width, double height) {
    return width * height;
}

int main() {
    std::cout << area(2.0, 3.0) << '\n';
    std::cout << area(2.5, 4.0) << '\n';
}

Обсудить лекцию с ИИ

Скопируйте промпт в свой ИИ-чат. В него уже включены материал этой лекции и примеры кода — можно сразу задавать вопросы и тренироваться.

Посмотреть промпт