Списки и очереди

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

Класс list: двусвязный список

Класс list реализует двусвязный список, элементы которого хранят указатели на предыдущий и последующий элементы. Благодаря этому вставка и удаление элементов происходит быстро, достаточно изменить несколько ссылок, а не перемещать элементы, как это делает динамический массив. Но получить доступ к элементу списка по индексу нельзя, ведь связный список не содержит индексов и элементы могут быть расположены в разных местах памяти. Прежде чем использовать класс, необходимо в начало программы добавить инструкцию:

#include <list>

Создание объекта

Объявление класса list:

template<typename _Tp, typename _Alloc = allocator<_Tp> >
   class list : protected _List_base<_Tp, _Alloc>;

Создать экземпляр класса list можно следующими способами (полный список конструкторов смотрите в документации):

  • объявить переменную без инициализации. Для этого перед названием переменной указывается название класса, а после названия внутри угловых скобок задается тип данных. В этом случае список не содержит элементов. Пример объявления без инициализации:
std::list<int> arr;
  • указать внутри круглых скобок количество элементов. Пример:
std::list<int> arr(5);
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 0 0 0 0 0

Все элементы будут иметь значения по умолчанию для типа. Например, для типа int все элементы будут содержать значение 0. Указать другое значение можно во втором параметре. Пример создания списка из 5 элементов со значением 1:

std::list<int> arr(5, 1);
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 1 1 1 1
  • перечислить значения через запятую внутри фигурных скобок:
std::list<int> arr1{1, 2, 3};
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3
std::list<int> arr2 = {4, 5, 6};
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 4 5 6
  • указать объект класса list внутри круглых скобок или после оператора = (доступны конструкторы копирования и перемещения):
std::list<int> arr1 = {1, 2, 3};
// Создание копии
std::list<int> arr2(arr1);
arr2.front() = 55;
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 55 2 3
// Перемещение элементов
std::list<int> arr3(std::move(arr1));
for (int &el : arr3) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3
std::cout << arr1.size() << std::endl; // 0
  • указать диапазон внутри контейнера с помощью итераторов. В первом параметре передается итератор, указывающий на начало диапазона, а во втором параметре — итератор, указывающий на конец диапазона:
std::list<int> arr1 = {1, 2, 3};
std::list<int> arr2(arr1.begin(), arr1.end());
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3

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

const int ARR_SIZE = 3;
int arr[ARR_SIZE] = {1, 2, 3};
std::list<int> arr2(arr, arr + ARR_SIZE);
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3

Над двумя объектами класса list определены операции ==, !=, <, <=, > и >=. Пример сравнения двух объектов:

std::list<int> arr1 = {1, 2, 3};
std::list<int> arr2(arr1.begin(), arr1.end());
if (arr1 == arr2) {
   std::cout << "arr1 == arr2" << std::endl;
}

Кроме того, один объект можно присвоить другому объекту. В этом случае выполняется поэлементное копирование (оператор копирования) или перемещение элементов (оператор перемещения). Пример:

std::list<int> arr1 = {1, 2, 3}, arr2, arr3;
// Создание копии
arr2 = arr1;
arr2.front() = 55;
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 55 2 3
// Перемещение элементов
arr3 = std::move(arr1);
for (int &el : arr3) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3
std::cout << arr1.size() << std::endl; // 0

Доступно также присваивание элементов из списка инициализации:

std::list<int> arr;
arr = {1, 2, 3};
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3

Вместо оператора = для присваивания значения можно воспользоваться методом assign(). Прототипы метода:

void assign(size_type count, const value_type &val);
template<typename _InputIterator>
   void assign(_InputIterator first, _InputIterator last);
void assign(initializer_list<value_type> list);

Первый прототип удаляет существующие элементы, а затем вставляет count элементов val. Второй прототип удаляет существующие элементы, а затем вставляет элементы из диапазона, ограниченного итераторами first и last. Третий прототип удаляет существующие элементы, а затем вставляет элементы из списка инициализации. Пример:

std::list<int> arr1 = {1, 2, 3}, arr2 = {4, 5};
arr1.assign(3, 0);
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 0 0 0
arr1.assign(arr2.begin(), arr2.end());
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 4 5
arr1.assign({6, 7, 8});
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 6 7 8

Пример присваивания элементов из обычного массива:

const int ARR_SIZE = 3;
int arr[ARR_SIZE] = {1, 2, 3};
std::list<int> arr2;
arr2.assign(arr, arr + ARR_SIZE);
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3

Вставка элементов

Вставить элементы позволяют следующие методы:

  • push_front() — добавляет элемент в начало списка (доступно копирование и перемещение значения);
  • push_back() — добавляет элемент в конец списка (доступно копирование и перемещение значения):
std::list<int> arr = {2, 3};
arr.push_front(1);  // В начало
arr.push_back(4);   // В конец
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3 4
  • emplace_front() — создает объект, передавая конструктору указанные через запятую значения, а затем добавляет объект в начало списка. Начиная со стандарта C++17, метод возвращает ссылку на созданный объект;
  • emplace_back() — создает объект, передавая конструктору указанные через запятую значения, а затем добавляет объект в конец списка. Начиная со стандарта C++17, метод возвращает ссылку на созданный объект:
class C {
public:
   int x, y;
   C(int a, int b) : x(a), y(b) { }
};
// ... Фрагмент опущен ...
std::list<C> arr;
arr.push_back(C(20, 20));
arr.emplace_front(10, 40);  // В начало
arr.emplace_back(30, 60);   // В конец
for (C &el : arr) std::cout << el.x << ' ';
std::cout << std::endl;     // 10 20 30
  • insert() — вставляет один или несколько элементов перед позицией pos, заданной с помощью итератора. Прототипы метода:
iterator insert(const_iterator pos, const value_type &val);
iterator insert(const_iterator pos, value_type &&val);
iterator insert(const_iterator pos, size_type count,
                const value_type &val);
template<typename _InputIterator>
   iterator insert(const_iterator pos, _InputIterator first,
                   _InputIterator last);
iterator insert(const_iterator pos,
                initializer_list<value_type> list);

Первые два прототипа вставляют элемент перед позицией pos, на которую указывает итератор и возвращают итератор, ссылающийся на вставленный элемент. Обратите внимание: итераторы списков не поддерживают операторы + и -, для перемещения итератора нужно использовать операторы ++ и -- или функцию advance():

std::list<int> arr = {10};
arr.insert(arr.begin(), 0);     // В начало
arr.insert(arr.end(), 20);      // В конец
std::list<int>::iterator it = arr.begin();
std::advance(it, 1);            // Перемещаем итератор
arr.insert(it, 5);              // В позицию 1
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 0 5 10 20

Третий прототип вставляет count копий элемента val перед позицией pos, на которую указывает итератор:

std::list<int> arr = {1, 2, 3};
arr.insert(arr.begin(), 3, 0);
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 0 0 0 1 2 3

Четвертый прототип вставляет элементы из диапазона, ограниченного итераторами first и last, перед позицией pos, на которую указывает итератор:

std::list<int> arr1 = {1, 2, 3}, arr2 = {4, 5, 6};
arr1.insert(arr1.end(), arr2.begin(), arr2.end());
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3 4 5 6

Пример вставки элементов из обычного массива:

std::list<int> arr1 = {1, 2, 3};
int arr2[] = {4, 5, 6};
arr1.insert(arr1.end(), arr2, arr2 + 3);
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3 4 5 6

Пятый прототип вставляет элементы из списка инициализации перед позицией pos, на которую указывает итератор:

std::list<int> arr = {1, 2, 3};
arr.insert(arr.end(), {4, 5, 6});
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3 4 5 6
  • emplace() — создает объект, передавая конструктору указанные через запятую значения во втором и последующих параметрах, а затем добавляет объект перед позицией, заданной в первом параметре. Метод возвращает итератор, ссылающийся на вставленный элемент. Добавим два объекта в конец списка и один в начало:
std::list<C> arr;
arr.emplace(arr.end(), 30, 40);
arr.emplace(arr.end(), 50, 60);
arr.emplace(arr.begin(), 10, 20);
for (C &el : arr) std::cout << el.x << ' ';
std::cout << std::endl; // 10 30 50
  • swap() — меняет элементы двух списков местами:
std::list<int> arr1 = {1, 2, 3}, arr2 = {4, 5, 6};
arr1.swap(arr2);
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 4 5 6
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3

Вместо метода swap() можно воспользоваться одноименной функцией:

std::list<int> arr1 = {1, 2, 3}, arr2 = {4, 5, 6};
std::swap(arr1, arr2);
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl; // 4 5 6
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3

Определение и изменение количества элементов

Для определения и изменения количества элементов списка предназначены следующие методы:

  • size() — возвращает количество элементов списка. Прототип метода:
size_type size() const noexcept;

Пример:

std::list<int> arr = {1, 2, 3};
std::cout << arr.size() << std::endl; // 3
  • empty() — возвращает значение true, если список не содержит элементов, и false — в противном случае. Прототип метода:
bool empty() const noexcept;

Пример:

std::list<int> arr1 = {1, 2, 3}, arr2;
std::cout << std::boolalpha;
std::cout << arr1.empty() << std::endl; // false
std::cout << arr2.empty() << std::endl; // true
  • max_size() — возвращает максимальное количество элементов, которое теоретически может содержать список. Прототип метода:
size_type max_size() const noexcept;

Пример:

std::list<int> arr = {1, 2, 3};
std::cout << arr.max_size() << std::endl;
// 768614336404564650
  • resize() — задает количество элементов, равное числу new_size. Если указанное количество элементов меньше текущего количества, то лишние элементы будут удалены. Если количество элементов необходимо увеличить, то в параметре val можно указать значение, которое заполнит новое пространство. Прототипы метода:
void resize(size_type new_size);
void resize(size_type new_size, const value_type &val);

Пример:

std::list<int> arr = {1, 2, 3};
arr.resize(2);
std::cout << arr.size() << std::endl;          // 2
arr.resize(5, 0);
std::cout << arr.size() << std::endl;          // 5
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl;                        // 1 2 0 0 0

Удаление элементов

Для удаления элементов предназначены следующие методы:

  • pop_front() — удаляет первый элемент. Прототип метода:
void pop_front();

Пример:

std::list<int> arr = {1, 2, 3};
arr.pop_front();
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 2 3
  • pop_back() — удаляет последний элемент. Прототип метода:
void pop_back();

Пример:

std::list<int> arr = {1, 2, 3};
arr.pop_back();
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 2
  • erase() — удаляет один элемент или элементы из диапазона. Прототипы метода:
iterator erase(const_iterator pos);
iterator erase(const_iterator first, const_iterator last);

Первый прототип удаляет элемент на который указывает итератор. Второй прототип удаляет элементы из диапазона, ограниченного итераторами first и last:

std::list<int> arr = {1, 2, 3, 4, 5};
arr.erase(arr.begin());
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 2 3 4 5
std::list<int>::iterator it1 = arr.begin();
std::list<int>::iterator it2 = arr.begin();
std::advance(it1, 1);   // Перемещаем итератор
std::advance(it2, 3);   // Перемещаем итератор
arr.erase(it1, it2);
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 2 5
  • clear() — удаляет все элементы. Прототип метода:
void clear() noexcept;

Пример:

std::list<int> arr = {1, 2, 3, 4, 5};
arr.clear();
std::cout << arr.size() << std::endl;     // 0
  • remove() — удаляет все элементы, имеющие значение val. Прототип метода:
void remove(const value_type &val);

Пример:

std::list<int> arr = {0, 1, 1, 1, 2, 1};
arr.remove(1);
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 0 2
  • remove_if() — удаляет элементы, для которых унарный функтор pred вернул значение true. Функтор, возвращающий логическое значение, называется предикатом. Прототип метода:
template<typename _Predicate>
   void remove_if(_Predicate pred);

Удалим все элементы, значения которых меньше двух:

std::list<int> arr = {0, 1, 1, 1, 2, 1, 3, 4};
arr.remove_if( [](int a){ return a < 2; } );
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 2 3 4
  • unique() — удаляет повторяющиеся элементы. Прототипы метода:
void unique();
template<typename _BinaryPredicate>
   void unique(_BinaryPredicate pred);

Первый прототип удаляет повторяющиеся элементы, расположенные рядом. Чтобы список состоял только из уникальных элементов необходимо предварительно отсортировать список. Второй прототип передает два элемента бинарному функтору. Если функтор вернет значение true, то элемент будет удален. Пример:

std::list<int> arr = {0, 1, 1, 1, 2, 1, 1, 1};
arr.unique();
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 0 1 2 1
arr = {0, 1, 1, 1, 2, 1, 1, 1};
arr.unique( [](int a, int b){ return a == b; } );
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 0 1 2 1

Доступ к элементам

Для доступа к элементам списка предназначены следующие методы (обратите внимание: получить доступ к элементу списка по индексу нельзя):

  • front() — возвращает ссылку на первый элемент. Метод позволяет как получить значение, так и изменить его. Прототипы метода:
reference front();
const_reference front() const;
  • back() — возвращает ссылку на последний элемент. Метод позволяет как получить значение, так и изменить его. Прототипы метода:
reference back();
const_reference back() const;

Пример:

std::list<int> arr = {1, 2, 3};
arr.front() = 55;
arr.back() = 88;
std::cout << arr.front() << std::endl; // 55
std::cout << arr.back() << std::endl;  // 88
  • begin(), end(), cbegin(), cend(), rbegin(), rend(), crbegin() и crend() — возвращают итераторы (см. разд. 16.1.1). Изменим и выведем значение первого элемента:
std::list<int> arr = {1, 2, 3};
std::list<int>::iterator it = arr.begin();
std::cout << *it << std::endl; // 1
*it = 0;
std::cout << *it << std::endl; // 0

Вместо методов begin() и end() можно воспользоваться одноименными функциями. Выведем значение последнего элемента:

std::list<int> arr = {1, 2, 3};
std::list<int>::iterator it = std::end(arr);
std::cout << *(--it) << std::endl; // 3

Обратите внимание: итераторы списков не поддерживают операторы + и -. Для перемещения итератора нужно использовать операторы ++ и -- или функцию advance(). Пример доступа к третьему элементу списка:

std::list<int> arr = {1, 2, 3, 4, 5};
std::list<int>::iterator it = arr.begin();
std::advance(it, 2);
std::cout << *it << std::endl; // 3

Перебор элементов

Перебрать все элементы можно с помощью цикла for each, итераторов и алгоритма for_each(). Пример использования цикла for each:

std::list<int> arr(3);
// Заполняем список значениями
int n = 1;
for (int &el : arr) {
   el = n++;
}
// Выводим значения
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3

Пример перебора элементов с помощью итераторов и цикла for:

std::list<int> arr = {1, 2, 3};
std::list<int>::iterator it1, it2;
for (it1 = arr.begin(), it2 = arr.end(); it1 != it2; ++it1) {
   std::cout << *it1 << ' ';
}
std::cout << std::endl; // 1 2 3

Пример перебора элементов с помощью итераторов и цикла while:

std::list<int> arr = {1, 2, 3};
auto it1 = arr.begin(), it2 = arr.end();
while (it1 != it2) {
   std::cout << *it1++ << ' ';
}
std::cout << std::endl; // 1 2 3

С помощью алгоритма for_each() умножим значение каждого элемента списка на 2, а затем выведем все значения в окно консоли:

// #include <algorithm>
std::list<int> arr = {1, 2, 3};
// Умножаем все элементы на 2
std::for_each( arr.begin(), arr.end(),
               [](int &a) { a *= 2; } );
// Выводим значения всех элементов
std::for_each( arr.begin(), arr.end(),
               [](int &a){ std::cout << a << ' '; } );
std::cout << std::endl; // 2 4 6

Сортировка списка

Для сортировки списка предназначен метод sort(). Прототипы метода:

void sort();
template<typename Compare> void sort(Compare comp);

Первый прототип производит сортировку по умолчанию (в пользовательских классах должен быть перегружен оператор <, иначе получите ошибку при компиляции):

std::list<int> arr = {4, 5, 3, 2, 6, 1};
arr.sort(); // Сортировка в прямом порядке
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3 4 5 6

Второй прототип позволяет указать пользовательский способ сравнения элементов. Функтор comp принимает два параметра и должен возвращать значение true, если первое значение меньше второго, и false — в противном случае. Отсортируем список в прямом и обратном порядке:

std::list<int> arr = {4, 5, 3, 2, 6, 1};
// Сортировка в прямом порядке
arr.sort( [](int &a, int &b) {return a < b;} );
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 2 3 4 5 6
// Сортировка в обратном порядке
arr.sort( [](int &a, int &b) {return a > b;} );
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 6 5 4 3 2 1

Переворачивание списка

Для изменения порядка следования элементов на противоположный предназначен метод reverse(). Обратите внимание на то, что метод переворачивает список, а не сортирует его. Элементы будут следовать в обратном порядке относительно исходного списка. Прототип метода:

void reverse() noexcept;

Пример:

std::list<int> arr = {4, 5, 3, 2, 6, 1};
arr.reverse();
for (int &el : arr) std::cout << el << ' ';
std::cout << std::endl; // 1 6 2 3 5 4

Перенос элементов из одного списка в другой

Выполнить перенос элементов из одного списка в другой позволяет метод splice(). Прототипы метода:

void splice(const_iterator pos, list &x) noexcept;
void splice(const_iterator pos, list &&x) noexcept;
void splice(const_iterator pos, list &x, const_iterator it) noexcept;
void splice(const_iterator pos, list &&x, const_iterator it) noexcept;
void splice(const_iterator pos, list &x, const_iterator first,
            const_iterator last) noexcept;
void splice(const_iterator pos, list &&x, const_iterator first,
            const_iterator last) noexcept;

Первые два прототипа переносят все элементы из списка x в текущий список, и вставляют их перед позицией pos, на которую указывает итератор. Пример переноса элементов в конец текущего списка:

std::list<int> arr1 = {1, 2, 3}, arr2 = {4, 5, 6};
arr1.splice(arr1.end(), arr2);
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl;                // 1 2 3 4 5 6
std::cout << arr2.size() << std::endl; // 0

Третий и четвертый прототипы переносят один элемент, на который указывает итератор it, из списка x в текущий список и вставляют его перед позицией pos, на которую указывает итератор. Перенесем второй элемент из списка arr2 в конец списка arr1:

std::list<int> arr1 = {1, 2, 3}, arr2 = {4, 5, 6};
std::list<int>::iterator it = arr2.begin();
++it;
arr1.splice(arr1.end(), arr2, it);
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl;                // 1 2 3 5
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl;                // 4 6

Пятый и шестой прототипы переносят элементы, входящие в диапазон, ограниченный итераторами first и last, из списка x в текущий список и вставляют их перед позицией pos, на которую указывает итератор. Перенесем все элементы, начиная со второго, из списка arr2 в конец списка arr1:

std::list<int> arr1 = {1, 2, 3}, arr2 = {4, 5, 6};
std::list<int>::iterator it = arr2.begin();
++it;
arr1.splice(arr1.end(), arr2, it, arr2.end());
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl;                // 1 2 3 5 6
for (int &el : arr2) std::cout << el << ' ';
std::cout << std::endl;                // 4

Объединение упорядоченных списков

Метод merge() предназначен для объединения упорядоченных списков. Результатом выполнения метода является упорядоченный текущий список. Для объединения не отсортированных списков можно использовать метод splice() (см. разд. 17.1.9). Прототипы метода merge():

void merge(list &x);
void merge(list &&x);
template<typename Compare> void merge(list &x, Compare comp);
template<typename Compare> void merge(list &&x, Compare comp);

Первые два прототипа переносят все элементы списка x в текущий список (в пользовательских классах должен быть перегружен оператор <, иначе получите ошибку при компиляции):

std::list<int> arr1 = {3, 5, 1}, arr2 = {4, 2};
arr1.sort(); // 1 3 5
arr2.sort(); // 2 4
arr1.merge(arr2);
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl;                // 1 2 3 4 5
std::cout << arr2.size() << std::endl; // 0

Третий и четвертый прототипы позволяют дополнительно указать пользовательский способ сравнения элементов. Функтор comp принимает два параметра и должен возвращать значение true, если первое значение меньше второго, и false — в противном случае. Произведем объединение списков, отсортированных в обратном порядке:

std::list<int> arr1 = {3, 5, 1}, arr2 = {4, 2};
arr1.sort( [](int &a, int &b) {return a > b;} ); // 5 3 1
arr2.sort( [](int &a, int &b) {return a > b;} ); // 4 2
arr1.merge(arr2, [](int &a, int &b) {return a > b;});
for (int &el : arr1) std::cout << el << ' ';
std::cout << std::endl;                // 5 4 3 2 1
std::cout << arr2.size() << std::endl; // 0

Учебник C++ (Qt Creator и MinGW)
Учебник C++ (Qt Creator и MinGW) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов