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

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

@easy_c_plus

Сайт: https://easyoffer.ru/
Все каналы: t.me/+xGeAw6ckJ4liYzQy

Контакт для рекламы: @sendme_ads
Subscribers
4.2K
Photos
37
Videos
1
Links
1.6K

Showing posts older than #2113 · Back to latest

Older Posts 20 shown
Post #2112 632
🤔 Может ли быть проблема со вставкой ста элементов через push_back?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 1
Post #2111 666
🤔 Для чего используется ключевое слово 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;
}


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2110 664
🤔 Именно override переопределяет метод?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
  • 💊 1
Post #2109 725
🤔 Расскажи про константные методы?

Это методы класса, которые гарантируют, что они не будут изменять состояние объекта, для которого вызываются. Эти методы можно вызывать на константных объектах и через константные ссылки или указатели. Ключевое слово 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 — неконстантный метод


Ставь 👍 и забирай 📚 Базу знаний
Post #2108 651
🤔 Что будет если в функции помеченной как noexcept бросить исключение?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 3
Post #2107 686
🤔 Когда нужно объявлять деструктор виртуальным?

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

🚩Зачем нужен виртуальный деструктор?

Если у базового класса деструктор не виртуальный, а удаление происходит через указатель на базовый класс, деструктор производного класса не вызовется! Это приведёт к утечке памяти или некорректному освобождению ресурсов.
Пример проблемы без виртуального деструктора
#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"; }
};


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Post #2106 702
🤔 Какой контейнер используется в priority_queue?

priority_queue в C++ обычно реализован на базе std::vector с использованием кучи (heap) для управления приоритетами.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 3
Post #2105 807
🤔 Как работает счетчик у shared_ptr?

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

🚩Как он работает

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

🟠Уничтожение и перемещение
Когда shared_ptr уничтожается или его значение присваивается другому shared_ptr, счетчик ссылок уменьшается.
Если shared_ptr перемещается (используя move-семантику), счетчик ссылок не изменяется, так как владение объектом передается новому shared_ptr, а старый перестает владеть объектом.

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

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

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

Ставь 👍 и забирай 📚 Базу знаний
  • 🔥 2
Post #2104 722
🤔 Как можно проверить, что нет double exception?

Используйте функцию std::uncaught_exceptions() (C++17) или std::uncaught_exception() (до C++17) для проверки активного исключения.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 2
Post #2103 806
🤔 Расскажи о различиях ссылки и указателя

Ссылки (references) и указатели (pointers) в C++ служат для работы с памятью и позволяют передавать и изменять данные по адресу. Однако у них есть важные различия, которые влияют на использование и безопасность кода.

🚩Подробное объяснение с примерами

🟠Ссылка – это псевдоним для переменной
Ссылка является просто альтернативным именем для существующей переменной. Она не может существовать без объекта, к которому привязана.
#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) или менять адрес
В большинстве случаев лучше использовать ссылки, так как они безопаснее

Ставь 👍 и забирай 📚 Базу знаний
Post #2102 686
🤔 Что такое абстрактный класс?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
  • 🔥 1
Post #2101 784
🤔 В чем разница 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;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2099 676
🤔 Асимптотическая сложность вставки и удаления в list и vector?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
Post #2097 721
🤔 Какова суть принципа подстановки Барбары Лисков?

Является одним из пяти принципов SOLID и был предложен Барбарой Лисков в 1987 году. Этот принцип гласит, что объекты базового (родительского) класса должны быть заменяемы объектами производного (дочернего) класса без нарушения правильности программы. Другими словами, если класс S является подтипом класса T, то объекты типа T должны быть заменяемы объектами типа S без изменения желаемых свойств программы.

🚩Почему это нужно?

LSP помогает обеспечить правильное использование наследования и полиморфизма в объектно-ориентированном программировании. Если принцип подстановки нарушен, то полиморфизм может привести к неожиданным ошибкам и некорректному поведению программы. Следование LSP делает код более гибким, надежным и легким для сопровождения.

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

1⃣Поддерживали контракт, заданный базовым классом.
2⃣Не изменяли ожидаемое поведение базового класса.
3⃣Не ослабляли инварианты базового класса.
4⃣Не нарушали постусловия и предусловия базового класса.

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

🟠Нарушение LSP
Здесь класс 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; }
};


Ставь 👍 и забирай 📚 Базу знаний
  • 🔥 1
Post #2096 637
🤔 Какая сложность работы с кучей?

Работа с кучей (heap) в алгоритмах обычно имеет временную сложность O(log n) для операций вставки и удаления, т.к. элементы необходимо сравнивать и переставлять для поддержания свойств кучи.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 4
Post #2095 765
🤔 Что такое процессы, потоки?

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

🚩Процессы

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

🟠Изоляция
Процессы изолированы друг от друга. Один процесс не может напрямую изменить данные другого процесса.
🟠Ресурсы
Каждый процесс имеет свое собственное адресное пространство, дескрипторы файлов и т. д.
🟠Создание
Создание нового процесса может быть дорогостоящим по времени и ресурсам, так как включает выделение памяти и инициализацию ресурсов.

#include <iostream>
#include <unistd.h>

int main() {
pid_t pid = fork(); // Создание нового процесса

if (pid == 0) {
// Дочерний процесс
std::cout << "Это дочерний процесс" << std::endl;
} else {
// Родительский процесс
std::cout << "Это родительский процесс, PID дочернего процесса: " << pid << std::endl;
}

return 0;
}


🚩Потоки

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

🟠Разделение ресурсов
Потоки разделяют память и ресурсы процесса, что делает межпоточное взаимодействие более эффективным.

🟠Создание
Создание потоков обычно быстрее и менее ресурсозатратно по сравнению с процессами.

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

#include <iostream>
#include <thread>

void printMessage(const std::string& message) {
std::cout << message << std::endl;
}

int main() {
std::thread t1(printMessage, "Привет из потока 1");
std::thread t2(printMessage, "Привет из потока 2");

t1.join(); // Ожидание завершения потока
t2.join();

return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2094 610
🤔 Что известно о истории умных указателей?

- Первым в стандарт попал auto_ptr в C++98 — оказался неудобным и небезопасным.
- Затем Boost предложил shared_ptr, scoped_ptr, weak_ptr.
- С выходом C++11 появились современные unique_ptr, shared_ptr, weak_ptr.
- auto_ptr был объявлен устаревшим и позже удалён (в C++17).
- Современные умные указатели активно используются для автоматического управления памятью и соблюдения RAII.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 🔥 2
  • 👍 1
Post #2093 626
🤔 Расскажите про ключевое слово _fastcall?

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

🚩Основные характеристики

🟠Передача аргументов через регистры
_fastcall передает первые два целочисленных аргумента (например, int, char, long) через регистры ECX и EDX на архитектуре x86. Остальные аргументы передаются через стек.

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

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

#include <iostream>

// Функция, использующая соглашение о вызове _fastcall
int __fastcall add(int a, int b) {
return a + b;
}

int main() {
int result = add(5, 3);
std::cout << "Result: " << result << std::endl; // Вывод: Result: 8
return 0;
}


🚩Примечания и ограничения

🟠Платформенная зависимость
_fastcall специфичен для архитектуры x86 и поддерживается не всеми компиляторами. На x64 используется другое соглашение о вызове, и _fastcall не применим.

🟠Совместимость с другими соглашениями о вызове
Если вы используете _fastcall вместе с другими соглашениями о вызове (например, __cdecl, __stdcall), нужно быть осторожным, чтобы избежать ошибок при вызове функций с разными соглашениями о вызове.

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

#include <iostream>

// Проверка платформы
#ifdef _M_IX86 // Если используется компилятор для архитектуры x86
int __fastcall add(int a, int b) {
return a + b;
}
#else
int add(int a, int b) {
return a + b;
}
#endif

int main() {
int result = add(5, 3);
std::cout << "Result: " << result << std::endl; // Вывод: Result: 8
return 0;
}


Ставь 👍 и забирай 📚 Базу знаний
Post #2092 604
🤔 Какая будет сложность удаления элемента с начала в vector?

Удаление элемента с начала вектора в C++ имеет временную сложность O(n), так как все элементы после удаляемого необходимо переместить ближе к началу вектора.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚Базу знаний
  • 👍 2
Post #2091 668
🤔 Что такое back_inserter, зачем он нужен?

std::back_inserter — это адаптер итератора из библиотеки STL, который позволяет удобно добавлять элементы в конец контейнера при использовании алгоритмов стандартной библиотеки (например, std::copy, std::transform и т. д.).

Он создает итератор-вставку (inserter iterator), который при попытке записи нового элемента фактически вызывает метод push_back() у контейнера.

🚩Почему он нужен?

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

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

Без back_inserter (приведет к ошибке!)
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination; // Пустой контейнер

// Ошибка! У destination нет места для элементов
std::copy(source.begin(), source.end(), destination.begin());

return 0;
}


Используем back_inserter (правильный вариант)
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination; // Начинаем с пустого контейнера

// Используем back_inserter
std::copy(source.begin(), source.end(), std::back_inserter(destination));

// Вывод результата
for (int num : destination) {
std::cout << num << " ";
}

return 0;
}


Вывод
1 2 3 4 5


🚩Где используется `back_inserter`?

🟠**std::transform – Преобразование элементов**
Применяем функцию ко всем элементам и добавляем результат в новый контейнер
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> squared;

std::transform(nums.begin(), nums.end(), std::back_inserter(squared),
[](int x) { return x * x; });

for (int num : squared) {
std::cout << num << " ";
}

return 0;
}


Вывод:
1 4 9 16 25


std::unique_copy – Удаление дубликатов
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
std::vector<int> nums = {1, 2, 2, 3, 4, 4, 5};
std::vector<int> unique_nums;

std::unique_copy(nums.begin(), nums.end(), std::back_inserter(unique_nums));

for (int num : unique_nums) {
std::cout << num << " ";
}

return 0;
}


Вывод:
1 2 3 4 5


Ставь 👍 и забирай 📚 Базу знаний
  • 👍 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →