C++. Базовый уровень
Эта проверка — быстрый способ убедиться, что база в порядке. Синтаксис, типы, массивы и файлы. Частые вопросы: индексация с нуля, корректный for, чтение строки с пробелами, разница между ifstream и ofstream, динамическая память и выход за границы.
Вопрос 1. Какой формат цикла for правильно перебирает массив A из n элементов?
1. for (int i = 1; i <= n; i++)
2. for (int i = 0; i < n; i++)
3. for (int i = 1; i <= n; i--)
5. for (int i = 0; i <= n; i++)
Нужно перебрать массив из n элементов. Обычно в C++ массивы индексируются с нуля: первый элемент — A[0], последний — A[n-1]. Значит, цикл должен идти от 0 до n-1.
- Вариант 1 (i = 1; i <= n) — тут начало с 1, значит первый элемент A[0] вообще пропускается. Ошибка.
- Вариант 2 (i = 0; i < n) — это как раз то, что нужно: с нуля до n-1. Всё правильно.
- Вариант 3 (i = 1; i <= n; i--) — бесконечный цикл, потому что i-- идёт вниз, а условие <= n всегда будет верным. Абсурд.
- Вариант 4 (foreach (auto x : A)) — в C++ действительно можно использовать range-based for. Это тоже корректный способ, он работает для массивов. Но вопрос звучит так, как будто хотят классический формат for.
- Вариант 5 (i = 0; i <= n) — тут цикл пойдёт от 0 до n, включая n. Но индекс A[n] не существует (последний A[n-1]), будет выход за границу. Ошибка.
Вывод: два способа технически правильные — это 2 и 4. Но если ориентироваться на «правильный классический формат перебора массива» (судя по формулировке), то ожидается именно вариант 2.
Выбранныйответ: for (int i = 0; i < n; i++)
Вопрос 2. Как правильно открыть файл для чтения в C++?
3. instream(file, "input.txt")
4. fstream file("input.txt", ios::out)
5. FILE* file = fopen("input.txt", "r")
Спрашивают про открытие для чтения именно в C++. Базовый современный способ — поток ввода std::ifstream, который по умолчанию открывает файл в режиме чтения (ios::in).
— (1) корректно: ifstream и путь к файлу — стандартный вариант для чтения.
— (2) ofstream — это для записи, не подходит.
— (3) instream в стандартной библиотеке нет — неверно.
— (4) fstream можно использовать, но здесь указан режим ios::out (запись), а не чтение — неверно; для чтения нужно было бы ios::in.
— (5) fopen(...,"r") — рабочий C-подход, в C++ тоже возможен, но вопрос, как правило, ожидает ответ со стандартными C++ потоками.
Вывод: выбираю классический C++-вариант с ifstream.
Выбранный ответ: ifstream file("input.txt")
Вопрос 3. Какое утверждение верно для данной функции?
1. Функция возвращает ноль для всех значений p, больших 25
2. Функция выводит «cpp» бесконечно, когда p = 25, пока стек не заполнится
3. Функция выводит «cpp» бесконечно для всех значений p, кроме 25, пока стек не заполнится
4. Функция возвращает -1, когда p = 25
5. Функция возвращает ноль для всех значений p, меньших 25
Локальная i всегда равна 25. Если p == 25, печатаем "cpp", сразу же вызываем f(p) снова и базового случая нет — рекурсия не заканчивается. Возврат -1 теоретически стоит после рекурсивного вызова, но до него никогда не дойдём: стек будет расти, пока не переполнится. Если p != 25, ветка else мгновенно возвращает 0.
Ключевой эффект функции — бесконечная рекурсия при p == 25. Возвраты нуля при других значениях — верно, но это вторично по сравнению с «сломом» при 25.
Выбранный ответ: «Функция выводит “cpp” бесконечно, когда p = 25, пока стек не заполнится»
Вопрос 4. Что произойдёт после выполнения инкрементации итератора?
std::vector<int> vec = {1,2,3};
3. Итератор будет указывать на следующий элемент
5. Итератор станет не валидным
У нас std::vector<int> из трёх элементов. begin() даёт итератор на первый элемент (1). Оператор ++i — это префиксный инкремент итератора, который продвигает его на один шаг вперёд.
На что обращаю внимание: вектор не менялся, границы не нарушены — следующий элемент есть (это 2). Ошибки и инвалидности итератора не будет; «на 2» итератор не прыгает — для этого нужно было бы i += 2.
После ++i итератор укажет на элемент со значением 2, то есть на следующий.
Выбранный ответ: Итератор будет указывать на следующий элемент
Вопрос 5. Программа 3D-моделирования должна хранить объекты в векторно-полигональной модели (набор координат вершин) большого размера: миллион вершин или больше. С объектами требуется выполнять различные операции, например масштабирование или перемещение. Какой STL-контейнер будет эффективен для хранения и обработки таких данных?
Нужно хранить очень много однотипных числовых данных (вершины) и быстро проходиться по ним для операций наподобие масштабирования/перемещения. Тут важны компактность, кэш-локальность и быстрый последовательный обход.
На что обращаю внимание:
— std::vector держит элементы подряд в памяти → быстрый линейный проход, случайный доступ O(1), хорошо для SIMD.
— std::list хранит узлы по указателям → лишняя память на указатели и плохая кэш-локальность.
— std::queue ограничивает доступ концами и построен поверх другого контейнера — не подходит.
— unordered_map/unordered_set — ассоциативные контейнеры с хеш-структурами и накладными расходами; нам не нужно хранить пары ключ-значение или уникальность по ключу.
Для массивов вершин большого размера наилучший базовый выбор — динамический массив.
Вопрос 6. Выберите верное утверждение для абстрактного класса.
1. Должен содержать чисто виртуальную функцию, определенную вне класса
2. Должен содержать дружественный метод
3. Не может содержать чисто виртуальных функций
4. Должен содержать только чисто виртуальные функции
5. Должен содержать хотя бы одну чисто виртуальную функцию
Спрашивают базовое определение абстрактного класса в C++. Абстрактный — это класс, у которого есть хотя бы одна чисто виртуальная функция (= 0) либо унаследованная, не реализованная.
На что обращаю внимание:
— Никаких требований к «дружественным методам» нет — пункт 2 лишний.
— Пункт 3 противоречит определению.
— Пункт 4 чрезмерно строг: в абстрактном классе могут быть обычные методы и поля.
— Пункт 1 некорректно сужает: чисто виртуальная может иметь реализацию вне класса, но абстрактность не требует именно такого определения.
Логичный вариант — «хотя бы одна чисто виртуальная».
Выбранный ответ: Должен содержать хотя бы одну чисто виртуальную функцию
Вопрос 7. Как нужно объявить переменную count на месте пропуска в классе Player, чтобы на экран было выведено 2?
Нужно общее для всех объектов поле-счётчик, чтобы оба созданных Player инкрементировали одну и ту же переменную. Такое поле объявляется как static. В коде уже есть внешнее определение int Player::count = 0; и статический метод getCount(), который может обращаться только к статическим полям.
friend — не про хранение данных, int count (нестатическое) не скомпилируется со статическим методом, const изменять нельзя. int static count синтаксически допустимо, но обычно ожидают форму static int count.
Выбранный ответ: static int count
Вопрос 8. Проанализируйте код ниже. Какие поля есть у класса C?
C публично наследуется от A и B. У A есть поле a (public), у B — поле b (private), у самого C — поле c (private).
На что обращаю внимание: модификатор private в базовом классе влияет на доступ, а не на наличие поля в объекте-наследнике. То есть объект C содержит под-объект A с полем a, под-объект B с полем b и своё поле c. Доступ к b из кода C запрещён, но само поле в памяти есть. У класса C присутствуют все три поля — a, b, c.
Вопрос 9. Выражение const int* wsk означает:
1. Указатель на константный объект
4. Константный указатель на константный объект
const стоит слева от типа, а звёздочка рядом с int. Это читается как «указатель на const int». Значит, менять значение по адресу нельзя (*wsk = 5; — ошибка), но сам указатель можно переназначать (wsk = другой_адрес; — можно).
На что обращаю внимание: «константный указатель» был бы int* const wsk — здесь const после *. Комбинация «константный указатель на константный объект» выглядела бы как const int* const wsk. Вывод: это именно указатель на константный объект.
Выбранный ответ: Указатель на константный объект
Вопрос 10.
Что произойдёт после выполнения этого кода?
1. Возникнет утечка памяти, так как new не сбалансирован с delete
2. Указатель ptr будет валидным
3. Указатель ptr будет невалидным
4. Память автоматически освободится после выхода из func
5. Программа завершится с ошибкой из-за двойного освобождения
Внутри func памяти выделяется через new — это куча, она не освобождается сама по себе при выходе из функции. Мы возвращаем адрес в ptr. Сам ptr указывает на корректно выделенную память, но в коде нет соответствующего delete.
На что обращаю внимание: без delete ptr; эта память останется висеть до конца жизни ptr/программы — это классическая утечка. Варианты про автоматическое освобождение и двойное освобождение не подходят. Фраза «ptr валиден» технически верна, но тест обычно проверяет именно проблему управления памятью.
Код создаёт утечку, потому что new не сопровождается delete (нужно либо явно удалить, либо использовать std::unique_ptr).
Выбранный ответ: Возникнет утечка памяти, так как new не сбалансирован с delete
Заключение
Если спотыкаетесь на границах массива, вводе-выводе и указателях — вернитесь к простым задачам и пишите код руками. Пользуйтесь стандартными контейнерами, проверяйте условия цикла, закрывайте new парой delete, не путайте const T* и T* const.