Namespace CounterNameSpace 4 страница

 

getch(); return 0;

}

Результати виконання цієї програми є такими:

Розмір = 0

Новий розмір = 10

Поточний вміст:

0 1 2 3 4 5 6 7 8 9

Новий розмір =20

Новий вміст:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Вміст подвоєно:

0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38

Розглянемо уважно код цієї програми. У функції main() створюється вектор vek для зберігання int-елементів. Оскільки під час його створення не було передбачено ніякої ініціалізації, то вектор vek вийшов порожнім, а його місткість дорівнює нулю. Іншими словами, ми створили вектор нульової довжини. Це підтверджується викликом функції-члена класу size(). Потім, використовуючи функцію-члена push_back(), у кінець цього вектора ми поміщаємо 10 елементів, що примушує вектор збільшитися в розмірі, щоб розмістити нові елементи. Тепер розмір вектора став таким, що дорівнює 10. Звернемо Вашу увагу на те, що для відображення вмісту вектора vek використовується стандартний запис індексації масивів. Після цього у вектор додаються ще 10 елементів, і вектор vek автоматично збільшується в розмірі, щоб і їх прийняти на зберігання. Нарешті, використовуючи знову-таки стандартний запис індексації масивів, ми змінюємо значення елементів вектора vek.

Зверніть також увагу на те, що для керування циклами, які використовуються для відображення вмісту вектора vek і його модифікації, як ознака їх завершення застосовується значення розміру вектора, що отримується за допомогою функції vek.size(). Одна з переваг векторів перед масивами полягає у тому, що у нас є змога дізнатися поточний розмір вектора, який у певних ситуаціях є дуже корисним засобом.

22.3.1. Доступ до вектора за допомогою ітератора

Як уже зазначалося вище, масиви і покажчики у мові програмування C++ тісно пов'язані між собою. До елементів масиву можна отримати доступ як за допомогою індексу, так і за допомогою покажчика. У бібліотеці STL аналогічний зв'язок існує між векторами й ітераторами. Це означає, що до членів вектора можна звертатися як за допомогою індексу, так і за допомогою ітератора. Цю можливість продемонстровано у наведеному нижче коді програми.

Код програми 22.2. Демонстрація механізму доступу до вектора за допомогою ітератора

#include <iostream>// Для потокового введення-виведення

#include <vector>// Для роботи контейнерним класом "Вектор"

using namespace std; // Використання стандартного простору імен

 

int main()

{

vector<char> vek; // Побудова вектора нульової довжини

int i;

 

// Поміщаємо значення у вектор.

for(i=0; i<10; i++) vek.push_back('A' + i);

 

// Отримуємо доступ до вмісту вектора за допомогою індексу.

for(i=0; i<10; i++) cout << vek[i] << " ";

cout << endl;

 

// Отримуємо доступ до вмісту вектора за допомогою ітератора.

vector<char>::iterator p = vek.begin();

while(p != vek.end()) {

cout << *p << " ";

p++;

}

 

getch(); return 0;

}

Ось як виглядають результати виконання цієї програми:

ABCDEFGHIJ

ABCDEFGHIJ

У цій програмі спочатку створюється вектор нульової довжини. Потім за допомогою функції push_back() у кінець вектора поміщаються символи, внаслідок чого розмір вектора відповідним чином збільшується.

Звернемо Вашу увагу на те, як оголошується ітератор р. Тип цього ітератора визначається контейнерними класами. Тому для отримання ітератора для конкретного контейнера використовуйте оголошення, аналогічне показаному у цьому прикладі: просто вкажіть для цього ітератора ім'я контейнера. У нашій програмі ітератор р ініціалізувався так, щоб він указував на початок вектора (за допомогою функції-члена класу begin()). Ітератор, який повертає ця функція, можна потім використовувати для поелементного доступу до вектора, інкрементуючи його відповідним чином. Цей процес аналогічний тому, як можна використовувати покажчик для доступу до елементів масиву. Щоб визначити, коли буде досягнуто кінець вектора, використовується функція-член end(). Ця функція повертає ітератор, встановлено за останнім елементом вектора. Тому, якщо значення р дорівнює vek.end(), то кінець вектора досягнуто.

22.3.2. Вставлення та видалення елементів з вектора

Окрім занесення нових елементів у кінець вектора, у нас є можливість вставляти елементи у середину вектора, використовуючи функцію insert(). Видаляти елементи можна за допомогою функції erase(). Використання функцій insert() і erase() продемонстровано у наведеному нижче коді програми.

Код програми 22.3. Демонстрація механізму вставлення елементів у вектор і видалення їх з нього

#include <iostream>// Для потокового введення-виведення

#include <vector>// Для роботи контейнерним класом "Вектор"

using namespace std; // Використання стандартного простору імен

 

int main()

{

vector<char> vek; // Побудова вектора нульової довжини

unsigned int i;

// Поміщаємо значення у вектор.

for(i=0; i<10; i++) vek.push_back('A' + i);

 

// Відображаємо початковий вміст вектора.

cout << "Розмір = " << vek.size() << endl;

cout << "Початковий вміст вектора:\n";

for(i=0; i<vek.size(); i++) cout << vek[i] << " ";

cout << endl;

// Отримуємо доступ до вмісту вектора за допомогою ітератора.

vector<char>::iterator p = vek.begin();

p += 2; // Покажчик на 3-й елемент вектора

 

// Вставляємо 10 символів 'x' у вектор vek.

vek.insert(p, 10, 'x');

 

// Відображаємо вміст вектора після вставки символів.

cout << "Розмір вектора після вставки = " << vek.size() << endl;

cout << "Вміст вектора після вставки:\n";

for(i=0; i<vek.size(); i++) cout << vek[i] << " ";

cout << endl;

 

// Видалення вміщених елементів.

p = vek.begin();

p += 2; // Покажчик на 3-й елемент вектора

vek.erase(p, p+10); // Видаляємо 10 елементів підряд.

 

// Відображаємо вміст вектора після видалення символів.

cout << "Розмір вектора після видалення символів = "

<< vek.size() << endl;

cout << "Вміст вектора після видалення символів:\n";

for(i=0; i<vek.sized; i++) cout << vek[i] << " ";

cout << endl;

 

getch(); return 0;

}

У процесі виконання ця програма відображає такі результати:

Розмір = 10

Початковий вміст вектора:

A B C D E F G H I J

 

Розмір вектора після вставки = 20

Вміст вектора після вставки:

A B x x x x x x x x x x C D E F G H I J

 

Розмір вектора після видалення символів = 10

Вміст вектора після видалення символів:

A B C D E F G H I J

22.3.3. Збереження у векторі об'єктів класу

У попередніх прикладах вектори слугували для зберігання значень тільки вбудованих типів, але цим їх можливості не обмежуються. У вектор можна поміщати об'єкти будь-якого типу, в т.ч. об'єкти класів, що створюються програмістом. Розглянемо приклад, у якому вектор використовують для зберігання об'єктів класу kooClass. Звернемо Вашу увагу на те, що у цьому класі визначаються конструктор за замовчуванням і перевантажені версії операторів "<" і "==". Майте на увазі, що, можливо, Вам доведеться визначити і інші оператори порівняння. Це залежить від того, як використовуваний Вами компілятор реалізує бібліотеку STL.

Код програми 22.4. Демонстрація механізму зберігання у векторі об'єктів класу

#include <vcl>

#include <iostream>// Для потокового введення-виведення

#include <conio>// Для консольного режиму роботи

#include <vector>// Для роботи контейнерним класом "Вектор"

using namespace std; // Використання стандартного простору імен

class kooClass { // Оголошення класового типу

int x, y, z; // Тривимірні координати

public:

kooClass() {x = y = z = 0;}

kooClass(int a, int b, int c) {x = a; y = b; z = c;}

// Повертає модифікований об'єкт, адресований покажчиком

kooClass &operator+(int a) { x += a; y += a; z += a; return *this;}

friend ostream &operator<<(ostream &stream, kooClass obj);

friend bool operator<(kooClass a_ob, kooClass b_ob);

friend bool operator==(kooClass a_ob, kooClass b_ob);

};

// Відображаємо координати x, y, z за допомогою оператора

// виведення для класу kooClass.

ostream &operator<<(ostream &stream, kooClass obj)

{

stream << "x= " << obj.x << ", ";

stream << "y= " << obj.y << ", ";

stream << "z= " << obj.z << "\n";

return stream; // Повертає посилання на параметр stream

}

 

bool operator<(kooClass a_ob, kooClass b_ob)

{

return (a_ob.x + a_ob.y + a_ob.z) < (b_ob.x + b_ob.y + b_ob.z);

}

 

bool operator==(kooClass a_ob, kooClass b_ob)

{

return (a_ob.x + a_ob.y + a_ob.z) == (b_ob.x + b_ob.y + b_ob.z);

}

 

int main()

{

vector<kooClass> vek; // Побудова вектора об'єктів нульової довжини

unsigned int i;

 

// Додаємо у вектор об'єкти.

for(i=0; i<10; i++) vek.push_back(kooClass(i, i+2, i-3));

 

cout << "Відображаємо вміст початкового вектора.\n";

for(i=0; i<vek.size(); i++) cout << i << " ==> " << vek[i];

cout << endl;

 

// Модифікуємо об'єкти у векторі.

for(i=0; i<vek.size(); i++) vek[i] = vek[i] + 10;

 

cout << "Відображаємо вміст модифікованого вектора.\n";

for(i=0; i<vek.size(); i++) cout << i << " ==> " << vek[i];

 

getch(); return 0;

}

Внаслідок виконання цієї програми на моніторі буде відображено такі результати:

Відображаємо вміст початкового вектора.

0 ==> x= 0, y= 2, z= -3

1 ==> x= 1, y= 3, z= -2

2 ==> x= 2, y= 4, z= -1

3 ==> x= 3, y= 5, z= 0

4 ==> x= 4, y= 6, z= 1

5 ==> x= 5, y= 7, z= 2

6 ==> x= 6, y= 8, z= 3

7 ==> x= 7, y= 9, z= 4

8 ==> x= 8, y= 10, z= 5

9 ==> x= 9, y= 11, z= 6

 

Відображаємо вміст модифікованого вектора.

0 ==> x= 10, y= 12, z= 7

1 ==> x= 11, y= 13, z= 8

2 ==> x= 12, y= 14, z= 9

3 ==> x= 13, y= 15, z= 10

4 ==> x= 14, y= 16, z= 11

5 ==> x= 15, y= 17, z= 12

6 ==> x= 16, y= 18, z= 13

7 ==> x= 17, y= 19, z= 14

8 ==> x= 18, y= 20, z= 15

9 ==> x= 19, y= 21, z= 16

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

22.3.4. Доцільність використання ітераторів

Частково потужність бібліотеки STL зумовлена тим, що багато її функцій використовують ітератори. Цей факт дає змогу виконувати операції з двома контейнерами одночасно. Розглянемо, наприклад, такий формат векторної функції insert().

template <class InIter> void insert(iterator i, InIter start, InIter end);

Ця функція вставляє початкову послідовність, що визначається параметрами start і end, уприймальну послідовність, починаючи з позиції i. При цьому немає ніяких вимог, щоб ітератор i належав тому ж вектору, з яким пов'язані ітератори start і end. Таким чином, використовуючи цю версію функції insert(), можна один вектор вставити в іншій. Розглянемо такий приклад.

Код програми 22.5. Демонстрація механізму використання ітераторів для вставлення одного вектора у іншій

#include <iostream>// Для потокового введення-виведення

#include <vector>// Для роботи контейнерним класом "Вектор"

using namespace std; // Використання стандартного простору імен

 

int main()

{

vector<char> vek, vek2; // Побудова векторів нульової довжини

unsigned int i;

// Поміщаємо значення у вектор.

for(i=0; i<10; i++) vek.push_back('A' + i);

 

// Відображаємо початковий вміст вектора.

cout << "Початковий вміст вектора:\n";

for(i=0; i<vek.size(); i++) cout << vek[i] << " ";

cout << endl << endl;

 

// Ініціалізуємо другий вектор.

char str[] = "-STL –– це сила!-";

for(i=0; str[i]; i++) vek2.push_back(str[i]);

 

/* Отримуємо ітератори для середини вектора vek,

а також початку і кінця вектора vek2. */

vector<char>::iterator p = vek.begin()+5;

vector<char>::iterator p2start = vek2.begin();

vector<char>::iterator p2end = vek2.end();

 

// Вставляємо вектор vek2 у вектор vek.

vek.insert(p, p2.start, p2.end);

 

// Відображаємо результат вставки.

cout << "Вміст вектора vek після вставки:\n";

for(i=0; i<vek.size(); i++) cout << vek[i] << " ";

cout << endl;

getch(); return 0;

}

У процесі виконання ця програма відображає такі результати:

Початковий вміст вектора:

A B C D E F G H I J

 

Вміст вектора vek після вставки:

A B C D E – S T L - - ц е c и л а ! – F G Н I J

Як бачите, вміст вектора vek2 поміщено у середину вектора vek.

У міру подальшого вивчення можливостей, що надаються STL, можна дізнатися, що| ітератори є зв'язними засобами, які роблять бібліотеку єдиним цілим. Вони дають змогу виконувати дії з двома (і більше) об'єктами STL одночасно, але особливо корисні під час використання алгоритмів, описаних нижче у цьому розділі.

22.4. Робота зі списками

Клас list підтримує функціонування двоспрямованого лінійного списку. На відміну від вектора, у якому реалізовано підтримку довільного доступу, список дає змогу отримувати до своїх елементів тільки послідовний доступ. Двоспрямованість списку означає, що доступ до його елементів можливий у двох напрямах: від початку до кінця і від кінця до початку.

Список – це контейнер з двоспрямованим послідовним доступом до його елементів.

Шаблонна специфікація класу list має такий вигляд.

template <class myType, class Allocator = allocator<myType> class list

У цьому записі myType – тип даних, що зберігаються у списку, а елемент Allocator означає розподільник пам'яті, який за замовчуванням використовує стандартний розподільник. У класі list визначено такі конструктори:

explicit list(const Allocator &a = Allocator());

explicit list(size_type num|, const myType &val = myType (),

const Allocator &a = Allocator());

list(const list<myType, Allocator> &ob);

template <class InIter>list(InIter start, InIter end,

const Allocator &a = Allocator());

Конструктор, який представлено у першій формі, створює порожній список. Друга форма призначена для створення списку, який містить num елементів із значенням val. Третястворює список, який містить ті самі елементи, що і об'єкт ob. Четвертастворює список, який містить елементи у діапазоні, заданому параметрами start і end.

Для класу list визначено такі оператори порівняння: ==, <, <=, !=, > і >=.

22.4.1. Використання базових операцій для роботи зі списком

Функції-члени, визначені у класі list, подано у табл. 22.3. У кінець списку, як і у кінець вектора, елементи можна поміщати за допомогою функції push_back(), але за допомогою функції push_front() можна поміщати елементи у початок списку. Елемент можна також вставити і у середину списку, для цього використовується функція insert(). Один список можна помістити в інший, використовуючи функцію splice(). A за допомогою функції merge() два списки можна об'єднати і упорядкувати результат.

Табл. 22.3. Функції-члени класу list

Функція-член Опис
template <class InIter> void assign(InIter start, InIter end); Поміщає у список послідовність, що визначається параметрами start і end
void assign(size_type num, const myType &val); Поміщає у список num елементів із значенням val
reference back(); const_reference back() const; Повертає посилання на останній елемент у списку
iterator begin(); const_iterator begin() const; Повертає ітератор для першого елемента у списку
void clear(); Видаляє всі елементи із списку
bool empty() const; Повертає дійсне значення, якщо використовуваний список порожній, і помилкове – інакше
iterator end(); const_iterator end() const; Повертає ітератор, який відповідає кінцю списку
iterator erase(iterator i); Видаляє елемент, яка адресується ітератором i; повертає ітератор, який вказує на елемент, розташований після видаленого
iterator erase(iterator start, iterator end); Видаляє елементи у діапазоні, що задається параметрами start і end; повертає ітератор для елемента, розташованого за останнім видаленим елементом
reference front(); const_reference front() const; Повертає посилання на перший елемент у списку
allocator_type get_allocator() const; Повертає розподільник пам'яті списку
iterator insert(iterator i, const myType &val = myType()); Вставляє значення val безпосередньо перед елементом, заданим параметром i; повертає ітератор для цього елемента
void insert(iterator i, size_type num, const myType &val); Вставляє num копій значення val безпосередньо перед елементом, заданим параметром i
template <class InIter> void insert(iterator i, InIter start, InIter end); Вставляє у список послідовність, що визначається параметрами start і end, безпосередньо перед елементом, заданим параметром i
size_type max_size() const; Повертає максимальну кількість елементів, яке можу містити список
void merge(list<myType, Allocator> &ob>); template <class Comp> void merge(list<myType, Allocator> &ob, Comp cmpfn); Об'єднує впорядкований список, що міститься в об'єкті ob, із впорядкованим списком, який його викликає. Результат також упорядковується. Після об'єднання список, що міститься в ob, залишається порожнім. У другому форматі може бути задана функція порівняння, яка визначає, за яких умов один елемент є меншим від іншого
void pop_back(); Видаляє останній елемент у списку
void pop_front(); Видаляє перший елемент у списку
void push_back(const myType &val); Додає у кінець списку елемент із значенням, що задається параметром val
void push_front(const myType &val); Додає у початок списку елемент із значенням, що задається параметром val
reverse_iterator rbegin(); const_reverse_iterator rbegin() const; Повертає реверсивний ітератор для кінця списку
reverse_iterator rend(); const_reverse_iterator rend() const; Повертає реверсивний ітератор для початку списку
void remove(const myType &val); Видаляє із списку елементи із значенням val
template <class UnPred> void remove_if(UnPred pr); Видаляє елементи, для яких унарний предикат pr дорівнює значенню true
void resize(size_type num, myType val = myType()); Встановлює розмір списку, що дорівнює значенню, заданому параметром num. Якщо список для цього по­трібно подовжити, то у кінець списку додаються елементи із значенням, що задається параметром val
void reverse(); Реверсує список
size_type size() const; Повертає поточну кількість елементів у списку
void sort(); template <class Сomp> void sort(Сomp cmpfn); Сортує список. Друга форма сортує список за допомогою функції порівняння cmpfn, яка дає змогу визначати, за яких умов один елемент є меншим від іншого
void splice(iterator i, list<myType, Allocator> &ob); Вставляє вміст списку ob у список, який його викликає, у позицію, вказану ітератором i. Після виконання цієї операції список ob залишається порожнім
void splice(iterator i, list<myType, Allocator> &ob>, iterator el); Видаляє із списку ob елемент, яка адресується ітератором el, і зберігає його у списку, який його викликає, у позицію, яка адресується ітератором i
void splice(iterator i, list<myType, Allocator> &ob, iterator start, iterator end); Видаляє із списку ob діапазон, визначений параметрами start і end, і зберігає його у списку, який його викликає, починаючи з позиції, яка адресується ітератором i
void swap(list<myType, Allocator> &ob); Виконує обмін елементами списку, який його викликає, і списку ob
void unique(); template <class BinPred> void unique(BinPred pr); Видаляє із списку елементи-дублікати. Друга форма для визначення унікальності використовує предикат pr

Щоб досягти максимальної гнучкості і переносності для будь-якого об'єкта, який підлягає зберіганню у списку, необхідно визначити конструктор за замовчуванням і оператор "<" (і бажано інші оператори порівняння). Точніші вимоги до об'єкта (як до потенційного елемента списку) необхідно погоджувати відповідно до документації на використовуваний Вами компілятор.

Розглянемо простий приклад використання списку.

Код програми 22.6. Демонстрація механізму використання базових операцій для роботи зі списком

#include <iostream>// Для потокового введення-виведення

#include <list> // Для роботи зі списками

using namespace std; // Використання стандартного простору імен

 

int main()

{

list<char> lst; // Створення порожнього списку

int i;

 

// Поміщаємо значення у список

for (i=0; i<10; i++) lst.push_back('A1+i);

 

// Відображаємо початковий розміру списку

cout << "Розмір = " << lst.size() << endl;

 

// Відображаємо початковий вміст списку

cout << "Вміст: ";

list<char>::iterator p = lst.begin();

// Отримуємо доступ до вмісту списку за допомогою ітератора.

while(p != lst.end()) {

cout << *p << " ";

p++;

}

cout << endl;

 

getch(); return 0;

}

Результати виконання цієї програми є такі:

Розмір = 10

Вміст: A B C D E F G H I J

У процесі виконання ця програма створює список символів. Спочатку створюється порожній об'єкт списку. Потім у нього поміщається десять букв (від А до J). Заповнення списку реалізується шляхом використання функції push_back(), яка поміщає кожне нове значення у кінець наявного списку. Після цього відображається розмір списку і його вміст. Вміст списку виводиться на екран внаслідок виконання такого коду програми: