* Вектори (std::vector)
Публикувано на 29 ноември 2008 в раздел С/С++.
Досега се стараехме да не използваме готови библиотеки, класове и обекти. За шаблонният клас vector обаче си заслужава да се отдели специално внимание. Векторите са изключително удобен и ефективен заместител на стандартните масиви, като значително спестява ресурси при редица операции. Чрез него имаме възможност за създаване на т.нар. "динамични масиви".
Първата стъпка е да прибавим класа чрез #include. Вектор се дефинира по следния начин:
#include <vector> //... std::vector<тип> v;
Синтаксисът изисква указване на типа елементи във вектора. Често е удобно да използваме typedef при дефиниране на вектори. Например:
typedef std::vector<int> int_vec; int_vec v1, v2;
Така например създадохме вектор с елементи от тип int. Използването му по-нататък е много сходно с това на масивите. Ето например как създаваме вектор от 10 елемента, и запълваме елементите му с числата от 1 до 10:
std::vector<int> v(10); for(int i=0; i<10; i++) v[i] = i+1;
Виждате, че в класът вектор е предефиниран операторът []. Аналогично действа член-функцията at(int):
v.at(1000) = 5;
Ако елемент с такъв индекс не съществува, то ще бъде върнат обект от тип std::out_of_range. Ще разгледаме подробно как може да бъде използван в тема за обработка на грешки. Съществена разлика между [int] и .at(int) се явява ако сте насочили указател към вектор. При използване на [] то е възможно векторът да се премести на нов адрес (например при разширение, което ще покажем по-долу) и така указателят ви да сочи на грешна позиция. При функцията at този проблем не съществува, тъй като тя работи с фиксиран размер на вектора. Поради тази причина бъдете особено внимателни при използването на указатели към вектор.
Дефиниран е и операторът =:
std::vector<int> v2(10); v2 = v;
Две член функции begin и end връщат адреса на началото и края на вектора. Използват се ефективно при употребата на interator. Следният пример демонстрира обхождането на вектор и отпечатването му на екрана:
vector<int>::iterator it; std::cout << "v contains:"; for (it=v.begin(); it<v.end();it++ ){ std::cout << " " << *it; }
Принципно можете да пъстигнете абсолютно същия ефект чрез използването на .at или [] и обхождане на вектора чрез указатели. Въпреки това свикването за използване на iterator се насърчава, тъй като ще е значително по-лесно да го използвате при стандартни алгоритми като сортиране например, тъй като член-функция за тази цел не се предлага, а я има реализирана като шаблонна функция.
Дотук не показахме нищо значително ново в сравнение с обикновените масиви. Нека да разгледаме следния случай - представете си, че имаме масив, в който записваме динамично данни. В един момент може да се окаже, че масива се е запълнил и нямаме достатъчно място. Векторите решават този проблем:
#include "stdafx.h" #include <vector> #include <iostream> using namespace std; void main(){ // Дефинираме празен вектор vector<int> v; // Функцията size() показва броя елементи на вектора cout << "initial: " << v.size() << endl; // Чрез push_back можем да добавим елемент v.push_back(12); cout << "after I add one element: " << v.size() << endl; // Добавяме още 10 елемента for (int i=0; i<10; i++) v.push_back(i); cout << "after I add 10 more: " << v.size() << endl; // Отпечатваме съдържанието на вектора cout << "the array contents are:"; for (i=0; i<v.size(); i++) cout << " " << v[i]; cout << endl; }
Виждате, че функцията push_back добавя елемент в края на вектора. Тук демонстрирахме и създаването на празен вектор (в предишните примери резервирахме памет за поне десет елемента), както и член-функцията size. Често се използват още две функции - capacity и max_size. Проверете резултатът от следния пример
#include "stdafx.h" #include <vector> #include <iostream> using namespace std; void main(){ vector<int> v; for (int i=0; i<100; i++) v.push_back(i); cout << "size: " << (int) v.size() << endl; cout << "capacity: " << (int) v.capacity() << endl; cout << "max_size: " << (int) v.max_size() << endl; }
Ще забележите, че size връща текущия брой елементи записани във вектора, capacity показва памет за колко елемента е резервирана (т.е. векторът може по подразбиране да задели повече памет отколкото използваме!), а max_size определя максималният в момента свободен блок памет, който може да бъде заделен за нашият вектор. Ако искаме да резервираме допълнително памет (да увеличим capacity), то можем да използваме функцията reserve:
v.reserve(200); cout << "now capacity is: " << (int) v.capacity() << endl;
Ако подадем по-малка стойност на reserve отколкото е текущия capacity, то функцията няма да направи нищо. С resize обаче можем да "изрязваме" елементи от вектор:
v.resize(12); cout << "size: " << (int) v.size() << endl; cout << "capacity: " << (int) v.capacity() << endl; cout << "max_size: " << (int) v.max_size() << endl;
Изпълнете примера и отпечатайте съдържанието на вектора. Ще видите, че capacity се е запазило, но size е станал 12 и всички елементи след този с индекс 11 са изтрити. Нека сега да извикаме resize чрез втория му конструктор:
v.resize(20, 5);
По този начин добавихме 8 елемента със стойност числото 5 в края на вектора. Това е просто по-лесен начин за добавяне на множество еднакви елементи. Като бързина в сравнение с push_back няма почти никаква разлика.
Вече споменахме функцията at(int). Други две функции, които можем да използваме са front() и back(), чрез които достъпваме съответно първия и последния елемент на вектор. Друга полезна функция е empty(). Тя връща 0 ако в масива има елементи и true ако векторът е празен. Използва се като еквивалент на size()==0.
Много ценна възможност на векторите е да ги използваме за реализацията на структурата стек. Вече се запознахте с функцията push_back, която практически добавя елемент в края на стек. В класа vector са се погрижили и за функция за премахване на елемент от стек:
v.pop_back();
Така просто изтриваме последния елемент на вектора. Имайте предвид, че тя ще намали size(), но не и capacity().
Накрая една изключително силна възможност на векторите, е тяхната функционалност на структура от тип списък, а именно: вмъкване и изтриване на елементи във вектора. За целта е нужно използването на iterator:
#include "stdafx.h" #include <iostream> #include <vector> using namespace std; void showvector(vector<int> &v){ if (v.empty()) cout << "empty vector" << endl; else{ vector<int>::iterator it; for (it=v.begin(); it<v.end();it++ ){ cout << " " << *it; } } cout << endl; } int main (){ // създава вектор от три елемента // всеки от които със стойност 5 vector<int> v(3,5); // добавяме още три елемента v.push_back(10); v.push_back(11); v.push_back(12); // показва елементите на вектора showvector(v); vector<int>::iterator it; // накочваме инетатора към 5ти елемент it = v.begin()+4; // добавяме числото 33 като пети елемент v.insert(it, 33); showvector(v); // създаваме втори вектор vector<int> v2(0); v2.push_back(999); v2.push_back(999); // насочваме итератора към трети елемент it = v.begin()+2; // вмъкваме вектор v2 преди трети елемент на v v.insert(it, v2.begin(), v2.end()); showvector(v); // изтриваме 6ти елемент it = v.begin() + 5; v.erase(it); showvector(v); // изтриваме елементите от 3ти до 7ми включително it = v.begin(); v.erase(it+2, it + 7); showvector(v); // разменяме съдържанието на двата вектора cout << "the vectors are:" << endl; showvector(v); showvector(v2); cout << "swapped" << endl; v.swap(v2); showvector(v); showvector(v2); // изтриваме съдържанието на вектор v.clear(); showvector(v); return 0; }
Добави коментар