Раздел 3 — Типы, переменные, константы, операции

Где хранятся значения программы, сколько места они занимают и что с ними можно делать

Прогресс курса Раздел 3 из 20

Что вы освоите в этом разделе

10 академических часов теории, практической работы в разделе нет. Все программы страницы — полные: скопируйте, соберите командой g++ -std=c++17 -Wall -Wextra файл.cpp -o файл.exe и сравните вывод со страницей. Раздел заканчивается тестом на портале; раздел 4 с первыми практическими работами на C++ открывается после теста.
01

Типы данных и sizeof

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

Основные (встроенные) типы C++:

ТипЧто хранитБайт в Windows x64Литерал
boolлогическое значение1true, false
charкод одного символа (маленькое целое)1'A'
shortцелое2—
intцелое — основной целый тип442
longцелое442L
long longцелое842LL
floatвещественное, около 7 значащих цифр43.5f
doubleвещественное, 15–16 значащих цифр — основной дробный тип83.5
long doubleвещественное повышенной точности163.5L

У целых типов есть беззнаковые варианты со словом unsigned: unsigned int, unsigned long long. Литерал беззнакового типа — с суффиксом u: 42u. Строки std::string из раздела 2 — не встроенный тип, а тип стандартной библиотеки (раздел 10).

Размер типа в байтах возвращает операция sizeof. Стандарт C++ задаёт только минимальные размеры, точные выбирает компилятор под систему, поэтому их проверяют, а не угадывают:

#include <iostream>

int main()
{
    std::cout << "bool        " << sizeof(bool) << '\n';
    std::cout << "char        " << sizeof(char) << '\n';
    std::cout << "short       " << sizeof(short) << '\n';
    std::cout << "int         " << sizeof(int) << '\n';
    std::cout << "long        " << sizeof(long) << '\n';
    std::cout << "long long   " << sizeof(long long) << '\n';
    std::cout << "float       " << sizeof(float) << '\n';
    std::cout << "double      " << sizeof(double) << '\n';
    std::cout << "long double " << sizeof(long double) << '\n';
    return 0;
}
bool        1
char        1
short       2
int         4
long        4
long long   8
float       4
double      8
long double 16
long в Windows — 4 байта, как int, даже в 64-битной программе. В Linux тот же long — 8 байт. Код, который рассчитывает на 8-байтовый long, в Windows молча теряет старшие разряды. Нужны большие целые — пишите long long, он 8 байт везде. По этой же причине Windows API объявляет свои типы с точным размером: DWORD — беззнаковое 32-битное, BYTE — 8-битное; они встретятся с раздела 14.
02

Переменные: объявление, инициализация, область видимости

Переменная — именованная область памяти, в которой хранится значение одного типа. Объявление задаёт тип и имя; инициализация — начальное значение в момент объявления. В C++ три равноправные записи:

int layers = 2;          // копирующая инициализация — основная в курсе
int rooms(3);            // в круглых скобках
int floors{5};           // в фигурных скобках — запрещает сужение (пункт 05)
int total{};             // пустые фигурные скобки — ноль
double width = 3.2, height = 2.7;   // несколько переменных одного типа

Переменная без инициализации — не ноль: в ней лежит то, что осталось в памяти от прошлого использования. Чтение такой переменной — ошибка, и -Wall о ней предупреждает:

int count;
std::cout << count << '\n';
uninit.cpp:6:27: warning: 'count' is used uninitialized [-Wuninitialized]

Исключение — переменная, которую сразу заполняет ввод, как age в анкете раздела 2: её можно не инициализировать. Правило курса — объявлять переменную там, где у неё впервые появляется значение, а не списком в начале main.

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

#include <iostream>

int total = 100;                    // глобальная: видна во всём файле

int main()
{
    int count = 5;                  // локальная: видна до конца main
    std::cout << total << ' ' << count << '\n';
    {
        int count = 7;              // другая переменная, закрывает внешнюю
        int extra = 1;
        std::cout << count << ' ' << extra << '\n';
    }
    std::cout << count << '\n';     // снова внешняя count; extra здесь нет
    return 0;
}
100 5
7 1
5

Внутренняя count — отдельная переменная, которая на время блока скрывает внешнюю; после блока внешняя снова видна со своим значением 5. Обращение к extra после блока — ошибка компиляции 'extra' was not declared in this scope: переменной там уже нет.

Глобальные переменные в курсе не используются: их может изменить любая строка файла, и найти, кто испортил значение, трудно. Исключение — глобальные константы (пункт 06). Одинаковые имена во вложенных блоках компилятор не запрещает и не отмечает даже с -Wall -Wextra, но читающий код путается — давайте переменным разные имена.
03

Диапазоны, переполнение, знаковые и беззнаковые

В n битах помещается 2n разных значений. Беззнаковый тип отдаёт их все неотрицательным числам: от 0 до 2n − 1. Знаковый делит пополам: от −2n−1 до 2n−1 − 1. Для int (32 бита) это ±2,1 миллиарда, для unsigned int — от 0 до 4,29 миллиарда. Границы типов записаны в заголовке <climits>:

#include <iostream>
#include <climits>

int main()
{
    std::cout << "short:        " << SHRT_MIN << " .. " << SHRT_MAX << '\n';
    std::cout << "int:          " << INT_MIN << " .. " << INT_MAX << '\n';
    std::cout << "unsigned int: 0 .. " << UINT_MAX << '\n';
    std::cout << "long long:    " << LLONG_MIN << " .. " << LLONG_MAX << '\n';

    unsigned int counter = 0;
    counter = counter - 1;              // беззнаковое: по кругу, к максимуму
    std::cout << "0 - 1 in unsigned int = " << counter << '\n';

    short small = 32767;
    small = small + 1;                  // 32768 не помещается в short
    std::cout << "32767 + 1 in short = " << small << '\n';
    return 0;
}
short:        -32768 .. 32767
int:          -2147483648 .. 2147483647
unsigned int: 0 .. 4294967295
long long:    -9223372036854775808 .. 9223372036854775807
0 - 1 in unsigned int = 4294967295
32767 + 1 in short = -32768

Выход результата за диапазон — переполнение. Что при этом происходит, зависит от типа:

Если переполнение видно уже при компиляции, g++ предупреждает:

int big = 2147483647 + 1;
overflow.cpp:5:26: warning: integer overflow in expression of type 'int' results in '-2147483648' [-Woverflow]

Но значения, пришедшие с ввода, компилятор не видит. Тип выбирают заранее, по самому большому значению, которое может получиться: количество людей — int, население Земли или сумма в копейках — long long, размер файла в байтах — long long (файл бывает больше 2 ГБ).

Не смешивайте знаковые и беззнаковые в сравнении. При сравнении int с unsigned int знаковое значение переводится в беззнаковое: −1 становится 4294967295. Поэтому -1 < 1u — ложь. Компилятор предупреждает:
signcmp.cpp:7:27: warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int' [-Wsign-compare]
Беззнаковые типы в курсе — для флагов (пункт 11) и там, где их требует Windows API. Счётчики и величины — int и long long.
04

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

float и double хранят число в виде мантиссы и порядка — как 1.5e3 в литерале, только в двоичной системе. Диапазон огромный (у double — до 10308), но значащих цифр ограниченное число: у float около 7, у double 15–16. Кроме того, большинство десятичных дробей в двоичной системе бесконечны, как 1/3 в десятичной, и хранятся с округлением.

#include <iostream>
#include <iomanip>

int main()
{
    double sum = 0.1 + 0.2;
    std::cout << sum << '\n';
    std::cout << std::setprecision(17) << sum << '\n';
    std::cout << (sum == 0.3) << '\n';

    float big = 16777216.0f;
    std::cout << std::setprecision(10) << big + 1.0f << '\n';
    return 0;
}
0.3
0.30000000000000004
0
16777216

std::cout по умолчанию показывает 6 значащих цифр и округляет — поэтому первая строка выглядит точной. std::setprecision(n) из заголовка <iomanip> задаёт число значащих цифр вывода и действует до следующего изменения. С 17 цифрами видно, что сумма не равна 0.3, и сравнение даёт 0 — «ложь». В последней строке к 16 777 216 прибавили единицу, а число не изменилось: у float кончились значащие цифры.

Вещественные числа не сравнивают на точное равенство == — только «разница меньше допуска», это будет в разделе 4. Деньги не хранят в double: копейки теряются при сложении тысяч сумм. Сумма хранится целым числом копеек в long long. Основной вещественный тип курса — double; float — только там, где его требует библиотека.
05

Преобразование типов: неявное и static_cast

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

Явное преобразование записывается static_cast<тип>(выражение). Оно нужно, когда неявное делает не то, что требуется, и чтобы показать читателю: потеря данных здесь намеренная.

#include <iostream>

int main()
{
    int apples = 7;
    int people = 2;

    double share1 = apples / people;                        // целое деление, потом в double
    double share2 = static_cast<double>(apples) / people;   // деление вещественное
    std::cout << share1 << ' ' << share2 << '\n';

    double price = 99.99;
    int rubles = static_cast<int>(price);                   // дробная часть отбрасывается
    std::cout << rubles << '\n';

    char letter = 'A';
    std::cout << letter + 1 << ' ' << static_cast<char>(letter + 1) << '\n';
    std::cout << static_cast<int>(letter) << '\n';

    std::cout << 7 / 2 << ' ' << 7.0 / 2 << ' ' << 1 / 3 * 3 << '\n';
    return 0;
}
3 3.5
99
66 B
65
3 3.5 0

В share1 деление выполнено над двумя int, результат 3 — и только потом превращён в double. Приводить нужно операнд до деления, как в share2; static_cast<double>(apples / people) даст ту же тройку. char хранит код символа: 'A' — 65, letter + 1 — число 66, а приведённое обратно к char — буква B.

Инициализация в фигурных скобках запрещает сужение — преобразование с потерей данных:

int count{3.9};
narrowing.cpp:5:15: error: narrowing conversion of '3.8999999999999999e+0' from 'double' to 'int' [-Wnarrowing]
В чужом коде встречается приведение в стиле C: (double)apples или double(apples). Оно работает, но в курсе не используется: в разделах о Windows API такая запись молча превращает что угодно во что угодно, включая указатели, а static_cast разрешает только осмысленные преобразования и сразу видна при чтении.
06

Константы: const и constexpr

Литерал — константа без имени. Число 1.1 в середине формулы расхода краски из раздела 1 не говорит, что это запас 10 % на потери, а если запас изменится, придётся искать все места, где оно записано. Такие числа называют магическими. Вместо них заводят именованные константы:

ЗаписьЗначение известноПример
constк моменту объявления, в том числе при выполненииconst double area = PI * radius * radius;
constexprуже при компиляции — только из литералов и других constexprconstexpr int SECONDS_IN_HOUR = 60 * 60;

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

#include <iostream>

constexpr double PI = 3.14159265358979;     // значение известно при компиляции
const int DAYS_IN_WEEK = 7;

enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
enum class Access { READ = 1, WRITE = 2, EXECUTE = 4 };

int main()
{
    double radius;
    std::cout << "Radius: ";
    std::cin >> radius;
    const double area = PI * radius * radius;   // значение известно только при выполнении
    std::cout << "Area: " << area << '\n';

    Weekday today = FRIDAY;
    std::cout << today << ' ' << DAYS_IN_WEEK - today << '\n';

    Access mode = Access::WRITE;
    std::cout << static_cast<int>(mode) << '\n';
    return 0;
}
Radius: 2
Area: 12.5664
4 3
2

Перечисления enum — в пункте 07. Константу нельзя изменить и нельзя оставить без значения — обе ошибки ловит компилятор:

const int LIMIT = 10;
LIMIT = 20;          // error: assignment of read-only variable 'LIMIT'

const int MAX;         // error: uninitialized 'const MAX' [-fpermissive]
Правило курса: всё, что не должно меняться после вычисления, объявляется const. Это не только защита от случайного присваивания: читая const double area, не нужно просматривать остаток функции, чтобы понять, какое значение у area в конце. constexpr — для настоящих констант задачи: коэффициентов, пределов, размеров массивов (раздел 8 — размер массива обязан быть известен при компиляции).
07

#define против const; перечисления enum

Константу можно задать и макросом: #define DAYS_IN_WEEK 7. Так пишут в старом коде на C и в заголовках Windows API. Разница — в том, кто обрабатывает запись (раздел 2, пункт 07):

#defineconst / constexpr
Обрабатываетпрепроцессор, как тексткомпилятор, как переменную
Типнет — подставляется текстесть, проверяется
Область видимостиот строки #define до конца файла, во всех функцияхблок, как у переменной
Ошибкисообщение указывает на место подстановки, имени макроса в нём может не бытьсообщение называет константу
В курсетолько условная компиляциявсе именованные константы

Константы Windows API — макросы: GENERIC_READ, INFINITE, FOREGROUND_RED. Причина — API написан на C, где constexpr нет. Пользоваться ими можно как обычными константами, а свои константы в курсе — только const и constexpr.

Перечисление enum — тип с набором именованных целых констант. Если значения не заданы, первая равна 0, каждая следующая на 1 больше: в примере пункта 06 FRIDAY — 4. Перечисление заменяет «коды» вида «1 — понедельник, 2 — вторник»: today = FRIDAY читается без комментария.

Вид перечисления два:

Access mode = Access::WRITE;
int code = mode;
enumclass.cpp:8:16: error: cannot convert 'Access' to 'int' in initialization
Имена констант простого enum попадают в окружающую область видимости и могут столкнуться с чужими именами: в windows.h уже есть макросы ERROR, DELETE, IN, OUT, и перечисление с такой константой после подключения windows.h не соберётся. enum class от этого защищён, поэтому для новых перечислений в курсе — он.
08

Арифметические операции; целочисленное деление и остаток

ОперацияСмыслПримерРезультат
+ -сложение, вычитание7 - 25
*умножение7 * 214
/деление; у двух целых — целая часть7 / 2, 7.0 / 23, 3.5
%остаток от деления, только для целых7 % 21
унарный -смена знака-x

Целочисленное деление и остаток — главные инструменты работы с разрядами и единицами измерения: n % 10 — последняя цифра числа, n / 10 — число без неё, n % 2 — 0 у чётного. Перевод секунд в часы, минуты и секунды:

#include <iostream>

int main()
{
    int total_seconds;
    std::cout << "Seconds: ";
    std::cin >> total_seconds;

    int hours = total_seconds / 3600;
    int minutes = total_seconds % 3600 / 60;
    int seconds = total_seconds % 60;

    std::cout << hours << " h " << minutes << " min " << seconds << " s\n";
    std::cout << -7 / 2 << ' ' << -7 % 2 << ' ' << 7 % -2 << '\n';
    return 0;
}
Seconds: 7384
2 h 3 min 4 s
-3 -1 1

Целое деление отбрасывает дробную часть в сторону нуля: −7 / 2 = −3, а не −4. Знак остатка совпадает со знаком делимого: −7 % 2 = −1. Всегда верно равенство (a / b) * b + a % b == a.

Деление целого на ноль — неопределённое поведение; в Windows программа обычно аварийно завершается с кодом 0xC0000094 (STATUS_INTEGER_DIVIDE_BY_ZERO). Если ноль виден при компиляции, g++ предупреждает warning: division by zero [-Wdiv-by-zero], но делитель из ввода компилятор не видит — проверять его придётся ветвлением (раздел 4). Вещественное деление на ноль не аварийное: 1.0 / 0 даёт inf.
09

Присваивание, составное присваивание, ++ и --

Присваивание x = выражение вычисляет правую часть и записывает результат в переменную слева. Слева может стоять только то, у чего есть место в памяти: 5 = x и a + b = 3 — ошибки. Присваивание само является выражением со значением присвоенного, поэтому цепочка x = y = z = 10 выполняется справа налево.

Составное присваивание — сокращение: x += 5 значит x = x + 5. Так же работают -= *= /= %= и побитовые &= |= ^= <<= >>= (пункт 11).

Инкремент ++ увеличивает переменную на 1, декремент -- уменьшает. У каждого две формы, и разница видна, когда результат используется в выражении:

#include <iostream>

int main()
{
    int a = 5;
    int b = a++;            // b получает 5, затем a становится 6
    int c = ++a;            // a становится 7, затем c получает 7
    std::cout << a << ' ' << b << ' ' << c << '\n';

    int stock = 20;
    stock -= 3;             // stock = stock - 3
    stock *= 2;
    stock /= 5;
    stock %= 4;
    std::cout << stock << '\n';

    int x, y, z;
    x = y = z = 10;         // присваивание выполняется справа налево
    std::cout << x + y + z << '\n';
    return 0;
}
7 5 7
2
30

stock: 20 − 3 = 17, × 2 = 34, / 5 = 6 (целое деление), % 4 = 2.

Не изменяйте одну переменную дважды в одном выражении: результат i++ + i++ не определён стандартом, разные компиляторы дают разное. g++ предупреждает:
seqpt.cpp:6:14: warning: operation on 'i' may be undefined [-Wsequence-point]
Правило курса: ++ и -- — отдельной инструкцией (count++;) или в заголовке цикла (раздел 5), а не внутри формул.
10

Сравнение, логические операции, приоритет

Операции сравнения == != < <= > >= дают значение типа bool. Логические операции соединяют условия:

ОперацияСмыслИстинно, когда
&&Иистинны оба операнда
||ИЛИистинен хотя бы один
!НЕоперанд ложен
#include <iostream>

int main()
{
    int age = 17;
    int score = 82;

    bool adult = age >= 18;
    bool passed = score >= 70 && score <= 100;
    std::cout << adult << ' ' << passed << '\n';
    std::cout << std::boolalpha << adult << ' ' << passed << '\n';
    std::cout << (adult || passed) << ' ' << !adult << '\n';
    std::cout << (2 + 3 * 4 > 10 && age != 17) << '\n';
    return 0;
}
0 1
false true
true true
false

bool выводится как 1 и 0; после std::boolalpha — словами. Условие «число в диапазоне от 70 до 100» записывается двумя сравнениями через &&. && и || вычисляются сокращённо: если левый операнд && ложен, правый не вычисляется — результат уже известен. В разделе 4 на этом строятся защитные условия вида «делитель не ноль и частное больше 5».

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

УровеньОперации
1 (раньше всех)(), a++ a--, static_cast
2++a --a, !, ~, унарный -, sizeof
3* / %
4+ -
5<< >> — сдвиги и вывод cout
6< <= > >=
7== !=
8, 9, 10&, затем ^, затем |
11&&
12||
13 (позже всех)= += -= *= /= %= &= |= ^= <<= >>=

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

Три ловушки приоритета.
1. 0 < x < 10 — это (0 < x) < 10: сначала получается 0 или 1, и это всегда меньше 10. При x = 50 результат — истина. g++ предупреждает: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]. Правильно — 0 < x && x < 10.
2. std::cout << adult || passed; — << выполняется раньше ||: выводится только adult, а passed попадает в логическую операцию с потоком, результат которой отбрасывается. Эта строка собирается без единого предупреждения. Логическое выражение в выводе — всегда в скобках.
3. = — присваивание, == — сравнение. Путаница между ними — самая частая ошибка раздела 4.
11

Побитовые операции и флаги

Побитовые операции работают с отдельными битами целого числа. Каждый бит результата вычисляется из битов операндов в той же позиции.

ОперацияБит результата равен 1, когдаПример (4 бита)
& — Иоба бита 10101 & 0011 = 0001 (5 & 3 = 1)
| — ИЛИхотя бы один 10101 | 0011 = 0111 (5 | 3 = 7)
^ — исключающее ИЛИбиты разные0101 ^ 0011 = 0110 (5 ^ 3 = 6)
~ — НЕбит был 0~0101 = 1010
<< n — сдвиг влевобиты сдвигаются на n позиций к старшим; = умножение на 2n1 << 4 = 16
>> n — сдвиг вправок младшим; для неотрицательных = деление на 2n200 >> 3 = 25

Главное применение в системном программировании — флаги: несколько независимых признаков «да/нет» в одном целом числе, по биту на признак. Так устроены почти все параметры Windows API: права доступа к файлу, цвет символа в консоли, способ создания процесса. Флаг — константа, в которой установлен ровно один бит; набор флагов собирается операцией |.

#include <iostream>
#include <bitset>

const unsigned int FLAG_READ    = 1u << 0;     // 0001
const unsigned int FLAG_WRITE   = 1u << 1;     // 0010
const unsigned int FLAG_EXECUTE = 1u << 2;     // 0100
const unsigned int FLAG_HIDDEN  = 1u << 3;     // 1000

int main()
{
    unsigned int flags = FLAG_READ | FLAG_WRITE;
    std::cout << std::bitset<4>(flags) << '\n';

    flags |= FLAG_HIDDEN;       // установить флаг
    flags &= ~FLAG_WRITE;       // снять флаг
    flags ^= FLAG_EXECUTE;      // переключить флаг
    std::cout << std::bitset<4>(flags) << '\n';

    std::cout << std::boolalpha;
    std::cout << ((flags & FLAG_WRITE) != 0) << ' '
              << ((flags & FLAG_HIDDEN) != 0) << '\n';

    std::cout << (5 & 3) << ' ' << (5 | 3) << ' ' << (5 ^ 3) << ' '
              << (1 << 4) << ' ' << (200 >> 3) << '\n';
    return 0;
}
0011
1101
false true
1 7 6 16 25

std::bitset<4>(x) из заголовка <bitset> выводит 4 младших бита числа — удобно для проверки. Четыре приёма работы с флагами:

ДействиеЗаписьПочему работает
установитьflags |= F;ИЛИ с 1 даёт 1, с 0 — не меняет бит
снятьflags &= ~F;~F — все биты 1, кроме нужного; И с 0 даёт 0
переключитьflags ^= F;исключающее ИЛИ с 1 меняет бит на противоположный
проверить(flags & F) != 0остаётся только нужный бит; не ноль — флаг установлен

Та же запись в Windows API — красный яркий символ в консоли (раздел 15) и доступ к файлу на чтение и запись (раздел 17):

FOREGROUND_RED | FOREGROUND_INTENSITY
GENERIC_READ | GENERIC_WRITE
Проверка флага без скобок flags & FLAG_WRITE != 0 читается компилятором как flags & (FLAG_WRITE != 0): != по приоритету выше &. g++ предупреждает: suggest parentheses around comparison in operand of '&' [-Wparentheses]. Не путайте побитовые & | с логическими && ||: 1 & 2 равно 0, а 1 && 2 — истина. Флаги храните в беззнаковых типах: сдвиг влево отрицательного знакового числа в C++17 — неопределённое поведение, а с беззнаковыми сдвиги и ~ работают одинаково на любом компиляторе.

Ключевые выводы раздела

Запомните главное

  • Тип определяет размер, диапазон и операции; размер узнают через sizeof. В Windows long — 4 байта, большие целые — long long
  • Переменную инициализируют при объявлении; локальная переменная живёт до конца своего блока
  • Беззнаковые при переполнении идут по кругу, знаковые — неопределённое поведение; тип выбирают по наибольшему возможному значению
  • double хранит 15–16 значащих цифр с округлением: 0.1 + 0.2 == 0.3 ложно, деньги — целыми копейками
  • Деление двух целых отбрасывает дробную часть; для дробного результата приводят операнд: static_cast<double>(a) / b
  • Вместо магических чисел — const и constexpr; #define — только условная компиляция; перечисления — enum class
  • Арифметика выполняется раньше сравнения, сравнение раньше логики; 0 < x < 10 — ошибка, логическое выражение в cout — в скобках
  • Флаги: установить |=, снять &= ~, переключить ^=, проверить (flags & F) != 0
Связи раздела. Опирается на раздел 2: литералы и их запись, сборка с -Wall -Wextra и чтение предупреждений, макросы #define, переменные анкеты. Нужен для: раздела 4 — логические выражения в условиях и проверка делителя; разделов 5–6 — счётчики, ++, составное присваивание, разряды числа через / и %; раздела 8 — constexpr для размера массива; разделов 14–19 — типы Windows API с точным размером, флаги процессов, консоли, каналов и памяти.
Тест по разделу — на учебном портале. Тест, попытки и оценки — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Раздел 2: Язык C++ Тест на портале