Привіт усім!

В даній статі ми розглянемо шаблонний клас std::vector з бібліотеки C++ STL.

Вектор являється контейнером, який містить послідовності елементів, і який може динамічно змінювати свій розмір. На подобі масивів, клас vector використовує послідовне скупчення елементів у пам'яті, що дозволяє виконувати арифметичні дії над вказівниками для доступу до його елементів. Також, vector має власний вбудований клас ітераторів вільного доступу, над якими користувач може виконувати операції і які за функціоналом подібні до звичайних вказівників.

Для оптимізації використання пам’яті, std::vector містить механізм резервного надвиділення пам’яті, який дозволяє скоротити витрати на повторне виділення пам’яті для нових об'єктів. Тобто, клас vector, самостійно виділяє більше пам’яті ніж потрібно, щоб забезпечити швидку вставку елементів у кінець послідовності. Об’єм резервної пам’яті можна контролювати за допомогою спеціальної функції reserve, яка також дозволяє заздалегідь виділити необхідну пам’ять для усіх об’єктів вектора.

По суті діла, vector - це клас подібний на масив, який самостійно управляє пам’яттю, що звільняє програміста від рутинної роботи над виділенням пам’яті, її звільненням, переміщенням елементів і т.д.

У порівнянні з іншими класами-контейнерами послідовностей (на подобі std:deque і std::list), std::vector має перевагу у швидкому доступі до його елементів.

Для використання даного шаблонного класу необхідно підключити файл "vector", у файлі, в якому ви плануєте використовувати клас:

#include <vector> /* усе що зв'язане з std::vector */
using namespace std ; /* друкуємо vector без простору імен std */

Оголошення класу

template < class T, class Alloc = allocator > class vector ;

Методи

iterator vector::begin ()

Повертає ітератор, який вказує на початок послідовності елементів.

iterator vector::end ()

Повертає ітератор, який вказує на кінцевий неіснуючий елемент.

reverse_iterator vector::rbegin ()

Повертає ітератор, який призначений для зворотнього перебору і вказує на останній елемент послідовності.

reverse_iterator vector::rend ()

Повертає ітератор, який призначений для зворотнього перебору і вказує на неіснуючий елемент перед початком послідовності.

size_type vector::size ()

Повертає кількість елементів у об'єкті.

size_type vector::max_size ()

Повертає максимальну кількість елементів, яка може бути у об'єкті.

void vector::resize (size_type n, value_type val = value_type()) ;

Змінює розмір вектора. Якщо кількість елементів об'єкта менша за n - усі елементи, які знаходяться за індексом n-1 знищуються. Якщо кількість елементів об'єкта більша за n - вони створюються і вставляються у кінець послідовності, якщо вказаний параметр val - усі новостворені елементи будуть являтися копіями даного.

size_type vector::capacity ()

Повертає максимальну кількість елементів, які може містити даний об'єкт класу vector, у відповідності до поточнї зайнятої ним пам'яті.

bool vector::empty () const ;

Повертає true, якщо поточний об'єкт не містить елементів.

void vector::reserve (size_type n) ;

Спричиняє зміну внутрішніх полів об'єкта класу vector так, щоб він міг зберегти хоча б n елементів без виділення додаткової пам'яті. Дана функція не змінює об'єкти в послідовності і їхнью кількість.

reference vector::operator[] (size_type n) ;

Повертає посилання на оригінальний елемент послідовності. На відміну від методу vector::at(), даний оператор не перевіряє коректність параметру n відносно реальних меж індексів об'єкта. Вказування параметру n на неіснуючий елемент спричиняє невизначену поведінку (undefined behavior).

reference vector::at (size_type n) ;

Повертає посилання на оригінальний елемент послідовності. Метод перевіряє коректність параметра відносно реальних меж об'єкта, і генерує виключення out_of_range, якщо n знаходиться поза ними.

reference vector::front () ;

Повертає посилання на оригінальний перший елемент послідовності. Виклик цієї функції на пустому векторі спричиняє невизначену поведінку.

void vector::assign (InputIterator first, InputIterator last);

void vector::assign (size_type n, const value_type& val) ;

Заповняє об'єкт новим вмістом, заміняючи його вміст новими елементами. Друга версія даного методу створює n копій val і заповняє ними об'єкт.

void vector::push_back (const value_type& val) ;

Додає новий елемент val у кінець послідовності. За необхідності, об'єкт перевиділяє пам'ять для нових елементів.

void vector::pop_back () ;

Знищує останній елемент послідовності поточного об'єкта, ефективно зменшуючи його кількість на 1.

iterator vector::insert (iterator position, const value_type& val) ;
void vector::insert (iterator position, size_type n, const value_type& val) ;
template <class InputIterator> void vector::insert (iterator position, InputIterator first, InputIterator last);

За допомогою даних методів у вектор вставляються нові елементи перед елементом на який вказує ітератор position. Параметри first і last вказують на початковий і кінцевий елемент послідовності, з якої будуть копіюватися елементи у поточний об'єкт класу vector. Вставляються елементи починаючи з first включно і закінчуючи елементом last-1, елемент last не копіюється у поточний об'єкт.

iterator vector::erase (iterator position) ;
iterator vector::erase (iterator first, iterator last) ;

Видаляє елемент або проміжок елементів з поточного об'єкта std::vector. У випадку проміжку елементів видаляються елементи починаючи з first включно і закінчуючи last-1 включно, елемент last не видаляється з об'єкта.

void vector::swap (vector& x) ;

Після виклику даного методу поточний об'єкт класу std::vector обмінюється своїми елементами з вектором x, після чого усі елементи з вектору x відносяться до поточного об'єкту, а усі елементи з поточного об'єкта переносяться до вектору x. Розміри векторів можуть відрізнятися. Усі вказівники, ітератори залишаються валідними після виконання обміну.

void vector::clear () ;

Спричиняє очищення поточного вектора і деструкцію кожного елементу.

Приклади використання std::vector

Приклад #1

Розглянемо перший найпростіший приклад використання std::vector - програма створює примірник класу для дійсних чисел, заповняє її певною послідовністю чисел по якому-небудь математичному закону і виводить результати ітерування функції на екран. Ось сама програма:

#include <iostream> /* для вводу і виводу в термінал (об'єкти cin cout) */
#include <vector> /* усе що зв'язане з std::vector */
using namespace std ; /* друкуємо vector без простору імен std */

/* головна функція програми */
int main (int argc, /* кількість аргументів*/
          char** argv) /* аргументи в масивах символів */
{
  /* оголошуємо примірник класу std::vector 
  ** для розміщення послідовності дійсних чисел */
  vector<double> v ;
  
  /* заповнюємо вектор послідовністю f(x) = (x*x) / (x+x) 
  ** (x в другій степені) в проміжку [1;10]
  ** за допомогою методу push_back() */
  for (int x=1; x<=10; ++x)
  {
    v.push_back ( ((double)x*x) / (x+x) ) ;
  }
  
  /* виводимо на екран результати
  ** ітерації функції на проміжку */
  for (int i=0; i<v.size(); ++i)
  {
    cout << v.at(i) << " " ; 
  }
  
  /* новий рядок після результатів*/
  cout << endl ;
  
  /* вихід з головної функції програми */
  return 0 ;
}

Її компілювання в файл "executable" (за допомогою компілятора GNU/GCC) і виконання в терміналі виглядає наступним чином:

cpp_std_vector_sample1_simple_demo_program

Де виділення пам'яті? Їх немає - клас std::vector самостійно прикрив деталі роботи над пам'яттю за своєю абстракцією, так сказати, за своїми могутніми плечима. Ця проста програма демонструє Вам вибір, який можна зробити використовуючи С++ — ви можете як працювати з пам'яттю, так і не працювати з нею ;).

Приклад #2

Наступний приклад продемонструє, як клас std::vector управляє свєю внутрішньою пам'яттю для збереження елементів послідовності. Для цього необхідно модифікувати код з попереднього прикладу наступним чином:

#include <iostream> /* для вводу і виводу в термінал (об'єкти cin cout) */
#include <vector> /* усе що зв'язане з std::vector */
using namespace std ; /* друкуємо vector без простору імен std */

/* головна функція програми */
int main (int argc, /* кількість аргументів*/
          char** argv) /* аргументи в масивах символів */
{
  /* оголошуємо примірник класу std::vector 
  ** для розміщення послідовності дійсних чисел */
  vector<double> v ;
  
  /* поглянемо початкову вмістимість об'єкта: */
  cout << "Початкова вмістимість: " 
       << v.capacity() << " елементів."
       << endl ;
  
  /* заповнюємо вектор послідовністю f(x) = (x*x) / (x+x) 
  ** (x в другій степені) в проміжку [1;10]
  ** за допомогою методу push_back() */
  for (int x=1; x<=10; ++x)
  {
    double element = ((double)x*x) / (x+x) ;
    
    v.push_back ( element ) ;
   
    /* виводимо в термінал поточний розразований елемент 
    ** і поточну вмістимість вектора в елементах */ 
    cout << "Збережений елемент: " << element 
         << ", вмістимість: " << v.capacity()
         << " елементів." << endl ;
  }
  
  /* вихід з головної функції програми */
  return 0 ;
}

Після компілювання даного коду у файл "executable" і слідуюче його виконання відобразить в термінал наступні дані:

cpp_std_vector_sample2-cxx_simple_demo_program_for_vector_capacityЯкщо ми поглянемо на показник вмістимості, відображений на терміналі, можна зробити висновок, що закон по якому клас std::vector приймає рішення відносно перевиділення пам'яті для збереження нових і старих елементів, являє собою ніщо інше як f(x) = 2x; де x - кількість елементів, які об'єкт може зберегти в даний момент. Тобто коли кількіть елементів, які необхідно зберегти, перевищує поточну вмістимість об'єкта - об'єкт збільшує свою вміcтимість вдвічі.

Приклад #3

Тепер переглянемо приклад в якому ми використаємо функцію assign, і конструктори копіювання класу. Програма також демонструє копіювання даних з класу std::string у клас std::vector:

#include <iostream> /* для вводу і виводу в термінал (об'єкти cin cout) */
#include <vector> /* усе що зв'язане з std::vector */
using namespace std ; /* друкуємо vector без простору імен std */

/* головна функція програми */
int main (int argc, /* кількість аргументів*/
          char** argv) /* аргументи в масивах символів */
{
  /* оголошуємо примірник класу std::vector 
  ** для розміщення послідовності дійсних чисел */
  vector<double> v ;
  
  /* заповнюємо вектор послідовністю f(x) = (x*x) / (x+x) 
  ** (x в другій степені) в проміжку [1;10]
  ** за допомогою методу push_back() */
  for (int x=1; x<=10; ++x)
  { v.push_back ( ((double)x*x) / (x+x) ) ; }
  
  /* Створимо примірник класу 
  ** використовуючи конструктор копіювання */
  vector<double> v2 (v) ;
  
  /* Тепер вміст v2 такий як у об'єкта v */
  cout << "Вміст v2:" << endl ;
  for (unsigned int iter=0; iter<v2.size(); ++iter)
  { cout << v2[iter] << "; " ; }
  /* новий рядок після даних*/
  cout << endl ;
  
  /* Коли ми бажаємо не повністю скопіювати 
  ** початковий об'єкт, а лиш деякі його елементи, 
  ** в даному випадку половину елементів,
  ** тут використовуються ітератори - 
  ** внутрішній клас std::vector */
  vector<double> v3 (v.begin(), v.begin() + v.size()/2 ) ;
  
  /* Тепер вміст v3 такий як у 
  ** половини оригінального об'єкта v */
  cout << "Вміст v3:" << endl ;
  for (unsigned int iter=0; iter<v3.size(); ++iter)
  { cout << v3[iter] << "; " ; }
  /* новий рядок після даних*/
  cout << endl ;
  
  /* Тепер розглянемо як скопіювати 
  ** вміст класу string у vector */
  
  /* Створюємо примірник класу string з бажаним вмістом */
  string str = "Hallo, it`s 4-th object of std::vector in this program." ;
  
  vector<char> v4 ;
  
  /* Тепер за допомогою функції assign, яка приймає два ітератори
  ** скопіюємо вміст з string у vector */
  v4.assign (str.begin(), str.end()) ;
  
  /* Виводимо результат на термінал*/
  cout << "Вміст v4:" << endl ;
  for (unsigned int iter=0; iter<v4.size(); ++iter)
  { cout << v4[iter] ; }
  /* новий рядок після даних*/
  cout << endl ;
  
  /* вихід з головної функції програми */
  return 0 ;
}

Виконання даного скомпільованого коду в терміналі виглядає наступним чином:

cpp_std_vector_sample3_demo_program_for_vector_data_copying

Приклад #4

В наступному прикладі коду продемонстровано копіювання вмісту вектора з масиву елемнетів вбудованого типу int до вектора і його динамічна зміна розміру і кількості елементів:

#include <iostream> /* для вводу і виводу в термінал (об'єкти cin cout) */
#include <vector> /* усе що зв'язане з std::vector */
using namespace std ; /* друкуємо vector без простору імен std */

/* головна функція програми */
int main (int argc, /* кількість аргументів*/
          char** argv) /* аргументи в масивах символів */
{
  /* створюємо масив цілочисельних елементів з знаком
  ** які ми перенесемо в об'єкт класу std::vector */
  int iArray [] = {1, 3, 5, 7, 11, 13, 17, 19} ;
   
  /* оголошуємо примірник класу std::vector 
  ** для розміщення послідовності цілих чисел */
  vector<int> v ;
  
  /* копіюємо вміст масиву в вектор, використовуючи 
  ** функцію assign і вказівники в якості ітераторів */
  v.assign (iArray, /* початкова адреса масиву */
            /* розраховуємо кінець масиву (операція з вказівниками) */
            iArray + sizeof(iArray)/sizeof(iArray[0]) ) ;

  cout << "#1: "
  /* виводимо на екран результати копіювання */
  for (int i=0; i<v.size(); ++i)
  { cout << v.at(i) << "; " ; }
  /* новий рядок після результатів*/
  cout << endl ;
  
  /* збільшуємо кількість на 10 елементів 
  ** і заповняємо їх нулями */
  v.resize (v.size()+5, 0) ;
  
  cout << "#2: "
  /* виводимо на екран результати зміни розміру */
  for (int i=0; i<v.size(); ++i)
  { cout << v.at(i) << "; " ; }
  /* новий рядок після результатів*/
  cout << endl ;
  
  /* вихід з головної функції програми */
  return 0 ;
}
Виконання програми у терміналі: cpp_std_vector_sample4-cxx_demo_peogram_output

Приклад #5

Розлянемо приклад видалення довільних елементів з об'єкта класу vector за допомогою функції erase. Заодно поглянемо як зміниться вмістимість об'єкту після виконання даної операції.
#include <iostream> /* для вводу і виводу в термінал (об'єкти cin cout) */
#include <vector> /* усе що зв'язане з std::vector */
using namespace std ; /* друкуємо vector без простору імен std */

/* головна функція програми */
int main (int argc, /* кількість аргументів*/
          char** argv) /* аргументи в масивах символів */
{
  /* оголошуємо примірник класу std::vector 
  ** для розміщення послідовності цілих чисел */
  vector<int> v ;
  
  /* заповнюємо вектор послідовністю чисел від 1 до 20
  ** за допомогою методу push_back() */
  for (int x=1; x<=20; ++x)
  { v.push_back (x) ; }
  
  /* показуємо вмістимість вектора */
  cout << "Вмістимість вектора до видалення: " 
       << v.capacity () << " елементи." << endl ;
  
  /* використовуючи метод erase і ітератори, 
  ** видалимо кожне непарне число з об'єкта */
  for (vector<int>::iterator iter=v.begin(); /* ітератор класу vector*/
       iter<v.end();  /* охорона циклу */
       ++iter) /* крок ітерації */
  {
    /* видаляємо елемент, на який вказує ітератор iter */
    v.erase (iter) ;
  }
  
  /* виводимо на екран вміст вектору */
  for (int i=0; i<v.size(); ++i)
  {
    /* небезпечний оператор [] ! */
    cout << v[i] << " " ; 
  }
  /* новий рядок після результатів*/
  cout << endl ;
  
  /* показуємо вмістимість вектора */
  cout << "Вмістимість вектора після видалення: " 
       << v.capacity () << " елементи." << endl ;
  
  /* вихід з головної функції програми */
  return 0 ;
}
Виконання даного коду буде покаже наступні дані в терміналі: cpp_std_vector_sample5-cxx_demo_program_for_erase_method Метод не впливає на вмістимість об'єкта, але впливає на його вміст.

Резюме

Скільки б довелося писати коду програмісту щоб реалізувати усі ці приклади, і яка економія часу! Отже клас vector вартує витрат на його освоєння.