
- Главная
- Каталог
- Интернет технологии
- C/C++ | Вопросы собесов
C/C++ | Вопросы собесов
Разбираем вопросы с собеседований на С/С++ разработчика
Статистика канала
Square нарушает LSP, потому что он изменяет поведение методов setWidth и setHeight, что может привести к неожиданным результатам при использовании объекта Square как Rectangle.
class Rectangle {
protected:
int width, height;
public:
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
int getWidth() const { return width; }
int getHeight() const { return height; }
int area() const { return width * height; }
};
class Square : public Rectangle {
public:
void setWidth(int w) override {
width = w;
height = w; // Изменяем и ширину, и высоту
}
void setHeight(int h) override {
height = h;
width = h; // Изменяем и высоту, и ширину
}
};{}
🟠Соблюдение LSP
Лучше всего избегать такого наследования, которое приводит к нарушению LSP. В этом случае можно использовать другой подход, например, композицию вместо наследования. Теперь Rectangle и Square наследуют от абстрактного класса Shape, и каждый класс реализует метод area согласно своей логике, не нарушая LSP.
class Shape {
public:
virtual int area() const = 0;
virtual ~Shape() = default;
};
class Rectangle : public Shape {
protected:
int width, height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
int getWidth() const { return width; }
int getHeight() const { return height; }
int area() const override { return width * height; }
};
class Square : public Shape {
int side;
public:
Square(int s) : side(s) {}
void setSide(int s) { side = s; }
int getSide() const { return side; }
int area() const override { return side * side; }
};{}
Ставь 👍 и забирай 📚 Базу знанийnew и освобождается с помощью delete. Однако ручное управление памятью приводит к ошибкам:
Утечки памяти: забыли вызвать delete, и память остаётся занятой.
Двойное освобождение: дважды вызвали delete на одном указателе.
Использование освобождённой памяти: доступ к указателю после delete.
Эти проблемы стали мотиватором для появления решений, которые бы автоматически управляли памятью.
🟠Ручная реализация умных указателей в C++98
До стандартизации разработчики часто писали свои собственные классы для управления динамической памятью. Такой класс оборачивал указатель и вызывал delete в своём деструкторе:
class SmartPointer {
int* ptr;
public:
explicit SmartPointer(int* p = nullptr) : ptr(p) {}
~SmartPointer() { delete ptr; }
int& operator*() { return *ptr; }
int* operator->() { return ptr; }
};{}
🟠Boost и развитие библиотеки умных указателей
В библиотеке Boost появились первые стандартизированные умные указатели:
boost::shared_ptr: реализует совместное владение указателем (reference counting).
boost::scoped_ptr: уникальное владение (аналог будущего std::unique_ptr).
boost::weak_ptr: слабая ссылка для работы с циклическими зависимостями.
Boost сыграл огромную роль в стандартизации умных указателей, так как многие из них легли в основу C++11.
🟠C++11: стандартные умные указатели
Стандарт C++11 ввёл библиотеку <memory>, которая предоставляет три типа умных указателей:
std::unique_ptr:
Гарантирует уникальное владение ресурсом.
Замена для обычных указателей с более строгими гарантиями.
Не поддерживает копирование, только перемещение.
std::unique_ptr<int> ptr(new int(5));
{}
std::shared_ptr:
Совместное владение ресурсом через подсчёт ссылок.
Освобождает память, когда последний shared_ptr указывает на объект.
std::shared_ptr<int> sp1 = std::make_shared<int>(10);
{}
std::weak_ptr:
Не увеличивает счётчик ссылок в std::shared_ptr.
Используется для работы с циклическими зависимостями.
std::weak_ptr<int> wp = sp1;
{}
🟠Современное использование умных указателей
С выходом C++14, C++17 и C++20 умные указатели остаются важной частью языка. Они используются для:
Ресурсного управления (RAII).
Устранения утечек памяти.
Упрощения кода.
Ставь 👍 и забирай 📚 Базу знанийstd::vector, которые могут привести к множественному копированию элементов. Рассмотрим основные из них.
🟠Изменение размера (`resize`)
Метод resize() может вызвать множественное копирование, если новый размер вектора превышает его текущую вместимость (capacity).
#include <iostream>
#include <vector>
struct Data {
int value;
Data(int v) : value(v) {}
Data(const Data& other) { // Конструктор копирования
value = other.value;
std::cout << "Copying Data: " << value << std::endl;
}
};
int main() {
std::vector<Data> vec(3, Data(10)); // Заполняем 3 элементами
std::cout << "Resizing..." << std::endl;
vec.resize(10, Data(20)); // Вектор расширяется, возможны копирования
}{}
🟠Вставка элемента (`insert` и `emplace`)
Если std::vector не имеет достаточного запаса (capacity), вставка нового элемента может привести к реаллокации и копированию всех элементов.
std::vector<int> vec = {1, 2, 3};
vec.insert(vec.begin(), 0); // Вставка в начало → все элементы сдвигаются{}
🟠Присваивание (`operator=`)
Если один std::vector присваивается другому, все элементы копируются.
std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::vector<int> vec2;
vec2 = vec1; // Копирование всех элементов{}
🟠Возвращение `std::vector` из функции
Если возвращаем std::vector по значению, может произойти копирование (если не работает оптимизация RVO/NRVO или перемещение).
std::vector<int> createVector() {
std::vector<int> vec(100, 42);
return vec; // Может скопироваться, если нет RVO
}{}
Ставь 👍 и забирай 📚 Базу знаний
class DatabaseConnection {
public:
DatabaseConnection(const std::string& connectionString) {
if (!connect(connectionString)) {
throw std::runtime_error("Connection failed");
}
}
private:
bool connect(const std::string& connectionString) {
// Логика подключения
return false; // Имитация ошибки подключения
}
};{}
🟠Безопасность ресурсов
Конструкторы часто выделяют ресурсы, такие как память, файлы или сетевые соединения. Если инициализация не удалась, необходимо убедиться, что все выделенные ресурсы будут корректно освобождены. Исключения помогают автоматизировать этот процесс, используя RAII (Resource Acquisition Is Initialization). Если файл не удалось открыть, исключение будет выброшено, и деструктор корректно освободит ресурсы.
class FileHandler {
public:
FileHandler(const std::string& filename) {
file = fopen(filename.c_str(), "r");
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandler() {
if (file) {
fclose(file);
}
}
private:
FILE* file;
};{}
🟠Целостность программы
Исключения в конструкторах позволяют предотвратить использование некорректно инициализированных объектов. Это важно для поддержания целостности программы и избежания непредсказуемых ошибок. Если загрузка конфигурации не удалась, исключение предотвращает создание некорректного объекта ConfigLoader.
class ConfigLoader {
public:
ConfigLoader(const std::string& configPath) {
if (!loadConfig(configPath)) {
throw std::runtime_error("Failed to load config");
}
}
private:
bool loadConfig(const std::string& configPath) {
// Логика загрузки конфигурации
return false; // Имитация ошибки загрузки
}
};{}
🚩Обработка исключений в конструкторах
Исключения в конструкторах должны быть обработаны на более высоком уровне программы, чтобы гарантировать корректное завершение программы или повторение попытки инициализации. В этом примере ошибка инициализации обрабатывается в main, что позволяет программе корректно завершить работу или предпринять другие действия.
int main() {
try {
DatabaseConnection db("connection_string");
} catch (const std::exception& e) {
std::cerr << "Initialization failed: " << e.what() << std::endl;
// Дополнительные действия по обработке ошибки
}
return 0;
}{}
Ставь 👍 и забирай 📚 Базу знаний
#include <iostream>
void modify(int& ref) {
ref = 20; // Меняем значение переданного объекта
}
int main() {
int a = 10;
int& refA = a; // refA теперь синоним a
std::cout << "До: " << a << std::endl; // 10
modify(refA);
std::cout << "После: " << a << std::endl; // 20
return 0;
}{}
nullptr, поддерживают арифметику.
#include <iostream>
void modify(int* ptr) {
if (ptr) {
*ptr = 30; // Разыменование указателя для изменения значения
}
}
int main() {
int a = 10;
int* ptrA = &a; // ptrA хранит адрес a
std::cout << "До: " << a << std::endl; // 10
modify(ptrA);
std::cout << "После: " << a << std::endl; // 30
return 0;
}{}
Указатели могут менять адреса, ссылки – нет
int a = 10, b = 20;
int* ptr = &a; // ptr указывает на a
ptr = &b; // Теперь ptr указывает на b
int& ref = a; // ref – это псевдоним a
ref = b; // Это НЕ смена привязки! Теперь a == 20{}
int arr[] = {1, 2, 3};
int* ptr = arr; // ptr указывает на первый элемент
std::cout << *ptr << std::endl; // 1
std::cout << *(ptr + 1) << std::endl; // 2 (следующий элемент)
std::cout << *(ptr + 2) << std::endl; // 3{}
nullptr) или менять адрес
В большинстве случаев лучше использовать ссылки, так как они безопаснее
Ставь 👍 и забирай 📚 Базу знанийОтзывы канала
- Добавлен: Сначала новые
- Добавлен: Сначала старые
- Оценка: По убыванию
- Оценка: По возрастанию
Каталог Телеграм-каналов для нативных размещений
C/C++ | Вопросы собесов — это Telegam канал в категории «Интернет технологии», который предлагает эффективные форматы для размещения рекламных постов в Телеграмме. Количество подписчиков канала в 4.3K и качественный контент помогают брендам привлекать внимание аудитории и увеличивать охват. Рейтинг канала составляет 5.2, количество отзывов – 1, со средней оценкой 5.0.
Вы можете запустить рекламную кампанию через сервис Telega.in, выбрав удобный формат размещения. Платформа обеспечивает прозрачные условия сотрудничества и предоставляет детальную аналитику. Стоимость размещения составляет 4195.8 ₽, а за 3 выполненных заявок канал зарекомендовал себя как надежный партнер для рекламы в TG. Размещайте интеграции уже сегодня и привлекайте новых клиентов вместе с Telega.in!
Вы снова сможете добавить каналы в корзину из каталога
Комментарий