C++. Средний уровень
Дальше — не о синтаксисе, а об аккуратном пользовании стандартной библиотекой. Здесь спрашивают про итераторы и их валидность, компараторы в sort, умные указатели, исключения и RAII, шаблоны и перегрузки, базовые отношения между классами.
Вопрос 1. Выберите правильный формат для объявления динамического массива A из float, размерностью n.
Нужен динамический массив — значит, указатель на первый элемент и new [].
— (1) указатель на указатель — лишний уровень.
— (2) синтаксис неверный, похоже на placement new, но записано неправильно.
— (3) слева float, а справа — адрес из new → несовместимо.
— (4) это автоматический массив (и VLA в стандарте C++ нет) — не динамическая аллокация.
— (5) корректный указатель на float с выделением n элементов. Не забыть delete[] A; после использования.
Выбранный ответ: №5 — float *A = new float[n]
Вопрос 2. Как правильно прочитать строку с пробелами?
5. std::getline(std::cin, line)
Нужно считать всю строку, включая пробелы.
— Оператор >> (вариант 2) читает только до первого пробела — потеряем остаток.
— std::cin.read (1) работает с сырым буфером (char* и количеством байт), не со std::string.
— Варианты с std::cout (3, 4) — это вывод, а не ввод.
— std::getline (5) читает всю строку до '\n', как раз то, что нужно. На практике помню про нюанс: если до этого читали числа через >>, нужно сначала съесть оставшийся '\n'.
Выбранныйответ: std::getline(std::cin, line)
Вопрос 3. Для хранения номеров заказов клиентов нужна структура данных, которая позволяет хранить уникальные элементы и быстро проверять их наличие. Какой контейнер выбрать?
Нужны уникальные значения и быстрый поиск/вставка.
На что обращаю внимание:
— std::unordered_set — хеш-таблица: автоматом обеспечивает уникальность, среднее O(1) на insert/find.
— vector, list, deque требуют линейного поиска O(n) для проверки наличия.
— stack — это обёртка LIFO без произвольного поиска.
Вывод: оптимально хеш-множество.
Выбранный ответ: №2 — std::unordered_set
Вопрос 4. Какой тип связи используется между Teacher и Student?
void attendClass(Teacher* teacher) {
std::cout << "Attending class by teacher " << teacher;
void teach(Student* student) {
std::cout << "Teaching student " << student;
Классы не хранят друг друга как поля, а лишь принимают указатель в параметрах методов.
На что обращаю внимание? Передача указателя на время вызова — это «знает/взаимодействует», без владения и без жизненного цикла. Для композиции/агрегации нужен член-объект; наследования здесь нет; «инкапсуляция» — не тип связи между классами.
Какой вывод? Это обычная ассоциация: объекты могут ссылаться друг на друга при взаимодействии, но не владеют.
Выбранный ответ: №2 — Ассоциация
Вопрос 5. Программист, разрабатывая класс UserManager, добавил функции, которые работают на разном уровне абстракции. Какаяфункциялишняяв UserManager?
void addUser(User user) { /* добавление */ }
void deleteUser(int id) { /* удаление */ }
void sendEmail(User user) { /* рассылка */ }
1. Хранение данных пользователя
2. Обработка данных пользователя
UserManager выполняет операции управления — добавить/удалить. Отправка писем — это другая ответственность (сервис уведомлений). По хорошему дизайну и принципу единственной ответственности, рассылка не должна жить в менеджере пользователей. Её стоит вынести в EmailService/Notifier, а UserManager максимум вызывает тот сервис, но сам письма не шлёт.
«рассылка сообщений» — лишняя функция для UserManager.
Выбранный ответ: №3 — Рассылка сообщений
Вопрос 6. Вам нужно передать объект по указателю в функцию, при этом важно обеспечить отсутствие других указателей на этот же объект. Какой тип указателей подходит для этих целей?
Требуется гарантия единственного владельца.
На что обращаю внимание:
— std::unique_ptr именно это и даёт — эксклюзивное владение, копировать нельзя, только move. Значит, других «живых» владельцев быть не может.
— std::shared_ptr допускает много владельцев — не подходит.
— std::weak_ptr не владеет объектом вообще.
— std::single_ptr и std::unit_ptr в стандартной библиотеке отсутствуют.
Нужен умный указатель с эксклюзивным владением.
Выбранный ответ: №4 — std::unique_ptr
Вопрос 7. Проанализируйте код и выберите утверждение, верное относительно возможной утечки памяти.
std::shared_ptr<Player> companion;
~Player() { std::cout << "~Player\n"; }
std::shared_ptr<Player> A = std::make_shared<Player>();
std::shared_ptr<Player> B = std::make_shared<Player>();
1. В коде есть проблема утечки памяти, и ее можно решить с помощью std::weak_ptr
2. В коде утечек памяти нет, но при использовании std::weak_ptr они появятся
3. В коде есть проблема утечки памяти, но решить ее заменой на другой умный указатель невозможно
4. В коде есть проблема утечки памяти, и ее можно решить с помощью std::unique_ptr
5. В коде утечек памяти нет и не будет при использовании любого умного указателя
Два объекта Player владеют друг другом через std::shared_ptr. Получается цикл владения: A держит B, B держит A. Счётчики ссылок никогда не станут нулём — деструкторы не вызовутся (строка ~Player\n не напечатается). Это типичный пример утечки из-за цикла shared_ptr.
На что обращаю внимание: разорвать цикл можно, если одна из ссылок не будет владеть объектом — для этого и существует std::weak_ptr. Он не увеличивает счётчик владения.
Почему не unique_ptr: взаимная ссылка unique_ptr невозможна (эксклюзивное владение); переработка модели возможна, но прямой заменой здесь проблему не решишь.
Выбранный ответ: №1 — В коде есть проблема утечки памяти, и ее можно решить с помощью std::weak_ptr
Вопрос 8. Какое выражение нужно вставить на место пропусков в коде ниже, чтобы обеспечить безопасность при исключении?
auto ptr = std::make_unique<int>(42);
throw std::runtime_error("Error");
try { process(); } catch(...) {}
2. std::lock_guard<std::mutex> и lock(mutex)
4. unique_ptr уже обеспечивает безопасность
5. try { } catch(...) { delete ptr; }
Ресурс обёрнут в std::unique_ptr. По правилу RAII его деструктор вызовется при выходе из функции, даже если происходит throw. Значит, память освободится автоматически.
На что обращаю внимание:
— erase у unique_ptr не существует.
— reset(nullptr) лишний: освобождение и так произойдёт при выходе из области видимости.
— lock_guard не к месту.
— Вариант с delete в catch неверен и не компилируется: ptr вне области видимости catch.
Вывод: ничего добавлять не нужно — уже безопасно.
Выбранный ответ: №4 — unique_ptr уже обеспечивает безопасность
Вопрос 9. Что выведет программа ниже?
В test есть статическая переменная y, она инициализируется один раз (2) и сохраняет значение между вызовами.
На что обращаю внимание: выражение cout << test(3) << test(4) вычисляется слева направо — сначала печатаем результат test(3), затем test(4), без пробелов между ними.
Считаю:
— первый вызов: y = 2 + 3 = 5, печатаем 5;
— второй вызов: y = 5 + 4 = 9, печатаем 9.
Выбранный ответ: 59
Вопрос 10. Проанализируйте код ниже. Каков будет результат выполнения кода?
1. Ошибка компиляции: неоднозначность вывода типа T
3. Ошибка компиляции: нельзя сравнивать разные типы
Шаблон требует, чтобы оба параметра имели один и тот же тип T.
На что обращаю внимание: вызываем f(5, 3.5) — первый аргумент int, второй double. При выводе шаблонного параметра компилятор пытается вывести T из каждого аргумента: получается T=int и T=double одновременно — это конфликт. Шаблон так вызвать нельзя без явного указания типа (например, f<double>(5, 3.5) тогда бы вывело 5.0).
Вывод: тип T по аргументам вывести нельзя, получаем ошибку компиляции.
Выбранный ответ: №1 — Ошибка компиляции: неоднозначность вывода типа T
Вопрос 11. Какой из вариантов вызовет перемещение (move semantics), а не копирование?
2. std::string s1 = std::move(s2)
3. std::string s1 = new String(s2)
4. std::string s1 = const_cast<const std::string&>(s2)
Чтобы сработал move-конструктор, источником должен быть rvalue (временный/«перемещаемый» объект).
— Вариант 1: s2 — lvalue → копирование.
— Вариант 2: std::move(s2) превращает s2 в rvalue → сработает перемещение.
— Вариант 3: new String(s2) возвращает указатель и ещё и тип String — не подходит.
— Вариант 4: получаем const std::string& — константная lvalue-ссылка, значит копирование.
— Вариант 5: &s2 — это указатель, типы несовместимы.
Выбранныйответ: №2 — std::string s1 = std::move(s2)
Вопрос 12. После добавления элемента в std::vector какие итераторы останутся валидными?
2. Только итераторы на добавленные элементы
4. Все итераторы станут невалидными
При добавлении элемента vector может перераспределить память (reallocation). Если это происходит, инвалидируются все итераторы и ссылки, в том числе end(). Бывают случаи без перераспределения (часть итераторов остаётся валидной), но задача спрашивает про гарантии — в общем случае нужно считать, что всё сломается.
Выбранный ответ: Все итераторы станут невалидными
Заключение
Думайте о владении ресурсом и жизненном цикле объектов. Избегайте слайсинга и циклов владения, всегда пишите предикат в cv.wait, помните: модификация vector может инвалидировать все итераторы. Шаблоны требуют видимости деклараций — объявляйте до использования.
Средний уровень — это дисциплина владения ресурсами и знание «острых углов» STL и многопоточности.