
- Главная
- Каталог
- Интернет технологии
- C/C++ | Вопросы собесов
C/C++ | Вопросы собесов
Разбираем вопросы с собеседований на С/С++ разработчика
Статистика канала
View), которые её используют. Если контроллер слишком тесно связан с представлением, код становится сложно поддерживать.
В сложных GUI-приложениях (например, Qt, MFC) модель может изменяться из разных мест, и контроллеру трудно управлять обновлениями.
🚩Слабая масштабируемость в больших проектах
MVC не даёт чёткого разделения ответственности, если приложение очень сложное.
Один контроллер может управлять сразу несколькими представлениями, что создаёт запутанный код. Часто приходится создавать "промежуточные" контроллеры для разруливания логики → код становится сложнее.
В сложных веб-приложениях (React, Angular) MVC превращается в "спагетти", потому что представления (View) и контроллеры (Controller) начинают выполнять логические задачи, которые должны быть в модели.
Использовать слоистую архитектуру (Layered Architecture) или Service Layer.
🚩"Толстый" контроллер (Fat Controller)
Если бизнес-логика попадает в контроллер, он становится раздутым и сложно поддерживаемым. Контроллер начинает обрабатывать данные, проверять их, а не просто передавать управление.
class UserController {
public:
void login(std::string username, std::string password) {
if (username.empty() || password.empty()) {
std::cout << "Ошибка: пустые данные\n";
return;
}
if (username == "admin" && password == "1234") {
std::cout << "Вход выполнен!\n";
} else {
std::cout << "Неверный логин/пароль\n";
}
}
};{}
Здесь контроллер сам проверяет данные и логику аутентификации, что нарушает принцип разделения ответственности (SRP).
Вынести бизнес-логику в Model или Service.
Использовать слоистую архитектуру (Service Layer, Repository Pattern).
class AuthService {
public:
bool authenticate(const std::string& username, const std::string& password) {
return (username == "admin" && password == "1234");
}
};
class UserController {
AuthService authService;
public:
void login(const std::string& username, const std::string& password) {
if (authService.authenticate(username, password)) {
std::cout << "Вход выполнен!\n";
} else {
std::cout << "Неверный логин/пароль\n";
}
}
};{}
🚩Трудности тестирования (особенно View и Controller)
Model тестируется легко, потому что она "чистая" (без UI-кода).
Controller и View сложно тестировать, потому что они зависят от пользовательского ввода и интерфейса.
Unit-тестирование UI-кода практически невозможно, требуется мокирование.
🚩Не всегда подходит для многопоточных приложений
В многопоточных приложениях несколько представлений могут обращаться к одной модели, вызывая гонки данных.
Если контроллер один на несколько потоков, он становится "узким местом" (bottleneck).
Ставь 👍 и забирай 📚 Базу знаний* после типа данных.
int* ptr; // Указатель на целое число
double* dptr; // Указатель на число с плавающей {}
🟠Инициализация
Указатели могут быть инициализированы адресом существующей переменной с помощью оператора & (адресной операции).
int value = 42;
int* ptr = &value; // ptr хранит адрес переменной {}
Пример использования указателей
#include <iostream>
int main() {
int value = 42;
int* ptr = &value; // ptr хранит адрес переменной value
std::cout << "Значение value: " << value << std::endl; // Вывод: 42
std::cout << "Адрес value: " << &value << std::endl; // Вывод: Адрес переменной value
std::cout << "Значение, на которое указывает ptr: " << *ptr << std::endl; // Вывод: 42
std::cout << "Адрес, хранимый в ptr: " << ptr << std::endl; // Вывод: Адрес переменной value
// Изменение значения через указатель
*ptr = 10;
std::cout << "Новое значение value: " << value << std::endl; // Вывод: 10
return 0;
}{}
🚩Операции с указателями
🟠Доступ к значению по указателю
Используя оператор разыменования *, можно получить или изменить значение, на которое указывает указатель.
int value = 42;
int* ptr = &value;
*ptr = 10; // Изменение значения value через {}
🟠Адресные операции
& оператор используется для получения адреса переменной.
* оператор используется для разыменования указателя.
🟠Арифметика указателей
Указатели поддерживают арифметические операции, такие как сложение и вычитание, что позволяет перемещаться по массивам.
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr;
std::cout << *(ptr + 1) << std::endl; // Вывод: 2 (второй элемент массива{}
🚩Типы указателей
🟠Указатель на объект
Указатель, который хранит адрес конкретного объекта.
int value = 42;
int* ptr = &value;{}
🟠Указатель на массив
Указатель может указывать на первый элемент массива, и арифметика указателей позволяет перемещаться по массиву.
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr;{}
🟠Указатель на функцию
Указатель, который хранит адрес функции.
void func() {
std::cout << "Hello, World!" << std::endl;
}
void (*funcPtr)() = func;
funcPtr(); // Вызов функции через указатель{}
Ставь 👍 и забирай 📚 Базу знаний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;
}{}
Ставь 👍 и забирай 📚 Базу знанийconst после объявления метода указывает, что метод является константным.
🚩Объявление и определение константного метода
Чтобы объявить метод как константный, нужно добавить ключевое слово const после списка параметров метода:
class MyClass {
public:
int getValue() const; // Константный метод
void setValue(int newValue); // Неконстантный метод
private:
int value;
};
// Определение константного метода
int MyClass::getValue() const {
return value; // Допустимо: метод не изменяет состояние объекта
}
// Определение неконстантного метода
void MyClass::setValue(int newValue) {
value = newValue; // Допустимо: метод изменяет состояние объекта
}{}
🚩Пример использования константных методов
#include <iostream>
class MyClass {
public:
MyClass(int v) : value(v) {}
int getValue() const { // Константный метод
return value;
}
void setValue(int newValue) { // Неконстантный метод
value = newValue;
}
private:
int value;
};
int main() {
MyClass obj(10);
std::cout << "Value: " << obj.getValue() << std::endl; // Вывод: 10
obj.setValue(20);
std::cout << "New value: " << obj.getValue() << std::endl; // Вывод: 20
// Константный объект
const MyClass constObj(30);
std::cout << "Const object value: " << constObj.getValue() << std::endl; // Вывод: 30
// constObj.setValue(40); // Ошибка: setValue не может быть вызван для константного объекта
return 0;
}{}
🚩Почему и когда использовать константные методы
🟠Гарантия неизменности
Константные методы гарантируют, что состояние объекта не будет изменено. Это полезно для методов, которые предназначены только для чтения данных объекта, например, методы доступа (геттеры).
🟠Безопасность и семантика
Константные методы помогают лучше понимать код и повышают его безопасность, так как ясно видно, какие методы могут изменять состояние объекта, а какие нет.
🟠Работа с константными объектами
Константные методы можно вызывать на константных объектах и через константные ссылки или указатели. Это позволяет использовать объекты в контексте, где важно гарантировать их неизменность.
🚩Вызов константных методов на константных объектах
const MyClass constObj(30);
constObj.getValue(); // Допустимо: getValue — константный метод
// constObj.setValue(40); // Ошибка: setValue — неконстантный метод{}
Ставь 👍 и забирай 📚 Базу знаний
#include <iostream>
class Base {
public:
~Base() { std::cout << "Деструктор Base\n"; } // НЕ виртуальный!
};
class Derived : public Base {
public:
~Derived() { std::cout << "Деструктор Derived\n"; }
};
int main() {
Base* obj = new Derived();
delete obj; // Проблема! Деструктор Derived НЕ вызывается!
}{}
Вывод
Деструктор Base{}
🚩Решение: сделать деструктор виртуальным
Если объявить деструктор базового класса виртуальным (virtual), то при удалении через указатель на базовый класс будет вызван полный цепной деструктор. Используем virtual
class Base {
public:
virtual ~Base() { std::cout << "Деструктор Base\n"; } // Виртуальный!
};
class Derived : public Base {
public:
~Derived() { std::cout << "Деструктор Derived\n"; }
};
int main() {
Base* obj = new Derived();
delete obj; // Теперь вызываются оба деструктора
}{}
Вывод
Деструктор Derived
Деструктор Base{}
🚩Когда не нужно делать деструктор виртуальным?
Когда класс не предназначен для наследования
Например, std::vector, std::string, std::unique_ptr – у них нет виртуального деструктора, так как они не предполагают полиморфное использование
Когда удаление всегда происходит по ссылке/указателю на сам класс, а не на базовый
Derived* obj = new Derived();
delete obj; // В любом случае вызовет правильный деструктор{}
Если класс "заморожен" (final)
class FinalClass final {
public:
~FinalClass() { std::cout << "Деструктор\n"; }
};{}
Ставь 👍 и забирай 📚 Базу знанийОтзывы канала
- Добавлен: Сначала новые
- Добавлен: Сначала старые
- Оценка: По убыванию
- Оценка: По возрастанию
Каталог Телеграм-каналов для нативных размещений
C/C++ | Вопросы собесов — это Telegam канал в категории «Интернет технологии», который предлагает эффективные форматы для размещения рекламных постов в Телеграмме. Количество подписчиков канала в 4.3K и качественный контент помогают брендам привлекать внимание аудитории и увеличивать охват. Рейтинг канала составляет 5.7, количество отзывов – 1, со средней оценкой 5.0.
Вы можете запустить рекламную кампанию через сервис Telega.in, выбрав удобный формат размещения. Платформа обеспечивает прозрачные условия сотрудничества и предоставляет детальную аналитику. Стоимость размещения составляет 3076.92 ₽, а за 4 выполненных заявок канал зарекомендовал себя как надежный партнер для рекламы в TG. Размещайте интеграции уже сегодня и привлекайте новых клиентов вместе с Telega.in!
Вы снова сможете добавить каналы в корзину из каталога
Комментарий