Главная / Курсы / C++ по спирали / Майлстоун. Вы познакомились с основами C++
# Майлстоун. Вы познакомились с основами C++ ## Для чего нужны майлстоуны Майлстоун — это: - Промежуточный экзамен для проверки, насколько хорошо вы поняли теорию. - Практика по чтению чужого кода. Очень пригодится на работе. - Промежуточный итог прохождения курса. Итак, поехали. В подсказке к каждой задаче вы найдёте не только полезные наводки, но и ссылки на пройденные темы, которые нужно знать для решения. ## Разогрев Как называется пространство имён стандартной библиотеки C++? Напишите его вместе с оператором разрешения области видимости. {.task_text} ```consoleoutput {.task_source #cpp_chapter_0057_task_0010} ``` Оператор разрешения области видимости: `::`. **Темы, затронутые в задаче:** [пространства имён](/courses/cpp/chapters/cpp_chapter_0053/). {.task_hint} ```cpp {.task_answer} std:: ``` Что выведет этот код? {.task_text} В случае ошибки компиляции напишите `err`. {.task_text} В случае необработанного исключения напишите `ex`. {.task_text} ```cpp {.example_for_playground} import std; class A { public: A() { std::print("a"); throw std::runtime_error("e"); } void f() { std::print("f"); } }; int main() { std::print("1"); try { A a; a.f(); } catch(const std::exception & e) { std::print("x"); } std::print("2"); } ``` ```consoleoutput {.task_source #cpp_chapter_0057_task_0020} ``` Выполнение кода, бросившего исключение, прерывается. Управление передаётся обработчику исключения. **Темы, затронутые в задаче:** [исключения,](/courses/cpp/chapters/cpp_chapter_0052/) [классы](/courses/cpp/chapters/cpp_chapter_0055/). {.task_hint} ```cpp {.task_answer} 1ax2 ``` ## Кто соскучился по побитовым операторам? Что выведет этот код? {.task_text} В случае ошибки компиляции напишите `err`. {.task_text} ```cpp {.example_for_playground} import std; enum Notification { Email = 1, // 0b0001 Sms = 2, // 0b0010 Push = 4, // 0b0100 Messenger = 8, // 0b1000 }; int main() { const Notification user_settings = static_cast<Notification>(Push | Sms); const bool send_email = user_settings & Email; const bool send_sms = user_settings & Sms; std::println("{} {}", send_email, send_sms); } ``` ```consoleoutput {.task_source #cpp_chapter_0057_task_0030} ``` Обратите внимание, что значения констант перечисления `Notification` — это степени двойки. При объединении двух значений `Notification` побитовым «ИЛИ» вы получите их комбинацию. Применение к такому числу побитового «И» со значением `Notification` нужно, чтобы проверить, установлено ли интересующее свойство. **Темы, затронутые в задаче:** [перечисления](/courses/cpp/chapters/cpp_chapter_0054/#block-enum) `enum`; [двоичное представление чисел и побитовые операторы](/courses/cpp/chapters/cpp_chapter_0023/) `&` и `|`; [явное приведение типов](/courses/cpp/chapters/cpp_chapter_0011/#block-static-cast) через `static_cast`, [неявное приведение типов.](/courses/cpp/chapters/cpp_chapter_0011/#block-implicit-cast). {.task_hint} ```cpp {.task_answer} false true ``` В этой задаче применяется трюк для упаковки нескольких флагов в одну переменную. Причём в нашем случае у переменной тип — перечисление, а его значения равняются степеням двойки: `0b0001`, `0b0010`, `0b0100`, `0b1000`. Значит, их можно объединять побитовым «ИЛИ», чтобы устанавливать несколько флагов: `Push | Sms`. А чтобы проверить, установлен ли конкретный флаг, к значению нужно применить побитовое «И» с константой из перечисления: `user_settings & Sms`. Упаковка множества флагов в одно число практикуется и для хранения данных в БД. Чем больше записей в таблице и чем критичнее время доступа, тем больший выигрыш вы получите от этого подхода. Представьте, что у вас таблица с десятками миллионов записей. При добавлении в нее нового поля типа `bool` придется проводить миграцию. Если же вместо нескольких полей типа `bool` в таблице выделено целочисленное поле под флаги, миграция не потребуется. Вы просто начнёте использовать не занятый бит числа. ## Это какой-то неправильный калькулятор Что выведет этот код? {.task_text} В случае ошибки компиляции напишите `err`. {.task_text} В случае исключения напишите `ex`. {.task_text} ```cpp {.example_for_playground} import std; int main() { const char op = '/'; int a = 8; int b = 3; switch(op) { case '*': a *= b; case '/': a /= b; case '-': a-=b; break; case '+': a+=b; break; default: throw std::runtime_error("Wrong operator"); } std::println("{}", a); } ``` ```consoleoutput {.task_source #cpp_chapter_0057_task_0040} ``` Если в блоке `case` отсутствует `break`, то выполнение переходит к следующему `case`. **Темы, затронутые в задаче:** [конструкция](/courses/cpp/chapters/cpp_chapter_0032/) `switch-case`; [операторы составного присваивания.](/courses/cpp/chapters/cpp_chapter_0022/#block-compound-assignment) {.task_hint} ```cpp {.task_answer} -1 ``` Мы уже предупреждали, что забытый `break` — это самая популярная ошибка при использовании `switch-case`. ## Шаблоны и линейная интерполяция Линейная интерполяция (lerp, linear interpolation) используется в геймдеве, 3D-графике и других областях для плавного перехода между двумя значениями. Например, между цветами, координатами или громкостью. {.task_text} Функция `lerp()` находит промежуточное значение между точками `start` и `end` на прямой с использованием коэффициента `t`, который обычно принимает значения от 0 до 1. {.task_text} **Какой тип** у значения, возвращаемого при вызове `lerp()` внутри `main()`? {.task_text} В случае ошибки компиляции вместо типа напишите `err`. {.task_text} ```cpp {.example_for_playground} import std; template <class T, class P> T lerp(T start, T end, P t) { return start + t * (end - start); } int main() { std::println("{}", lerp(-1, 1, 0.5)); } ``` ```consoleoutput {.task_source #cpp_chapter_0057_task_0050} ``` Компилятор найдёт вызов шаблонной функции `lerp()` и для данного набора аргументов инстанцирует шаблон: создаст функцию с типами аргументов `int`, `int`, `double` и типом возвращаемого значения, совпадающим с типами двух первых аргументов. **Темы, затронутые в задаче:** [шаблоны.](/courses/cpp/chapters/cpp_chapter_0056/) {.task_hint} ```cpp {.task_answer} int ``` Шаблонный код в C++ может выглядеть запутанным. Но чем чаще вы будете практиковаться читать чужой код с шаблонами и писать собственный, тем больше вам будут нравиться шаблоны. ## Ох уж эти вопросы с собесов про инкремент и декремент! Что выведет этот код? {.task_text} В случае ошибки компиляции напишите `err`. {.task_text} ```cpp {.example_for_playground} import std; int main() { std::size_t a = 8; std::size_t b = 9; std::size_t x = ++a; std::size_t y = b++; std::println("{}", x, y); } ``` ```consoleoutput {.task_source #cpp_chapter_0057_task_0060} ``` Префиксный оператор возвращает обновлённое значение переменной, а постфиксный — старое. **Темы, затронутые в задаче:** [пре-инкремент и пост-инкремент.](/courses/cpp/chapters/cpp_chapter_0022/#block-increment) {.task_hint} ```cpp {.task_answer} 9 9 ``` Что выведет этот код? {.task_text} В случае ошибки компиляции напишите `err`. {.task_text} ```cpp {.example_for_playground} import std; int main() { int a = 4; int b = 8; int res = a > b ? a++ : ++b; std::println("{} {}", a, b); } ``` ```consoleoutput {.task_source #cpp_chapter_0057_task_0070} ``` Как и конструкция `if-else` тернарный оператор гарантирует, что вычисляется только одна из веток условия. Префиксный оператор возвращает обновлённое значение переменной, а постфиксный — старое. **Темы, затронутые в задаче:** [пре-инкремент и пост-инкремент,](/courses/cpp/chapters/cpp_chapter_0022/#block-increment) [тернарный оператор.](/courses/cpp/chapters/cpp_chapter_0031/#block-ternary-operator) {.task_hint} ```cpp {.task_answer} 4 9 ``` Что выведет этот код? {.task_text} В случае ошибки компиляции напишите `err`. {.task_text} ```cpp {.example_for_playground} import std; int main() { std::size_t a = 0; std::size_t b = 0; if (a++ && ++b) std::println("1 {} {}", a, b); else std::println("2 {} {}", a, b); } ``` ```consoleoutput {.task_source #cpp_chapter_0057_task_0080} ``` В C++ логические операторы выполняются по короткой схеме. **Темы, затронутые в задаче:** [пре-инкремент и пост-инкремент,](/courses/cpp/chapters/cpp_chapter_0022/#block-increment) вычисление логических операторов [по короткой схеме.](/courses/cpp/chapters/cpp_chapter_0022/#block-short-circuit) {.task_hint} ```cpp {.task_answer} 2 1 0 ``` Чтобы легко отвечать на вопросы про операторы `++` и `--`, нужно помнить три вещи: - Приоритет постфиксных форм инкремента и декремента выше, чем префиксных. - Префиксный оператор возвращает обновлённое значение переменной, а постфиксный — старое. - Префиксные формы возвращают саму переменную, а постфиксные — её неизменяемую копию. Если эти операторы участвуют в логическом выражении, то помните, что в C++ реализована [стратегия вычисления по короткой схеме](/courses/cpp/chapters/cpp_chapter_0022/#block-short-circuit) (short-circuit evaluation). ---------- ## Что дальше Если вы легко преодолели майлстоун — это отличная причина ~~похвалить себя~~ пожаловаться нам, что задачи слишком лёгкие. Если же с какими-то задачами справиться не получилось, не сдавайтесь. Просто перечитайте перечисленные в подсказках разделы глав и попробуйте снова.
Отправка...
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!