Перейти к основному содержимому

3. Контейнер vector

Основные сведения

Шаблонный класс vector входит в библиотеку STL. Вскоре вы убедитесь, что можно очень много сделать, если знать основные принципы работы класса vector, не разбираясь в его реализации.

В классе vector поддерживаются динамические массивы. Динамическим массивом называется массив, размеры которого могут увеличиваться по мере необходимости. Класс vector оформлен в виде шаблона, что позволяет эффективно использовать его с разными типами. Другими словами, можно создать вектор объектов double, вектор объектов int, вектор объектов string и т.д. При помощи шаблона можно создать «класс чего угодно». Чтобы сообщить компилятору, с каким типом данных будет работать класс (в данном случае — какие элементы будут храниться в векторе), укажите имя нужного типа в угловых скобках <...>. Так, вектор объектов string обозначается vector<string>. Такая запись определяет специализированный вектор, в котором могут храниться только объекты string. Если попытаться занести в него объект другого типа, компилятор выдаст сообщение об ошибке.

Поскольку класс vector представляет собой "контейнер", то есть предназначается для хранения однотипных элементов, в нем должны быть предусмотрены средства для сохранения и извлечения элементов. Новые элементы добавляются в конец вектора функцией push_back() (помните, что эта функция принадлежит классу, поэтому, чтобы вызвать ее для конкретного объекта, нужно отделить ее имя от имени объекта символом точки). Извлечь элементы из вектора можно, используя индексацию: в классе выполнена перегрузка индексации. Благодаря перегрузке операторов программист работает с вектором как с массивом.

Учитывая все сказанное, рассмотрим пример использования векторов. Для этого следует включить в программу заголовочный файл <vector>:

// Копирование всего содержимого файла в вектор строк
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
#ifdef WIN32
system("chcp 65001");
#else
setlocale(LC_ALL, "Russian");
#endif
std::vector<std::string> v;
std::ifstream in("text.txt");
std::string line;
while (getline(in, line))
v.push_back(line); // Занесение строки в конец вектора
// Нумерация строк:
for (int i = 0; i < v.size(); i++) {
std::cout << i << ": " << v[i] << std::endl;
}
}

Результат выполнения программы:

0: Aaaaddad@lll
1: weqwe231321
2: 123adsad
3: 12312sadasd
4: 3dffdsf
5: asdasdsadasd

Мы открываем файл и последовательно читаем его строки в объекты string. Объекты заносятся в конец вектора v. После завершения цикла while все содержимое файла будет находиться в памяти внутри объекта v.

Условие проверки цикла for означает, что для продолжения работы цикла счетчик i должен быть меньше количества элементов в векторе v (количество элементов определяется функцией size() класса vector).

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

Подход оператора >> к работе с потоками позволяет легко изменить программу так, чтобы файл разбивался не по строкам, а по словам, разделенным пропусками:

// Разбиение файла по словам, разделенный пропусками 
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
#ifdef WIN32
system("chcp 65001");
#else
setlocale(LC_ALL, "Russian");
#endif
std::vector<std::string> words;
std::ifstream in("text.txt");
std::string word;
while (in >> word) words.push_back(word);
for (int i = 0; i < words.size(); i++) {
std::cout << words[i] << std::endl;
}
}

Результат выполнения программы:

Aaaaddad@lll
weqwe231321
123adsad
12312sadasd
3dffdsf
asdasdsadasd

Следующее выражение обеспечивает ввод очередного "слова": while (in >> word). Когда условие цикла становится ложным, это означает, что был достигнут конец файла. Чтобы убедиться в том, как просто работать с классом vector, рассмотрим следующий пример, в котором создается вектор с элементами типа int:

// Создание вектора для хранения целых чисел 
#include <iostream>
#include <vector>

int main() {
std::vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
for (int i = 0; i < v.size(); i++) {
std::cout << v[i] << ", ";
}
std::cout << std::endl;
for (int i = 0; i < v.size(); i++) {
v[i] = v[i] * 10; // Присваивание
}
for (int i = 0; i < v.size(); i++) {
std::cout << v[i] << ", ";
}
std::cout << std::endl;
}

Результат выполнения программы:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
0, 10, 20, 30, 40, 50, 60, 70, 80, 90,

Для класса vector определяются следующие операторы сравнения: =, <, <=, !=, >, >=.

Наиболее важными функциями-членами класса vector являются функции size(), begin(), end(), push_back(), insert() и erase(). Функция size() возвращает текущий размер вектора. Эта функция особенно полезна, поскольку позволяет узнать размер вектора во время выполнения программы. Помните, вектор может расти по мере необходимости, поэтому размер вектора необходимо определять не в процессе компиляции, а в процессе выполнения программы. Функция begin() возвращает итератор начала вектора. Функция end() возвращает итератор конца вектора. Как уже говорилось, итераторы очень похожи на указатели и с помощью функций begin() и end() можно получить итераторы (читай: указатели) начала и конца вектора. Функция push_back() помещает значение в конец вектора. Если это необходимо для размещения нового элемента, вектор удлиняется. В середину вектора элемент можно добавить с помощью функции insert(). Вектор можно инициализировать. В любом случае, если в векторе хранятся элементы, то с помощью оператора индекса массива к этим элементам можно получить доступ и их изменить. Удалить элементы из вектора можно с помощью функции erase().

Примеры работы с векторами

Пример 1

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

#include <iostream>
#include <vector>

int main() {
std::vector<int> v; // создание вектора нулевой длины
int i;
// помещение значений в вектор
for (i = 0; i < 10; i++) {
v.push_back(i);
}
// доступ к содержимому вектора
// с использованием оператора индекса
for (i = 0; i < 10; i++) {
std::cout << v[i] << " ";
}
std::cout << std::endl;
// доступ к вектору через итератор
std::vector<int>::iterator p = v.begin();
while (p != v.end()) {
std::cout << *p << " ";
p++;
}
std::cout << std::endl;
}

Результат выполнения программы:

0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9

В этой программе сначала создается вектор v нулевой длины. Далее с помощью функции-члена push_back() к концу вектора v добавляются некоторые значения и размер вектора v увеличивается. Обратите внимание на объявление итератора р. Тип iterator определяется с помощью класса-контейнера. То есть, чтобы получить итератор для выбранного контейнера, объявить его нужно именно так, как показано в примере: просто укажите перед типом iterator имя контейнера. С помощью функции-члена begin() итератор инициализируется, указывая на начало вектора. Возвращаемым значением этой функции как раз и является итератор начала вектора. Теперь, применяя к итератору оператор инкремента, можно получить доступ к любому выбранному элементу вектора. Этот процесс совершенно аналогичен использованию указателя для доступа к элементам массива. С помощью функции-члена end() определяется факт достижения конца вектора. Возвращаемым значением этой функции является итератор того места, которое находится сразу за последним элементом вектора, Таким образом, если итератор р равен возвращаемому значению функции v.end(), значит, конец вектора был достигнут.

Пример 2

Помимо возможности размещения элементов в конце вектора, с помощью функции-члена insert() их можно вставлять в его середину. Удалять элементы из вектора можно с помощью функции-члена erase(). В примере представлена демонстрация функций insert() и erase().

#include <iostream>
#include <vector>

int main() {
#ifdef WIN32
system("chcp 65001");
#else
setlocale(LC_ALL, "Russian");
#endif
std::vector<float> v(5, 1); // создание пятиэлементного вектора из единиц
// вывод на экран исходных размера и содержимого вектора
std::cout << "Размер = " << v.size() << std::endl;
std::cout << "Исходное содержимое:\n";
for (unsigned int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
v.clear();
for (float i = 0; i < 10; i += 1.1) {
v.push_back(i);
}
std::cout << "\nИсходное содержимое:\n";
for (unsigned int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
std::vector<float>::iterator p = v.begin();
p += 2; // указывает на третий элемент
// вставка в вектор на то место,
// куда указывает итератор, десяти новых элементов,
// каждый из которых равен 9
v.insert(p, 3, 9);
// вывод на экран размера
// и содержимого вектора после вставки
std::cout << "\n\nРазмер после вставки = " << v.size();
std::cout << "\nСодержимое после вставки:\n";
for (unsigned int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
std::cout << std::endl;
// удаление вставленных элементов
p = v.begin();
p += 2; // указывает на третий элемент
v.erase(p, p + 10); // удаление следующих десяти элементов
// за элементом, на который указывает
// итератор р
// вывод на экран размера
// и содержимого вектора после удаления
std::cout << "\nРазмер после удаления — " << v.size();
std::cout << "\nСодержимое после удаления:\n";
for (unsigned int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
std::cout << std::endl;
}

Результат выполнения программы:

Размер = 5
Исходное содержимое:
1 1 1 1 1
Исходное содержимое:
0 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9

Размер после вставки = 13
Содержимое после вставки:
0 1.1 9 9 9 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9

Размер после удаления — 3
Содержимое после удаления:
0 1.1 9.9

Пример 3

В следующем примере вектор используется для хранения объектов класса, определённого программистом. Обратите внимание, что в классе определяются конструктор по умолчанию и перегруженные версии операторов < и ==.

#include <iomanip>
#include <iostream>
#include <vector>

class Demo {
double d;

public:
Demo() { d = 0.0; }
Demo(double x) { d = x; }
Demo &operator-(double x) {
d = x;
return *this;
}
double getd() { return d; }
friend bool operator<(Demo a, Demo b) { return a.getd() < b.getd(); }
friend bool operator==(Demo a, Demo b) { return a.getd() == b.getd(); }
};

int main() {
#ifdef WIN32
system("chcp 65001");
#else
setlocale(LC_ALL, "Russian");
#endif
std::vector<Demo> v = {Demo(6.1), Demo(3.9), Demo(2.7), Demo(1.2)};
unsigned int i;
std::cout << std::setprecision(3);
for (i = 0; i < 10; i++) {
v.push_back(Demo(i / 3.0));
}
for (i = 0; i < v.size(); i++) {
std::cout << v[i].getd() << '\t';
}
std::cout << std::endl;
for (i = 0; i < v.size(); i++) {
v[i] = v[i].getd() * 2.1;
}
for (i = 0; i < v.size(); i++) {
std::cout << v[i].getd() << '\t';
}
std::cout << std::endl;
}

Результат выполнения программы:

6.1     3.9     2.7     1.2     0       0.333   0.667   1       1.33    1.67    2       2.33    2.67    3
12.8 8.19 5.67 2.52 0 0.7 1.4 2.1 2.8 3.5 4.2 4.9 5.6 6.3

В этой программе сначала создается вектор v, в который сразу записывается четыре объекта класса Demo. Затем в вектор в цикле добавляется ещё десять объектов. Следующий цикл выводит на экран содержимое поля d каждого объекта, при этом используется метод getd(). Ешё следующий цикл меняет значение поля d каждого объекта с помощью индексации и присваивания. И последний цикл снова выводит содержимое вектора на экран.

Пример 4

Следующий пример – это доработанный пример 3. Здесь используется функция вывода вектора на экран.

#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

class Demo {
double d;

public:
Demo() { d = 0.0; }
Demo(double x) { d = x; }
Demo& operator-(double x) {
d = x;
return *this;
}
double getd() { return d; }
bool operator<(Demo& a) { return getd() < a.getd(); }
bool operator==(Demo& a) { return getd() == a.getd(); }
};
void print(std::vector<Demo> v) {
unsigned int i;
for (i = 0; i < v.size(); i++) {
std::cout << v[i].getd() << '\t';
}
}

int main() {
#ifdef WIN32
system("chcp 65001");
#else
setlocale(LC_ALL, "Russian");
#endif
std::vector<Demo> v = {Demo(6.1), Demo(3.9), Demo(2.7), Demo(1.2)};
unsigned int i;
std::cout << std::setprecision(3);
for (i = 0; i < 10; i++) {
v.push_back(Demo(i / 3.0));
}
print(v);
std::cout << std::endl;
for (i = 0; i < v.size(); i++) {
v[i] = v[i].getd() * 2.1;
}
print(v);
std::cout << std::endl;
}

Результат выполнения программы:

6.1     3.9     2.7     1.2     0       0.333   0.667   1       1.33    1.67    2       2.33    2.67    3
12.8 8.19 5.67 2.52 0 0.7 1.4 2.1 2.8 3.5 4.2 4.9 5.6 6.3