Инструкции и выражения
В прошлом уроке ты уже написал рабочую программу с std::cout, #include и return 0; — но не называл вещи, из которых она состоит, своими именами. Этот урок вводит два термина, которые дальше будут использоваться в каждой теме курса без дополнительных объяснений: инструкция (statement) и выражение (expression).
01Что ты изучишь
- Чем инструкция отличается от выражения
- Что такое блок кода и зачем нужны фигурные скобки
{ } - Как выражение можно вычислить прямо внутри другой инструкции, без промежуточной переменной
- Какие ошибки чаще всего возникают из-за путаницы между инструкцией и выражением
02Почему это важно
Дальше в курсе появятся условия (if), циклы (for, while) и функции — и в объяснении каждой из этих тем будут использоваться слова «инструкция», «выражение» и «блок» без повторного определения. Если сейчас не закрепить разницу между ними, придётся либо возвращаться назад за определением, либо додумывать его по контексту — и не всегда правильно. Это короткий урок именно потому, что он вводит словарь, а не новую механику: ты уже писал и инструкции, и выражения в прошлом уроке, просто ещё не называл их так.
03Инструкция и выражение
Код внутри main() состоит из инструкций (statements) — законченных действий. Почти каждая инструкция заканчивается точкой с запятой ;.
int main()
{
int x = 10;
x = x + 5;
return 0;
}
Внутри инструкций часто встречаются выражения (expressions) — конструкции, которые вычисляются и дают значение (а иногда попутно ещё и производят побочный эффект — например, меняют переменную). В строке x = x + 5; есть оба понятия сразу: x + 5 — это выражение, оно вычисляется в значение 15. Вся же строка x = x + 5; целиком — это инструкция присваивания: она берёт значение выражения x + 5 и сохраняет его в переменную x.
Инструкция и выражение — это не взаимоисключающие категории, а два разных уровня одного и того же кода: выражение вычисляется в значение, инструкция — то, из чего строится последовательность выполнения программы. Сам по себе кусок x + 5 — выражение, а не инструкция: у него нет ;, и он не является самостоятельным шагом программы. Но если приписать к нему ;, получится x + 5; — и это уже отдельная инструкция, построенная из выражения (её называют expression statement, «инструкция-выражение»); именно этот случай разобран в разделе «Типичные ошибки» ниже — компилятор такую строку примет, но вычисленное значение будет никуда не сохранено, потеряно. Другие инструкции устроены иначе и expression statement не являются: int x = 10; — инструкция объявления, return 0; — инструкция возврата, { ... } — составная инструкция (блок, см. ниже). Пока не нужно запоминать все виды — важно не путать «выражение» (вычисляет значение) и «инструкцию» (организует выполнение программы) и знать, что первое становится вторым, если добавить ;.
При этом выражение — не всегда «просто значение без последствий»: x++ тоже выражение, но при вычислении ещё и меняет x; вызов функции вроде foo() — тоже выражение, и он может делать что угодно внутри себя, в том числе выводить текст (как в следующем абзаце). Подробности не нужны прямо сейчас, но полезно сразу не запоминать «выражение ничего не делает» как абсолютное правило.
Из прошлого урока: std::cout << "Hello, C++!\n"; — тоже инструкция (expression statement, построенная из выражения std::cout << "Hello, C++!\n"), а "Hello, C++!\n" внутри неё — строковый литерал, тоже разновидность выражения (у него есть значение — сама строка).
04Блоки кода
Фигурные скобки { } группируют несколько инструкций в один блок (составную инструкцию) — тело функции main() целиком является таким блоком. Всё, что находится внутри { }, выполняется по порядку, сверху вниз, как единое целое.
{
int y = 1;
y = y + 1;
std::cout << y;
}
Это разделение — инструкция / выражение / блок — будет всплывать в каждом следующем уроке: у if есть блок, который выполняется, если условие истинно; у цикла есть блок, который повторяется; у функции есть блок, который выполняется при её вызове. Поэтому полезно закрепить его сейчас, на самом простом примере, а не разбираться параллельно с новой темой позже.
05Выражение можно вычислить прямо на месте
Необязательно сначала сохранять результат выражения в переменную, а потом отдельно его выводить — выражение можно вычислить прямо внутри вызова std::cout:
int x = 10; // инструкция объявления — x существует и равен 10
x = x + 5; // инструкция присваивания — внутри неё выражение x + 5
std::cout << x; // выводит уже готовое значение x (15)
std::cout << (x + 100); // выражение x + 100 вычисляется прямо здесь — выведет 115, x при этом не меняется
В последней строке ничего не сохраняется в переменную — x + 100 вычисляется, полученное значение сразу передаётся в std::cout, а сам x остаётся равным 15. Скобки вокруг x + 100 здесь не обязательны, но делают код нагляднее — явно видно, что именно является одним выражением.
06Инструкция или выражение — таблица
| Код | Что это | Пояснение |
|---|---|---|
x + 5 | выражение | вычисляется, даёт значение 15; без ; — не является инструкцией, само по себе не сохраняется и не выводится |
x + 5; | инструкция (expression statement) | то же выражение x + 5, но с ; — синтаксически полноценная инструкция; значение вычисляется и тут же отбрасывается (см. «Типичные ошибки») |
x++ | выражение с побочным эффектом | вычисляет значение и изменяет x — пример того, что выражение не всегда «просто значение без последствий» |
x = x + 5; | инструкция (присваивания) | берёт значение выражения x + 5 и сохраняет его в x |
int x = 10; | инструкция (объявления) | создаёт переменную x и сразу инициализирует её значением 10 |
std::cout << x; | инструкция (expression statement) | построена из выражения std::cout << x, где x — вложенное выражение, вычисляющееся в своё текущее значение |
{ ... } | блок (составная инструкция) | группирует несколько инструкций в одну единицу |
07Хуже → лучше: промежуточная переменная ради одного использования
Задача — вывести x + 100. Три способа решить её, от лишнего кода до идиоматичного.
int x = 10;
x = x + 5;
int resultValue = x + 100;
std::cout << resultValue;
Работает правильно, но resultValue объявлена, использована ровно один раз и больше никогда не встречается — лишняя инструкция и лишнее имя, которое нужно придумать и прочитать, не добавляя понимания.
int x = 10;
x = x + 5;
std::cout << (x + 100);
Ровно то же поведение, без промежуточного шага — именно этому учит раздел 05 выше и это же проверяет практика ниже (задание 2). Правило не абсолютное: если бы выражение было длинным и малопонятным без контекста, переменная с хорошим именем могла бы, наоборот, сделать код яснее — но x + 100 достаточно короткое и самоочевидное, чтобы инлайн был лучшим выбором.
int x = 10;
x = x + 5;
std::cout << "x = " << x << ", x + 100 = " << (x + 100) << "\n";
Здесь применена цепочка << из прошлого урока вместе с инлайн-вычислением из этого — один читаемый вывод вместо двух разрозненных инструкций std::cout. Не всегда нужно доводить код именно до такого вида — но полезно видеть, что оба приёма сочетаются.
08Попробуй изменить
Прежде чем переходить к практике ниже, поэкспериментируй с примером из раздела 05 прямо в редакторе кода на этой странице:
- Замени
std::cout << (x + 100);наstd::cout << (x * 2);— результат станет 30, а не 115. - Добавь ещё одну инструкцию
x = x + 1;между присваиванием и выводом — итоговое значениеxизменится на единицу. - Попробуй убрать
;в конце любой инструкции — компилятор откажется собирать программу (см. типичные ошибки ниже).
09Живая отладка: типичные ошибки
Точный текст ошибки зависит от компилятора и его версии — ниже характерный вид в стиле g++.
; в конце инструкцииint x = 10;
x = x + 5 // забыт ; в конце инструкции
return 0;
Ошибка привязана к строке 5, хотя реальная причина — конец этой же строки: компилятор понимает, что чего-то не хватает, только дойдя до return на следующей строке.
x + 5; // вычислено и тут же отброшено — результат нигде не сохранён
Это синтаксически корректная инструкция-выражение (expression statement, см. раздел 03) — компилятор примет её без единой ошибки или предупреждения в консоли, вычислит x + 5 и отбросит результат. Опаснее синтаксической ошибки именно потому, что компилятор молчит, а результат работы программы просто не будет тем, что ожидалось.
int x = 10;
int x = 20; // не присваивание, а повторное объявление — ошибка
Компилятор обычно ещё и указывает («note»), где было первое объявление. Чтобы изменить значение существующей переменной, используется присваивание без int: x = 20;.
10Проверь себя
Три вопроса на разное: классификация («что это»), предсказание результата и типичная ловушка. Сначала попробуй ответить сам — ответ никуда не убежит.
1)
y + 12)
y = y + 1;3)
{ int y = 1; y = y + 1; }4)
std::cout << y;Показать объяснение
1) y + 1 — выражение: вычисляется в значение, но без ; не является отдельным шагом программы. 2) y = y + 1; — инструкция присваивания: берёт значение выражения y + 1 и сохраняет в y. 3) { ... } — блок (составная инструкция): группирует две инструкции внутри { }. 4) std::cout << y; — инструкция (expression statement), построенная из выражения std::cout << y. Частые ошибки: считать y + 1 без ; уже инструкцией (вариант a), или наоборот — считать, что раз выражение лежит внутри, то и y = y + 1;/std::cout << y; остаются «просто выражениями» (вариант b).
int a = 5;
a = a + 2;
std::cout << (a * 2) << "\n";
std::cout << a;Показать объяснение
Сначала 14, потом 7. После a = a + 2; переменная a равна 7. Выражение a * 2 в первой строке вывода вычисляется «на лету» и никуда не сохраняется — сам a от этого не меняется, поэтому вторая строка выводит по-прежнему 7, а не удвоенное значение.
int b = 3;
b + 10;
std::cout << b;Показать объяснение
Выведет 3. Строка b + 10; — синтаксически корректная инструкция-выражение (см. §03 и §09): значение 13 вычисляется и тут же отбрасывается, b при этом не меняется — присваивания здесь нет. Чтобы получить 13, нужно либо b = b + 10; (изменить b), либо std::cout << (b + 10); (вывести выражение напрямую).
11Практика
★ Добавь ещё одну инструкцию, изменяющую x (например, x = x * 2;), и выведи итоговое значение x через std::cout.
Раздел 05 показывает, что x можно менять несколько раз подряд — какая ещё операция дала бы вторую инструкцию присваивания (сложение, умножение и т. п.)?
Решение: x = x * 2; — вторая инструкция присваивания после x = x + 5;, затем std::cout << x; выводит итоговое значение.
★★ Выведи через std::cout выражение x + 100, не сохраняя его предварительно в отдельную переменную.
Раздел 05 показывает, что выражение можно вычислить прямо внутри std::cout, без отдельной переменной — попробуй подставить x + 100 туда же, куда обычно подставляешь x.
Решение: std::cout << (x + 100); — выражение вычисляется прямо внутри вызова cout, промежуточная переменная не нужна.
★★★ Напиши короткую программу с нуля: заведи переменную с любым именем и начальным значением, измени её значение минимум двумя разными инструкциями, а затем выведи через std::cout выражение с этой переменной, вычисленное прямо на месте — без сохранения в отдельную переменную. Конкретное имя, тип, числа и порядок действий выбирай сам; используй только то, что разобрано в этом и прошлом уроке.
Начни с объявления переменной с любым именем и начальным значением — что-то вроде int name = число;.
Дальше измени её значение минимум два раза — подойдёт любая форма из этого урока: name = name + число;, повторённая дважды с разными операциями (сложение, умножение и т. п.).
В конце не сохраняй финальное выражение в отдельную переменную — вычисли его прямо внутри std::cout, как в разделе 05.
Пример решения (один из многих возможных): int score = 5; score = score + 3; score = score * 2; std::cout << (score - 1); — подойдёт и любой другой набор из переменной, двух изменений и одного инлайн-выражения.
12Что ты теперь умеешь
До этого урока ты уже писал и инструкции, и выражения — просто не называл их так и не различал сознательно. Теперь у тебя есть словарь и понимание, что стоит за этими словами:
- Ты можешь отличить выражение от инструкции в любом фрагменте кода и объяснить, по какому признаку ты это определил.
- Ты можешь объяснить своими словами разницу между «вычисляет значение» (выражение) и «выполняет шаг программы» (инструкция) — и то, что первое становится вторым при добавлении
;. - Ты можешь найти выражение внутри более крупной конструкции — например, увидеть
x + 100внутриstd::cout << (x + 100);илиy + 1внутриy = y + 1;. - Ты можешь разобрать простую C++-программу построчно на инструкции, выражения и блоки — не как абстрактное упражнение, а чтобы понимать, что реально происходит при выполнении.
- Ты можешь вычислить выражение прямо внутри инструкции (например, внутри
std::cout), не заводя для этого промежуточную переменную — и понимаешь, когда это уместно, а когда лучше сохранить в переменную с именем.
13Кратко
- Инструкция (statement) — синтаксическая единица, из которой строится выполнение программы; почти всегда заканчивается
;. - Выражение (expression) — код, который вычисляется и даёт значение; может иметь и побочный эффект (например,
x++меняетx). - Выражение с
;на конце (x + 5;) само становится инструкцией — expression statement, «инструкцией-выражением». Это не исключение из правила, а один из видов инструкций наравне с инструкцией объявления, присваивания, возврата и т. д. - Блок
{ }группирует несколько инструкций в одну составную инструкцию. - Выражение можно вычислить прямо внутри другой инструкции (например, внутри
std::cout << ...), без промежуточной переменной. - Expression statement без сохранения результата (
x + 5;само по себе) синтаксически корректен, но бессмысленен — вычисленное значение нигде не остаётся.
Дальше эти два слова — «инструкция» и «выражение» — будут использоваться без повторных объяснений. Следующий урок переходит от терминологии к самому мышлению программиста: как раскладывать желание в план действий ещё до того, как начал писать код.
Разделение на инструкции, выражения и блоки — часть самого языка C++, а не что-то специфичное для консольных программ. Внутри любой Unreal-функции — BeginPlay(), Tick(), обработчика делегата — код устроен ровно так же: тело функции — это блок, состоящий из инструкций, а инструкции содержат выражения. Никаких дополнительных правил Unreal здесь не добавляет.