Основы языка
Первая программа C++20: сохранение, сборка и запуск, типы, вычисления и проверка ввода.
Функция main: точка входа в программу
#include <iostream>
int main() {
std::cout << "Hello, C++!\n";
return 0;
}Сохранить, собрать и запустить
- Создайте папку и сохраните пример как
main.cpp, а не main.cpp.txt. - Откройте терминал в этой папке. В GCC или Clang выберите C++20 и предупреждения.
- После успешной сборки запускайте именно полученный файл. При ошибке сборки исправьте первое сообщение и не запускайте старую программу.
Windows / PowerShell:
> g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o app.exe
> .\app.exeLinux / macOS: замените имя результата на app и запускайте ./app. В IDE выберите C++20 в настройках проекта. #include <iostream> предоставляет ввод и вывод; std:: уточняет имена стандартной библиотеки.
Точка входа (entry point) — Место, в которое среда выполнения передаёт управление для начала основной работы программы. В обычном приложении C++, запускаемом операционной системой, эту роль выполняет глобальная функция main.
main — специальное имя, о котором договорились язык и среда запуска. Мы не пишем вызов main() сами: стартовый код вызывает её после необходимой подготовки. Компилятор не выбирает первую функцию в файле и не ищет функцию с «подходящим» смыслом имени. Если переименовать main в start, обычная сборка приложения не получит требуемую точку входа.
У main есть тело — инструкции внутри фигурных скобок. При входе управление переходит к первой инструкции тела, затем идёт дальше по правилам языка: условия выбирают ветки, циклы повторяют действия, вызовы временно передают управление другим функциям.
| Часть записи | Смысл |
|---|---|
int | Тип результата. Возвращаемое целое используется как код завершения приложения. |
main | Специальное имя основной функции. Для такой программы она определяется в глобальной области. |
() | В этой форме у функции нет параметров. |
{ ... } | Тело функции с инструкциями, которые выполняются после входа в неё. |
return 0; | Завершает main и сообщает об успешном завершении. Этот ноль не печатается на экран. |
Почему действия начинаются с main
Языку нужна заранее известная функция, с которой можно начать основную работу. Благодаря этому в проекте может быть много исходных файлов и вспомогательных функций, а точка входа остаётся одной. Определение вспомогательной функции только описывает её поведение; чтобы выполнить тело, нужен вызов.
#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, но первой печатается строка 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, а не надеяться, что переменная поменяет его сама.
Тип определяет, какие вычисления доступны переменной. Визуализация позволяет проследить преобразования, а примеры — сравнить явное приведение и инициализацию в фигурных скобках. Сначала попробуйте предсказать результат, затем сверьтесь с выводом.
#include <iostream>
int main() {
double price = 12.75;
int whole = static_cast<int>(price);
double half = whole / 2.0;
std::cout << whole << ' ' << half << '\n';
}#include <iostream>
int main() {
int count{3};
double price{12.5};
// int rounded{price}; // Error: narrowing conversion.
int rounded{static_cast<int>(price)};
std::cout << count << ' ' << rounded << '\n';
}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. Символ Показано 128 из 128
Важно: ASCII — 7-битная кодировка. Отдельный | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 комбинаций.
Выбери разрядность в модели справа и посмотри, как меняются границы. Почему есть минус? У знакового числа часть комбинаций кодирует отрицательные значения. Для обычного двоичного дополнительного кода диапазон асимметричен на единицу.
Не стоит запоминать границы int по одной платформе: программа может узнать их через numeric_limits. Для беззнаковых чисел переход через границу определён правилами языка; у знаковых целых переполнение имеет другие последствия.
На схеме — распространённые размеры. Стандарт C++ не фиксирует количество байтов для всех базовых целочисленных типов.
#include <iostream>
#include <limits>
int main() {
const int maximum = std::numeric_limits<int>::max();
int value = maximum - 1;
if (value < maximum) {
++value;
}
std::cout << std::boolalpha << (value == maximum) << '\n';
}#include <iostream>
#include <limits>
int main() {
unsigned int value = std::numeric_limits<unsigned int>::max();
++value;
std::cout << value << '\n';
}Если по условию задачи могут появиться числа больше ≈ 2,1 млрд, проверь, достаточно ли int. Для диапазона порядка 10¹⁸ обычно используют long long.
signed, unsigned и char
unsigned хранит только неотрицательные целые. Для signed часть диапазона используется под отрицательные значения.
Одинаковое количество бит, но разные границы. При этом char не обязан быть знаковым.
int a = 42;
long long big = 9'000'000'000LL;
unsigned int n = 10u;
bool flag = (a > 0);
char letter = 'A';Символ — тоже целочисленный код
Таблица ASCII
Базовая 7-битная таблица: 128 кодов от 0 до 127. Символ 'A' имеет код 65 (0x41), 'a' — 97 (0x61).
| DEC | HEX | Символ | Значение |
|---|---|---|---|
| 000 | 0x00 | NUL | Нулевой символ |
| 001 | 0x01 | SOH | Начало заголовка |
| 002 | 0x02 | STX | Начало текста |
| 003 | 0x03 | ETX | Конец текста |
| 004 | 0x04 | EOT | Конец передачи |
| 005 | 0x05 | ENQ | Запрос |
| 006 | 0x06 | ACK | Подтверждение |
| 007 | 0x07 | BEL | Звуковой сигнал |
| 008 | 0x08 | BS | Возврат на позицию |
| 009 | 0x09 | HT | Горизонтальная табуляция |
| 010 | 0x0A | LF | Перевод строки |
| 011 | 0x0B | VT | Вертикальная табуляция |
| 012 | 0x0C | FF | Перевод страницы |
| 013 | 0x0D | CR | Возврат каретки |
| 014 | 0x0E | SO | Shift Out |
| 015 | 0x0F | SI | Shift In |
| 016 | 0x10 | DLE | Data Link Escape |
| 017 | 0x11 | DC1 | Управление устройством 1 |
| 018 | 0x12 | DC2 | Управление устройством 2 |
| 019 | 0x13 | DC3 | Управление устройством 3 |
| 020 | 0x14 | DC4 | Управление устройством 4 |
| 021 | 0x15 | NAK | Отрицательное подтверждение |
| 022 | 0x16 | SYN | Синхронизация |
| 023 | 0x17 | ETB | Конец блока передачи |
| 024 | 0x18 | CAN | Отмена |
| 025 | 0x19 | EM | Конец носителя |
| 026 | 0x1A | SUB | Замена |
| 027 | 0x1B | ESC | Escape |
| 028 | 0x1C | FS | Разделитель файлов |
| 029 | 0x1D | GS | Разделитель групп |
| 030 | 0x1E | RS | Разделитель записей |
| 031 | 0x1F | US | Разделитель единиц |
| 032 | 0x20 | ␠ | Пробел (SPACE) |
| 033 | 0x21 | ! | Знак препинания / спецсимвол |
| 034 | 0x22 | " | Знак препинания / спецсимвол |
| 035 | 0x23 | # | Знак препинания / спецсимвол |
| 036 | 0x24 | $ | Знак препинания / спецсимвол |
| 037 | 0x25 | % | Знак препинания / спецсимвол |
| 038 | 0x26 | & | Знак препинания / спецсимвол |
| 039 | 0x27 | ' | Знак препинания / спецсимвол |
| 040 | 0x28 | ( | Знак препинания / спецсимвол |
| 041 | 0x29 | ) | Знак препинания / спецсимвол |
| 042 | 0x2A | * | Знак препинания / спецсимвол |
| 043 | 0x2B | + | Знак препинания / спецсимвол |
| 044 | 0x2C | , | Знак препинания / спецсимвол |
| 045 | 0x2D | - | Знак препинания / спецсимвол |
| 046 | 0x2E | . | Знак препинания / спецсимвол |
| 047 | 0x2F | / | Знак препинания / спецсимвол |
| 048 | 0x30 | 0 | Цифра |
| 049 | 0x31 | 1 | Цифра |
| 050 | 0x32 | 2 | Цифра |
| 051 | 0x33 | 3 | Цифра |
| 052 | 0x34 | 4 | Цифра |
| 053 | 0x35 | 5 | Цифра |
| 054 | 0x36 | 6 | Цифра |
| 055 | 0x37 | 7 | Цифра |
| 056 | 0x38 | 8 | Цифра |
| 057 | 0x39 | 9 | Цифра |
| 058 | 0x3A | : | Знак препинания / спецсимвол |
| 059 | 0x3B | ; | Знак препинания / спецсимвол |
| 060 | 0x3C | < | Знак препинания / спецсимвол |
| 061 | 0x3D | = | Знак препинания / спецсимвол |
| 062 | 0x3E | > | Знак препинания / спецсимвол |
| 063 | 0x3F | ? | Знак препинания / спецсимвол |
| 064 | 0x40 | @ | Знак препинания / спецсимвол |
| 065 | 0x41 | A | Латинская заглавная буква |
| 066 | 0x42 | B | Латинская заглавная буква |
| 067 | 0x43 | C | Латинская заглавная буква |
| 068 | 0x44 | D | Латинская заглавная буква |
| 069 | 0x45 | E | Латинская заглавная буква |
| 070 | 0x46 | F | Латинская заглавная буква |
| 071 | 0x47 | G | Латинская заглавная буква |
| 072 | 0x48 | H | Латинская заглавная буква |
| 073 | 0x49 | I | Латинская заглавная буква |
| 074 | 0x4A | J | Латинская заглавная буква |
| 075 | 0x4B | K | Латинская заглавная буква |
| 076 | 0x4C | L | Латинская заглавная буква |
| 077 | 0x4D | M | Латинская заглавная буква |
| 078 | 0x4E | N | Латинская заглавная буква |
| 079 | 0x4F | O | Латинская заглавная буква |
| 080 | 0x50 | P | Латинская заглавная буква |
| 081 | 0x51 | Q | Латинская заглавная буква |
| 082 | 0x52 | R | Латинская заглавная буква |
| 083 | 0x53 | S | Латинская заглавная буква |
| 084 | 0x54 | T | Латинская заглавная буква |
| 085 | 0x55 | U | Латинская заглавная буква |
| 086 | 0x56 | V | Латинская заглавная буква |
| 087 | 0x57 | W | Латинская заглавная буква |
| 088 | 0x58 | X | Латинская заглавная буква |
| 089 | 0x59 | Y | Латинская заглавная буква |
| 090 | 0x5A | Z | Латинская заглавная буква |
| 091 | 0x5B | [ | Знак препинания / спецсимвол |
| 092 | 0x5C | \ | Знак препинания / спецсимвол |
| 093 | 0x5D | ] | Знак препинания / спецсимвол |
| 094 | 0x5E | ^ | Знак препинания / спецсимвол |
| 095 | 0x5F | _ | Знак препинания / спецсимвол |
| 096 | 0x60 | ` | Знак препинания / спецсимвол |
| 097 | 0x61 | a | Латинская строчная буква |
| 098 | 0x62 | b | Латинская строчная буква |
| 099 | 0x63 | c | Латинская строчная буква |
| 100 | 0x64 | d | Латинская строчная буква |
| 101 | 0x65 | e | Латинская строчная буква |
| 102 | 0x66 | f | Латинская строчная буква |
| 103 | 0x67 | g | Латинская строчная буква |
| 104 | 0x68 | h | Латинская строчная буква |
| 105 | 0x69 | i | Латинская строчная буква |
| 106 | 0x6A | j | Латинская строчная буква |
| 107 | 0x6B | k | Латинская строчная буква |
| 108 | 0x6C | l | Латинская строчная буква |
| 109 | 0x6D | m | Латинская строчная буква |
| 110 | 0x6E | n | Латинская строчная буква |
| 111 | 0x6F | o | Латинская строчная буква |
| 112 | 0x70 | p | Латинская строчная буква |
| 113 | 0x71 | q | Латинская строчная буква |
| 114 | 0x72 | r | Латинская строчная буква |
| 115 | 0x73 | s | Латинская строчная буква |
| 116 | 0x74 | t | Латинская строчная буква |
| 117 | 0x75 | u | Латинская строчная буква |
| 118 | 0x76 | v | Латинская строчная буква |
| 119 | 0x77 | w | Латинская строчная буква |
| 120 | 0x78 | x | Латинская строчная буква |
| 121 | 0x79 | y | Латинская строчная буква |
| 122 | 0x7A | z | Латинская строчная буква |
| 123 | 0x7B | { | Знак препинания / спецсимвол |
| 124 | 0x7C | | | Знак препинания / спецсимвол |
| 125 | 0x7D | } | Знак препинания / спецсимвол |
| 126 | 0x7E | ~ | Знак препинания / спецсимвол |
| 127 | 0x7F | DEL | Удаление (Delete) |
Важно: ASCII — 7-битная кодировка. Отдельный char хранит кодовую единицу; в C++ фактическая кодировка символов зависит от среды, а Unicode не ограничивается таблицей ASCII.
При сложении двух char обычно срабатывает целочисленное продвижение: результат имеет тип int.
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 a = 0; // false
bool b = -25; // true
int x = true; // 1Важно: переполнение знакового целого типа может приводить к неопределённому поведению. Для беззнаковых целых результат вычисляется по модулю 2N. Это одна из причин внимательно следить за типом выражений.
Целочисленные литералы и суффиксы
42 — целочисленный литерал; 42u — беззнаковый, 42LL — long long. Запись 0xFF использует шестнадцатеричную систему, 0b1010 — двоичную (с C++14), а 1'000'000 разделяет разряды для чтения.
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 байт | Зависит от платформы и реализации |
Напоминает научную запись 1.64 × 10⁻³, но в двоичной системе основание степени — 2.
Мантисса и экспонента (significand · exponent) — Значащая часть хранит точные цифры, а показатель степени определяет масштаб. Поэтому расстояние между представимыми числами растёт с увеличением модуля числа.
Число 0.5 можно точно представить как половину, а для 0.1 в обычном двоичном формате требуется округление. Поэтому результат 0.1 + 0.2 не стоит бездумно проверять на точное равенство 0.3. Когда задача допускает погрешность, сравнивают близость значений с выбранным допуском (насколько большое расхождение ещё приемлемо). Допуск должен соответствовать масштабу и смыслу задачи.
У числа в памяти и его записи на экране разные задачи. Форматирование меняет только вывод; проверка близости чисел относится к самим вычислениям. Во вкладках показаны эти два случая отдельно.
#include <iomanip>
#include <iostream>
int main() {
double total = 12.5 * 3;
std::cout << std::fixed << std::setprecision(2);
std::cout << total << '\n';
}#include <cmath>
#include <iostream>
int main() {
double actual = 0.1 + 0.2;
double expected = 0.3;
const double tolerance = 1e-9;
bool close = std::abs(actual - expected) <= tolerance;
std::cout << std::boolalpha << close << '\n';
}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>.
>>cinint xОператор пытается извлечь значение из потока и записать его в переменную.
<<xcoutОператор вставляет данные в поток вывода, где они форматируются как текст.
Что возвращают операторы?
Оба оператора возвращают ссылку на сам поток, а не прочитанное число и не число записанных символов. Это позволяет выстраивать цепочки:
cin >> a→ std::istream&cout << a→ std::ostream&Поэтому cin >> a >> b — это (cin >> a) >> b, а cout << a << b — это (cout << a) << b.
cin >> aвозвращает cincin >> bвозвращает cinstd::istream&результат выражения#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;: вычисление завершается. Знак ! меняет истину на ложь и наоборот. Подробные ветвления изучаются в следующей теме маршрута. Не используйте значения после отказа чтения.
#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: он позволяет повторить один и тот же ввод. Второй пример показывает восстановление после ошибки.
12 8cin >> acin >> bУсловие if (cin >> a >> b) истинно, сумма равна 20.
#include <iostream>
#include <sstream>
int main() {
std::istringstream input{"12 30"};
int first = 0;
int second = 0;
if (input >> first >> second) {
std::cout << first + second << '\n';
} else {
std::cout << "Invalid input\n";
}
}#include <iostream>
#include <limits>
#include <sstream>
int main() {
std::istringstream input{"oops\n42\n"};
int value = 0;
if (!(input >> value)) {
input.clear();
input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
if (input >> value) {
std::cout << value << '\n';
}
}Если ввод оказался неправильным
После ошибки обычная повторная операция чтения не поможет: сначала нужно вызвать std::cin.clear(), а затем убрать проблемный остаток строки с помощью ignore(...).
#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, но берёт данные из строки.
Три небольших задания
- Прочитайте число и выведите его с подписью.
- Прочитайте два числа одной проверяемой цепочкой, выведите сумму.
- Введите слово вместо второго числа: программа должна сообщить об отказе и не выполнять вычисление.
Сначала достаточно прекратить работу при ошибке. Восстановление потока через clear/ignore и строгий разбор всей строки — следующий уровень.
Сущности языка
Первые программы удобнее читать не как набор непонятных символов, а как описание объектов и действий над ними. В строке int count = 3; слово int задаёт тип, count становится именем переменной, а 3 — начальным значением. Эти части отвечают на разные вопросы: какие данные допустимы, как к ним обратиться и с чего начать вычисления.
Программа на C++ состоит из объявлений сущностей; тела функций содержат инструкции, которые при выполнении изменяют состояние объектов, вычисляют выражения и управляют ходом программы.
Грамматически это разные понятия: тип определяет, какие значения и операции допустимы; переменная — имя некоторой сущности; оператор участвует в построении выражения; инструкция описывает действие внутри функции.
| Элемент языка | Что означает | Короткий пример |
|---|---|---|
Тип (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); сообщает, как функцию можно вызвать, но её действия станут понятны только из определения с телом (кода внутри фигурных скобок).
#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 — когда уже запущенная программа выполняет вычисления. Некоторые ошибки компилятор ловит заранее, другие выявляются только во время работы.
Глобальная область, подключения и runtime
Начало файла C++ обычно содержит подключения заголовков и объявления, а действия программы мы записываем внутри функций. Это не означает, что файл выполняется сверху вниз, как сценарий: компилятор сначала разбирает весь исходный текст и строит программу. Место определения функции в файле само по себе не определяет момент её вызова.
Глобальная область (global namespace scope) — Область вне тел функций, классов и явно объявленных пространств имён. Здесь можно объявлять переменные, функции, типы и пространства имён. Имя, объявленное здесь, доступно в допустимых местах после объявления, если его не скрывает другое имя.
Что разрешено писать вне функций
Запись int score = 10; объявляет и определяет глобальную переменную. Определение int twice(int value) { return value * 2; } описывает действия функции, но ещё не вызывает её. А отдельная инструкция std::cout << score; прямо в глобальной области недопустима: там ожидаются объявления, а не последовательность команд для выполнения.
#include <iostream>
int score = 10; // определение глобальной переменной
int twice(int value) { // определение глобальной функции
return value * 2;
}
// std::cout << score; // ошибка: отдельная инструкция вне функции
int main() {
std::cout << twice(score) << '\n'; // вызов и вывод — внутри функции
return 0;
}Глобальная область и время выполнения — разные понятия. Первая отвечает на вопрос «где объявлено имя и откуда оно видно?», второе — «когда происходят действия?». Обычная вспомогательная функция может быть определена глобально, а исполниться позже, когда её вызовет 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. Поэтому утверждение «в глобальной области вообще ничего не выполняется» слишком сильное: нельзя размещать там произвольные инструкции, но можно определить объект с вычисляемым начальным значением.
#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. Строка с "global initialization" появится раньше строки из main. Такой порядок не стоит обобщать на все глобальные объекты большого проекта: для объектов из разных файлов действуют дополнительные правила. На старте проще держать основную последовательность действий внутри функций и явно вызывать их из main.
C++ и версии языка
Когда мы пишем программу, мы составляем инструкции для обработки данных: получить исходные значения, выполнить вычисления и сообщить результат. C++ позволяет достаточно подробно управлять тем, как хранятся эти данные и какие операции над ними выполняются. За такую свободу приходится отвечать внимательностью к типам, границам и времени жизни объектов (то есть понимать, с какими данными программа сейчас имеет право работать).
C++ вырос из языка C и дополнил его классами, шаблонами, контейнерами стандартной библиотеки и другими возможностями. Большая часть концепций C сохранена, но C++ не является полным надмножеством C.
указатели
STL · ООП · и многое другое
Это схема развития идей, а не строгое включение всех корректных программ C в C++.
⚙ Производительность
Контроль ресурсов и эффективная работа с памятью.
◇ Разные парадигмы
Процедурное, объектно-ориентированное и обобщённое программирование.
▣ Системное применение
Движки, инфраструктура, производительные программы и библиотеки.
Поколения стандарта
Версии языка регулируются стандартом ISO. Начиная с C++11 произошёл особенно крупный скачок возможностей.
Компилятор может поддерживать стандарт не полностью. Версия языка ≠ версия компилятора ≠ версия стандартной библиотеки.
Контекст лекции: запись относится к 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 может иметь любую из них. |
В конспекте запоминать необязательно: достаточно знать, что они существуют и где искать их описание.
Преобразования и потеря точности
int a = 5;
double b = a; // 5.0
int c = 3.9; // 3 — потеря дробной части
unsigned u = 0;
// u - 1 даёт UINT_MAX, а не отрицательное значениеПри смешивании int и unsigned возможны неожиданные результаты. Это не «общее правило выбирать больший диапазон»: работают стандартные правила обычных арифметических преобразований.
Как проверять код
Кнопка «Диагностика» в примерах показывает лишь учебные эвристики. Для реальной проверки нужны g++ или clang++ с предупреждениями.
# g++ -std=c++20 -Wall -Wextra -Wconversion main.cpp -o main
# ./mainТермины
Стандарт C++ не предъявляет требований к последствиям выполнения такой программы. Это не обязательная ошибка компиляции или падение.
Примеры кода
Вычисление с проверкой ввода
#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.
#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';
}Обсудить лекцию с ИИ
Скопируйте промпт в свой ИИ-чат. В него уже включены материал этой лекции и примеры кода — можно сразу задавать вопросы и тренироваться.