Стандардна библиотека на шаблони / контејнери , итератори и алгоритми




Стандардната библиотека на шаблони (STL - Standard Template Library) претставува множество од структури и алгоритми кои може да вршат операции над различни типови на податоци. Да разгледаме еден едноставен пример на програма која создава листа од 10 цели броеви, ја подредува истата во растечки редослед и потоа ја печати на екран:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
for (int i=1; i<=10; i++)
v.push_back((i*i*i) % 20);
//v={1, 8, 7, 4, 5, 16, 3, 12, 9, 0}
//podredi gi broevite
sort(v.begin(), v.end());
for (int i=0; i<v.size(); i++)
cout << v[i] << " "; //pechati "0 1 3 4 5 7 8 9 12 16 "
return 0;
}

Да започнеме со објаснување на составните делови на Стандардната библиотека на шаблони: контејнерите, итераторите и алгоритмите .

Контејнерите претставуваат структури на податоци во кои може да се чуваат разни информации. Досега,  зборувавме за само еден симплифициран "контејнер" на податоци: низата. Всушност, во целиот програмски јазик C++ (без STL), не постои друг контејнер во кој може да се сместуваат повеќе податоци од еден ист тип. Во примерот даден погоре, вектор (vector) претставува контејнер за чување на податоци од тип int (vector<int>). Сите контејнери кои се дел од Стандардната библиотека на шаблони се имплементирани како параметаризирани структури. Ова значи дека еден контејнер може да служи за чување на податоци од кој било тип (int, float, double, string, vector<int>, vector<float>, vector<vector<float> >, итн). За да може да користиме  контејнер, потребно е да ја вклучиме датотеката во која е тој дефиниран -  наредба #include <vector>.

Итераторите претставуваат еден вид покажувачи кон елементите од контејнерите. Итераторите се користат за движење низ елементите од еден контејнер, како и за означување на делови од податоци врз кои сакаме да вршиме операции. Во програмата дадена погоре, како параметри на функцијата sort испративме два итератора: v.begin() и v.end(). Овие итератори означуваат дека сакаме да ги подредиме сите елементи од векторот v - сите елементи помеѓу v.begin() и v.end().

Алгоритмите служат за вршење на операции врз податоци (подредување, пронаоѓање на најголема вредност, итн.). Во програмата дадена погоре, извршивме операција подредување врз сите податоци помеѓу v.begin() и v.end(). Ова го направивме со повикување на функцијата sort(pocetok, kraj). И тука, повторно, забележете дека е потребно вклучување на соодветната датотека во која е дефинирана функцијата за подредување (#include <algorithm>)

Вектор

Векторот (vector) е наједноставниот контејнер од Стандардната библиотека на шаблони. Векторот всушност претставува низа чија големина се определува динамички, т.е. за време на извршување на програмата (еден вид "паметна" низа). Дополнително, за разлика од низите, векторот нуди едноставни методи (функции) преку кои може да се одреди или промени неговата големина, да се додаде или избрише одреден елемент, итн.

Следнава програма креира празен вектор, додава неколку елементи (од тип string) во векторот и, на крај, ја печати неговата содржина.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> zborovi;
zborovi.push_back("zdravo");
zborovi.push_back("pero");
zborovi.push_back("kako");
zborovi.push_back("si");
for (int i=0; i<zborovi.size(); i++)
cout << zborovi[i] << " "; //pechati "zdravo pero kako si "
return 0;
}

До елементите на векторот пристапуваме на идентичен начин како што пристапуваме и до елементите на една низа - со користење на операторот [p]. Како што може да забележите од програмата дадена погоре, елементи во векторот додаваме со повик на методот push_back(string str). На тој начин, ја зголемуваме големината на векторот за 1. Векторот ја одредува потребната меморија автоматски, па не се грижете за перформансите на операцијата за додавање на нов елемент. Всушност, за да може да додаваме елементи во векторот во константно O(1) време, кога се додаваат одредени елементи, векторот врши алоцирање на повеќе меморија отколку што е тоа потребно во тој момент. Со методот capacity() може да одредиме за колку точно елементи е резервирано меморија - односно колку елементи може да се сместат во векторот пред неговата големина да биде (автоматски) зголемена.

Бројот на елементи во векторот може да се открие со повик на методот size(). Важно е да знаете дека size() враќа целобројни типови на податоци (што е нормално), но податоци кои се без знак (unsigned). Поради тоа, кога ја преведуваме програмата дадена погоре, компајлерот ќе не предупреди дека споредуваме (i < zborovi.size()) два различни целобројни типови на податоци (едниот со знак - i, а другиот без знак). Поради тоа, потребно е или да го промениме типот на променливата i, или да извршиме кастирање (претопување) на податокот кој ќе го врати методот size() - на пример, i < (int) zborovi.size(). Кај сите примери кои ќе бидат дадени во продолжение, ќе биде извршено претопување (во тип int) на податокот кој го враќа методот size().

Големината на векторот (бројот на елементи) може да ја специфицираме и при неговото креирање - со наведување на бројот на елементи и нивната иницијална вредност: по името на променливата, во загради. Покрај ова, за време на извршување на програмата, со повикување на методот resize може да го зголемиме или намалиме бројот на елементи во векторот. Доколку, како аргумент на resize, пратиме вредност која е поголема од тековниот број на елементи, тогаш на крајот од векторот ќе се додадат нови елементи. Во спротивно, доколку како аргумент испратиме вредност која е помала од тековниот број на елементи, тогаш од векторот ќе се избришат последните неколку елементи. Можеме да ги избришеме сите елементи од векторот (да ја поставиме неговата големина на 0) со повикување на методот clear().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v0; //v={}
vector<int> v1(5, 0); //v1={0, 0, 0, 0, 0}
vector<int> v2(3, 5); //v2={5, 5, 5}
//ako ne navedeme vtor argument, se pretpostavuva 0
vector<int> v3(3); //v3={0, 0, 0}
vector<int> v4(v2); //v4={5, 5, 5}
//v1.resize(n, v) -> [n] oznacuva kolku elementi, a [v] koja vrednost
//treba da se iskoristi dokolku treba da se dodadat novi elementi
v1.resize(7, 3); //v1={0, 0, 0, 0, 0, 3, 3}
v1.push_back(2); //v1={0, 0, 0, 0, 0, 3, 3, 2}
v1.resize(2, 5); //v1={0, 0}
v1.resize(2, 8); //v1={0, 0}
v1.push_back(5); //v1={0, 0, 5}
v1.resize(4, 3); //v1={0, 0, 5, 3}
v1.resize(5); //v1={0, 0, 5, 3, 0}
v1.clear(); //v1={}
v2.clear(); //v2={}
cout << v1.size() + v2.size() << endl; //pechati '0'
return 0;
}

Два вектори може да се споредат со примена на операторите == и != (на пример, v1 == v2). Притоа, споредувањето се врши елемент по елемент. 

Дозволено е и креирање на повеќедимензионални низи. Притоа, треба да се внимава да се остави празно место помеѓу двата знака '>' на крајот од дефиницијата на еден вектор. Ова треба да се направи бидејќи, во C++, веќе постои оператор '>>' (кој, како што веќе знаете, служи за поместување на битови).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <vector>
using namespace std;
int main()
{
//edna dimenzija
vector<string> v1(3, "ABC"); //v1={"ABC", "ABC", "ABC");
//dve dimenzii (zabelezhete '> >')
vector<vector<int> > v2(3); //v2={{}, {}, {})
v2[0].push_back(1); //v2={{1}, {}, {})
v2[0].push_back(2); //v2={{1, 2}, {}, {})
v2[1].push_back(3); //v2={{1, 2}, {3}, {})
v2[2].push_back(4); //v2={{1, 2}, {3}, {4})
v2[2].push_back(5); //v2={{1, 2}, {3}, {4, 5})
v2[2].push_back(6); //v2={{1, 2}, {3}, {4, 5, 6})
cout << v2[0][0] + v2[0][1] << endl; //pechati '3' (1+2)
cout << v2[1][0] << endl; //pechati '3' (3)
cout << v2[2][0] + v2[2][1] + v2[2][2] << endl; //pechati '15' (4+5+6)
cout << v2[0].size() << endl; //pechati '2'
cout << v2[1].size() << endl; //pechati '1'
cout << v2[2].size() << endl; //pechati '3'
return 0;
}

Постојат и методи (insert и erase) кои служат за вметнување на елемент (или елементи) на произволна позиција во векторот (не само на крајот, како што го прави тоа push_back), односно бришење на произволен елемент (или елементи) од векторот. Овие операции имаат линеарна сложеност: за додавање или бришење на елемент е потребно поместување на сите елементи кои се наоѓаат по тој елемент - на десно, доколку се додава нов елемент, или на лево, доколку се брише одреден елемент. 

Следната програма го илустрира ефектот од методите кои не беа искористени во претходните програми:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v; //v={}
v.push_back(3); //v={3}
v.push_back(1); //v={3, 1}
v.push_back(5); //v={3, 1, 5}
v.pop_back(); //v={3, 1} - brishenje na posledniot element
v.front() = 1; //v={1, 1} - promena na prviot element
v.back() = 7; //v={1, 7} - promena na posledniot element
cout << v.front() + v.back() << endl; //pechati '8' (1+7)
if (v.empty() == false)
cout << v.size() << endl; //pechati '2'
v.insert(v.begin(), 9); //v={9, 1, 7}
v.insert(v.begin(), 3, 2); //v={2, 2, 2, 9, 1, 7}
v.erase(v.begin()); //v={2, 2, 9, 1, 7}
cout << v.size() << endl; //pechati '5'
return 0;
}

Итератори

Итераторите овозможуваат движење низ елементите од еден контејнер, но и означување на групи податоци врз кои сакаме да вршиме одредени операции. Итераторите (интересно) нудат слични можности како и покажувачите: движење нанапред или наназад со помош на операторите ++ и --, скокање нанапред или наназад со одреден чекор (v.begin() + 20, v.end() - 4), добивање на вредноста кон која покажува итераторот (*it), споредување со други итератори (!=, ==, <, <=, >, >=) и одредување на растојанието (во број на елементи) помеѓу два итератори (користејќи го изразот it1 - it2, или преку повикување на методот distance).

Забележете дека, поради фактот што итераторите нудат слични операции како покажувачите, во C++ е овозможено користење на алгоритмите кои се дел од Стандардната библиотека на шаблони врз низи од податоци.

Да разгледаме еден пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v;
for (int i=0; i<10; i++)
v.push_back((i*i*i) % 10);
//v={0, 1, 8, 7, 4, 5, 6, 3, 2, 9}
sort(v.begin(), v.end());
//v={0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
//vrz nizi
int array[10];
for (int i=0; i<10; i++)
array[i] = (i*i*i) % 10;
//array={0, 1, 8, 7, 4, 5, 6, 3, 2, 9}
sort(array, array+10);
//array={0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
vector<int>::iterator it = v.begin();
cout << *it << endl; //pechati '0'
it++;
cout << *it << endl; //pechati '1'
*it = 7;
//v={0, 7, 2, 3, 4, 5, 6, 7, 8, 9}
cout << *it << endl; //pechati '7'
it += 2;
cout << *it << endl; //pechati '3'
//izminuvanje na site elementi
it = v.begin();
while (it != v.end())
{
cout << *it << endl;
it++;
}
cout << distance(v.begin(), v.end()) << endl; //pechati '10'
return 0;
}

Забележете дека итераторот begin() покажува на првиот елемент на еден контејнер (заменето со array - што е локацијата на првиот елемент на низата array). Од друга страна, итераторот end() не покажува на последниот елемент!!! од контејнерот, туку на еден елемент после него (заменето со array + 10 - локацијата на array[10]). На овој начин, можеме ефикасно да ги изминеме сите елементи од еден контејнер - се движиме низ елементите се додека не биде исполнет условот it == container.end(). Сите контејнери и алгоритми кои се дел од Стандардната библиотека на шаблони го користат овој факт - при повикување на која било функција која очекува опсег на елементи (почетен и краен итератор), се вршат операции од почетниот итератор (вклучувајќи го и елементот на кој тој покажува) до крајниот итератор (но, не вклучувајќи го елементот на кој тој покажува).

Всушност, во претходната програма, елементите v[10] и array[10] не ни постојат, и пристапувањето до нивната вредност би било грешка.

Што е вектор во C ++?

Векторите во C ++ се динамички низи што се користат за складирање на податоци. За разлика од низите, кои се користат за складирање на последователни податоци и имаат статична природа, векторите обезбедуваат поголема флексибилност на програмата - можат автоматски да ја менуваат големината кога некој елемент се вметнува или брише.

Синтакса

vector< object_type > variable_name;

Задолжително е да се одреди типот и името на променливата. Пример vector <int > a;
За да може да користиме вектор, потребно е да вклучиме со директивата  #include <vector>
Пристап до елементите на вектор e слично како кај низите. Со a[3] пристапуваме до членот со реден број 3 

Методи 
push_back                    - додавање елемент на крај на вектор      
a.push_back(13);
ime_na_ vector .size    - одредување големина на вектор / број на елементи    
vector < int > b={10,20,30,40,50};
cout << "Vektorot ima " << b.size() << " elemenati" << endl;
ime_na_ vector .front - прв елемент на вектор 
vector < int > grupa={"Mira","Pero","Goran","Natasa"};
cout << "Prv vo grupata e " << grupa.front() << endl; 
output: Mira
ime_na_ vector .back - пoследен елемент на вектор 
vector < int > grupa={"Mira","Pero","Goran","Natasa"};
cout << "Posleden vo grupata e " << grupa.back() << endl; 
output: Natasa
ime_na_ vector .pop_back - бришење на последниот елемент во векторот 
vector < double > temperature={23.2 , 25.8 , 11, 15.3, 17};
temperature.pop_back(); 
output:23.2, 25.8, 11, 15.3

ime_na_ vector .erase - бришење на елемент на одредена позиција во векторот 

vector < int > c={2,3,4};
c.erase(c.begin() + 1);                 //go brise elementot na pozicija 1 vo vektorot

ime_na_ vector .clear - бришење на сите елементи

vector < int > c={2,3,4};
c.clear();//brisenje na site elementi vo vektorot, posle ova toj e prazen

Печатење на елементите на вектор
#include <iostream>
#include < vector >
using namespace std;
int main() {
vector < int > broevi={1,2,3,4,5};
for(int i = 0; i < broevi.size(); i++)
{
cout << broevi[i] << " ";
}
cout << endl;}

​Output: 1 2 3 4 5

Сортирање на елементи 
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> a={4,2,7,5,9};
sort(a.begin(),a.end());
//Ispis
for(int i = 0; i<a.size(); i++)
        cout<<a[i];
}
Output  24579


Iterators An iterator allows you to access the data elements stored within the C++ vector. It is an object that functions as a pointer.

  1. begin() –Returns an iterator pointing to the first element in the vector
    end() – Returns an iterator pointing to the theoretical element that follows the last element in the vector
    rbegin() – Returns a reverse iterator pointing to the last element in the vector (reverse beginning). It moves from last to first element
    rend() – Returns a reverse iterator pointing to the theoretical element preceding the first element in the vector (considered as reverse end)
    cbegin() – Returns a constant iterator pointing to the first element in the vector.
    cend() – Returns a constant iterator pointing to the theoretical element that follows the last element in the vector.
    crbegin() – Returns a constant reverse iterator pointing to the last element in the vector (reverse beginning). It moves from last to first element
    crend() – Returns a constant reverse iterator pointing to the theoretical element preceding the first element in the vector (considered as reverse end) 
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> vec1;   
    for (int i = 1; i <= 10; i++)
        vec1.push_back(i);    
    cout << "Understanding begin() and end() function: " << endl;
    //for (auto i = vec1.begin(); i != vec1.end(); ++i)
    //    cout << *i << " ";
   for ( int i = 0; i < vec1.size(); ++i) cout << vec1 [i];
return 0;
}

iterators

Output


Capacity

size() – Returns the number of elements in the vector.
max_size() – Returns the maximum number of elements that the vector can hold.
capacity() – Returns the size of the storage space currently allocated to the vector expressed as number of elements.
resize(n) – Resizes the container so that it contains ‘n’ elements.
empty() – Returns whether the container is empty.
shrink_to_fit() – Reduces the capacity of the container to fit its size and destroys all elements beyond the capacity.
reserve() – Requests that the vector capacity be at least enough to contain n elements.

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> vec1;    
    for (int i = 1; i <= 10; i++)
        vec1.push_back(i);    
    cout << "Size of our vector: " << vec1.size();
    cout << "\nCapacity of our vector: " << vec1.capacity();
    cout << "\nMax_Size of our vector: " << vec1.max_size();    
    // resizes the vector size to 4
    vec1.resize(4);    
    // prints the vector size after resize()
    cout << "\nSize of our vector after resize: " << vec1.size();    
    // checks if the vector is empty or not
    if (vec1.empty() == false)
        cout << "\nVector is not empty";
    else
        cout << "\nVector is empty";
    return 0;
}

Output: ????

// resizing of the vector
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec;
// 5 elements are inserted in the vector
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);
cout << "Contents of vector before resizing:" << endl;

// displaying the contents of the vector before resizing
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << " ";
cout << endl;

// vector is resized
vec.resize(4);
cout << "Contents of vector after resizing:" << endl;

// displaying the contents of the vector after resizing
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << " ";
return 0;
}
Output 
Contents of the vector before resizing: 1 2 3 4 5
Contents of the vector after resizing: 1 2 3 4


Modifiers:

    assign() – It assigns new value to the vector elements by replacing old ones
    push_back() – It push the elements into a vector from the back
    pop_back() – It is used to pop or remove elements from a vector from the back.
    insert() – It inserts new elements before the element at the specified position
    erase() – It is used to remove elements from a container from the specified position or range.
    swap() – It is used to swap the contents of one vector with another vector of same type. Sizes may differ.
    clear() – It is used to remove all the elements of the vector container
    emplace() – It extends the container by inserting new element at position
    emplace_back() – It is used to insert a new element into the vector container, the new element is added to the end of the vector
    // Modifiers in vector
    #include <iostream> //#include <bits/stdc++.h>
    #include <vector>
    using namespace std;
    int main()
    {
        // Assign vector
        vector<int> vec;
        // fill the array with 12 seven times
        vec.assign(7, 12);
        cout << "The vector elements are: ";
        for (int i = 0; i < 7; i++)
            cout << vec[i] << " ";    
        // inserts 24 to the last position
        vec.push_back(24);
        int s = vec.size();
        cout << "\nThe last element is: " << vec[s - 1];  
         // prints the vector
        cout << "\nThe vector elements after push back are: ";
        for (int i = 0; i < vec.size(); i++)
        cout << vec[i] << " ";    
        // removes last element
        vec.pop_back();    
        // prints the vector
        cout << "\nThe vector elements after pop_back are: ";
        for (int i = 0; i < vec.size(); i++)
            cout << vec[i] << " ";    
        // inserts 10 at the beginning
        vec.insert(vec.begin(), 10);
        cout << "\nThe first element after insert command is: " << vec[0];    
        // removes the first element
        vec.erase(vec.begin());
        cout << "\nThe first element after erase command is: " << vec[0];    
        // inserts at the beginning
        vec.emplace(vec.begin(), 5);
        cout << "\nThe first element emplace is: " << vec[0];    
        // Inserts 20 at the end
        vec.emplace_back(20);
        s = vec.size();
        cout << "\nThe last element after emplace_back is: " << vec[s - 1];    
        // erases the vector
        vec.clear();
        cout << "\nVector size after clear(): " << vec.size();    
        // two vector to perform swap
        vector<int> obj1, obj2;
        obj1.push_back(2);
        obj1.push_back(4);
        obj2.push_back(6);
        obj2.push_back(8);    
        cout << "\n\nVector 1: ";
        for (int i = 0; i < obj1.size(); i++)
            cout << obj1[i] << " ";    
        cout << "\nVector 2: ";
        for (int i = 0; i < obj2.size(); i++)
            cout << obj2[i] << " ";    
        // Swaps obj1 and obj2
        obj1.swap(obj2);
        cout << "\nAfter Swap nVector 1: ";
        for (int i = 0; i < obj1.size(); i++)
            cout << obj1[i] << " ";
        cout << "\nVector 2: ";
        for (int i = 0; i < obj2.size(); i++)
            cout << obj2[i] << " ";
    }

    Output:


      

No comments: