C/C++ | Вопросы собесов @easy_c_plus Channel on Telegram

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

@easy_c_plus


Cайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp

Тесты t.me/+zYofcX2VLTM3MGMy
Задачи t.me/+9WeVk7cGswkzNTIy
Вакансии t.me/+za2mJYs4riAzMzFi

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

Вы мечтаете о карьере в сфере разработки на С/С++? Тогда наш Telegram канал @easy_c_plus идеально подойдет для вас!

Здесь мы разбираем вопросы, которые часто встречаются на собеседованиях на позицию разработчика на С/С++. Мы поможем вам подготовиться к собеседованию, расскажем об актуальных темах и поделимся полезными советами.

Хотите узнать больше? Посетите наш сайт easyoffer.ru для дополнительной информации.

Не забудьте ознакомиться с рекламными возможностями нашего канала, обращайтесь к @easyoffer_adv.

Прокачайте свои навыки, решая интересные тесты по программированию - присоединяйтесь к @+zYofcX2VLTM3MGMy. Попробуйте свои силы, находясь в постоянном развитии и обучении - присоединяйтесь к @+9WeVk7cGswkzNTIy. Ищите работу своей мечты - обращайтесь к @+za2mJYs4riAzMzFi.

Присоединяйтесь к нашему каналу прямо сейчас и делайте шаг к успешной карьере в мире программирования на С/С++!

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

30 Jan, 09:10


🤔 Какие виды конструкторов могут быть у класса?

🟠Конструктор по умолчанию (Default Constructor)
Без параметров, создается автоматически компилятором, если не задан.
   class MyClass {
public:
MyClass() {}
};


🟠Параметризованный конструктор (Parameterized Constructor)
Принимает параметры для инициализации объекта.
   class MyClass {
private:
int x;
public:
MyClass(int value) : x(value) {}
};


🟠Конструктор копирования (Copy Constructor)
Создает копию существующего объекта.
   class MyClass {
private:
int x;
public:
MyClass(const MyClass &other) : x(other.x) {}
};


🟠Конструктор перемещения (Move Constructor)
Перемещает ресурсы из временного объекта.
   class MyClass {
private:
int* data;
public:
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
}
};


🟠Делегирующий конструктор (Delegating Constructor)
Вызывает другой конструктор того же класса.
   class MyClass {
private:
int x, y;
public:
MyClass(int value) : MyClass(value, 0) {}
MyClass(int value1, int value2) : x(value1), y(value2) {}
};


🟠Явный конструктор (Explicit Constructor)
Помечен explicit для предотвращения неявных преобразований.
   class MyClass {
public:
explicit MyClass(int value) {}
};


Ставь 👍 и забирай 📚 Базу знаний

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

29 Jan, 16:10


🤔 Что известно про фрагментацию кучи?

Фрагментация кучи возникает, когда память выделяется и освобождается в разном порядке, оставляя небольшие, неиспользуемые области между занятой памятью. Это снижает доступность больших блоков для выделения.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

29 Jan, 09:10


🤔 Какой подсчет ссылок имеется в shared_ptr?

В умном указателе shared_ptr используется два основных счётчика: счётчик сильных ссылок (strong reference count) и счётчик слабых ссылок (weak reference count). Эти счётчики управляют жизненным циклом объекта и связанных с ним ресурсов различными способами.

🚩Счётчик сильных ссылок

Увеличивается каждый раз, когда новый shared_ptr создаётся как копия другого shared_ptr или когда объект присваивается shared_ptr. Этот счётчик уменьшается, когда shared_ptr уничтожается или когда его значение присваивается другому объекту. Когда счётчик достигает нуля, это означает, что больше нет shared_ptr, управляющих этим объектом, и объект удаляется. Это гарантирует, что ресурсы, связанные с объектом, будут освобождены только тогда, когда не останется ни одной "сильной" ссылки.

🚩Счётчик слабых ссылок

Используется вместе с weak_ptr, другим типом умных указателей, который может ссылаться на объект, управляемый shared_ptr, но не увеличивает счётчик сильных ссылок. Слабые ссылки не предотвращают удаление объекта, к которому они имеют доступ, так как не участвуют в владении объектом. Счётчик слабых ссылок увеличивается каждый раз, когда создаётся weak_ptr, указывающий на объект, и уменьшается, когда такой weak_ptr уничтожается. Когда счётчик сильных ссылок достигает нуля и объект удаляется, память, выделенная под сам объект, освобождается, но "control block" (блок управления), содержащий счётчики, сохраняется до тех пор, пока счётчик слабых ссылок также не обнулится.

#include <iostream>
#include <memory>

int main() {
std::shared_ptr<int> sp1 = std::make_shared<int>(10);
std::cout << "sp1 use_count: " << sp1.use_count() << '\n'; // Вывод: 1

{
std::shared_ptr<int> sp2 = sp1; // Копирование shared_ptr
std::cout << "sp1 use_count after copy: " << sp1.use_count() << '\n'; // Вывод: 2

std::weak_ptr<int> wp1 = sp1; // Создание weak_ptr
std::cout << "wp1 use_count: " << wp1.use_count() << '\n'; // Вывод: 2
} // sp2 выходит из области видимости, use_count уменьшается до 1

std::cout << "sp1 use_count after sp2 destruction: " << sp1.use_count() << '\n'; // Вывод: 1
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

28 Jan, 16:10


🤔 В какой момент принимается решение, что хеш-таблице надо перестроиться?

Решение о перестроении (увеличении размера) принимается, когда коэффициент заполнения превышает пороговое значение. Это позволяет снизить количество коллизий и сохранить эффективность.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

28 Jan, 09:10


🤔 В чем разница vector и list?

Являются контейнерами из стандартной библиотеки, но они значительно различаются по своей структуре и оптимальности использования в разных ситуациях.

🚩std::vector

Это динамический массив, который обеспечивает быстрый доступ к элементам по индексу. Вектор хранит свои элементы в непрерывном блоке памяти, что делает возможным обращение к элементам за константное время O(1). Однако, такое хранение делает вставку и удаление элементов в начало или середину вектора менее эффективными (в среднем O(n), где n — количество элементов), так как может потребоваться перемещение большого количества элементов для освобождения места или заполнения пробела.
#include <vector>
#include <iostream>

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // Добавление элемента в конец вектора
std::cout << "Элемент по индексу 3: " << vec[3] << std::endl; // Быстрый доступ к элементам

return 0;
}


🚩std::list

В отличие от std::vector, представляет собой двусвязный список. Каждый элемент содержит данные и указатели на предыдущий и следующий элементы в списке. Это обеспечивает быструю вставку и удаление элементов в любом месте списка, так как требуется только изменение нескольких указателей (O(1)), независимо от размера списка. Однако, доступ к элементам в std::list осуществляется за линейное время O(n), так как для доступа к элементу нужно пройти через указатели от начала или конца списка.
#include <list>
#include <iostream>

int main() {
std::list<int> lst = {1, 2, 3, 4, 5};
lst.push_front(0); // Быстрая вставка в начало списка
lst.push_back(6); // Быстрая вставка в конец списка

for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

27 Jan, 16:10


🤔 Что будет с хеш-таблицей, если в неё постоянно добавлять элементы?

Таблица начнёт заполняться, увеличится количество коллизий, и производительность снизится. После достижения порогового коэффициента заполнения (обычно 70-80%) произойдёт ресайзинг.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

27 Jan, 09:10


🤔 Какую сложность от О большое имеет сортировка QuickSort, BubbleSort, HeapSort ?

В анализе алгоритмов сортировки, сложность по большому O показывает, как изменяется время выполнения в зависимости от размера входных данных. Рассмотрим три алгоритма: QuickSort, BubbleSort и HeapSort.

🚩QuickSort

🟠Худший случай O(n²)
Происходит, если при каждом разбиении выбирается самый большой или самый маленький элемент в качестве опорного (pivot). Например, это может случиться, если массив уже отсортирован или отсортирован в обратном порядке.
🟠Средний и лучший случай: O(n log n)
Средняя сложность достигается за счет удачных разбиений, где каждый раз массив делится примерно пополам. В лучшем случае массив уже делится равномерно на две части каждый раз.

void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}

int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return (i + 1);
}


🚩BubbleSort

🟠Худший и средний случай: O(n²)
Происходит, когда массив отсортирован в обратном порядке или в случайном порядке.
🟠Лучший случай: O(n)
Происходит, когда массив уже отсортирован. Тогда алгоритм делает только один проход по массиву для проверки.

void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
std::swap(arr[j], arr[j+1]);
}
}
}
}


🚩HeapSort

Худший, средний и лучший случай: O(n log n)
HeapSort всегда имеет сложность O(n log n), так как независимо от начального порядка данных, он сначала строит двоичную кучу (heap), что занимает O(n), а затем повторно извлекает максимальный элемент и перестраивает кучу, что занимает O(log n) на каждый элемент.
void heapify(int arr[], int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;

if (left < n && arr[left] > arr[largest]) {
largest = left;
}

if (right < n && arr[right] > arr[largest]) {
largest = right;
}

if (largest != i) {
std::swap(arr[i], arr[largest]);
heapify(arr, n, largest);
}
}

void heapSort(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}

for (int i = n - 1; i > 0; i--) {
std::swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
}


Ставь 👍 и забирай 📚 Базу знаний

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

26 Jan, 16:10


🤔 За счёт чего достигается средняя константная сложность в хеш-таблицах?

Константная сложность достигается за счёт равномерного распределения ключей по ячейкам таблицы хеш-функцией. Это минимизирует коллизии, а для их обработки применяются цепочки или открытая адресация.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

26 Jan, 09:10


🤔 Какой тип стоит использовать в качестве ключа для денежного эквивалента?

Для хранения денежных эквивалентов в C++ обычно используют тип double или int. В зависимости от требований точности и объема данных может быть предпочтительным использовать либо double, либо int. Рассмотрим подробнее оба варианта: Для представления денежных значений в программе необходимо выбрать тип данных, который будет хранить количество денег с достаточной точностью и возможностью выполнения арифметических операций.

🚩Как это используется?

🟠Использование типа double
Тип double позволяет хранить числа с плавающей точкой двойной точности. Он подходит для большинства финансовых операций, поскольку может хранить дробные значения, которые часто встречаются в денежных расчетах.

🟠Использование типа int
Тип int используется, когда требуется высокая точность и исключение ошибок округления. Часто денежные значения хранятся в целых центах (или копейках), а не в долларах (рублях) и центах (копейках).

🚩Примеры

Пример с double
int main() {
double price = 19.99;
double taxRate = 0.08;
double total = price + (price * taxRate);
std::cout << "Total price: $" << std::fixed << std::setprecision(2) << total << std::endl;
return 0;
}


Пример с int
int main() {
int priceInCents = 1999; // 19.99 dollars
int taxRateInPercent = 8;
int totalInCents = priceInCents + (priceInCents * taxRateInPercent / 100);
std::cout << "Total price in cents: " << totalInCents << std::endl;
std::cout << "Total price in dollars: $" << totalInCents / 100 << "." << totalInCents % 100 << std::endl;
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

25 Jan, 16:10


🤔 Какое время доступа к элементу хеш-таблицы?

Среднее время доступа к элементу хеш-таблицы — O(1), так как используется хеш-функция для вычисления индекса. В худшем случае (при коллизиях) время может возрасти до O(n).

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

25 Jan, 09:10


🤔 Как называется одинаковый результат после применения хэш функции?

Одинаковый результат после применения хэш-функции называется коллизией.

🚩Что такое коллизия?

Коллизия возникает, когда две разные входные величины (ключи) при обработке одной и той же хэш-функцией дают одинаковое хэш-значение. Это проблема, так как хэш-функции обычно используются для быстрого доступа к данным (например, в хэш-таблицах), и одинаковые хэши для разных данных нарушают уникальность ключей.

🚩Почему коллизии возникают?

🟠Ограниченный размер хэш-значений
Хэш-функция генерирует значения фиксированной длины (например, 32 или 64 бита), поэтому существует конечное количество возможных хэшей. Однако количество входных данных, которые можно подать на вход, теоретически бесконечно.
🟠Особенности алгоритма хэширования
Некоторые хэш-функции менее устойчивы к коллизиям из-за их структуры.

🚩Как справляться с коллизиями?

Есть несколько способов обработки коллизий в хэш-таблицах:

🟠Метод цепочек (chaining)
Каждое значение хэша хранит список элементов, которые получили одинаковый хэш. При добавлении нового ключа он просто добавляется в связанный список.
#include <iostream>
#include <list>
#include <vector>
using namespace std;

class HashTable {
int size;
vector<list<int>> table;

public:
HashTable(int s) : size(s), table(s) {}

int hashFunction(int key) {
return key % size;
}

void insert(int key) {
int index = hashFunction(key);
table[index].push_back(key);
}

void display() {
for (int i = 0; i < size; i++) {
cout << i << ": ";
for (int key : table[i]) {
cout << key << " -> ";
}
cout << "NULL" << endl;
}
}
};

int main() {
HashTable ht(5);
ht.insert(10);
ht.insert(15);
ht.insert(20);
ht.insert(7);
ht.insert(2);
ht.display();
return 0;
}


🟠Открытая адресация (open addressing)
При коллизии новое значение записывается в следующую свободную ячейку в таблице. Используются разные стратегии, такие как линейное пробирование, квадратичное пробирование или двойное хэширование.

🟠Использование криптографических хэш-функций
Устойчивые хэш-функции (например, SHA-256) уменьшают вероятность коллизий, но они медленнее и чаще используются в безопасности.

Ставь 👍 и забирай 📚 Базу знаний

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

24 Jan, 16:10


🤔 Какие известны уровни exception гарантий?

1. No-throw Guarantee: метод не выбрасывает исключений (например, деструкторы).
2. Strong Guarantee: при исключении состояние объекта остаётся неизменным.
3. Basic Guarantee: объект остаётся в корректном, но не обязательно неизменном состоянии.
4. No Guarantee: никаких гарантий при выбросе исключений не предоставляется.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

23 Jan, 16:10


🤔 Что известно про exception safety?

Это способность программы корректно обрабатывать исключения, не нарушая её состояние.
1. Исключения должны либо не влиять на состояние объекта, либо откатывать изменения к предыдущему стабильному состоянию.
2. Это важный аспект для написания надёжного и предсказуемого кода


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

23 Jan, 09:10


🤔 Для чего используется ключевое слово volatile?

Используется для обозначения переменных, значение которых может изменяться неожиданно для компилятора. Это может происходить, например, из-за внешних факторов вне контроля программы, таких как аппаратные регистры, прерывания или многопоточные среды. volatile говорит компилятору, что он не должен оптимизировать код, касающийся этих переменных, и всегда должен считывать их значения непосредственно из памяти.

🚩Основные применения

🟠Аппаратные регистры
Переменные, связанные с аппаратными устройствами, такими как порты ввода-вывода, часто меняются вне контроля программы. Использование volatile предотвращает оптимизации, которые могли бы кэшировать значение регистров.
volatile int* port = reinterpret_cast<int*>(0x400);
*port = 42; // Запись в аппаратный регистр


🟠Переменные, изменяемые прерываниями
В системах реального времени и встроенных системах значения переменных могут изменяться в обработчиках прерываний. volatile гарантирует, что каждое обращение к такой переменной будет действительным.
volatile bool interruptFlag = false;

void interruptHandler() {
interruptFlag = true; // Устанавливается в обработчике прерывания
}

void mainFunction() {
while (!interruptFlag) {
// Ожидание установки флага прерывания
}
// Обработка прерывания
}


🟠Многопоточные приложения
Переменные, которые могут изменяться из других потоков, также могут быть помечены как volatile. Однако следует отметить, что volatile не обеспечивает защиту от гонок данных и не заменяет средства синхронизации, такие как мьютексы или атомарные операции.
volatile bool stopThread = false;

void workerThread() {
while (!stopThread) {
// Выполнение работы
}
}

int main() {
std::thread t(workerThread);
// ...
stopThread = true; // Остановка рабочего потока
t.join();
return 0;
}


🚩Особенности и ограничения

🟠Оптимизация компиляции
volatile предотвращает оптимизации компилятором, которые могли бы удалить или кэшировать доступы к переменной.

🟠Не потокобезопасность
volatile не обеспечивает потокобезопасность. Для обеспечения корректной работы в многопоточной среде следует использовать мьютексы, атомарные операции или другие механизмы синхронизации.

🟠Совместное использование с `const`
Переменные могут быть как const volatile, если они не должны изменяться программой, но могут изменяться внешними факторами.

🚩Пример: Аппаратные регистры

#include <iostream>

volatile int* hardwareRegister = reinterpret_cast<int*>(0x400);

void writeToRegister(int value) {
*hardwareRegister = value; // Запись в регистр
}

int readFromRegister() {
return *hardwareRegister; // Чтение из регистра
}

int main() {
writeToRegister(100);
std::cout << "Register value: " << readFromRegister() << std::endl;
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

22 Jan, 16:10


🤔 Почему со стеком работать быстрее, чем с кучей?

1. Стек управляется автоматически: выделение и освобождение памяти выполняются последовательно, без накладных расходов.
2. Операции со стеком (например, выделение памяти) работают быстрее благодаря линейной структуре.
3. Куча требует управления памятью, что приводит к дополнительным затратам на поиск свободных блоков.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

22 Jan, 09:10


🤔 Какая сложность поиска в 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;
}


Ставь 👍 и забирай 📚 Базу знаний

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

21 Jan, 16:10


🤔 Почему не сделать стек вызова очень большим?

1. Большой стек увеличивает потребление памяти, что критично при большом количестве потоков.
2. Увеличение стека может привести к снижению эффективности использования оперативной памяти.
3. Большие стеки не оправданы, так как большинство задач используют небольшой объём памяти.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

21 Jan, 09:10


🤔 Какие есть виды полиморфизма?

В программировании, включая C++, полиморфизм (многоформенность) – это способность объекта или функции принимать разные формы. Полиморфизм является ключевой концепцией объектно-ориентированного программирования (ООП).

🚩Компиляторный (статический) полиморфизм

Этот вид полиморфизма реализуется во время компиляции. Он достигается с помощью перегрузки функций (function overloading) и перегрузки операторов (operator overloading).

🟠Перегрузка функций
В перегрузке функций одна функция имеет несколько определений с разными параметрами.
#include <iostream>

void print(int value) {
std::cout << "Целое число: " << value << std::endl;
}

void print(double value) {
std::cout << "Вещественное число: " << value << std::endl;
}

void print(const std::string& value) {
std::cout << "Строка: " << value << std::endl;
}

int main() {
print(42); // Вызов функции для int
print(3.14); // Вызов функции для double
print("Привет!"); // Вызов функции для строки
return 0;
}


🟠Перегрузка операторов
Перегрузка операторов позволяет определить, как стандартные операторы работают с пользовательскими типами данных.
#include <iostream>

class Complex {
double real, imag;
public:
Complex(double r, double i) : real(r), imag(i) {}

Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}

void display() const {
std::cout << real << " + " << imag << "i" << std::endl;
}
};

int main() {
Complex c1(1.0, 2.0), c2(3.0, 4.0);
Complex c3 = c1 + c2; // Используется перегрузка оператора +
c3.display(); // Вывод: 4 + 6i
return 0;
}


🚩Рантаймный (динамический) полиморфизм

Этот вид полиморфизма проявляется во время выполнения программы. Реализуется с использованием виртуальных функций и наследования.

Виртуальные функции
#include <iostream>

class Animal {
public:
virtual void sound() const { // Виртуальная функция
std::cout << "Некоторый звук" << std::endl;
}
};

class Dog : public Animal {
public:
void sound() const override { // Переопределение
std::cout << "Гав-гав" << std::endl;
}
};

class Cat : public Animal {
public:
void sound() const override { // Переопределение
std::cout << "Мяу" << std::endl;
}
};

void makeSound(const Animal& animal) {
animal.sound(); // Динамическое определение, какой sound() вызывать
}

int main() {
Dog dog;
Cat cat;

makeSound(dog); // Вывод: Гав-гав
makeSound(cat); // Вывод: Мяу
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

20 Jan, 16:10


🤔 Что известно про ограничение размера стека вызовов?

1. Размер стека вызовов ограничен операционной системой и задаётся при создании потока.
2. Ограничение связано с выделением памяти: стек обычно выделяется статически.
3. Это предотвращает неконтролируемое переполнение стека, которое приводит к ошибке stack overflow.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

20 Jan, 09:10


🤔 В каком порядке элементы списка инициализируются в конструкторе после двоеточия?

Порядок инициализации элементов списка инициализации конструктора (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;
}
};


Ставь 👍 и забирай 📚 Базу знаний

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

08 Jan, 16:10


🤔 Зачем нам нужна move семантика?

Move семантика позволяет передавать ресурсы (например, память) от одного объекта другому без копирования.
1. Оптимизация: Уменьшает накладные расходы на копирование, особенно для объектов с большим объёмом данных.
2. Контроль ресурсов: Позволяет явно передать владение ресурсами с помощью конструкций, таких как std::move.
3. Move семантика используется для повышения производительности и предотвращения ненужного дублирования данных.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

08 Jan, 09:10


🤔 Зачем нужен weak_ptr?

std::weak_ptr используется в C++ для решения проблемы циклических ссылок, которые могут возникать при использовании std::shared_ptr, а также для наблюдения за объектами, управляемыми std::shared_ptr, без влияния на время их жизни.

🚩Проблема циклических ссылок

Циклические ссылки возникают, когда два или более объекта содержат std::shared_ptr друг на друга, создавая цикл, который не позволяет счетчикам ссылок достичь нуля. Это приводит к утечкам памяти, так как объекты никогда не удаляются.
#include <iostream>
#include <memory>

class B; // Предварительное объявление

class A {
public:
std::shared_ptr<B> ptrB;
~A() { std::cout << "A destroyed\n"; }
};

class B {
public:
std::shared_ptr<A> ptrA;
~B() { std::cout << "B destroyed\n"; }
};

int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptrB = b;
b->ptrA = a; // Циклическая ссылка: объекты A и B никогда не будут удалены

return 0;
}


🚩Решение с помощью `std::weak_ptr`

std::weak_ptr — это слабый указатель, который не увеличивает счетчик ссылок на объект. Он позволяет разорвать цикл, наблюдая за объектом без участия в его управлении временем жизни.
#include <iostream>
#include <memory>

class B; // Предварительное объявление

class A {
public:
std::shared_ptr<B> ptrB;
~A() { std::cout << "A destroyed\n"; }
};

class B {
public:
std::weak_ptr<A> ptrA; // Использование weak_ptr разрывает цикл
~B() { std::cout << "B destroyed\n"; }
};

int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptrB = b;
b->ptrA = a; // Цикл разорван, объекты будут корректно удалены

return 0;
}


🚩Плюсы

Предотвращение утечек памяти
Как показано в примере выше, std::weak_ptr предотвращает циклические ссылки, которые могут привести к утечкам памяти.

Безопасный доступ к объекту
std::weak_ptr можно использовать для безопасного доступа к объекту, проверяя, существует ли он, прежде чем использовать его. Это достигается с помощью метода lock(), который возвращает std::shared_ptr если объект существует, или пустой std::shared_ptr, если объект был уже уничтожен.

Использование в кэшировании
std::weak_ptr часто используется в кэшах для наблюдения за объектами, управляемыми std::shared_ptr, без предотвращения их удаления, если они больше не используются.

Пример безопасного доступа с использованием std::weak_ptr
#include <iostream>
#include <memory>

class MyClass {
public:
void sayHello() const { std::cout << "Hello from MyClass\n"; }
};

int main() {
std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();
std::weak_ptr<MyClass> wp = sp;

if (std::shared_ptr<MyClass> locked = wp.lock()) {
locked->sayHello();
} else {
std::cout << "Object has been destroyed\n";
}

sp.reset(); // Уничтожение объекта

if (std::shared_ptr<MyClass> locked = wp.lock()) {
locked->sayHello();
} else {
std::cout << "Object has been destroyed\n";
}

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

07 Jan, 16:10


🤔 Как можно хранить денежный эквивалент в integer?

Денежный эквивалент можно хранить в integer, используя минимальную денежную единицу (например, центы или копейки).
1. Вместо долларов или рублей, значения хранятся в их целочисленном представлении (например, 1 доллар = 100 центов).
2. Это позволяет избежать ошибок округления и неточностей, связанных с числами с плавающей точкой.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

07 Jan, 09:10


🤔 Что такое MVP ?

Архитектурный шаблон, разделяющий приложение на три компонента: модель, представление и презентер.

🚩Основные компоненты

🟠Model (Модель)
Управляет данными и бизнес-логикой.
class UserModel {
private:
std::string name;
int age;

public:
std::string getName() const { return name; }
void setName(const std::string& newName) { name = newName; }

int getAge() const { return age; }
void setAge(int newAge) { age = newAge; }
};


🟠View (Представление)
Отображает данные и взаимодействует с пользователем.
class IUserView {
public:
virtual void displayUser(const std::string& name, int age) = 0;
virtual ~IUserView() = default;
};

class UserView : public IUserView {
public:
void displayUser(const std::string& name, int age) override {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};


🟠Presenter (Презентер)
Управляет логикой взаимодействия между моделью и представлением.
class UserPresenter {
private:
UserModel model;
IUserView& view;

public:
UserPresenter(UserModel m, IUserView& v) : model(m), view(v) {}

void setUserName(const std::string& name) {
model.setName(name);
updateView();
}

void setUserAge(int age) {
model.setAge(age);
updateView();
}

void updateView() {
view.displayUser(model.getName(), model.getAge());
}
};


Пример использования
int main() {
UserModel model;
UserView view;
UserPresenter presenter(model, view);

presenter.setUserName("John Doe");
presenter.setUserAge(30);

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

06 Jan, 16:10


🤔 Как удалить элемент из vector за константу, если не важен порядок?

Для удаления элемента из std::vector за O(1) можно заменить удаляемый элемент последним элементом и затем вызвать pop_back(). Это нарушает порядок, но сохраняет быструю производительность.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

06 Jan, 09:10


🤔 Что такое MVC ?

Это архитектурный шаблон, разделяющий приложение на три компонента: модель, представление и контроллер.

🚩Основные компоненты

🟠Model (Модель)
Управляет данными и бизнес-логикой.
class UserModel {
private:
std::string name;
int age;

public:
std::string getName() const { return name; }
void setName(const std::string& newName) { name = newName; }

int getAge() const { return age; }
void setAge(int newAge) { age = newAge; }
};


🟠View (Представление)
Отвечает за отображение данных пользователю.
class UserView {
public:
void displayUser(const std::string& name, int age) {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};


🟠Controller (Контроллер)
Обрабатывает ввод пользователя, обновляет модель и представление.
class UserController {
private:
UserModel model;
UserView view;

public:
UserController(const UserModel& m, const UserView& v) : model(m), view(v) {}

void setUserName(const std::string& name) {
model.setName(name);
}

void setUserAge(int age) {
model.setAge(age);
}

void updateView() {
view.displayUser(model.getName(), model.getAge());
}
};


Пример использования
int main() {
UserModel model;
UserView view;
UserController controller(model, view);

controller.setUserName("John Doe");
controller.setUserAge(30);
controller.updateView();

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

05 Jan, 16:10


🤔 Какой используется алгоритм в функции sort?

В нем используется гибридный алгоритм, основанный на Introsort. Это комбинация быстрой сортировки (QuickSort), сортировки вставками (InsertionSort) и сортировки слиянием (HeapSort), что позволяет добиться высокой производительности и устойчивости к худшим случаям.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

05 Jan, 09:10


🤔 Как вы будете реализовывать 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;


🚩Плюсы

Ленивая инициализация
Экономит ресурсы, так как объект создается только при необходимости.
Инициализация при запуске
Простая реализация, не требует дополнительных проверок, но объект создается даже если не используется.
Многопоточная безопасность
Обеспечивает корректное создание объекта в многопоточной среде, но сложнее в реализации.

Ставь 👍 и забирай 📚 Базу знаний

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

04 Jan, 16:10


🤔 Что нужно сделать, чтобы хранить свою структуру в map?

1. Реализовать оператор сравнения (operator<), так как map использует этот оператор для сортировки ключей.
2. Обеспечить, чтобы ваш оператор соблюдал строгий порядок (например, транзитивность и антисимметричность).
3. Альтернативно можно передать кастомный компаратор при создании map.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

04 Jan, 09:10


🤔 Что знаете о нотациях Big Endian, Little Endian, Middle Endian ?

Эти нотации определяют порядок байтов при хранении данных в памяти. Они важны для совместимости и правильной интерпретации данных между различными системами и архитектурами.

🚩Big Endian

В Big Endian порядок байтов таков, что старший байт (Most Significant Byte, MSB) хранится в самой младшей (первой) позиции памяти.

🟠Пример
Адрес 0: 0x12
Адрес 1: 0x34
Адрес 2: 0x56
Адрес 3: 0x78

🟠Использование
Big Endian порядок используется в сетевых протоколах и некоторых RISC-процессорах (например, SPARC).

🚩Little Endian

В Little Endian порядок байтов таков, что младший байт (Least Significant Byte, LSB) хранится в самой младшей (первой) позиции памяти.

🟠Пример
Если 32-битное значение 0x12345678 хранится в памяти, байты будут расположены следующим образом:
Адрес 0: 0x78
Адрес 1: 0x56
Адрес 2: 0x34
Адрес 3: 0x12

🟠Использование
Little Endian порядок используется в архитектурах x86 и ARM.

🚩Middle Endian

В Middle Endian байты хранятся в перемешанном порядке, который не является ни Big Endian, ни Little Endian. Это менее распространенный порядок и используется в специфических системах или форматах данных.

🟠Пример
Существует несколько вариантов Middle Endian. Один из них — "PDP-endian", используемый в некоторых старых системах, таких как PDP-11:
Адрес 0: 0x34
Адрес 1: 0x12
Адрес 2: 0x78
Адрес 3: 0x56

Ставь 👍 и забирай 📚 Базу знаний

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

03 Jan, 16:10


🤔 Что нужно сделать, чтобы хранить свою структуру в unordered_map?

1. Определить функцию хеширования для вашей структуры (путём реализации std::hash или создания собственной).
2. Реализовать оператор равенства (operator==) для корректного сравнения элементов.
3. Зарегистрировать хеш-функцию и оператор равенства для использования с unordered_map.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

03 Jan, 09:10


🤔 Как умножить или разделить на 2 целое число, используя битовые операции ?

Можно эффективно умножать и делить целые числа на 2 с помощью битовых операций. Для этого используются операции сдвига.

🚩Умножение на 2

Для умножения числа на 2 используется операция сдвига влево (<<). Каждый сдвиг влево на один бит эквивалентен умножению числа на 2.
int number = 5;        // 0101 в двоичном виде
int result = number << 1; // 1010 в двоичном виде, эквивалентно 10


🚩Деление на 2

Для деления числа на 2 используется операция сдвига вправо (>>). Каждый сдвиг вправо на один бит эквивалентен делению числа на 2.
int number = 10;       // 1010 в двоичном виде
int result = number >> 1; // 0101 в двоичном виде, эквивалентно 5


🚩Примеры кода

Умножение на 2
#include <iostream>

int main() {
int number = 5;
int result = number << 1;
std::cout << "Умножение на 2: " << result << std::endl; // Вывод: 10
return 0;
}


Деление на 2
#include <iostream>

int main() {
int number = 10;
int result = number >> 1;
std::cout << "Деление на 2: " << result << std::endl; // Вывод: 5
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

02 Jan, 16:10


🤔 Что можно придумать на замену float для хранения денежного эквивалента?

1. Использовать целые числа (например, хранить суммы в центах вместо долларов).
2. Применять библиотеки для работы с фиксированной точностью, такие как Decimal в Python или BigDecimal в Java.
3. Это помогает избежать ошибок округления и обеспечивает точность вычислений.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

02 Jan, 09:10


🤔 Что такое deadlock ?

Это ситуация в многозадачной системе, при которой два или более процесса блокируют друг друга, ожидая освобождения ресурсов, занятых друг другом. В результате ни один из процессов не может продолжить выполнение.

🚩Условия для возникновения

🟠Взаимное исключение (Mutual Exclusion)
Ресурс не может быть одновременно использован более чем одним процессом.
🟠Удержание и ожидание (Hold and Wait)
Процесс удерживает один ресурс и ожидает другой.
🟠Отсутствие принудительного освобождения (No Preemption)
Ресурсы не могут быть отобраны принудительно.
🟠Циклическое ожидание (Circular Wait)
Существует замкнутая цепочка процессов, где каждый процесс ожидает ресурс, занятый следующим процессом.

Два процесса захватывают два ресурса в разном порядке
#include <iostream>
#include <thread>
#include <mutex>

std::mutex resourceA;
std::mutex resourceB;

void process1() {
std::lock_guard<std::mutex> lockA(resourceA);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lockB(resourceB);
std::cout << "Process 1 finished" << std::endl;
}

void process2() {
std::lock_guard<std::mutex> lockB(resourceB);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lockA(resourceA);
std::cout << "Process 2 finished" << std::endl;
}

int main() {
std::thread t1(process1);
std::thread t2(process2);
t1.join();
t2.join();
return 0;
}


🚩Способы предотвращения

🟠Избегание циклического ожидания
Назначение порядка для захвата ресурсов.
🟠Использование тайм-аутов
Установка времени ожидания для захвата ресурсов.
🟠Избегание удержания и ожидания
Захват всех ресурсов сразу или освобождение всех, если не удалось захватить все необходимые.

Ставь 👍 и забирай 📚 Базу знаний

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

01 Jan, 16:10


🤔 Хорошая ли идея хранить float в качестве ключа контейнера?

Нет, это плохая идея.

1. Числа с плавающей точкой имеют проблемы с точностью, что может привести к неправильным результатам сравнения.
2. Например, два числа, которые должны быть равными, могут иметь незначительную разницу из-за округления, и контейнер будет считать их разными.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

01 Jan, 09:10


🤔 Что такое состояние гонки?

Это ситуация в многопоточной или многозадачной среде, когда результат выполнения программы зависит от неконтролируемого порядка выполнения потоков или процессов. Состояние гонки возникает, когда два или более потоков или процессов одновременно выполняют операции над общими ресурсами без должной синхронизации, что приводит к непредсказуемому поведению программы.

#include <iostream>
#include <thread>

int counter = 0;

void increment() {
for (int i = 0; i < 100000; ++i) {
++counter;
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();

std::cout << "Final counter value: " << counter << std::endl;
return 0;
}


🚩Почему возникает состояние гонки

В данном примере оба потока t1 и t2 одновременно изменяют значение переменной counter. Поскольку операции инкремента ++counter не атомарные (т.е., состоят из нескольких шагов: чтение, увеличение и запись), одновременное выполнение этих операций может привести к некорректным результатам. Итоговое значение counter может быть меньше ожидаемого 200000, поскольку некоторые инкременты могут "потеряться".

🚩Как предотвратить состояние гонки

Для предотвращения состояния гонки необходимо обеспечить правильную синхронизацию доступа к общим ресурсам. Один из способов сделать это — использовать мьютексы (mutexes).
Использование мьютекса
#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mtx;

void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();

std::cout << "Final counter value: " << counter << std::endl;
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

31 Dec, 16:10


🤔 Как бы сравнил два float на равенство?

Сравнение двух float выполняется с учётом допустимой погрешности:
1. Вычисляется разница между числами.
2. Если абсолютное значение разницы меньше заданного эпсилон (например, 1e-9), числа считаются равными.
Прямое сравнение может быть неточным из-за особенностей хранения чисел с плавающей точкой.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

31 Dec, 09:10


🤔 Можно ли в С++ объявить указатель на функцию?

Аббревиатура SOLID представляет собой набор из пяти принципов объектно-ориентированного программирования и проектирования, которые помогают создавать более понятные, гибкие и поддерживаемые системы.

🚩S - Single Responsibility Principle (Принцип единственной ответственности)

Принцип единственной ответственности утверждает, что у класса должна быть только одна причина для изменения, то есть класс должен иметь только одну обязанность или ответственность. Согласно принципу единственной ответственности, методы printReport и generateReport должны быть разделены на два разных класса, так как они имеют разные причины для изменения (например, изменения в логике печати и генерации отчета).
class Report {
public:
void printReport() {
// Код для печати отчета
}

void generateReport() {
// Код для генерации отчета
}
};


🚩O - Open/Closed Principle (Принцип открытости/закрытости)

Принцип открытости/закрытости гласит, что программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации. Добавление новых фигур, таких как Triangle, не потребует изменения существующих классов.
class Shape {
public:
virtual double area() const = 0;
};

class Circle : public Shape {
public:
Circle(double radius) : radius(radius) {}
double area() const override {
return 3.14159 * radius * radius;
}

private:
double radius;
};

class Rectangle : public Shape {
public:
Rectangle(double width, double height) : width(width), height(height) {}
double area() const override {
return width * height;
}

private:
double width;
double height;
};


🚩L - Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

Принцип подстановки Барбары Лисков утверждает, что объекты базового класса должны быть заменяемыми объектами производного класса без изменения правильности программы. Другими словами, функции, использующие указатели или ссылки на базовый класс, должны иметь возможность использовать объекты производных классов без необходимости знать об этом. Страус не может летать, поэтому наследование от Bird нарушает принцип подстановки. Лучше создать базовый класс Bird без метода fly или разделить классы.
class Bird {
public:
virtual void fly() = 0;
};

class Sparrow : public Bird {
public:
void fly() override {
// Реализация полета воробья
}
};

class Ostrich : public Bird {
public:
void fly() override {
// Страус не может летать, нарушение Liskov Substitution Principle
}
};


🚩I - Interface Segregation Principle (Принцип разделения интерфейса)

Принцип разделения интерфейса гласит, что клиенты не должны зависеть от интерфейсов, которые они не используют. Это означает, что большие интерфейсы должны быть разделены на более мелкие и специфичные, чтобы клиенты использовали только те методы, которые им нужны. Роботы не едят, поэтому интерфейс IWorker должен быть разделен на два более специфичных интерфейса.
class IWorker {
public:
virtual void work() = 0;
virtual void eat() = 0;
};

class Worker : public IWorker {
public:
void work() override {
// Работает
}

void eat() override {
// Ест
}
};

class Robot : public IWorker {
public:
void work() override {
// Работает
}

void eat() override {
// Роботы не едят, нарушение Interface Segregation Principle
}
};


Ставь 👍 и забирай 📚 Базу знаний

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

30 Dec, 16:10


🤔 Какое преимущество у vector перед list?

Обеспечивает быстрый доступ к элементам по индексу за O(1), тогда как в std::list доступ осуществляется за O(n). Также vector использует непрерывную область памяти, что уменьшает накладные расходы и улучшает производительность при кэшировании.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

30 Dec, 09:10


🤔 Можно ли в С++ объявить указатель на функцию?

Да, можно объявить указатель на функцию. Указатели на функции позволяют хранить адреса функций и вызывать их через этот указатель. Это может быть полезно для создания обратных вызовов (callbacks), реализации таблиц функций и динамического выбора функций во время выполнения.

🚩Объявление указателя на функцию

return_type (*pointer_name)(parameter_types);


return_type — это тип возвращаемого значения функции.
pointer_name — это имя указателя на функцию.
parameter_types — это список типов параметров функции.
#include <iostream>

// Обычная функция, которую мы будем использовать
void printMessage(const std::string& message) {
std::cout << message << std::endl;
}

int main() {
// Объявление указателя на функцию
void (*funcPtr)(const std::string&);

// Инициализация указателя на функцию
funcPtr = &printMessage;

// Вызов функции через указатель
funcPtr("Hello, World!"); // Вывод: Hello, World!

return 0;
}


🚩Указатели на функции и массивы

Указатели на функции также могут использоваться в массивах для хранения нескольких функций и динамического выбора функции для вызова.
#include <iostream>

void function1() {
std::cout << "Function 1" << std::endl;
}

void function2() {
std::cout << "Function 2" << std::endl;
}

void function3() {
std::cout << "Function 3" << std::endl;
}

int main() {
// Объявление и инициализация массива указателей на функции
void (*funcArray[3])() = { function1, function2, function3 };

// Вызов функций через указатели в массиве
for (int i = 0; i < 3; ++i) {
funcArray[i](); // Вывод: Function 1, Function 2, Function 3
}

return 0;
}


🚩Использование указателей на функции в структурах и классах

Указатели на функции можно использовать в структурах и классах для создания динамических систем вызова методов.
#include <iostream>

struct Operation {
double (*operation)(double, double);
};

double add(double a, double b) {
return a + b;
}

double subtract(double a, double b) {
return a - b;
}

int main() {
Operation op;

op.operation = add;
std::cout << "Addition: " << op.operation(5, 3) << std::endl; // Вывод: Addition: 8

op.operation = subtract;
std::cout << "Subtraction: " << op.operation(5, 3) << std::endl; // Вывод: Subtraction: 2

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

29 Dec, 16:10


🤔 На каких контейнерах реализованы stack и queue?

Реализованы как адаптеры контейнеров, обычно на основе std::deque по умолчанию. Однако их можно настроить для работы с std::vector или другими контейнерами, если это поддерживается.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

29 Dec, 09:10


🤔 Для чего используется ключевое слово decltype?

Используется для определения типа выражения на этапе компиляции. Оно позволяет вам получить тип переменной или выражения без необходимости явно указывать этот тип, что особенно полезно при использовании шаблонов и метапрограммирования.

🚩Основные применения

🟠Определение типа переменной
decltype позволяет определить тип переменной, основываясь на типе выражения или другой переменной. Это полезно для объявления переменных, тип которых зависит от сложных выражений или шаблонов.
int x = 10;
decltype(x) y = 20; // y имеет тип int


🟠Использование в шаблонах
decltype можно использовать для определения типов внутри шаблонов, делая шаблоны более гибкими и мощными.
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}

int main() {
auto result = add(1, 2.5); // result имеет тип double
return 0;
}


🟠Комбинация с `auto`
auto и decltype часто используются вместе для определения возвращаемого типа функции, особенно когда возвращаемый тип сложен или неизвестен заранее.
auto getValue() -> decltype(5.0) {
return 5.0;
}


🟠Инферирование типов в выражениях
decltype позволяет выводить типы сложных выражений, что полезно для обобщенных алгоритмов и метапрограммирования.
struct S {
int value;
};

int main() {
S s;
decltype(s.value) newValue = 10; // newValue имеет тип int
return 0;
}


🚩Примеры использования

1⃣Определение типа выражения
#include <iostream>

int main() {
int a = 10;
double b = 5.5;

decltype(a + b) result = a + b; // result имеет тип double

std::cout << "Result: " << result << std::endl; // Вывод: Result: 15.5
return 0;
}


2⃣Использование в шаблонах
#include <iostream>
#include <vector>

template <typename Container>
auto getFirstElement(Container& container) -> decltype(container[0]) {
return container[0];
}

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
int first = getFirstElement(vec); // first имеет тип int

std::cout << "First element: " << first << std::endl; // Вывод: First element: 1
return 0;
}


3⃣Сложные выражения
#include <iostream>

struct S {
int x;
double y;
};

int main() {
S s;
decltype(s.x + s.y) result = s.x + s.y; // result имеет тип double

std::cout << "Result: " << result << std::endl;
return 0;
}


🚩Плюсы

Удобство
Позволяет избежать явного указания типов, которые могут быть сложными или длинными.
Гибкость
Упрощает работу с шаблонами, делая их более обобщенными и мощными.
Безопасность типов
Обеспечивает безопасность типов на этапе компиляции, уменьшая вероятность ошибок, связанных с несоответствием типов.

Ставь 👍 и забирай 📚 Базу знаний

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

28 Dec, 16:10


🤔 Зачем компилятор помечает деструктор как noexcept?

Деструкторы помечаются как noexcept по умолчанию, чтобы гарантировать, что при уничтожении объекта не будут выброшены исключения. Это важно для корректной работы в случаях, когда деструкторы вызываются в процессе обработки исключений. Если деструктор выбросит исключение в такой ситуации, программа может завершиться аварийно.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

28 Dec, 09:10


🤔 Для чего используется ключевое слово volatile?

Используется для обозначения переменных, значение которых может изменяться неожиданно для компилятора. Это может происходить, например, из-за внешних факторов вне контроля программы, таких как аппаратные регистры, прерывания или многопоточные среды. volatile говорит компилятору, что он не должен оптимизировать код, касающийся этих переменных, и всегда должен считывать их значения непосредственно из памяти.

🚩Основные применения

🟠Аппаратные регистры
Переменные, связанные с аппаратными устройствами, такими как порты ввода-вывода, часто меняются вне контроля программы. Использование volatile предотвращает оптимизации, которые могли бы кэшировать значение регистров.
volatile int* port = reinterpret_cast<int*>(0x400);
*port = 42; // Запись в аппаратный регистр


🟠Переменные, изменяемые прерываниями
В системах реального времени и встроенных системах значения переменных могут изменяться в обработчиках прерываний. volatile гарантирует, что каждое обращение к такой переменной будет действительным.
volatile bool interruptFlag = false;

void interruptHandler() {
interruptFlag = true; // Устанавливается в обработчике прерывания
}

void mainFunction() {
while (!interruptFlag) {
// Ожидание установки флага прерывания
}
// Обработка прерывания
}


🟠Многопоточные приложения
Переменные, которые могут изменяться из других потоков, также могут быть помечены как volatile. Однако следует отметить, что volatile не обеспечивает защиту от гонок данных и не заменяет средства синхронизации, такие как мьютексы или атомарные операции.
volatile bool stopThread = false;

void workerThread() {
while (!stopThread) {
// Выполнение работы
}
}

int main() {
std::thread t(workerThread);
// ...
stopThread = true; // Остановка рабочего потока
t.join();
return 0;
}


🚩Особенности и ограничения

🟠Оптимизация компиляции
volatile предотвращает оптимизации компилятором, которые могли бы удалить или кэшировать доступы к переменной.

🟠Не потокобезопасность
volatile не обеспечивает потокобезопасность. Для обеспечения корректной работы в многопоточной среде следует использовать мьютексы, атомарные операции или другие механизмы синхронизации.

🟠Совместное использование с `const`
Переменные могут быть как const volatile, если они не должны изменяться программой, но могут изменяться внешними факторами.

🚩Пример: Аппаратные регистры

#include <iostream>

volatile int* hardwareRegister = reinterpret_cast<int*>(0x400);

void writeToRegister(int value) {
*hardwareRegister = value; // Запись в регистр
}

int readFromRegister() {
return *hardwareRegister; // Чтение из регистра
}

int main() {
writeToRegister(100);
std::cout << "Register value: " << readFromRegister() << std::endl;
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

27 Dec, 16:10


🤔 Что за ключевое слово noexcept?

Это спецификатор, который указывает, что функция не будет выбрасывать исключений. Если такая функция всё же выбросит исключение, программа завершится аварийно через std::terminate. Он помогает компилятору оптимизировать код и использовать такие функции в контекстах, где исключения недопустимы.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

27 Dec, 09:10


🤔 Для чего используется ключевое слово mutable?

Используется для объявления данных-членов класса, которые могут быть изменены, даже если они принадлежат объекту, объявленному как const. Это исключение из правила, что const объект не может изменять свои члены. mutable позволяет изменять конкретные данные-члены внутри const методов или когда объект сам является const.

🚩Основные применения

🟠Изменение состояния в `const` методах
В const методах, которые по контракту не должны изменять состояние объекта, иногда необходимо изменять некоторые внутренние данные, такие как кэшированные значения, счетчики или флаги. Использование mutable позволяет это делать.

🟠Логические константы
Это концепция, при которой объект логически остается неизменным с точки зрения внешнего наблюдателя, но внутренние детали реализации могут изменяться для оптимизации или учета состояния.

🚩Примеры использования

Счетчик использования метода
#include <iostream>

class Data {
private:
int value;
mutable int accessCount; // Счетчик доступа объявлен как mutable

public:
Data(int v) : value(v), accessCount(0) {}

int getValue() const {
++accessCount; // Можно изменить, так как accessCount объявлен как mutable
return value;
}

int getAccessCount() const {
return accessCount;
}
};

int main() {
Data d(42);
std::cout << d.getValue() << std::endl; // Вывод: 42
std::cout << d.getValue() << std::endl; // Вывод: 42
std::cout << "Access count: " << d.getAccessCount() << std::endl; // Вывод: Access count: 2
return 0;
}


Кэширование результатов вычислений
#include <iostream>

class ExpensiveComputation {
private:
mutable bool isCached;
mutable int cachedResult;
int input;

public:
ExpensiveComputation(int i) : isCached(false), cachedResult(0), input(i) {}

int compute() const {
if (!isCached) {
// Имитируем дорогостоящее вычисление
cachedResult = input * input;
isCached = true;
}
return cachedResult;
}
};

int main() {
ExpensiveComputation ec(5);
std::cout << ec.compute() << std::endl; // Вывод: 25
std::cout << ec.compute() << std::endl; // Вывод: 25 (использует кэшированное значение)
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

26 Dec, 16:10


🤔 Cout сразу пишет в файловый дескриптор или нет?

Нет, он не пишет сразу в файловый дескриптор. Он использует буферизацию, что означает, что данные сначала записываются в буфер и только потом, при его заполнении или явноaм вызове std::flush, отправляются в файловый дескриптор. Это повышает производительность операций ввода-вывода.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

26 Dec, 09:10


🤔 Что такое SFINAE?

Это концепция, которая играет важную роль в шаблонном программировании. Она позволяет компилятору игнорировать неподходящие шаблоны и продолжать поиск других подходящих, вместо того чтобы выдавать ошибку компиляции. Это приводит к более гибким и мощным шаблонным конструкциям.

Использование std::enable_if
#include <iostream>
#include <type_traits>

// Функция для целочисленных типов
template <typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
print(T value) {
std::cout << "Integral type: " << value << std::endl;
}

// Функция для плавающих типов
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, void>::type
print(T value) {
std::cout << "Floating point type: " << value << std::endl;
}

int main() {
print(5); // Вывод: Integral type: 5
print(3.14); // Вывод: Floating point type: 3.14
return 0;
}


🟠Встроенные механизмы SFINAE
SFINAE также может быть использован с использованием встроенных в C++ механизма проверки типов, таких как определение наличия методов или членов класса.
#include <iostream>
#include <type_traits>

// Проверка наличия метода size
template <typename T>
class has_size_method {
private:
template <typename U>
static auto check(int) -> decltype(std::declval<U>().size(), std::true_type{});

template <typename>
static std::false_type check(...);

public:
static const bool value = decltype(check<T>(0))::value;
};

// Функция для типов с методом size
template <typename T>
typename std::enable_if<has_size_method<T>::value, void>::type
print_size(const T& obj) {
std::cout << "Size: " << obj.size() << std::endl;
}

// Функция для типов без метода size
template <typename T>
typename std::enable_if<!has_size_method<T>::value, void>::type
print_size(const T& obj) {
std::cout << "No size method available" << std::endl;
}

int main() {
std::vector<int> vec = {1, 2, 3, 4};
int arr[] = {1, 2, 3, 4};

print_size(vec); // Вывод: Size: 4
print_size(arr); // Вывод: No size method available

return 0;
}


🚩Плюсы

Гибкость
SFINAE позволяет создавать более универсальные и гибкие шаблоны.
Безопасность типов
Код, использующий SFINAE, более безопасен с точки зрения типов, так как неверные подстановки приводят к исключению неподходящих шаблонов, а не к ошибке компиляции.
Улучшение читаемости и поддержки кода
Шаблоны, использующие SFINAE, могут быть легче поддерживать и расширять, так как они явно выражают намерения разработчика.

Ставь 👍 и забирай 📚 Базу знаний

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

25 Dec, 09:10


🤔 Для чего может применяться в С++ виртуальное наследование?

Виртуальное наследование применяется для решения проблемы "ромбовидного наследования" (или "diamond problem"), которая возникает в иерархиях классов с множественным наследованием. Это проблема возникает, когда класс наследуется от двух классов, которые в свою очередь наследуются от одного общего базового класса. В результате создаются две копии базового класса в наследнике, что приводит к неоднозначностям и увеличению использования памяти.

#include <iostream>

class A {
public:
void show() {
std::cout << "Class A" << std::endl;
}
};

class B : public A {};
class C : public A {};
class D : public B, public C {};

int main() {
D obj;
// obj.show(); // Ошибка: неоднозначность, show() есть и в B, и в C
return 0;
}


🚩Решение проблемы с использованием виртуального наследования

Виртуальное наследование решает эту проблему, гарантируя, что в иерархии будет только одна копия базового класса. Это достигается с помощью ключевого слова virtual при наследовании.
#include <iostream>

class A {
public:
void show() {
std::cout << "Class A" << std::endl;
}
};

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

int main() {
D obj;
obj.show(); // Корректно: вызов метода show() из A
return 0;
}


🚩Как работает виртуальное наследование

🟠Одна копия базового класса
При виртуальном наследовании в наследуемом классе создается только одна копия базового класса, независимо от того, сколько раз он виртуально наследуется.

🟠Оптимизация памяти
Виртуальное наследование предотвращает создание множества копий базового класса, что экономит память.

🟠Явное разрешение неоднозначности
Виртуальное наследование устраняет неоднозначности, связанные с вызовом методов и доступом к членам базового класса.

🟠Пример с конструкторами
При виртуальном наследовании конструкторы базовых классов вызываются инициализирующим классом.
#include <iostream>

class A {
public:
A() {
std::cout << "Constructor of A" << std::endl;
}
};

class B : virtual public A {
public:
B() {
std::cout << "Constructor of B" << std::endl;
}
};

class C : virtual public A {
public:
C() {
std::cout << "Constructor of C" << std::endl;
}
};

class D : public B, public C {
public:
D() {
std::cout << "Constructor of D" << std::endl;
}
};

int main() {
D obj; // Вывод: Constructor of A, Constructor of B, Constructor of C, Constructor of D
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний

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

24 Dec, 16:10


🤔 Из чего состоит shared_ptr?

shared_ptr состоит из указателя на объект и счетчика ссылок, управляющего временем жизни объекта. Когда количество ссылок становится нулевым, shared_ptr автоматически удаляет объект. Он также поддерживает совместное использование ресурсов между несколькими владельцами, что делает его удобным для совместного владения.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

24 Dec, 09:10


🤔 Какие 2 функции мы можем использовать в С++ для идентификации типов во времени выполнения программы?

Для идентификации типов во время выполнения программы используются две ключевые функции: typeid и dynamic_cast. Они принадлежат к системе динамического типа информации (RTTI - Run-Time Type Information), которая позволяет определять тип объектов и выполнять безопасное приведение типов.

🚩`typeid`

Это оператор, который используется для получения типа объекта во время выполнения программы. Он возвращает объект типа std::type_info, который содержит информацию о типе объекта.
#include <iostream>
#include <typeinfo>

class Base {
public:
virtual ~Base() = default; // Виртуальный деструктор для поддержки RTTI
};

class Derived : public Base {};

int main() {
Base* base = new Base();
Base* derived = new Derived();

std::cout << "Type of base: " << typeid(*base).name() << std::endl;
std::cout << "Type of derived: " << typeid(*derived).name() << std::endl;

delete base;
delete derived;

return 0;
}


🚩Основные особенности `typeid`

🟠Определение типа объектов во время выполнения
typeid используется для получения информации о типе объекта во время выполнения.

🟠Поддержка полиморфизма
Для корректной работы с полиморфными объектами (т.е. объектами, у которых есть виртуальные методы), базовый класс должен иметь хотя бы один виртуальный метод.

🟠Возвращает `std::type_info`
Объект, возвращаемый typeid, содержит информацию о типе, включая его имя.

🚩`dynamic_cast`

Это оператор приведения типа, который используется для безопасного приведения указателей или ссылок к типу базового или производного класса во время выполнения программы. Он проверяет корректность приведения типов и возвращает нулевой указатель (или выбрасывает исключение) при неудачном приведении.
#include <iostream>

class Base {
public:
virtual ~Base() = default; // Виртуальный деструктор для поддержки RTTI
};

class Derived : public Base {
public:
void derivedMethod() {
std::cout << "Method of Derived class" << std::endl;
}
};

void checkType(Base* base) {
if (Derived* derived = dynamic_cast<Derived*>(base)) {
std::cout << "Base is actually a Derived" << std::endl;
derived->derivedMethod();
} else {
std::cout << "Base is not a Derived" << std::endl;
}
}

int main() {
Base* base = new Base();
Base* derived = new Derived();

checkType(base); // Вывод: Base is not a Derived
checkType(derived); // Вывод: Base is actually a Derived
// Method of Derived class

delete base;
delete derived;

return 0;
}


🚩Основные особенности `dynamic_cast`

🟠Безопасное приведение типов
dynamic_cast используется для приведения типов с проверкой во время выполнения. Если приведение невозможно, он возвращает nullptr для указателей или выбрасывает исключение std::bad_cast для ссылок.

🟠Работа с полиморфными типами
Базовый класс должен иметь хотя бы один виртуальный метод для поддержки dynamic_cast.

🟠Использование для нисходящего приведения
dynamic_cast наиболее полезен для приведения указателей и ссылок к производным типам (нисходящее приведение).

Ставь 👍 и забирай 📚 Базу знаний

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

23 Dec, 16:10


🤔 Что такое виртуальное наследование?

Виртуальное наследование используется для предотвращения проблемы ромбовидного наследования, когда базовый класс наследуется через несколько цепочек. Оно гарантирует, что один объект базового класса будет включён в наследующем классе, устраняя дублирование.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

23 Dec, 09:10


🤔 Может ли шаблон иметь не фиксированное число аргументов?

Да, шаблоны могут иметь нефиксированное число аргументов. Это достигается с помощью вариадических шаблонов, введенных в стандарт C++11. Вариадические шаблоны позволяют создавать шаблоны, принимающие произвольное количество аргументов, что обеспечивает большую гибкость и универсальность.

🚩Вариадические шаблоны

Позволяют шаблону принимать произвольное количество типов и значений. Синтаксически это достигается с использованием шаблонных параметров с троеточием (...).
#include <iostream>

// Базовый случай: функция без аргументов
void print() {
std::cout << std::endl;
}

// Шаблонная функция с вариадическими параметрами
template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << " "; // Печать первого аргумента
print(args...); // Рекурсивный вызов для оставшихся аргументов
}

int main() {
print(1, 2, 3); // Вывод: 1 2 3
print("Hello", 1, 2.5); // Вывод: Hello 1 2.5

return 0;
}


🚩Шаблоны классов с вариадическими параметрами

Вариадические шаблоны также можно использовать в шаблонах классов, что позволяет создавать классы с произвольным количеством параметров шаблона.
#include <iostream>
#include <tuple>

// Шаблонный класс с вариадическими параметрами
template <typename... Args>
class MyClass {
public:
std::tuple<Args...> data;

MyClass(Args... args) : data(std::make_tuple(args...)) {}

void print() {
printTuple(data);
}

private:
// Функция для печати кортежа
template <typename Tuple, std::size_t... Is>
void printTupleImpl(const Tuple& t, std::index_sequence<Is...>) {
((std::cout << std::get<Is>(t) << " "), ...);
std::cout << std::endl;
}

template <typename... Ts>
void printTuple(const std::tuple<Ts...>& t) {
printTupleImpl(t, std::index_sequence_for<Ts...>{});
}
};

int main() {
MyClass<int, double, std::string> myObject(1, 2.5, "Hello");
myObject.print(); // Вывод: 1 2.5 Hello

return 0;
}


🚩Плюсы

Гибкость
Возможность создания функций и классов, которые могут работать с произвольным количеством аргументов.
Типобезопасность
Проверка типов происходит на этапе компиляции, что уменьшает вероятность ошибок.
Упрощение кода
Снижение дублирования кода, так как один шаблон может обрабатывать разные типы и количество аргументов.

🚩Примеры использования

🟠Функции вывода
Функции, которые принимают произвольное количество аргументов и выводят их на консоль или записывают в файл.
🟠Контейнеры данных
Шаблонные классы, такие как кортежи (std::tuple), которые могут содержать произвольное количество элементов разных типов.
🟠Обработчики событий
Функции обратного вызова, которые принимают переменное количество аргументов.

Ставь 👍 и забирай 📚 Базу знаний

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

22 Dec, 16:10


🤔 Какие есть lock_guard в стандартной библиотеке С++?

В стандартной библиотеке C++ есть std::lock_guard и std::unique_lock для управления мьютексами. std::lock_guard обеспечивает автоматическое блокирование мьютекса при создании и освобождение при выходе из области видимости. std::unique_lock более гибок, позволяя управлять временем блокировки и возможностью передачи мьютекса между функциями.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний

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

22 Dec, 09:10


🤔 Может ли функция иметь переменное число параментов?

Да, функция может принимать переменное число параметров.

🟠Старый стиль
C использованием заголовочного файла <cstdarg> и соответствующих макросов.
🟠Современный стиль
C использованием вариадических шаблонов, введенных в C++11.

🚩Старый стиль

Для создания функции с переменным числом параметров в старом стиле используется заголовочный файл <cstdarg> и макросы va_start, va_arg и va_end. Это подходит для функций, которые принимают параметры одного типа или могут обрабатывать параметры разных типов с помощью приведения типов.
#include <cstdarg>
#include <iostream>

void printNumbers(int count, ...) {
va_list args; // Объявление объекта для хранения аргументов
va_start(args, count); // Инициализация списка аргументов

for (int i = 0; i < count; ++i) {
int num = va_arg(args, int); // Получение следующего аргумента
std::cout << num << " ";
}

va_end(args); // Завершение работы со списком аргументов
std::cout << std::endl;
}

int main() {
printNumbers(3, 1, 2, 3); // Вывод: 1 2 3
printNumbers(5, 10, 20, 30, 40, 50); // Вывод: 10 20 30 40 50

return 0;
}


🚩Современный стиль

Вариадические шаблоны позволяют создавать более гибкие и типобезопасные функции с переменным числом параметров. Вариадические шаблоны поддерживают параметры разных типов и могут быть использованы с современными методами обработки типов (например, std::tuple, std::apply и std::initializer_list).
#include <iostream>

// Рекурсивная функция для печати аргументов
void print() {
std::cout << std::endl; // Базовый случай: ничего не делать
}

template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << " "; // Печать первого аргумента
print(args...); // Рекурсивный вызов для оставшихся аргументов
}

int main() {
print(1, 2, 3); // Вывод: 1 2 3
print("Hello", 1, 2.5, 'A'); // Вывод: Hello 1 2.5 A

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний