Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🤔1
Виртуальное наследование применяется для решения проблемы "ромбовидного наследования" (или "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;
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Значение переменной перейдёт в максимальное значение типа (например, UINT_MAX для unsigned int).
Это связано с переполнением, так как беззнаковые типы используют арифметику по модулю.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Существует множество алгоритмов сортировки, каждый из которых имеет свои преимущества и недостатки в зависимости от условий использования. Рассмотрим основные из них.
Простейший алгоритм, который многократно проходит по массиву, сравнивая соседние элементы и меняя их местами, если они стоят в неправильном порядке.
Сложность: 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]);
}
}
}
}На каждом шаге ищется минимальный элемент и ставится в начало неотсортированной части массива.
Сложность: O(n²) всегда.
Когда использовать: Если важна простота реализации, но нужна немного лучшая производительность, чем у пузырьковой сортировки.
void selectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
std::swap(arr[i], arr[minIdx]);
}
}Берём один элемент и вставляем его в правильное место среди уже отсортированных элементов.
Сложность: O(n²) в худшем случае, O(n) в лучшем (если массив почти отсортирован).
Когда использовать: Для небольших массивов или почти отсортированных данных.
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}Разделяем массив на две части, рекурсивно сортируем их и затем сливаем.
Сложность: O(n log n) всегда.
Когда использовать: Когда нужна стабильность и предсказуемая скорость работы.
void merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1, n2 = r - m;
int L[n1], R[n2];
for (int i = 0; i < n1; i++) L[i] = arr[l + i];
for (int i = 0; i < n2; i++) R[i] = arr[m + 1 + i];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2) arr[k++] = (L[i] < R[j]) ? L[i++] : R[j++];
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
void mergeSort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Это контейнеры из стандартной библиотеки, но у них есть важные различия в управлении памятью, гибкости и производительности.
std::vector использует динамическую память, выделяемую в куче (heap). Его размер может изменяться во время выполнения.std::array использует статическую память, выделяемую в стеке (stack) или в статической области памяти, и его размер фиксирован на этапе компиляции.#include <vector>
#include <array>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3}; // Размер может изменяться динамически
vec.push_back(4); // Добавляем новый элемент
std::array<int, 3> arr = {1, 2, 3}; // Размер фиксирован, нельзя добавить новый элемент
std::cout << "Vector size: " << vec.size() << std::endl; // Выведет 4
std::cout << "Array size: " << arr.size() << std::endl; // Выведет 3
return 0;
}
std::vector позволяет изменять размер в процессе работы, автоматически выделяя новую память при необходимости.std::array имеет фиксированный размер, который нельзя изменить после создания.std::vector<int> v = {1, 2, 3};
v.push_back(4); // Увеличиваем размер
std::array<int, 3> a = {1, 2, 3};
// a.push_back(4); // Ошибка! У std::array нет метода push_backstd::array работает быстрее, так как все данные хранятся в непрерывном участке памяти и нет затрат на динамическое выделение.std::vector может требовать дополнительное время при изменении размера, так как может потребоваться новое выделение памяти и копирование элементов.#include <vector>
#include <array>
#include <chrono>
#include <iostream>
int main() {
constexpr int N = 1'000'000;
std::vector<int> vec(N, 1); // Динамический массив
std::array<int, N> arr{}; // Статический массив
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i) vec[i] += 1;
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Vector time: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
<< " us" << std::endl;
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i) arr[i] += 1;
end = std::chrono::high_resolution_clock::now();
std::cout << "Array time: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
<< " us" << std::endl;
return 0;
}
std::array хранит данные как обычный C-массив, поэтому можно легко передавать его в функции, ожидающие int*.std::vector использует динамическую память, но можно получить указатель на внутренний буфер с помощью data().void processArray(int* arr, size_t size) {
for (size_t i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
}
int main() {
std::array<int, 3> arr = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
processArray(arr.data(), arr.size()); // std::array можно передавать в C-функции
processArray(vec.data(), vec.size()); // std::vector тоже можно передавать
return 0;
}Оба контейнера поддерживают итераторы и совместимы со стандартными алгоритмами из
#include <algorithm>#include <iostream>
#include <vector>
#include <array>
#include <algorithm>
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5};
std::array<int, 5> arr = {3, 1, 4, 1, 5};
std::sort(vec.begin(), vec.end());
std::sort(arr.begin(), arr.end());
for (int n : vec) std::cout << n << " "; // 1 1 3 4 5
std::cout << std::endl;
for (int n : arr) std::cout << n << " "; // 1 1 3 4 5
return 0;
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Деструктор должен быть виртуальным, если класс предназначен для использования в качестве базового, и предполагается полиморфное удаление через указатель (Base* ptr = new Derived; delete ptr;). Без виртуального деструктора деструкторы производных классов не будут вызваны, что приведет к утечке памяти.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Делиторы – это специальные функции, которые определяют, как должен уничтожаться объект при освобождении памяти умным указателем.
Обычно
std::unique_ptr и std::shared_ptr по умолчанию вызывают delete, но иногда это поведение нужно изменить. Например, если объект создан через
malloc(), fopen(), CreateFile(), то delete не подходит! Можно добавить
std::cout, логику очистки, сброс ресурсов. delete не удалит массив корректно, нужно delete[]. Например, в
std::shared_ptr можно передать free(), fclose() или CloseHandle(). Пример: освобождение памяти от
malloc() через std::free()#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int, void(*)(void*)> ptr(malloc(sizeof(int)), free);
*reinterpret_cast<int*>(ptr.get()) = 42;
std::cout << "Значение: " << *reinterpret_cast<int*>(ptr.get()) << "\n";
}
Здесь
free используется вместо delete, потому что память выделена malloc(). Пример: закрытие файла через
std::fclose()#include <iostream>
#include <memory>
#include <cstdio>
int main() {
std::unique_ptr<FILE, decltype(&std::fclose)> file(fopen("test.txt", "w"), &std::fclose);
if (file) {
std::fprintf(file.get(), "Hello, world!\n");
}
} // `fclose(file)` вызовется автоматически!
В
std::shared_ptr можно передавать делитор прямо в конструкторе#include <iostream>
#include <memory>
void customDeleter(int* ptr) {
std::cout << "Удаляю объект: " << *ptr << "\n";
delete ptr;
}
int main() {
std::shared_ptr<int> ptr(new int(100), customDeleter);
} // В конце `customDeleter(ptr)` вызовется автоматически!
По умолчанию
std::unique_ptr<int[]> сам вызывает delete[], но если std::unique_ptr<int> используется неправильно, то delete удалит только первый элемент массива! Ошибка:
delete вместо delete[]std::unique_ptr<int> arr(new int[10]); // ОШИБКА! `delete` вызовет утечку памяти!
Правильный вариант
std::unique_ptr<int[], std::default_delete<int[]>> arr(new int[10]);
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Равенство двух float проверяется с учётом допустимой разницы (эпсилон), чтобы избежать ошибок из-за неточности представления:
∣a−b∣<ϵ, где ϵ — небольшое значение, например 10−610^{-6}.
Такой подход помогает корректно сравнивать близкие числа, которые могут отличаться в пределах допустимой погрешности.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
В C++ можно запретить наследование от класса несколькими способами.
Ключевое слово
final запрещает наследование от класса.class Base final {
public:
void show() { std::cout << "Base class\n"; }
};
// Ошибка! Наследование запрещено
class Derived : public Base {
};Можно сделать так, чтобы класс нельзя было создать или скопировать в унаследованных классах.
class Base {
private:
Base() = default; // Приватный конструктор
};Можно сделать конструктор
protected, если хотим создать объекты внутри класса, но не разрешать наследование снаружи. class Base {
protected:
~Base() = default; // Деструктор защищённый
};Если сделать деструктор
private, то класс нельзя будет корректно удалить через указатель. class Base {
private:
~Base() = default; // Закрытый деструктор
};Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это класс, который содержит хотя бы одну чисто виртуальную функцию. Он не может быть создан как объект и предназначен для использования в качестве базового класса. Такие классы служат для определения интерфейсов и полиморфного поведения.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM