C++ Academy
16K subscribers
590 photos
125 videos
1 file
555 links
По всем вопросам- @haarrp

@itchannels_telegram - 🔥 best it channels

РКН: clck.ru/3FmxJF
Download Telegram
🧠Задача о максимальной сумме подмассива (алгоритм Кадане) С++

Дан целочисленный массив, найдите в нем непрерывный подмассив с наибольшей суммой.

Например:
Input: {-2, 1, -3, 4, -1, 2, 1, -5, 4}

Output: Subarray with the largest sum is {4, -1, 2, 1} with sum 6.


Мы можем легко решить эту задачу за линейное время, используя Алгоритм Кадане.
Идея состоит в том, чтобы поддерживать максимальный (с положительной суммой) подмассив, “заканчивающийся” на каждом индексе данного массива. Этот подмассив либо пуст (в этом случае его сумма равна нулю), либо состоит на один элемент больше, чем максимальный подмассив, оканчивающийся на предыдущем индексе.

Алгоритм может быть реализован следующим образом на C++:
#include <iostream>
#include <vector>
using namespace std;

// Функция для нахождения максимальной суммы непрерывного подмассива
// в заданном целочисленном массиве
int kadane(vector<int> const &arr)
{
// сохраняет максимальный суммарный подмассив, найденный на данный момент
int max_so_far = 0;

// сохраняет максимальную сумму подмассива, заканчивающегося на текущей позиции
int max_ending_here = 0;

// обход заданного массива
for (int i = 0; i < arr.size(); i++)
{
// обновить максимальную сумму подмассива, "заканчивающегося" на индексе "i" (путем добавления
// текущий элемент до максимальной суммы, заканчивающейся на предыдущем индексе 'i-1')
max_ending_here = max_ending_here + arr[i];

// если максимальная сумма отрицательна, устанавливаем ее в 0 (что представляет
// пустой подмассив)
max_ending_here = max(max_ending_here, 0);

// обновить результат, если текущая сумма подмассива окажется больше
max_so_far = max(max_so_far, max_ending_here);
}

return max_so_far;
}

int main()
{
vector<int> arr = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };

cout << "The maximum sum of a contiguous subarray is " << kadane(arr);

return 0;
}


результат:

The maximum sum of a contiguous subarray is 6

Временная сложность приведенного выше решения равна O(n) и не требует дополнительного места, где n это размер ввода.

Приведенный выше код не обрабатывает случай, когда все элементы массива отрицательные. Если массив содержит все отрицательные значения, ответом является максимальный элемент. Мы можем легко разместить эту проверку перед тем, как продолжить алгоритм. Реализацию можно увидеть ниже на C++:

Продолжение

@cpluspluc
📌 Что выведет код ?

#include <iostream>
using namespace std;
class A
{
int id;
static int count;
public:
A() {
count++;
id = count;
cout << "constructor for id " << id << endl;
}
~A() {
cout << "destructor for id " << id << endl;
}
};

int A::count = 0;

int main() {
A a[3];
return 0;
}


Ответ
Деструкторы в С++

В приведенной выше программе id - статическая переменная, и она увеличивается при каждом создании объекта. Объект a[0] создается первым, но первым уничтожается объект a[2]. Объекты всегда уничтожаются в порядке, обратном их созданию. Причина обратного порядка заключается в том, что объект, созданный позже, может использовать ранее созданный объект.

@cpluspluc
🖥 ZTG - графический движок для консолей Windows на С++.

Скомпилируйте библиотеку как статическую и включите ее в свой проект, включите файл ZTG.h

#include "ZTG/ZTG.H"

Github

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Чем отличаются ссылки от указателей в С++

В чем принципиальное отличие ссылки от указателя в С++? Какие ограничения есть у первых, а какие у вторых?

Вот некоторые из отличий::
🔘Нельзя объявить массив ссылок.
🔘У ссылки нет адреса.
🔘Существует арифметика указателей, но нет арифметики ссылок.
🔘Указатель может иметь «невалидное» значение с которым его можно сравнить перед использованием.

🔘Если вызывающая сторона не может не передать ссылку, то указатель может иметь специальное значение nullptr:
void f(int* num, int& num2)
{
if(num != nullptr) // if nullptr ignored algorithm
{
}
// can't check num2 on need to use or not
}


🔘Ссылка не обладает квалификатором const
#include <iostream>
int main()
{
std::cout << "Hello, world!\n";

const int v = 10;
//int& const r = v; // Ошибка
const int& r = v;

enum
{
is_const = std::is_const<decltype(r)>::value
};

if(!is_const)
std::cout << "const int& r is not const\n";
else
std::cout << "const int& r is const\n";
}


@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Предупреждение об арифметическое переполнение

▶️Итак, при запуске этого кода:
#include <iostream>
#include <vector>

int main() {
std::vector<int> a{0,1,2,3,4,5};
int b = 0;
std::cout << a[b + 1] << std::endl; // подчеркивает 'b+1'
return 0;
}

возникает проблема:
Арифметическое переполнение: использование оператора "+" на байтовом значении 4 и приведение результата к байтовому значению 8. Приведите значение к более широкому типу перед вызовом оператора "+", чтобы избежать переполнения (io.2)


Почему возникает такое предупреждение?


▶️В общем, в этом случае суть предупреждения такая: для индексации вектора на 64-битной платформе используется 64-битное беззнаковое целое, но выражение b+1 имеет тип int. Если предположить, что в b будет максимальное значение, представимое типом int, то получим UB.

Решение: привести хотя бы один из операндов к 64-битному типу.

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

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Некоторые тонкости генерации случайных чисел в C++

C и C++ имеют свои собственные встроенные генераторы случайных чисел. Они реализованы в двух отдельных функциях, которые находятся в заголовочном файле cstdlib:

srand() устанавливает передаваемое пользователем значение в качестве стартового. srand() следует вызывать только один раз — в начале программы (обычно в верхней части функции main())

rand() генерирует следующее случайное число в последовательности. Оно будет находиться в диапазоне от 0 до RAND_MAX (константа в cstdlib, значением которой является 32767).

Вот пример программы, в которой используются обе эти функции:
#include <iostream>
#include <cstdlib> // для функций rand() и srand()

int main()
{
srand(4541); // устанавливаем стартовое значение - 4 541

// Выводим 100 случайных чисел
for (int count=0; count < 100; ++count)
{
std::cout << rand() << "\t";

// Если вывели 5 чисел, то вставляем символ новой строки
if ((count+1) % 5 == 0)
std::cout << "\n";
}
}

// 14867 24680 8872 25432 21865
// ...



Рандомные числа в C++ 11

В C++ 11 добавили тонну нового функционала для генерации случайных чисел, включая алгоритм Вихрь Мерсенна, а также разные виды генераторов случайных чисел (например, равномерные, генератор Poisson и пр.). Доступ к ним осуществляется через подключение заголовочного файла random.

Вот пример генерации случайных чисел в C++11 с использованием Вихря Мерсенна на картинке.

Вихрь Мерсенна генерирует случайные 32-битные целые числа unsigned (а не 15-битные целые числа, как в случае с rand()), что позволяет использовать гораздо больший диапазон значений.
Существует также версия (std::mt19937_64) для генерации 64-битных целых чисел unsigned

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Некоторые тонкости генерации случайных чисел в C++

C и C++ имеют свои собственные встроенные генераторы случайных чисел. Они реализованы в двух отдельных функциях, которые находятся в заголовочном файле cstdlib:

srand() устанавливает передаваемое пользователем значение в качестве стартового. srand() следует вызывать только один раз — в начале программы (обычно в верхней части функции main())

rand() генерирует следующее случайное число в последовательности. Оно будет находиться в диапазоне от 0 до RAND_MAX (константа в cstdlib, значением которой является 32767).

Вот пример программы, в которой используются обе эти функции:
#include <iostream>
#include <cstdlib> // для функций rand() и srand()

int main()
{
srand(4541); // устанавливаем стартовое значение - 4 541

// Выводим 100 случайных чисел
for (int count=0; count < 100; ++count)
{
std::cout << rand() << "\t";

// Если вывели 5 чисел, то вставляем символ новой строки
if ((count+1) % 5 == 0)
std::cout << "\n";
}
}

// 14867 24680 8872 25432 21865
// ...



Рандомные числа в C++ 11

В C++ 11 добавили тонну нового функционала для генерации случайных чисел, включая алгоритм Вихрь Мерсенна, а также разные виды генераторов случайных чисел (например, равномерные, генератор Poisson и пр.). Доступ к ним осуществляется через подключение заголовочного файла random.

Вот пример генерации случайных чисел в C++11 с использованием Вихря Мерсенна на картинке.

Вихрь Мерсенна генерирует случайные 32-битные целые числа unsigned (а не 15-битные целые числа, как в случае с rand()), что позволяет использовать гораздо больший диапазон значений.
Существует также версия (std::mt19937_64) для генерации 64-битных целых чисел unsigned

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Интеграция HTML/CSS UI в разработку C++ приложений

▶️Итак, если вам требуется быстро интегрировать веб-интерфейс в приложение, написанное на C++, используйте Chromium Embedded Framework (CEF). Подключив экземпляр CEF к вашему проекту, загрузите HTML-документ и запустите цикл обработки событий CEF. Эта гибридная схема разработки позволит вам реализовать интерфейс на HTML/CSS, оставив при этом бекенд на C++. Выглядеть это может как-то так:
#include "include/cef_app.h"

int main(int argc, char* argv[]) {
CefMainArgs main_args(argc, argv);
CefSettings settings;

// выполняем инициализацию
CefInitialize(main_args, settings, nullptr, nullptr);

// создаем окно с UI
CefWindowInfo window_info;
CefBrowserHost::CreateBrowser(window_info, new SimpleHandler(), "file:///your_ui.html", CefBrowserSettings(), nullptr);

// цикл обработки сообщений
CefRunMessageLoop();

// Cef для окончания работы
CefShutdown();
}

Замените "file:///your_ui.html" на путь к вашему HTML-файлу. Вуаля! Ваш интерфейс на HTML/CSS уже встроен в приложение на C++.


▶️CEF зарекомендовал себя как надёжное решение для интеграции Chromium в приложения на C++, однако есть и другие варианты, которые стоит рассмотреть:

Electron: Объединяет Chromium и Node.js, позволяя использовать JavaScript для построения настольных приложений.

Ultralight: Идеально подходит для приложений на OpenGL или DirectX, где важна эффективность рендеринга HTML.

Qt WebEngine: Дает доступ к возможностям Qt с поддержкой интеграции веб-контента.

Sciter: Имеет компактный движок для работы с HTML/CSS, поддерживающий такие возможности, как SVG и WebSocket.

Awesomium, движки на базе WebKit или Gecko: Предлагают функциональность, схожую с CEF, однако с некими уникальными отличиями.


▶️При выборе фреймворка обратите внимание на его уровень зрелости, поддержку со стороны сообщества и доступность документации.

📎 Читать подробнее

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Чем пустой конструктор по умолчанию отличается от =default;?

▶️Вопрос: чем пустое тело отличается от того, что получается с =default (для конструктора по умолчанию)
X::X() {}
// и
X::X() = default;



▶️Что ж, наличие конструктора по умолчанию с пустым телом автоматически делает класс нетривиальным. Со всеми вытекающими отсюда особенностями. Например делает тип не POD. А также исключает возможность использовать агрегатную инициализацию:
#include <iostream>
#include <type_traits>

struct X {
//X() {}
X() = default;
int a;
int b;
};

int main( ) {
X x { 1, 2 }; // ошибка, если X - не тривиальный класс.
std::cout << std::boolalpha <<
std::is_trivial<X>::value << "\n";
}


При определении конструктора как = default тривиальность класса сохраняется, если она была до этого. В данном случае, это равносильно отсутствию явного упоминания конструктора в определении класса.

Если конструктор по умолчанию определен как = default вне определения класса, всё равно будет считаться, что конструктор предоставлен пользователем, и это тоже делает класс нетривиальным.

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 cpr — curl здорового человека

C++ Requests — это простая обертка вокруг libcurl, вдохновленная Python Requests.

Вот так можно отправить GET-запрос при помощи cpr:
#include <cpr/cpr.h>

int main(int argc, char** argv) {
cpr::Response r = cpr::Get(cpr::Url{"https://api.github.com/repos/libcpr/cpr/contributors"},
cpr::Authentication{"user", "pass", cpr::AuthMode::BASIC},
cpr::Parameters{{"anon", "true"}, {"key", "value"}});
r.status_code; // 200
r.header["content-type"]; // application/json; charset=utf-8
r.text; // JSON text string
}


🖥 GitHub
🟡 Доки

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🔓Что выведет следующий код?


#include <iostream>
using namespace std;

class Base {
public:
Base() {
cout << "Base constructor: ";
call();
}
virtual void call() { cout << "Base::call\n"; }
};

class Derived : public Base {
int x = init();

int init() {
cout << "Derived::init\n";
return 42;
}

public:
Derived() {
cout << "Derived constructor\n";
}

void call() override {
cout << "Derived::call, x = " << x << "\n";
}
};

int main() {
Derived d;
return 0;
}


🔢Варианты ответа:

A)

Derived::init
Derived constructor


B)

Derived::init
Derived constructor


C)

Base constructor: Derived::call, x = 42
Derived constructor


D)

Derived::call, x = <undefined>
Derived::init
Derived constructor


Правильный ответ: B

💡Почему?
В момент вызова конструктора Base, объект ещё не стал Derived. Виртуальная функция вызывается в контексте Base.
✔️ ЗАДАЧА: Что выведет код?



#include <iostream>
#include <vector>

struct Tracer {
Tracer(const char* name) : name(name) {
std::cout << "Constructing " << name << "\n";
}
~Tracer() {
std::cout << "Destructing " << name << "\n";
}
const char* name;
};

struct Example {
Tracer t1{"t1"};
std::vector<Tracer> list;
Tracer t2{"t2"};

Example() : list{Tracer("temp1"), Tracer("temp2")} {
std::cout << "Inside constructor\n";
}
};

int main() {
Example e;
std::cout << "End of main\n";
return 0;
}


---

ОТВЕТ:

Constructing t1
Constructing temp1
Constructing temp2
Inside constructor
Constructing t2
End of main
Destructing t2
Destructing temp2
Destructing temp1
Destructing t1

---

Почему так:

• Поля инициализируются в порядке объявления в структуре, а не в списке инициализации
• std::initializer_list создаёт временные объекты, которые копируются в вектор
• t2 создаётся после строки Inside constructor
• Деструкторы вызываются в обратном порядке: t2 → temp2 → temp1 → t1

Хитрость — в порядке инициализации, временных объектах и destructuring-порядке!


@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Задача для собеседования: "Быстрая очередь с удалением элемента за O(1)"

🔖 Условие:

Реализуйте структуру данных — очередь (`queue`), поддерживающую три операции:

- enqueue(x) — добавить элемент в конец очереди (должно работать за O(1));
- dequeue() — удалить элемент из начала очереди и вернуть его (должно работать за O(1));
- remove(x) — удалить первое вхождение элемента x из очереди за O(1).

Ограничения:
- Элементы могут повторяться.
- Если элемент отсутствует, remove(x) не делает ничего.
- Операции должны оставаться эффективными на больших объемах данных (миллионы элементов).
- Можно использовать стандартные структуры данных STL (`list`, unordered_map, unordered_set и т.д.).

▪️ Дополнительные вопросы к кандидату:

- Как обеспечить O(1) удаление произвольного элемента из очереди?
- Как избежать утечек памяти или "битых" итераторов после удаления элементов?
- Что будет, если очередь будет содержать множество одинаковых элементов?
- Как бы вы изменили решение, если бы нужно было удалять все вхождения элемента, а не только первое?

---

▪️ Разбор возможного решения:


Основная архитектура:

- Используем std::list<int> data для хранения реальной очереди.
- Почему list? Потому что позволяет за O(1) удалять элементы по итератору.
- Используем std::unordered_map<int, std::list<std::list<int>::iterator>> index.
- Для каждого значения храним список итераторов на его вхождения в data.

Операции:

- enqueue(x):
- Добавляем x в конец data.
- Сохраняем итератор на него в index[x].
- Все действия — за O(1).

- dequeue():
- Удаляем элемент из начала data.
- Находим соответствующий итератор в index, удаляем его.
- Если в списке итераторов для этого значения больше нет элементов, удаляем ключ из index.

- remove(x):
- Если в index[x] есть итераторы:
- Берем первый итератор.
- Удаляем его из data и из списка итераторов.
- Если список стал пустым — удаляем запись x из index.
- Все действия — за O(1).

---

▪️ Возможные подводные камни:

- Не проверять, пустой ли index[x], перед удалением итератора.
- Не удалять ключи из unordered_map, что приводит к накоплению "пустых" списков в памяти.
- Использовать vector вместо list — тогда удаление из середины будет занимать O(n).

---

▪️ Мини-пример интерфейса класса:


#include <list>
#include <unordered_map>

class FastQueue {
private:
std::list<int> data;
std::unordered_map<int, std::list<std::list<int>::iterator>> index;

public:
void enqueue(int x) {
auto it = data.insert(data.end(), x);
index[x].push_back(it);
}

int dequeue() {
if (data.empty()) throw std::out_of_range("Queue is empty");
int val = data.front();
auto it = index[val].front();
index[val].pop_front();
if (index[val].empty()) {
index.erase(val);
}
data.pop_front();
return val;
}

void remove(int x) {
if (index.count(x) == 0) return;
auto it = index[x].front();
data.erase(it);
index[x].pop_front();
if (index[x].empty()) {
index.erase(x);
}
}
};


▪️ Дополнительные вопросы на усложнение:


- Что если нужно сделать removeAll(x) — удаление всех вхождений элемента?
- Как изменится решение, если в очереди будут сложные объекты вместо int?
- Как минимизировать использование памяти, если очередь очень большая?
- Как обеспечить безопасность потоков (thread-safety) для многопоточного варианта очереди?

@cpluspluc
Please open Telegram to view this post
VIEW IN TELEGRAM
🎯 Задача: "Числа-близнецы с кастомным компаратором" (C++ для продвинутых)

У вас есть массив N целых чисел. Нужно найти все пары чисел (A, B), таких что:

1. |A - B| минимально среди всех пар в массиве
2. Aчетное, а Bнечетное
3. Если таких пар несколько, вернуть все, отсортированные по A, затем по B

Ограничения:
- 1 <= N <= 10^5
- -10^9 <= A[i] <= 10^9

Пример:

Вход:

arr = [4, 7, 9, 2, 8, 3]


Вывод:


(2,3)
(4,3)
(4,7)
(8,7)
(8,9)


Минимальная разница = 1

🧠 Уловки задачи:

Наивное сравнение O(N²) слишком медленно

Нужно использовать сортировку + два указателя или бинарный поиск

✍️ Решение:

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

Код:
```cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

using namespace std;

int main() {
int N;
cin >> N;
vector<int> even, odd;

for (int i = 0; i < N; ++i) {
int x;
cin >> x;
if (x % 2 == 0) even.push_back(x);
else odd.push_back(x);
}

sort(even.begin(), even.end());
sort(odd.begin(), odd.end());

vector<pair<int, int>> result;
int min_diff = INT_MAX;

int i = 0, j = 0;
while (i < even.size() && j < odd.size()) {
int a = even[i];
int b = odd[j];
int diff = abs(a - b);

if (diff < min_diff) {
min_diff = diff;
result.clear();
result.emplace_back(a, b);
} else if (diff == min_diff) {
result.emplace_back(a, b);
}

if (a < b) i++;
else j++;
}

sort(result.begin(), result.end());

for (auto& p : result) {
cout << "(" << p.first << "," << p.second << ")\n";
}

return 0;
}```

🔍 Пошаговый разбор:

Считываем входные данные

Разделяем числа на четные и нечетные

Сортируем оба массива

Используем два указателя, чтобы найти минимальную разницу за O(N)

Если нашли пару с меньшей разницей — сбрасываем результат и добавляем её

Если нашли пару с такой же разницей — просто добавляем

Финально сортируем все найденные пары по A, затем по B

Почему задача хитрая: Нужно оптимальное решение вместо лобового перебора
Включает тонкую работу с индексами
Требует правильно обрабатывать несколько минимальных пар
Объединяет знания сортировки, двух указателей и поиска пар

💪 Challenge для профи: 👉 Попробуйте реализовать эту задачу с помощью multiset + lower_bound / upper_bound, чтобы избежать сортировки массивов и упростить логику поиска ближайших чисел.
🦾 Задача с подвохом: Виртуальные функции и конструкторы

Условие:

Что выведет следующий код и почему?


#include <iostream>

class Base {
public:
Base() {
std::cout << "Base constructor\n";
foo();
}
virtual void foo() {
std::cout << "Base foo\n";
}
};

class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor\n";
}
void foo() override {
std::cout << "Derived foo\n";
}
};

int main() {
Derived d;
return 0;
}


Вопрос:
Что будет выведено? Почему результат может удивить даже опытных C++ разработчиков?

🔍 Разбор:

1️⃣ Мы создаём объект
Derived d;.
Это вызывает конструктор
Derived, но сначала выполняется конструктор `Base` (по правилам иерархии).

2️⃣ В конструкторе
Base есть вызов foo();.
Может показаться, что поскольку объект на самом деле
Derived, вызовется Derived::foo().

Но! Вот главный подвох:

➡️ В C++, когда вы вызываете виртуальную функцию из конструктора (или деструктора), она не виртуальна для объекта, который ещё не полностью сконструирован.

На момент вызова
foo() объект всё ещё только Base, потому что Derived ещё не инициализирован.

Пошаговое выполнение:

- Вызов конструктора
Base:
```
Base constructor
```
- Вызов
foo() внутри конструктора Base:
Это вызовет Base::foo(), а не
Derived::foo():
```
Base foo
```
- После завершения конструктора
Base, вызывается конструктор Derived:
```
Derived constructor
```

Итоговый вывод:

```
Base constructor
Base foo
Derived constructor
```

💥 Подвох:

• Многие ожидают, что виртуальные функции работают «магически» всегда.
• Но при вызове из конструктора (или деструктора) виртуальные функции не полиморфны, потому что объект ещё не «стал» Derived полностью.

🛡️ Что нужно помнить:

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

Вывод:

C++ строго следует правилам объектной модели: пока объект конструируется (или разрушается), он считается экземпляром того класса, конструктор которого выполняется в данный момент. Это поведение важно помнить при проектировании иерархий классов!


@cpluspluc
🧠 Задача с подвохом для продвинутых C++ разработчиков

🔹 Уровень: Advanced
🔹 Темы: std::vector, управление памятью, конструкторы/деструкторы, reserve() vs resize()

📌 Условие

Рассмотрим следующий код:


#include <iostream>
#include <vector>

struct Foo {
Foo() { std::cout << "Ctor\n"; }
~Foo() { std::cout << "Dtor\n"; }
Foo(const Foo&) { std::cout << "Copy\n"; }
Foo(Foo&&) noexcept { std::cout << "Move\n"; }
};

int main() {
std::vector<Foo> v;
v.reserve(3); // Резервируем место под 3 элемента

std::cout << "--- Pushing ---\n";
for (int i = 0; i < 3; ++i) {
v.push_back(Foo());
}

std::cout << "--- Done ---\n";
}


Вопросы

1. Что будет выведено на экран?
2. Почему reserve() не предотвращает конструкторы копирования/перемещения?
3. Что изменится, если заменить reserve(3) на resize(3)?

🔍 Разбор

Ожидаемый вывод:

--- Pushing ---
Ctor
Move
Dtor
Ctor
Move
Dtor
Ctor
Move
Dtor
--- Done ---
Dtor
Dtor
Dtor


🔧 Почему так происходит

- Foo() создаёт временный объект.
- push_back() вызывает перемещающий конструктор Move.
- Временный объект уничтожается (вызывается `Dtor`).
- reserve(3) выделяет память, но не создаёт объектов.

🔄 Если заменить `reserve(3)` на `resize(3)`

- resize(3) создаст 3 объекта Foo через конструктор по умолчанию.
- push_back(Foo()) добавит четвёртый, возможно вызовет realocation.
- Это может привести к копированию или перемещению уже созданных элементов.

⚠️ Подвох

Многие ошибочно считают, что reserve(n) создаёт n объектов. Но это не такreserve() только выделяет память, не вызывая конструкторы. Именно поэтому внутри push_back всё равно происходит перемещение или копирование.

🧠 Вывод

- reserve() — экономия на реаллокациях, без создания объектов.
- resize() — создаёт n объектов, вызывает конструкторы.
- Не путай эти методы — от этого зависит и производительность, и семантика.


// Резервирует память, не создаёт объекты
v.reserve(10);

// Создаёт 10 объектов Foo
v.resize(10);


📌 Совет:
Если не хочешь лишнего перемещения, используй emplace_back():


v.emplace_back(); // Вызывает конструктор Foo напрямую внутри вектора



@cpluspluc
🧠 Хитрая задача на C++: "Исчезающее число среди строк"

Условие
Дан список строк, представляющих числа от 1 до 100 включительно. Одного числа нет.
Твоя задача — найти, какого именно числа не хватает.
Нельзя использовать сортировку, std::accumulate, std::unordered_map, std::stoi можно.

Пример:
Массив: ["1", "2", "3", ..., "98", "99"] (без "100")
Ответ: 100

Формат:


int findMissingNumber(const std::vector<std::string>& data);


Решение с XOR


#include <iostream>
#include <vector>
#include <string>

int findMissingNumber(const std::vector<std::string>& data) {
int xor_full = 0;
int xor_data = 0;

for (int i = 1; i <= 100; ++i) {
xor_full ^= i;
}

for (const auto& s : data) {
xor_data ^= std::stoi(s);
}

return xor_full ^ xor_data;
}


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


int main() {
std::vector<std::string> data;
for (int i = 1; i <= 100; ++i) {
if (i != 57) { // Удалим 57
data.push_back(std::to_string(i));
}
}

std::cout << "Пропущено: " << findMissingNumber(data) << std::endl;
return 0;
}


Почему работает:
XOR — идеальное решение, когда нужно найти одну потерянную величину среди уникальных значений.
a ^ a = 0, 0 ^ b = b. Поэтому:
(XOR всех от 1 до 100) ^ (XOR из данных) = отсутствующее число.

@cpluspluc
🔍 C++ Задача для профи: кто вызовется?

У тебя есть следующий код:


#include <iostream>
#include <type_traits>

template<typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
process(T value) {
std::cout << "Integral: " << value << std::endl;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, void>::type
process(T value) {
std::cout << "Floating-point: " << value << std::endl;
}

int main() {
process(42); // (1)
process(3.14); // (2)
process('A'); // (3)
}


Вопросы:
1. Что напечатает программа?
2. Почему вызов process('A') может удивить?
3. Что произойдёт, если передать true?

🧠 Подвох:

- char — это integral type, а не string и не отдельный класс
- true — это тоже int`-подобный тип: `std::is_integral<bool>::value == true
- У тебя может возникнуть разный вывод в зависимости от перегрузки

Разбор:

- `process(42)` → `Integral: 42`
- `process(3.14)` → `Floating-point: 3.14`
- `process('A')` → `Integral: 65` (ASCII код символа!) ⚠️
- `process(true)` → `Integral: 1` (да, это `bool`)

🎯 Что проверяет задача:

- Понимание `SFINAE` и `enable_if`
- Типовую систему C++ (что именно считается integral)
- Разницу между `char`, `bool`, `int`, `float` в шаблонах
- Предсказание поведения перегрузки на уровне типов

@cpluspluc
🔍 C++ Задача для профи: кто вызовется?

У тебя есть следующий код:


#include <iostream>
#include <type_traits>

template<typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
process(T value) {
std::cout << "Integral: " << value << std::endl;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, void>::type
process(T value) {
std::cout << "Floating-point: " << value << std::endl;
}

int main() {
process(42); // (1)
process(3.14); // (2)
process('A'); // (3)
}

Вопросы:
1. Что напечатает программа?
2. Почему вызов process('A') может удивить?
3. Что произойдёт, если передать true?

🧠 Подвох:

- char — это integral type, а не string и не отдельный класс
- true — это тоже int`-подобный тип: `std::is_integral<bool>::value == true
- У тебя может возникнуть разный вывод в зависимости от перегрузки

Разбор:

- process(42)Integral: 42
- process(3.14)Floating-point: 3.14
- process('A')Integral: 65 (ASCII код символа!) ⚠️
- process(true)Integral: 1 (да, это `bool`)

🎯 Что проверяет задача:

- Понимание SFINAE и enable_if
- Типовую систему C++ (что именно считается integral)
- Разницу между char, bool, int, float в шаблонах
- Предсказание поведения перегрузки на уровне типов

@cpluspluc
⚙️ Задача для C++ разработчиков: «Непонятная ошибка, которая портит данные»


🎯 Цель: Найти и объяснить причину скрытого неопределённого поведения, которое проявляется не сразу

📍 Ситуация:

Ты разрабатываешь кроссплатформенное приложение на C++17, которое обрабатывает массивы бинарных данных.
На тестах — всё работает. Но у части пользователей (особенно на Linux) возникают:

- Повреждённые файлы после сериализации
- Непредсказуемые вылеты при больших объёмах данных
- Валидация данных случайно "съезжает" (байты путаются)

Вот фрагмент кода:


#include <vector>
#include <cstring>

struct Packet {
uint32_t id;
char data[64];
};

std::vector<uint8_t> serialize(const Packet& p) {
std::vector<uint8_t> buffer(sizeof(Packet));
std::memcpy(buffer.data(), &p, sizeof(Packet));
return buffer;
}


🔍 Визуально всё нормально. В unit-тестах — ок. На CI — ок.
Но на проде данные иногда повреждены, и никто не может воспроизвести баг стабильно.

🧩 Задача:

1. Почему memcpy здесь небезопасен, хотя кажется логичным?
2. Что может отличаться на разных платформах и влиять на поведение?
3. Как бы ты безопасно сериализовал структуру в std::vector<uint8_t>?
4. Как это можно поймать с помощью valgrind / asan / -fsanitize=undefined?
5. Как написать cross-platform-safe сериализацию?

💡 Подсказка:
В C++ `struct Packet` может иметь **padding** и **alignment**, которые отличаются на архитектурах. `memcpy` по `sizeof(Packet)` может захватить лишние или мусорные байты.

🛠 Решение:

1. `struct Packet` не является POD-структурой с гарантированным layout — в ней может быть **неинициализированный padding**, который `memcpy` тоже копирует.

2. Проблема усиливается на системах с разным выравниванием: x86 vs ARM, GCC vs MSVC.

3. Более безопасный способ — сериализовать поля по отдельности:

std::vector<uint8_t> serialize(const Packet& p) {
std::vector<uint8_t> buffer;
buffer.insert(buffer.end(), reinterpret_cast<const uint8_t*>(&
p.id),
reinterpret_cast<const uint8_t*>(&
p.id) + sizeof(p.id));
buffer.insert(buffer.end(),
p.data, p.data + sizeof(p.data));
return buffer;
}



4. Или использовать `std::ostringstream` / `std::span` / `protobuf` / `flatbuffers`.

5. Проверка с `-fsanitize=undefined` даст warning:
```
memcpy: reading padding bytes from stack frame
```

📌 **Вывод:**
В C++ `memcpy` на структуру — это **ловушка**, если ты не контролируешь padding. Никогда не сериализуй структуры напрямую через память, если это не `
#pragma pack` и не строго определённый layout.

💬 Это вопрос для собеседования на позицию C++ системного разработчика с уклоном в безопасность и низкоуровневую разработку.

@cpluspluc