tgindex
C/C++ | Вопросы собесов

C/C++ | Вопросы собесов

Статистика

Сайт: https://easyoffer.ru/ Все каналы: t.me/+xGeAw6ckJ4liYzQy Контакт для рекламы: @easyoffer_adv

Последний пост
11:05
Последнее чтение
12:22
Постов за неделю
16
Всего постов
34
Тип
открытый
Язык
русский
Категория
Технологии (по похожим)
В каталоге с
12 авг.
Подписчики
4 179
−6 за 3 дн.
Сутки
−2
−0,05%
Неделя
 
Месяц
 
Просмотров на пост
411
30 постов
Вовлечённость
9,8%
к подписчикам
Постов в день
2,3
всего 34
Упоминаний
2
каналов
Охват размещения
оценка
1/24сутки в ленте
351
1/48двое суток
402
1/72трое суток
433

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • 11:051022

    🤔 Как вы будете реализовывать Singletone ? Это шаблон проектирования, который гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру. Существует несколько способов реализации Singleton, включая ленивую инициализацию, использование static и многопоточную безопасность. 🚩Основные методы реализации 🟠Ленивая инициализация (Lazy Initialization) При ленивой инициализации объект создается только при первом обращении к нему. class Singleton { public: static Singleton& getInstance() { static Singleton instance; return instance; } // Удаляем конструкторы копирования и присваивания Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; private: Singleton() {} // Приватный конструктор }; 🟠Инициализация при запуске (Eager Initialization) При инициализации при запуске объект создается сразу при загрузке программы. class Singleton { public: static Singleton& getInstance() { return instance; } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; private: Singleton() {} static Singleton instance; // Статический экземпляр }; Singleton Singleton::instance; 🟠Многопоточная безопасность (Thread-safe Singleton) Используя std::call_once и std::once_flag, можно обеспечить безопасность при доступе из нескольких потоков. #include <mutex> class Singleton { public: static Singleton& getInstance() { std::call_once(initInstanceFlag, &Singleton::initSingleton); return *instance; } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; private: Singleton() {} static void initSingleton() { instance = new Singleton(); } static Singleton* instance; static std::once_flag initInstanceFlag; }; Singleton* Singleton::instance = nullptr; std::once_flag Singleton::initInstanceFlag; 🚩Плюсы ➕Ленивая инициализация Экономит ресурсы, так как объект создается только при необходимости. ➕Инициализация при запуске Простая реализация, не требует дополнительных проверок, но объект создается даже если не используется. ➕Многопоточная безопасность Обеспечивает корректное создание объекта в многопоточной среде, но сложнее в реализации. Ставь 👍 и забирай 📚 Базу знаний

  • видео или голосовое, без подписи

  • 22:2018614

    🤔 Какие есть тонкости при вызове виртуальной функции из конструктора или деструктора? Вызов виртуальных функций из конструктора или деструктора в C++ может привести к неожиданным результатам, поскольку полиморфизм в этих контекстах работает иначе. Рассмотрим эти тонкости подробнее. 🚩Поведение виртуальных функций в конструкторах и деструкторах 🟠Конструкторы Виртуальные функции в конструкторах вызываются в контексте текущего класса, а не в контексте производного класса. Когда конструктор базового класса вызывает виртуальную функцию, будет вызвана версия этой функции из самого базового класса, даже если объект принадлежит производному классу. Это связано с тем, что в момент вызова конструктора объект еще не полностью сконструирован как объект производного класса. 🟠Деструкторы Виртуальные функции в деструкторах также вызываются в контексте текущего класса. Когда деструктор базового класса вызывает виртуальную функцию, будет вызвана версия этой функции из самого базового класса, поскольку деструкторы вызываются в порядке от производного к базовому, и в момент вызова деструктора базового класса объект уже не является объектом производного класса. 🚩Пример для иллюстрации #include <iostream> class Base { public: Base() { std::cout << "Base constructor\n"; // Вызов виртуальной функции из конструктора this->virtualFunction(); } virtual ~Base() { std::cout << "Base destructor\n"; // Вызов виртуальной функции из деструктора this->virtualFunction(); } virtual void virtualFunction() { std::cout << "Base virtualFunction\n"; } }; class Derived : public Base { public: Derived() { std::cout << "Derived constructor\n"; } ~Derived() override { std::cout << "Derived destructor\n"; } void virtualFunction() override { std::cout << "Derived virtualFunction\n"; } }; int main() { Derived d; return 0; } 🤔 Вывод программы: Base constructor Base virtualFunction Derived constructor Derived destructor Base destructor Base virtualFunction Ставь 👍 и забирай 📚 Базу знаний

  • видео или голосовое, без подписи

  • 🤔 Что известно об умных указателях? Обёртки над обычными указателями, автоматически управляющие временем жизни объекта. - std::unique_ptr — единственный владелец, нельзя копировать. - std::shared_ptr — подсчёт ссылок, разделённое владение. - std::weak_ptr — не влияет на счётчик shared_ptr, нужен для избежания циклов. Плюсы: - Безопасное управление памятью. - Нет утечек при правильном использовании. - Легче соблюдать RAII. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚Базу знаний

  • 🤔 Расскажи о истории умных указателей История умных указателей (smart pointers) в C++ связана с необходимостью управления динамической памятью и улучшением безопасности работы с указателями. Они появились как способ автоматизировать управление памятью и избежать ошибок, таких как утечки памяти, двойное освобождение или использование освобожденной памяти. 🚩Основные этапы развития умных указателей 🟠Классические проблемы обычных указателей В языке C++ динамическая память выделяется с помощью new и освобождается с помощью delete. Однако ручное управление памятью приводит к ошибкам: Утечки памяти: забыли вызвать delete, и память остаётся занятой. Двойное освобождение: дважды вызвали delete на одном указателе. Использование освобождённой памяти: доступ к указателю после delete. Эти проблемы стали мотиватором для появления решений, которые бы автоматически управляли памятью. 🟠Ручная реализация умных указателей в C++98 До стандартизации разработчики часто писали свои собственные классы для управления динамической памятью. Такой класс оборачивал указатель и вызывал delete в своём деструкторе: class SmartPointer { int* ptr; public: explicit SmartPointer(int* p = nullptr) : ptr(p) {} ~SmartPointer() { delete ptr; } int& operator*() { return *ptr; } int* operator->() { return ptr; } }; 🟠Boost и развитие библиотеки умных указателей В библиотеке Boost появились первые стандартизированные умные указатели: boost::shared_ptr: реализует совместное владение указателем (reference counting). boost::scoped_ptr: уникальное владение (аналог будущего std::unique_ptr). boost::weak_ptr: слабая ссылка для работы с циклическими зависимостями. Boost сыграл огромную роль в стандартизации умных указателей, так как многие из них легли в основу C++11. 🟠C++11: стандартные умные указатели Стандарт C++11 ввёл библиотеку <memory>, которая предоставляет три типа умных указателей: std::unique_ptr: Гарантирует уникальное владение ресурсом. Замена для обычных указателей с более строгими гарантиями. Не поддерживает копирование, только перемещение. std::unique_ptr<int> ptr(new int(5)); std::shared_ptr: Совместное владение ресурсом через подсчёт ссылок. Освобождает память, когда последний shared_ptr указывает на объект. std::shared_ptr<int> sp1 = std::make_shared<int>(10); std::weak_ptr: Не увеличивает счётчик ссылок в std::shared_ptr. Используется для работы с циклическими зависимостями. std::weak_ptr<int> wp = sp1; 🟠Современное использование умных указателей С выходом C++14, C++17 и C++20 умные указатели остаются важной частью языка. Они используются для: Ресурсного управления (RAII). Устранения утечек памяти. Упрощения кода. Ставь 👍 и забирай 📚 Базу знаний

  • видео или голосовое, без подписи

  • 🤔 Какое присваивание разрешает unique_ptr? unique_ptr поддерживает только перемещение (move assignment), чтобы гарантировать уникальное владение объектом, но не копирование. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚Базу знаний

  • 🤔 Как можно взвести бит используя побитовую операцию ИЛИ? Чтобы взвести (установить) бит в определённое значение (1) в числе, можно использовать побитовую операцию ИЛИ (|). Эта операция позволяет установить конкретный бит в 1, не изменяя остальные биты числа. 🚩Как это работает Операция ИЛИ (|) сравнивает каждый бит двух чисел. Если хотя бы один из битов в соответствующей позиции равен 1, результат в этой позиции будет 1. Иначе, результат будет 0. 🚩Шаги 1⃣Создаём маску, которая имеет единицу в позиции n и нули в остальных позициях. Это можно сделать с помощью выражения 1 << n. 2⃣Применяем операцию ИЛИ между числом x и маской. #include <iostream> int main() { int x = 0b00001010; // Число, с которым работаем (10 в десятичной системе) int n = 3; // Позиция бита, которую хотим установить (начиная с 0) // Создаём маску с единицей в позиции n int mask = 1 << n; // Устанавливаем бит в позиции n x = x | mask; // Вывод результата std::cout << "Результат: " << std::bitset<8>(x) << std::endl; // Двоичный вывод std::cout << "Результат: " << x << std::endl; // Десятичный вывод return 0; } 1 << n создает маску, сдвигая 1 влево на n позиций. Например, если n = 3, результат будет 0b00001000. Операция x | mask устанавливает бит в x на позиции n в 1. Если бит в x на позиции n уже был 1, он останется 1, если он был 0, то станет 1. 🚩Пример вывода Если x было 0b00001010 и n = 3, результат будет: Маска: 0b00001000 x | mask: 0b00001010 | 0b00001000 = 0b00001010 В результате бит в позиции 3 установлен в 1, и итоговое значение числа в двоичном формате 0b00001010 (десятичное 10). Ставь 👍 и забирай 📚 Базу знаний

  • 13 авг.328удалён 15 авг.

    видео или голосовое, без подписи

  • 🤔 Принцип Open/Closed (открытости/закрытости)? Принцип открытости/закрытости гласит, что классы должны быть открыты для расширения, но закрыты для модификации. Это достигается за счёт использования абстракций, чтобы добавлять функциональность через наследование или композицию, не изменяя исходный код. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚Базу знаний

  • 🤔 В каком порядке элементы списка инициализируются в конструкторе после двоеточия? Порядок инициализации элементов списка инициализации конструктора (initializer list) определяется порядком объявления членов класса, а не порядком, указанным в списке инициализации. Это важно понимать, так как неправильный порядок может привести к неожиданным ошибкам, особенно при инициализации зависимых членов. #include <iostream> class MyClass { private: int a; int b; int c; public: MyClass(int x, int y, int z) : c(z), b(y), a(x) { std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl; } }; int main() { MyClass obj(1, 2, 3); return 0; } 🚩Список инициализации конструктора В конструкторе MyClass список инициализации указан в порядке c(z), b(y), a(x). Однако это не влияет на порядок инициализации членов класса. Члены класса будут инициализированы в порядке их объявления: a, затем b, затем c. 🚩Порядок инициализации 1⃣a инициализируется первым (значение x). 2⃣b инициализируется вторым (значение y). 3⃣c инициализируется третьим (значение z). 🚩Вывод программы Программа выведет a: 1, b: 2, c: 3 🚩Почему это важно Неправильный порядок в списке инициализации не приведет к ошибке компиляции, но может вызвать логические ошибки, особенно если один член зависит от другого. Рассмотрим пример с зависимыми членами: class MyClass { private: int a; int& ref; public: MyClass(int x) : ref(a), a(x) { // Неправильный порядок: ref инициализируется до a std::cout << "a: " << a << ", ref: " << ref << std::endl; } }; Ставь 👍 и забирай 📚 Базу знаний

  • 🤔 В каком стандарте появился override? Ключевое слово override было введено в стандарт C++11. До его появления разработчики не могли явно указывать, что метод должен переопределять виртуальный метод базового класса. Это приводило к ошибкам, если сигнатура метода в базовом классе менялась или имя метода было неправильно написано. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚Базу знаний

  • 🤔 Почему не сделать стек вызова очень большим? Может показаться логичным способом избежать переполнения стека, но в реальности это решение приводит к проблемам 🟠Неэффективное использование памяти Если стек слишком велик, это может привести к нерациональному использованию памяти. Память, выделенная под стек, может оставаться неиспользованной, что уменьшает доступное пространство для других частей программы и данных. 🟠Ограниченные системные ресурсы Системы имеют ограниченное количество оперативной памяти. Выделение слишком большого размера стека для каждого потока может привести к исчерпанию доступной памяти, особенно в многопоточных приложениях, где каждый поток имеет свой стек. 🟠Сложности с отладкой Большие стеки могут затруднить обнаружение ошибок и утечек памяти. Например, если стек слишком велик, ошибка переполнения стека может не проявиться сразу, что усложняет диагностику проблемы. 🟠Влияние на производительность Большие стеки могут повлиять на производительность системы из-за увеличения времени переключения контекста (context switching) и необходимости резервирования большого объема памяти при каждом запуске потока. 🚩Как оптимально настроить? 1⃣Анализ программы Оцените потребности вашей программы в памяти. Измерьте, сколько памяти реально требуется для выполнения типичных задач. 2⃣Тестирование Проведите тесты с различными размерами стека, чтобы определить оптимальный баланс между достаточным размером стека и эффективным использованием памяти. 3⃣Использование рекурсии с осторожностью Если ваш код активно использует рекурсию, убедитесь, что глубина рекурсии контролируется и не приводит к переполнению стека. #include <pthread.h> #include <stdio.h> void* myThreadFunction(void* arg) { // Код потока return NULL; } int main() { pthread_t thread; pthread_attr_t attr; size_t stacksize; // Инициализация атрибутов pthread_attr_init(&attr); // Установка размера стека stacksize = 16 * 1024 * 1024; // 16 МБ pthread_attr_setstacksize(&attr, stacksize); // Создание потока pthread_create(&thread, &attr, myThreadFunction, NULL); // Ожидание завершения потока pthread_join(thread, NULL); return 0; } Ставь 👍 и забирай 📚 Базу знаний

  • 10 авг.удалён 12 авг.

    видео или голосовое, без подписи

  • 🤔 Что можешь рассказать о little-endian и big-endian? Little-endian и big-endian — это порядки байтов в мультибайтовых данных, используемые в компьютерной архитектуре для хранения данных. В little-endian младший байт сохраняется по низкому адресу, а старший байт по высокому. В big-endian старший байт сохраняется по низкому адресу, а младший по высокому. Выбор порядка байтов влияет на совместимость и производительность системы в различных сценариях использования. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚Базу знаний

  • 🤔 Что будет если для беззнаковой переменной равной 0 сделать декремент? Если вы сделаете декремент (--) для беззнаковой переменной, значение которой равно 0, это приведет к переполнению, и переменная примет максимальное значение, которое может быть представлено этим типом. Рассмотрим, что произойдет с переменной типа unsigned int, если её значение равно 0 и мы применим к ней декремент: #include <iostream> int main() { unsigned int x = 0; std::cout << "x before decrement: " << x << std::endl; x--; // Декрементирование беззнаковой переменной 0 std::cout << "x after decrement: " << x << std::endl; // Переполнение return 0; } Для беззнаковой переменной unsigned int: Диапазон значений: от 0 до 4294967295 (для 32-битной реализации). Когда переменная x равна 0 и мы выполняем x--, происходит следующее: 🟠Значение 0 уменьшается на 1. 🟠Поскольку unsigned int не может представлять отрицательные значения, происходит переполнение. 🟠Переполнение для беззнакового типа приводит к "оборачиванию" значения через максимальное значение типа. 🟠Максимальное значение для unsigned int (при 32-битной реализации) равно 4294967295. Таким образом, после выполнения x--, значение x становится 4294967295. Результат выполнения приведенного выше кода будет следующим: x before decrement: 0 x after decrement: 4294967295 Для беззнаковых типов в C++ (например, unsigned char, unsigned short, unsigned int, unsigned long): Если значение переменной равно 0 и к ней применяется декремент, результатом будет максимальное значение этого типа. Ставь 👍 и забирай 📚 Базу знаний

  • 🤔 Хорошо ли хранить числа с плавающей запятой в качестве ключа контейнера? Нет, хранить числа с плавающей запятой как ключи не рекомендуется. 1. Числа с плавающей запятой могут иметь проблемы с точностью, что приведёт к некорректному сравнению ключей. 2. Лучше использовать целые числа или строки в качестве ключей, а дробные значения преобразовывать к целым или строковым представлениям. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚Базу знаний

  • 🤔 Какая сложность поиска в set и unordered_set? Контейнеры set и unordered_set представляют собой различные структуры данных, каждая из которых имеет свои особенности по скорости выполнения основных операций, включая поиск. Вот как работают эти контейнеры и какова сложность их операций поиска: 🚩set Реализуется как сбалансированное двоичное дерево поиска, обычно как красно-черное дерево. Он хранит элементы в отсортированном порядке, что позволяет выполнять двоичный поиск. Сложность поиска: Поиск в нем выполняется за логарифмическое время, \(O(\log n)\), где \(n\) — количество элементов в set. Эта эффективность достигается за счёт использования структуры сбалансированного дерева, которое позволяет быстро делить данные на меньшие сегменты. 🚩unordered_set Реализуется с использованием хеш-таблицы. Это позволяет, при идеальных условиях, выполнять поиск за константное время. Сложность поиска: В среднем, поиск в нем занимает константное время \(O(1)\). Однако в худшем случае, например, при неудачной работе хеш-функции или при большом количестве коллизий, поиск может деградировать до \(O(n)\). В таких ситуациях все ключи могут оказаться в одной "корзине" или "ведре" (bucket), и для нахождения правильного элемента потребуется просмотреть все элементы в этом ведре. Для set #include <iostream> #include <set> int main() { std::set<int> mySet = {5, 3, 9, 1}; auto search = mySet.find(3); if (search != mySet.end()) { std::cout << "Found " << *search << std::endl; } else { std::cout << "Not found" << std::endl; } return 0; } Для unordered_set #include <iostream> #include <unordered_set> int main() { std::unordered_set<int> mySet = {5, 3, 9, 1}; auto search = mySet.find(3); if (search != mySet.end()) { std::cout << "Found " << *search << std::endl; } else { std::cout << "Not found" << std::endl; } return 0; } Ставь 👍 и забирай 📚 Базу знаний

  • 🤔 Что такое рекурсивный mutex? Рекурсивный mutex — это тип мьютекса, который позволяет одному и тому же потоку захватывать его несколько раз без блокировки. Это полезно, когда одна и та же функция вызывает себя рекурсивно или косвенно через другие функции и при этом требуется захватить один и тот же мьютекс. В отличие от обычного мьютекса, рекурсивный мьютекс учитывает количество захватов и освобождается только после того, как поток выполнит соответствующее количество вызовов unlock(). Это предотвращает взаимоблокировки при рекурсивных вызовах. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚Базу знаний

C/C++ | Вопросы собесов — tgindex