Что такое хеш-функция? Объясните на примере
Хеш-функция — это функция, которая принимает на вход некоторые данные и возвращает фиксированный размер выходных данных, называемый хешем. Такие функции широко используются в C++ для решения задач с поиском и индексированием данных. Они позволяют быстро находить элементы в массивах или хранилищах данных.
Рассмотрим применение на примере кода выше:
Мы создаем объект хеш-функции для строки и используем его для вычисления хеша для строки "Hello, world!". Результатом выполнения программы будет вывод на экран хеш-значения для этой строки.
Хеш-функция — это функция, которая принимает на вход некоторые данные и возвращает фиксированный размер выходных данных, называемый хешем. Такие функции широко используются в C++ для решения задач с поиском и индексированием данных. Они позволяют быстро находить элементы в массивах или хранилищах данных.
Рассмотрим применение на примере кода выше:
Мы создаем объект хеш-функции для строки и используем его для вычисления хеша для строки "Hello, world!". Результатом выполнения программы будет вывод на экран хеш-значения для этой строки.
Что такое void указатель? Можно ли разыменовать указатель void, не зная его типа?
void указатель — это указатель, который можно использовать для указания на любые данные любого произвольного типа. Указатель void может быть разыменован только после явного приведения. Например:
int a = 5;
void *b = &a;
printf(“%d\n”, *((int*)b));
void указатель — это указатель, который можно использовать для указания на любые данные любого произвольного типа. Указатель void может быть разыменован только после явного приведения. Например:
int a = 5;
void *b = &a;
printf(“%d\n”, *((int*)b));
Что такое виртуальный деструктор и зачем он используется в C++?
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
Бывает такое, что оператор new не выделяет память?
Ответ:
Да, бывает, когда new передаётся указатель на уже выделенную память (например, с помощью malloc). Это называется placement new. И оператор new без изменения возвращает второй параметр - указатель (void* operator new(std::size_t, void*)). Это используется для создания объектов в выделенном "хранилище" или после malloc.
Важно! В этом случае деструктор нужно вызывать самостоятельно!
Ответ:
Важно! В этом случае деструктор нужно вызывать самостоятельно!
Что делает алгоритм move?
Синтаксис:
std::move(first, last, result);
Перемещает элементы диапазона [first,last) в диапазон, начиная с позиции result.
Значение элементов в [first,last) массиве передается элементам, на которые указывает result. После вызова элементы в диапазоне [first,last) остаются в неопределенном, но допустимом состоянии.
Синтаксис:
std::move(first, last, result);
Перемещает элементы диапазона [first,last) в диапазон, начиная с позиции result.
Значение элементов в [first,last) массиве передается элементам, на которые указывает result. После вызова элементы в диапазоне [first,last) остаются в неопределенном, но допустимом состоянии.
Что такое класс хранения?
Класс, который определяет срок существования, компоновку и расположение переменных/функций в памяти.
В C++ поддерживаются такие классы хранения: auto, static, register, extern и mutable.
Обратите внимание, что register устарел для C++11. Для C++17 он был удален и зарезервирован для будущего использования.
Класс, который определяет срок существования, компоновку и расположение переменных/функций в памяти.
В C++ поддерживаются такие классы хранения: auto, static, register, extern и mutable.
Обратите внимание, что register устарел для C++11. Для C++17 он был удален и зарезервирован для будущего использования.
Объясните ключевые слова mutable и volatile.
Ключевое слово volatile сообщает компилятору, что переменная может измениться без ведома компилятора. Переменные, объявленные как volatile, не будут кэшироваться компилятором и, таким образом, всегда будут считываться из памяти.
Ключевое слово mutable можно использовать для переменных-членов класса. Изменяемые переменные могут меняться из константных функций-членов класса.
Ключевое слово volatile сообщает компилятору, что переменная может измениться без ведома компилятора. Переменные, объявленные как volatile, не будут кэшироваться компилятором и, таким образом, всегда будут считываться из памяти.
Ключевое слово mutable можно использовать для переменных-членов класса. Изменяемые переменные могут меняться из константных функций-членов класса.
Для чего используется ключевое слово explicit?
Ключевое слово explicit используется для того, чтобы пометить конструкторы, которые не должны неявно преобразовывать типы. Оно является необязательным для конструкторов, которые принимают ровно один аргумент, и работает на конструкторах (с одним аргументом), так как только эти конструкторы могут использоваться при приведении типов.
Ключевое слово explicit используется для того, чтобы пометить конструкторы, которые не должны неявно преобразовывать типы. Оно является необязательным для конструкторов, которые принимают ровно один аргумент, и работает на конструкторах (с одним аргументом), так как только эти конструкторы могут использоваться при приведении типов.
Перечислите все способы синхронизации процессов.
1. Использование мьютексов (std::mutex). Мьютекс позволяет захватывать его одним потоком, блокируя доступ другим потокам.
2. Использование семафоров (std::semaphore). Семафоры позволяют ограничивать количество потоков, которые могут одновременно захватить семафор.
3. Использование условных переменных (std::condition_variable). Они позволяют блокировать поток до наступления некоторого события.
4. Использование флагов и барьеров (std::atomic_flag, std::barrier).
5. Передача сообщений между потоками через очереди (std::queue).
6. Использование фьючерсов и промисов (std::promise, std::future).
Выбор конкретного механизма зависит от сценария использования и требований к синхронизации. Главное при этом избегать длительных и взаимных блокировок.
1. Использование мьютексов (std::mutex). Мьютекс позволяет захватывать его одним потоком, блокируя доступ другим потокам.
2. Использование семафоров (std::semaphore). Семафоры позволяют ограничивать количество потоков, которые могут одновременно захватить семафор.
3. Использование условных переменных (std::condition_variable). Они позволяют блокировать поток до наступления некоторого события.
4. Использование флагов и барьеров (std::atomic_flag, std::barrier).
5. Передача сообщений между потоками через очереди (std::queue).
6. Использование фьючерсов и промисов (std::promise, std::future).
Выбор конкретного механизма зависит от сценария использования и требований к синхронизации. Главное при этом избегать длительных и взаимных блокировок.
Какой код выполняется до функции main?
Ответ:
Конструкторы глобальных объектов.
Ответ:
Что такое cache miss и как это выявить?
Cache miss — это событие, когда система или приложение делает запрос на получение данных из кэша, но эти данные в данный момент отсутствуют в кэш-памяти. В результате системе или приложению приходится делать вторую попытку найти данные, на этот раз в более медленной основной базе данных.
Чтобы выявить cache miss в C++, вы можете использовать инструменты профилирования, которые могут анализировать события, связанные с кэшем. Некоторые из таких инструментов включают:
Cachegrind: инструмент для профилирования кэша, который является частью набора инструментов Valgrind. Cachegrind может анализировать поведение кэша вашего приложения и предоставлять информацию о cache miss и других событиях, связанных с кэшем.
Perf: инструмент для профилирования производительности в Linux, который может использовать аппаратные счетчики процессора для анализа событий, связанных с кэшем.
OProfile: еще один инструмент для профилирования производительности в Linux, который также может использовать аппаратные счетчики процессора для анализа событий, связанных с кэшем.
Cache miss — это событие, когда система или приложение делает запрос на получение данных из кэша, но эти данные в данный момент отсутствуют в кэш-памяти. В результате системе или приложению приходится делать вторую попытку найти данные, на этот раз в более медленной основной базе данных.
Чтобы выявить cache miss в C++, вы можете использовать инструменты профилирования, которые могут анализировать события, связанные с кэшем. Некоторые из таких инструментов включают:
Cachegrind: инструмент для профилирования кэша, который является частью набора инструментов Valgrind. Cachegrind может анализировать поведение кэша вашего приложения и предоставлять информацию о cache miss и других событиях, связанных с кэшем.
Perf: инструмент для профилирования производительности в Linux, который может использовать аппаратные счетчики процессора для анализа событий, связанных с кэшем.
OProfile: еще один инструмент для профилирования производительности в Linux, который также может использовать аппаратные счетчики процессора для анализа событий, связанных с кэшем.
Могут ли статичные функции быть виртуальными в С++?
В C++ статическая функция-член класса не может быть виртуальной. Виртуальные функции вызываются, когда у вас есть указатель или ссылка на экземпляр класса. Статические функции не привязаны к экземпляру класса, но они привязаны к самому классу. C++ не имеет указателей на класс, поэтому нет сценария, в котором вы могли бы виртуально вызвать статическую функцию.
Например, программа с картинки в примере 1 выдаст ошибку во время компиляции.
Кроме того, статическая функция-член класса не может иметь одновременно идентификаторы const и volatile. Код из примера 2 тоже не скомпилируется.
В C++ статическая функция-член класса не может быть виртуальной. Виртуальные функции вызываются, когда у вас есть указатель или ссылка на экземпляр класса. Статические функции не привязаны к экземпляру класса, но они привязаны к самому классу. C++ не имеет указателей на класс, поэтому нет сценария, в котором вы могли бы виртуально вызвать статическую функцию.
Например, программа с картинки в примере 1 выдаст ошибку во время компиляции.
Кроме того, статическая функция-член класса не может иметь одновременно идентификаторы const и volatile. Код из примера 2 тоже не скомпилируется.
Что такое критическая секция?
Критическая секция — это участок кода, в котором происходит доступ к общему ресурсу (например, переменной или структуре данных), и который должен быть выполнен атомарно, то есть без возможности прерывания другими потоками.
Для обеспечения безопасного доступа к критической секции в C++ используются механизмы синхронизации, такие как мьютексы (std::mutex) и блокировки (std::lock_guard, std::unique_lock). Перед выполнением критической секции поток должен захватить мьютекс, блокируя его для других потоков. После завершения работы в критической секции мьютекс освобождается, позволяя другим потокам получить доступ к ресурсу.
Использование критических секций и мьютексов позволяет избежать состояний гонки (race conditions) и обеспечить корректную и безопасную работу с общими данными в многопоточных приложениях.
Критическая секция — это участок кода, в котором происходит доступ к общему ресурсу (например, переменной или структуре данных), и который должен быть выполнен атомарно, то есть без возможности прерывания другими потоками.
Для обеспечения безопасного доступа к критической секции в C++ используются механизмы синхронизации, такие как мьютексы (std::mutex) и блокировки (std::lock_guard, std::unique_lock). Перед выполнением критической секции поток должен захватить мьютекс, блокируя его для других потоков. После завершения работы в критической секции мьютекс освобождается, позволяя другим потокам получить доступ к ресурсу.
Использование критических секций и мьютексов позволяет избежать состояний гонки (race conditions) и обеспечить корректную и безопасную работу с общими данными в многопоточных приложениях.
Какие преимущества композиции перед наследованием?
Композиция в C++ позволяет создавать новый класс из более мелких существующих классов, тогда как наследование от других классов позволяет использовать существующие свойства класса.
Основное преимущество композиции заключается в том, что она обеспечивает более гибкую архитектуру и более легкое изменение поведения класса в будущем.
В то время как если использовать наследование при проектировании классов, то это может ограничить возможности изменений в будущем и привести к большему количеству ошибок в программе.
Композиция в C++ позволяет создавать новый класс из более мелких существующих классов, тогда как наследование от других классов позволяет использовать существующие свойства класса.
Основное преимущество композиции заключается в том, что она обеспечивает более гибкую архитектуру и более легкое изменение поведения класса в будущем.
В то время как если использовать наследование при проектировании классов, то это может ограничить возможности изменений в будущем и привести к большему количеству ошибок в программе.
Почему вызов container.size() перед каждой итерацией цикла является плохой практикой?
Вызов container.size() перед каждой итерацией по контейнеру не является оптимальным, так как это приводит к линейной сложности O(n).
Вместо этого лучше сохранить размер контейнера в переменную до цикла, таким образом размер вычисляется только один раз, а доступ к элементам в цикле происходит за константное время O(1).
Это позволяет избежать лишних вычислений размера на каждой итерации и улучшает производительность.
Вызов container.size() перед каждой итерацией по контейнеру не является оптимальным, так как это приводит к линейной сложности O(n).
Вместо этого лучше сохранить размер контейнера в переменную до цикла, таким образом размер вычисляется только один раз, а доступ к элементам в цикле происходит за константное время O(1).
Это позволяет избежать лишних вычислений размера на каждой итерации и улучшает производительность.