* Вектори (std::vector)

Публикувано на 29 ноември 2008 в раздел С/C++.

Досега се стараехме да не използваме готови библиотеки, класове и обекти. За шаблонният клас 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;
	}



RSS за коментарите

Пусни коментар