Управление потоком выполнения
Концепты
Заголовок раздела «Концепты»ifelseelse ifwhile (true)continuebreakdo ... while
Примеры на понимание
Заголовок раздела «Примеры на понимание»1. true в условии
Заголовок раздела «1. true в условии»if (true){ std::cout << "Hello" << std::endl;}Ответ
true в условии заставляет тело if-а выполняться всегда.
2. false в условии
Заголовок раздела «2. false в условии»if (false){ std::cout << "Hello" << std::endl;}Ответ
Этот код гарантировано никогда не выполнится.
3. bool переменная в условии
Заголовок раздела «3. bool переменная в условии»bool execute = true;if (execute){ std::cout << "Hello" << std::endl;}
bool notExecute = !execute;if (notExecute){ std::cout << "Not executed" << std::endl;}Ответ
При запуске, выведется только Hello.
4. Числа в условии
Заголовок раздела «4. Числа в условии»if (1){ std::cout << "1" << std::endl;}if (123){ std::cout << "123" << std::endl;}if (0){ std::cout << "0" << std::endl;}Ответ
В C++ if принимает любое выражение, приводимое к bool.
Данный код скомпилируется и выведет 1 и 123.
5. Указатель в условии
Заголовок раздела «5. Указатель в условии»int* a { nullptr };if (a){ std::cout << "a is true" << std::endl;}
int b {};int* pb { &b };if (pb){ std::cout << "pb is true" << std::endl;}if (b){ std::cout << "b is true" << std::endl;}
bool c { false };bool* pc { &c };if (pc){ std::cout << "pc is true" << std::endl;}Ответ
if, когда ему передают указатель, проверяет, если указатель не является нулевым (nullptr).
a is true тут не выведется, а b is true и pc is true выведется.
if (b) -> if (0) -> if (false).
b is true не выведется.
6. Несколько инструкции в теле
Заголовок раздела «6. Несколько инструкции в теле»if (false){ std::cout << "A" << std::endl; std::cout << "B" << std::endl;}Ответ
Ничего не напечатается.
7. Без фигурных скобок
Заголовок раздела «7. Без фигурных скобок»if (false) std::cout << "A" << std::endl; std::cout << "B" << std::endl;Ответ
Напечатается только B, потому что лишь первая инструкция прикреплена к if.
Эквивалентный код:
if (false){ std::cout << "A" << std::endl;}std::cout << "B" << std::endl;8. else
Заголовок раздела «8. else»if (false){ std::cout << "A" << std::endl;}else{ std::cout << "B" << std::endl;}Ответ
Данный код выведет B, поскольку блок else выполняется тогда, когда не выполняется условие.
9. Перезапись переменной из условия в теле if
Заголовок раздела «9. Перезапись переменной из условия в теле if»bool a = true;if (a){ a = false;}else{ std::cout << "B" << std::endl;}Ответ
B не напечатается, потому что то, выполнится ли else, определяется на момент проверки a в if,
а это происходит до его изменения.
10. return в if
Заголовок раздела «10. return в if»void F(){ if (true) { return; } else { std::cout << "Else" << std::endl; } std::cout << "After Else" << std::endl;}Ответ
Здесь Else не напечатается.
То, выполнится ли блок else, зависит лишь от условия в if.
return полностью пропускает все последующие интсрукции.
After Else тоже не напечатается.
11. Код после else
Заголовок раздела «11. Код после else»if (true) std::cout << "A" << std::endl;else std::cout << "B" << std::endl;std::cout << "C" << std::endl;Ответ
B не напечатается. Напечатаются A и C.
12. if без фигурных скобок, else — с
Заголовок раздела «12. if без фигурных скобок, else — с»if (true) std::cout << "A" << std::endl;else{ std::cout << "B" << std::endl;}Ответ
Допустимо комбинировать прилепление инструкции и явный блок.
13. Цепочка if-else
Заголовок раздела «13. Цепочка if-else»Как обычно записывают данный код, используя цепочку if-else?
if (a){ std::cout << "A" << std::endl;}else{ if (b) { std::cout << "B" << std::endl; } else { if (c) { std::cout << "C" << std::endl; } }}Ответ
if обычно цепляют как инструкцию, сопровождающую else:
if (a){ std::cout << "A" << std::endl;}else if (b){ std::cout << "B" << std::endl;}else if (c){ std::cout << "C" << std::endl;}14. Early return
Заголовок раздела «14. Early return»Попытайтесь представить данный код как цепочку if-else, ему идентичную.
Как сделать этот код через early return / guard clause?
if (a){ std::cout << "A" << std::endl;}else{ std::cout << "After A" << std::endl;
if (b) { std::cout << "B" << std::endl; } else { std::cout << "After B" << std::endl;
if (c) { std::cout << "C" << std::endl; } else { std::cout << "After C" << std::endl; } }}Ответ (цепочка)
Этот код невозможно представить как цепочку.
Некуда поставить After B и After C так, чтобы они выполнялись по тем же правилам.
Можно попробовать их продублировать, но тогда они не будут семантически эквивалентны:
if (a){ std::cout << "A" << std::endl;}else if (b){ std::cout << "After A" << std::endl; std::cout << "B" << std::endl;}else if (c){ std::cout << "After A" << std::endl; std::cout << "After B" << std::endl; std::cout << "C" << std::endl;}else{ std::cout << "After A" << std::endl; std::cout << "After B" << std::endl; std::cout << "After C" << std::endl;}Ответ (guard clause / early return)
- Создается функция для этого кусочка кода;
- Внутри каждого
ifпрописываетсяreturn; elseи блоки пропадают.
#include <iostream>
void f(bool a, bool b, bool c){ if (a) { std::cout << "A" << std::endl; return; } std::cout << "After A" << std::endl;
if (b) { std::cout << "B" << std::endl; return; } std::cout << "After B" << std::endl;
if (c) { std::cout << "C" << std::endl; return; } std::cout << "After C" << std::endl;}
int main(){ // предполагается, что a, b, c были созданы ранее ... f(a, b, c);}Зачем этот guard clause / early return?
- Чтобы поднять обработку ошибок вверх функции, а основную логику опустить вниз. Это делает очевидным тот факт, что логика зависит от корректности данных, которая проверялась на момент обработки ошибок (контракт).
- Убирает лишнюю вложенность условий;
- Способствует локальности кода проверки ошибки и ее обработки.
Пример кода без применения guard clause / early return:
void sendWelcomeEmail(User* user){ // Условия перечислены с увеличением вложенности. if (user != nullptr) { if (user->IsActive) { if (user->EmailConfirmed) { // Код с самим действием спрятан в середине функции. std::cout << "Sending email to " << user->email << std::endl; } else { std::cout << "Email not confirmed." << std::endl; } } else { std::cout << "User is not active." << std::endl; } } // Не соблюдается локальность: // обработка удалена в исходном коде от связанной проверки. else { std::cout << "User not found." << std::endl; }}Тот же код, с его применением:
void sendWelcomeEmail(User* user){ // Можно блоком разграничить контракт // (необходимые условия для выполнения основного действия), // или вынести его в свою функцию. { // Соблюдена локальность: условия рядом с их обработкой. if (user == nullptr) { std::cout << "User not found." << std::endl; return; }
if (!user->isActive) { std::cout << "User is not active." << std::endl; return; }
if (!user->emailConfirmed) { std::cout << "Email not confirmed." << std::endl; return; } }
// Основной код находится после всех проверок, а не в середине. std::cout << "Sending email to " << user->email << std::endl;}15. Сложный пример
Заголовок раздела «15. Сложный пример»int i = 0;while (true){ if (i == 4) { std::cout << "ERROR: Should not happen" << std::endl; break; } if (i == 3) { std::cout << "Exit" << std::endl; break; } if (i == 0) { std::cout << "Increase by 2 on first iter" << std::endl; i += 2; continue; }
std::cout << "Increase by 1 normally" << std::endl; i++;}Что делают break и continue
break прекращает выполнение цикла (переходит на первую инструкцию после цикла).
continue переходит в начало цикла (дальнейшие инструкции из тела цикла не выполняются для этой итерации).
Ответ
“Increase by 2 on first iter” напечатается в первой итерации цикла, i++ не выполнится из-за continue.
“Increase by 1 normally” напечатается во второй итерации цикла,
после прохождения с неудачей всех проверок if-ов.
“Exit” напечатается в третьей итерации, тогда как проверка i == 0 и инструкция i++ не выполнится,
поскольку break прервет выполнение цикла.
Increase by 2 on first iterIncrease by 1 normallyExit16. return изнутри while
Заголовок раздела «16. return изнутри while»int F(){ while (true) { if (true) { return 0; } break; } return 1;}Ответ
Функция вернет 0. break и return 1 не выполнятся.
17. Пример использования if
Заголовок раздела «17. Пример использования if»#include <iostream>#include <cassert>
struct Result{ bool isDenominatorZero; // Should only be read if there is no error. int result;};
Result ceilingDivide(int numerator, int denominator){ if (denominator == 0) { return { .isDenominatorZero = true, .result = 0, }; }
int value = (numerator + denominator - 1) / denominator; return { .isDenominatorZero = false, .result = value, };}
int main(){ { int totalStudents { 25 }; int studentsPerTable { 2 }; Result result { ceilingDivide(totalStudents, studentsPerTable) }; if (!result.isDenominatorZero) { int numTablesNeeded = result.result; std::cout << "Number of tables needed is " << numTablesNeeded << std::endl; } else { std::cout << "Can't seat any students if no students are seated per table" << std::endl; assert(studentsPerTable == 0); } } { Result result { ceilingDivide(30, 0) }; if (result.isDenominatorZero) { std::cout << "Could not divide" << std::endl; } else { std::cout << "The result is " << result.result << std::endl; } }}Рефакторинг
Заголовок раздела «Рефакторинг»Зарефакторьте этот код, используя guard clause / early return.
#include <iostream>
int main(){ int number = 10;
if (number == 5) { std::cout << "The number is 5" << std::endl; } else { std::cout << "The number is not 5" << std::endl;
if (number % 2 == 1) { std::cout << "The number is not divisible by 2" << std::endl; } else { std::cout << "The number is divisible by 2" << std::endl;
if (number == 6) { std::cout << "The number is 6" << std::endl; } } }}Сложное логическое выражение
Заголовок раздела «Сложное логическое выражение»Дан базовый код:
#include <iostream>
int main(){ int apples; int pears; int oranges;
std::cout << "Apples:"; std::cin >> apples;
std::cout << "Pears:"; std::cin >> pears;
std::cout << "Oranges:"; std::cin >> oranges;
// ...
return 0;}-
Если количество яблок больше 5, количество груш меньше 8 и количество апельсинов ровно в 2 раза больше чем количество яблок, напечатайте на экран “Hello”.
-
Сделайте временную переменную с результатом выражения. Проверяйте ее в if.
-
Сделайте по временной переменной для каждого из подусловий. Вычисляйте общее условие, используя эти подусловия.
-
Объявите временные переменные подусловий в блоке, чтобы эти переменные не были видны за пределами блока. Используйте внешнее условие вне блока (блок существует для инициализации общего условия).
Разрешается также из этого блока сделать функцию.
-
Зарефакторьте 3 переменные с количеством фруктов в одну структуру (
FruitCounts) с 3 полями, по одному для каждого типа фруктов. Переделайте код, чтобы везде использовалась эта структура.