9.23 – Знакомство с std::vector

Добавлено 10 июня 2021 в 22:42
Глава 9 – Массивы, строки, указатели и ссылки  (содержание)

В предыдущем уроке мы представили std::array, который обеспечивает функциональность встроенных фиксированных массивов C++ в более безопасной и удобной форме.

Аналогичным образом стандартная библиотека C++ предоставляет функциональные возможности, которые делают более безопасной и простой работу с динамическими массивами. Это средство называется std::vector.

В отличие от std::array, который точно соответствует базовой функциональности фиксированных массивов, std::vector содержит некоторые дополнительные возможности. Это помогает сделать std::vector одним из самых полезных и универсальных инструментов в вашем наборе инструментов C++.

Знакомство с std::vector

Представленный в C++03, std::vector предоставляет функциональность динамического массива, которая обеспечивает собственное управление памятью. Это означает, что вы можете создавать массивы, длина которых устанавливается во время выполнения, без необходимости явно выделять и освобождать память с помощью операторов new и delete. std::vector находится в заголовке <vector>.

Объявить std::vector просто:

#include <vector>
 
// в объявлении не нужно указывать длину
std::vector<int> array;
// использовать список инициализаторов для инициализации массива (до C++11) 
std::vector<int> array2 = { 9, 7, 5, 3, 1 };
// использовать унифицированную инициализацию для инициализации массива
std::vector<int> array3 { 9, 7, 5, 3, 1 }; 
 
// как и в случае с std::array, тип может быть опущен, начиная с C++17
std::vector array4 { 9, 7, 5, 3, 1 }; // вывести к std::vector<int>

Обратите внимание, что как в неинициализированном, так и в инициализированном случае вам не нужно указывать длину массива во время компиляции. Это связано с тем, что std::vector будет динамически выделять память для своего содержимого, сколько потребуется.

Как и в std::array, доступ к элементам массива может осуществляться с помощью оператора [] (который не проверяет границы) или функции at() (которая выполняет проверку границ):

array[6] = 2;    // без проверки границ
array.at(7) = 3; // проверяет границы

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

Начиная с C++11, вы также можете присваивать значения std::vector, используя список инициализаторов:

array = { 0, 1, 2, 3, 4 }; // ok, длина массива теперь 5
array = { 9, 8, 7 };       // ok, длина массива теперь 3

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

Самоочистка предотвращает утечку памяти

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

void doSomething(bool earlyExit)
{
    int *array{ new int[5] { 9, 7, 5, 3, 1 } }; // выделяем память с помощью new
 
    if (earlyExit)
        return; // выходит из функции без освобождения памяти, выделенной выше
 
    // здесь что-то делаем
 
    delete[] array; // никогда не вызывается
}

Если для earlyExit установлено значение true, массив никогда не будет освобожден, и произойдет утечка памяти.

Однако если array является std::vector, этого не произойдет, потому что память будет освобождена, как только массив выйдет за пределы области видимости (независимо от того, завершится функция раньше или нет). Это делает std::vector более безопасным в использовании, чем самостоятельное выделение памяти.

Векторы запоминают свою длину

В отличие от встроенных динамических массивов, которые не знают длину массива, на который они указывают, std::vector отслеживает свою длину. Мы можем запросить длину вектора с помощью функции size():

#include <iostream>
#include <vector>
 
void printLength(const std::vector<int>& array)
{
    std::cout << "The length is: " << array.size() << '\n';
}
 
int main()
{
    std::vector array { 9, 7, 5, 3, 1 };
    printLength(array);
 
    return 0;
}

Приведенный выше пример печатает:

The length is: 5

Как и в случае с std::array, size() возвращает значение вложенного типа size_type (полный тип в приведенном выше примере будет std::vector<int>::size_type), который является целочисленным значением без знака.

Изменение размера вектора

Изменение размера встроенного динамически размещаемого массива сложно. Изменить размер std::vector так же просто, как вызвать функцию resize():

#include <iostream>
#include <vector>
 
int main()
{
    std::vector array { 0, 1, 2 };
    array.resize(5); // устанавливаем размер 5
 
    std::cout << "The length is: " << array.size() << '\n';
 
    for (int i : array)
        std::cout << i << ' ';
 
    std::cout << '\n';
 
    return 0;
}

Этот код печатает:

The length is: 5
0 1 2 0 0

Здесь следует отметить два момента. Во-первых, когда мы изменили размер вектора, значения существующих элементов были сохранены! Во-вторых, новые элементы инициализируются значением по умолчанию для типа (которое для int равно 0).

Размер векторов может быть уменьшен:

#include <vector>
#include <iostream>
 
int main()
{
    std::vector array { 0, 1, 2, 3, 4 };
    array.resize(3); // устанавливаем размер 3
 
    std::cout << "The length is: " << array.size() << '\n';
 
    for (int i : array)
        std::cout << i << ' ';
 
    std::cout << '\n';
 
    return 0;
}

Этот код печатает:

The length is: 3
0 1 2

Изменение размера вектора требует больших вычислительных ресурсов, поэтому вы должны стремиться минимизировать количество таких операций. Если вам нужен вектор с определенным количеством элементов, но вы не знаете значений элементов в момент объявления, вы можете создать вектор со значениями элементов по умолчанию:

#include <iostream>
#include <vector>
 
int main()
{
    // Используя прямую инициализацию, мы можем создать вектор из 5 элементов,
    // каждый элемент равен 0. Если мы используем инициализацию с фигурными
    // скобками, у вектора будет 1 элемент со значением 5.
    std::vector<int> array(5);
 
    std::cout << "The length is: " << array.size() << '\n';
 
    for (int i : array)
        std::cout << i << ' ';
 
    std::cout << '\n';
 
    return 0;
}

Эта программа печатает:

The length is: 5
0 0 0 0 0

Мы поговорим о том, почему прямая инициализация и инициализация с фигурными скобками обрабатываются по-разному в уроке «16.7 – std::initializer_list». Практическое правило: если тип представляет собой какой-то список, и вы не хотите инициализировать его списком, используйте прямую инициализацию.

Уплотнение логических значений

У std::vector есть еще один крутой трюк. Существует специальная реализация для std::vector типа bool, которая сжимает 8 логических значений в один байт! Это происходит за кулисами и не меняет способ использования std::vector.

#include <vector>
#include <iostream>
 
int main()
{
    std::vector<bool> array { true, false, false, true, true };
    std::cout << "The length is: " << array.size() << '\n';
 
    for (int i : array)
        std::cout << i << ' ';
 
    std::cout << '\n';
 
    return 0;
}

Этот код печатает:

The length is: 5
1 0 0 1 1

Еще не всё

Обратите внимание, что это вводная статья, предназначенная для ознакомления с основами std::vector. В уроке «10.11 – Емкость и стековое поведение std::vector» мы рассмотрим некоторые дополнительные возможности std::vector, включая разницу между длиной и емкостью вектора, и более подробно рассмотрим, как std::vector обрабатывает выделение памяти.

Заключение

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

Теги

arrayC++ / CppLearnCppstd::vectorSTL / Standard Template Library / Стандартная библиотека шаблоновВекторДля начинающихМассивОбучениеПрограммированиеУтечка памяти

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.