БлогNot. C++: изучаем стандартный контейнер vector

C++: изучаем стандартный контейнер vector

Стандартная библиотека шаблонов STL (Standard Template Library)позволяет создавать списки, векторы, очереди, стеки и т.д. из элементов любых типов данных. Не для всех контейнеров есть готовые шаблоны, скажем, их нет для матрицы, произвольного дерева узлов и т.д. Но в таких случаях можно использовать одни шаблоны для создания других.

Класс vector - самый простой и часто применяемый шаблон стандартной библиотеки STL. По сути, он призван заменить "неуправляемый" (точнее, управляемый программистом) динамический массив. Вектор удобнее, чем обычный динамический массив. Он не ускорит работу вашей программы и не предоставит каких-либо "секретных механизмов" управления оперативной памятью. Он просто возьмёт на себя заботу о своей размерности и предоставит методы для типовых операций с массивами.

Для работы с векторами подключается заголовочный файл vector и стандартное пространство имен:

#include <vector>
using namespace std;

Пространство имён можно, конечно, и не подключать, но тогда придётся везде писать std::vector вместо vector.

Для начала опишем пустой вектор:

vector <int> v; 

Из описания видно, что мы имеем дело с шаблоном, то есть, вектор можно составлять из элементов любого простого или составного типа. Здесь мы использовали тип элементов int.

Теперь посмотрим, как вектор управляет распределением памяти:

v.reserve(10); 

Этим оператором память зарезервирована под 10 элементов, но размерность вектора, возвращаемая методом v.size(), всё равно равна нулю.

v.resize(10); 

Вот теперь размерность вектора действительно равна 10, и метод v.size() вернёт это значение.

Все показанные три действия мы могли выполнить одним оператором:

vector <int> v(10);

Здесь произошёл вызов конструктора класса vector, мы которому передали аргумент-размерность, равный 10.

С элементами вектора можно работать двумя основными способами:

  • переопределенным оператором [] - корректность индекса тогда не контролируется, как и для обычных массивов C/C++;
  • методом at(i), где i - индекс нужного элемента, при этом, контролируется корректность индекса, и, если нужно, генерируется исключение.

Обработка исключений в простейшем случае делается так:

1. Подключить заголовочный файл <exception>;

2. "Подозрительный" код, который может привести к ошибке, заключить в блок try ("попытаться"):

try { /* сюда */ }

3. Обработку ошибочных ситуаций вынести в блок catch ("перехватить"):

catch (exception e) { /* сюда */ }

В частности, системное сообщение об ошибке будет возвращаться методом e.what().

Например, показанный ниже код не сгенерирует исключения (в новых сборках Visual Studio 2019 уже сгенерируется системное исключение):

try {
 v[11] = 11; //не вылетит, но и не покажет элемент, 
 //[] не контролирует корректность индекса, как и нативный оператор
}
catch (exception e) { 
 //Этого исключения мы никогда не увидим
 cout << "\nError 1: " << e.what() << endl; 
}

А вот этот - сгенерирует:

try {
 v.at(11) = 11;
}
catch (exception e) { cout << "\nError 2: " << e.what() << endl;  }

Проверить, пуст ли вектор, можно методом empty:

if (!c.empty()) {
 /* контейнер не пуст */
}

Как и для динамических массивов, для обработки векторов часто пишут функции. Так как вектор - это шаблонный класс, удобнее в функциях также применять шаблоны. Например, метод печати вектора в консоль может иметь вид:

template <typename T>
void printv (vector <T> &v) { //функция для вывода вектора в консоль
 int n = v.size();
 cout << endl << "Size=" << n << " Items=";
 for (int i = 0; i < n; i++) cout << v[i] << " ";
}

Это сработает с любым скалярным типом данных, для которого компилятор смог переопределить оператор []:

vector <int> v(10); 
printv(v);
vector <double> f(10);
printv(f);

Скопировать вектор можно как минимум двумя способами:

1. Вызвать конструктор, которому передать ранее определенный вектор:

vector <int> b(v); 

2. Скопировать вектор поэлементно в цикле for с помощью оператора [] или метода at, думаю, Вы легко напишете такой код самостоятельно.

Векторы можно сравнивать переопределённым оператором ==:

cout << endl << (v == b);

Для добавления элементов сущестует метод insert, имеющий несколько перегрузок:

v.insert(v.end(), 10); //добавили элемент со значением 10 в конец вектора
v.insert(v.begin(), -10); //добавили элемент со значением -10 в начало вектора

Здесь мы уже чуть забежали вперёд, потому что метод insert, как и другие методы класса, для перебора элементов контейнера требует определить итератор (аналог указателя для обычных массивов):

vector <int>::iterator it = v.begin(); //определил итератор и поставил на начало
it += 2; //сдвинулся на 2 позиции вправо
v.insert(it, 5); //и вставил туда элемент

Можно вставить значение в вектор и другим методом:

v.emplace(it, 7); 

Аналогично обстоит дело с методом erase для удаления элементов и другими методами класса. Более того, многие имена методов применимы и к другим контейнерам STL, а не только к вектору.

Чаще всего для добавления элемента в конец вектора пишут так:

v.push_back(13); //добавить в конец контейнера элемент

Возможен и вполне традиционный ввод элементов вектора с клавиатуры:

int n = 0; 
cin.clear();
cout << "Size="; cin >> n;
vector <int> c(n);
cout << "Items=";
for (int i = 0; i < c.size(); i++) cin >> c[i];
printv(c);

Изучив итераторы и встроенные алгоритмы, мы могли бы выполнить обмен данными с консолью и без "видимых" циклов:

#include <iterator>
//...
copy(c.begin(), c.end(), ostream_iterator<int>(cout, " "));
//вывели вектор в консоль с разделителем элементов - пробелом
//...
copy(istream_iterator<int>(cin), istream_iterator<int>(),back_inserter(c));
//ввод вектора с помощью итератора из консоли, завершение - комбинация клавиш Ctrl+D

Как и на обычные динамические структуры, на контейнеры можно создавать указатели:

int *ptr = c.data();

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

Можно также инициализировать контейнер "обычным" статическим массивом:

int d[3] = { 1,2,3 };
vector <int> v(begin(d), end(d));
printv(v);

- классический код, или просто

vector <int> v { 1,2,3 };

в компиляторах Studio 2015 и выше.

Ну а всё остальное Вы без проблем освоите сами, изучив STL детальней :)

30.04.2017, 14:49 [13074 просмотра]


теги: c++ учебное

показать комментарии (1)