11 CPP 1. STL sequential containers

STL dzieli się na

- Containers
- Algorithms

Kontenery zawierają dane np:

int a[10];

dostęp

a[3]=3;
cout<<a[3]<<endl;

ograniczenia:
-nie można zmienić rozmiaru
-tablica nie zna swojego rozmiaru
-i tym saym nie można sprawdzić czy robimy poprawny dostęp
-zorganizowane w jednym dużym bloku

możemy stworzyć własną klasę kontenerową, która rozwiąże 2 pierwsze wady:

.
#include <exception>
#include <iostream>
class Array
{
       int * _array;
       unsigned int _size;

public:
       Array(unsigned size = 0);

       void add(int value);

       void delItem(unsigned index);

       virtual ~Array();
       unsigned int getSize() const;
       int & operator[](unsigned index);
};

Array::Array(unsigned size) :
               _array(0), _size(size)
{
       if (size > 0)
       {
               _array = new int[this->_size];
       }
}

void Array::add(int value)
{
       if (_size == 0)
       {
               _array = new int[1];
       }
       else
       {
               int * tmp = new int[_size + 1];
               for (unsigned i = 0; i < _size; i++)
               {
                       tmp[i] = _array[i];
               }
               delete[] _array;
               _array = tmp;
       }
       _array[_size++] = value;
}

void Array::delItem(unsigned index)
{
       if (_size == 1)
       {
               delete[] _array;
               _array = 0;
       }
       else
       {
               int * tmp = new int[_size - 1];
               for (unsigned i = 0, j = 0; i < _size; i++, j++)
               {
                       if (i == index)
                       {
                               j--;
                       continue;

                       }
                       tmp[j] = _array[i];
               }
               delete[] _array;
               _array = tmp;
       }
       _size--;
}

unsigned int Array::getSize() const
{
       return _size;
}
int & Array::operator [](unsigned index)
{
       if (index > _size -1)
       {
               std::exception e;
               throw e;
       }
       return _array[index];
}

Array::~Array()
{
       delete[] _array;
}

o możemy jej używać:

int main()
{
       Array A(10);
       for(unsigned i=0; i < A.getSize(); ++i)
       {
               A[i] = i;
       }
       for (unsigned i=0; i < A.getSize(); ++i)
       {
               std::cout<<A[i]<<" ";
       }
       std::cout<<"\n";
       return 0;
}

-korzystać z metody getSize, i używać co ważne operatora []  (jest implementacja ::operator)

ale:
STL ma wbudowaną kasę Vector, która niweluje wszystkie wady i może obsługiwać różne typy:

.
#include <vector>
#include <iostream>
using namespace std;

int main()
{
       vector <int> v1(10);
       vector <int> v2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
       for(unsigned i = 0; i < v1.size(); ++i)
       {
               v1[i] = i;
       }
       for(unsigned i = 0; i < v1.size(); ++i)
       {
               cout << v1[i] << " ";
               cout << v2[i] << " ";
       }
       cout << endl;
       
       cout << v1.size() << endl;
       v1.push_back(100);
       cout << v1.size() << endl;
       v1.pop_back();
       cout << v1.size() << endl;
       return 0;
}

tu korzystamy z metod push_back() ktora dodaje element na końcu i pop_back() która usuwa.

przykład krótkiej inicjacji:

vector<int> v1 = {5, 6, 9};

SORT
Przykład sortowania Vektora, za pomocą wbudowanej, albo funkcji, albo klasy
.
// sort algorithm example
#include <iostream>     // std::cout
#include <algorithm>    // std::sort
#include <vector>       // std::vector

bool myfunction (int i,int j) { return (i<j); }

struct myclass {
  bool operator() (int i,int j) { return (i<j);}
} myobject;

int main () {
  int myints[] = {32,71,12,45,26,80,53,33};
  std::vector<int> myvector (myints, myints+8);               // 32 71 12 45 26 80 53 33

  // using default comparison (operator <):
  std::sort (myvector.begin(), myvector.begin()+4);           //(12 32 45 71)26 80 53 33

  // using function as comp
  std::sort (myvector.begin()+4, myvector.end(), myfunction); // 12 32 45 71(26 33 53 80)

  // using object as comp
  std::sort (myvector.begin(), myvector.end(), myobject);     //(12 26 32 33 45 53 71 80)

  // print out content:
  std::cout << "myvector contains:";
  for (std::vector<int>::iterator it=myvector.begin(); it!=myvector.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}



STL może być podzielone na wiele części np:

1)containers
2)algorithms
3)input/output
4)strings
5)numeric library
6)iterators
7)utilities

1) Containers
można podzielić na kategorie:

sequential containers:
-vector
-list
-deque


associative containers:
-set
-multiset
-map
-multimap


container adaptors:
-stack
-queue
-priority_queue


2) Algorithms - przekształcanie danych(zwykle w kontenerach) w formie funkcji
Kontenery to tylko struktury danych, bez algorytmów niewielka użyteczność.

kategorie algorytmów:

non-modifying sequence operations
modifying sequence operations
sorting
set operations
binary search
heap operations
min/max operations

3) Input/output library

odpowiedzialne za wszystkie wejscia i wyjscia (np cout, iostream)

4) String library
odpowiedz na char  * problem, również jest kontenerem choć nie jest doskonała

5) Numeric library
powiązana z matematyką, valarray type, rodzaj tablicy dla matematycznych operacji

6) Iterators
uogólnienie wskaźników, pozwalają na dostęp do elementów kolekcji

typy iteratorów:
-input iterator
-output iterator
-forward iterator
-bidirectional iterator
-random access iterator

każdy iterator ma swój zestaw operacji: first(),next(),isDone(),currentElement()

7) Utilities
narzędzia do manipulacji danymi, np funcional objects library

obiekty są prostymi algorytmami określonymi do prostych zadań



Containers kategorie

Sequence Containers:
zachowują pewną kolejność elementów, która może być kontrolowana przez programistę
trzy rozwiązania

-vector
-deque
-list

Vector:

nagłówek

<vector>


Definition:

template<

       class T,

       class Allocator = std::allocator<T>

> class vector;

Vector to dynamiczna tablica, zajmuje ciągły obszar pamięci. Największą zaletą wektora jest możliwość dostępu do jego elementów losowo (za pomocą indeksu) w stałym czasie.

Alokator odpowiada za udostępnienie modelu pamięci dla elementów kontenera. Zwykle używany jest domyślny (std :: allocator <T>), ale można też podać inny. Z technicznego punktu widzenia można używać innego przydziału dla każdego typu kontenera.

Definicja wektora pokazuje, że wektor jest klasą szablonu Template i konieczne jest określenie typu jego elementów podczas tworzenia instancji. Na przykład:

class A;

vector<int> v1;

vector<float> v2;

vector<A> v3;


Tworzenie Vektora:

Nie określamy tylko typ zmiennych ale metodę konstrukcji:

1)
explicit vector ( const Allocator& = Allocator() );

2)
explicit vector ( size_type n, const T& value= T(), const Allocator& = Allocator() );

3)
template <class InputIterator>
          vector ( InputIterator first, InputIterator last, const Allocator& = Allocator() );

4)
vector ( const vector<T,Allocator>& x );


Pierwszy to standardowy konstruktor, allocator opcjonalny.

Drugi tworzy vektor zawierający n objektów o wartości value

Trzeci to konstruktor iteracyjny. Stworzy Vektor z kopią wartości z first(dołącza) do last (niedołącza)
Generalnie do tworzenia nowego Vektora z istniejących danych.
Może zainicjować ze zwykłej tablicy jak w przykładzie z sortowaniem.

Przykład:

#include <vector>
#include <iostream>

using namespace std;

int main()
{
    //first one
    vector<int> v1(10, 0);
    for(unsigned i = 0; i < v1.size(); ++i)
    {
        v1[i]=i+1;
    }
    cout<<"Size (v1):  "<<v1.size()<<endl;
    for(unsigned i = 0; i < v1.size(); ++i)
    {
        cout<< v1[i]<<" ";
    }
    cout<<endl;
    //second one;
    vector<int> v2(v1.begin(), v1.begin()+5);
    cout<<"Size (v2):  "<<v2.size()<<endl;
    for(unsigned i = 0; i < v2.size(); ++i)
    {
        cout<< v2[i]<<" ";
    }
    cout<<endl;
    return 0;
}

wersja z pobraniem danych z tablicy:

int main()
{
    int a1[]={1,2,3,4,5,6,7,8,9,10};
    //first one
    vector<int> v1(a1, a1+10);


Czwarty to konstruktor kopiujący. Tworzy nowy v2 obiekt z istniejącego v1.

vector<int> v2(v1);


Vektor zawierający obiekty (typy niewbudowane)


problem niejawnej konwersji (implicit conversion)

class A
{
    int number;
public:
    A(int _number):number(_number) {}
};

int main()
{
    vector<A> v1;
    v1.push_back(1);
    return 0;
}

pomimo, że nie mamy stworzonego obiektu, wywołanie push_back(1) wyśle 1 i niejawnie stworzy.
Aby temu zapobiec dodajemy explicit przed konstruktorem w klasie.

#include <vector>
#include <iostream>

using namespace std;

class A
{
    int number;
public:
    explicit A(int _number):number(_number) {}
};

int main()
{
    vector <A> v1;
    v1.push_back(1); // compilation error
    return 0;
}


Przykład 3 konstruktorów, róznego ich wywołania i efektu

#include <vector>
#include <iostream>

using namespace std;

class A
{
    int number;
    int number2;
public:
    A(int _number):number(_number),number2(0)
    {
        cout<<"Normal constructor\n";
    }
    A()
    {
        cout<<"Default constructor\n";
    }
    
    A(const A& source)
    {
        number = source.number;
        number2 = source.number2;
        cout<<"Copy constructor\n";
    }

    A & operator=(const A& source)
    {
        number = source.number;
        number2 = source.number2;
        cout<<"Assignment operator\n";
        return *this;
    }
};

int main()
{
    vector <A> v1(1); //(1)
    v1.push_back(1); //(2)
    v1[0]=10;        //(3)
    return 0;
}
.

Output:
Default constructor ****(1)
Normal constructor ****(2)
Copy constructor     ****(2)
Copy constructor     ****(2)
Normal constructor ****(3)
Assignment operator  ****(3)


i Przykład z tą samą klasę i kopiowaniem całego vektora

int main()
{
    vector <A>v1;              //(1)
    v1.push_back(1);        //(1)
    cout<<"First ready!\n";
    //copy constructor
    vector <A>v2(v1);        //(2)
    cout<<"Second ready!\n";
    //assignment operator - empty target
    vector <A> v3;            //(3)
    v3 = v2;            //(3)
    cout<<"Third ready!\n";
    //assignment - not empty target
    vector <A> v4(2);        //(4)
    v4 = v2;            //(4)
    return 0;
}

Output:
Normal constructor        (1)
Copy constructor        (1)
First ready!
Copy constructor        (2)
Second ready!
Copy constructor        (3)
Third ready!
Default constructor        (4)
Default constructor        (4)
Assignment operator        (4)


Destruktor

ciekawy przypadek, przy dodawaniu kolejnych elementow do wektora, każdy z poprzednich jest realokowany, co widać ilości konstruktorow i destruktorow

#include <vector>
#include <iostream>

using namespace std;

class A
{
    int number;
public:
    A(int _number):number(_number)
    {
        cout<<"Normal constructor\n";
    }
    A()
    {
        cout<<"Default constructor\n";
    }
    
    A(const A& source)
    {
        number = source.number;
        cout<<"Copy constructor\n";
    }

    A & operator=(const A& source)
    {
        number = source.number;
        cout<<"Assignment operator\n";
        return *this;
    }

    ~A()
    {
        cout<<"Destructor\n";
    }
};

int main()
{
    vector <A> v1;
        v1.push_back(1);
        cout<<"First ready!\n";
        v1.push_back(2);
        cout<<"Second ready!\n";
        v1.push_back(3);
        cout<<"Third ready!\n";
        return 0;
}


Output:
Normal constructor
Copy constructor
Destructor
First ready!
Normal constructor
Copy constructor
Copy constructor
Destructor
Destructor
Second ready!
Normal constructor
Copy constructor
Copy constructor
Copy constructor
Destructor
Destructor
Destructor
Third ready!
Destructor
Destructor
Destructor


powyższe działa tylko w stosunku do statycznie tworzonych (nie wskaźniki)

poniżej przykład dla dynamicznie tworzonych objektów

int main()
{
    vector<A*> v1;
    v1.push_back(new A(1));
    cout<<"First ready!\n";
    v1.push_back(new A(2));
    cout<<"Second ready!\n";
    v1.push_back(new A(3));
    cout<<"Third ready!\n";
    return 0;
}
.
Output:
Normal constructor
First ready!
Normal constructor
Second ready!
Normal constructor
Third ready!

destruktor nie uruchamiany wcale nawet kiedy kolekcja niszczona, moze doprowadzac do wyciekow pamieci.


Deque - (double-ended queue) różni się od vektora tym, że nie zajmuje ciągłego obszaru pamięci, tym samym można łątwo dodać jeden element w środku bez kopiowania całych obszarów pamięci.
Header:
<deque>


Definition:


template<

      class T,

      class Allocator = std::allocator<T>

> class deque;


Deque ma 4 konstruktory identyczne z tymi z vektora:



1) explicit deque ( const Allocator& = Allocator() );

2) explicit deque
( size_type n, const T& value= T(), const Allocator& = Allocator() );

3) template <class InputIterator>
        deque ( InputIterator first, InputIterator last, const Allocator& = Allocator() );

4) deque ( const deque<T,Allocator>& x );


Mamy domyślny, inicjalizujący, iteracyjny i kopiujący

#include <deque>
#include <iostream>

using namespace std;

int main()
{
    deque<int> d1(10, 0);
    cout<<"Size: "<<d1.size()<<endl;
    for(unsigned i = 0; i < d1.size(); ++i)
    {
        cout<< d1[i]<<" ";
    }
    cout<<endl;
    return 0;
}

Output:
Size: 10
0 0 0 0 0 0 0 0 0 0



Iterator construktor przykład:

int main()
{
    int a1[]={1,2,3,4,5,6,7,8,9,10};
    //first one
    deque <int>d1(a1, a1+10);
    cout<<"Size (d1):  "<<d1.size()<<endl;
    for(unsigned i = 0; i < d1.size(); ++i)
    {
        cout<< d1[i]<<" ";
    }
    cout<<endl;
    //second one;
    deque <int>d2(a1+5,a1+10);
    cout<<"Size (d2):  "<<d2.size()<<endl;
    for(unsigned i = 0; i < d2.size(); ++i)
    {
        cout<< d2[i]<<" ";
    }
    cout<<endl;
    return 0;
}

Output:
Size (d1):  10
1 2 3 4 5 6 7 8 9 10
Size (d2):  5
6 7 8 9 10

Iteracyjny konstruktor ale vektor użyty jako źródło elementów

#include <vector>
#include <deque>
#include <iostream>

using namespace std;

int main()
{
    //vector
    vector <int>v(10, 0);
    for(unsigned i = 0; i < v.size(); ++i)
    {
        v[i]=i+1;
    }
    cout<<"Size (v):  "<<v.size()<<endl;
    for(unsigned i = 0; i < v.size(); ++i)
    {
        cout<< v[i]<<" ";
    }
    cout<<endl;
    //deque
    deque <int>d(v.begin(), v.begin()+5);
    cout<<"Size (d):  "<<d.size()<<endl;
    for(unsigned i = 0; i < d.size(); ++i)
    {
        cout<< d[i]<<" ";
    }
    cout<<endl;
    return 0;
}

Output:
Size (v):  10
1 2 3 4 5 6 7 8 9 10
Size (d):  5
1 2 3 4 5


Konstruktor kopiujący:

int main()
{
    int a1[]={1,2,3,4,5,6,7,8,9,10};
    //first one
    deque <int> d1(a1, a1+10);
    cout<<"Size (d1):  "<<d1.size()<<endl;
    for(unsigned i = 0; i < d1.size(); ++i)
    {
        cout<< d1[i]<<" ";
    }
    cout<<endl;
    //second one;
    deque <int> d2(d1);
    cout<<"Size (d2):  "<<d2.size()<<endl;
    for(unsigned i = 0; i < d2.size(); ++i)
    {
        cout<< d2[i]<<" ";
    }
    cout<<endl;
    return 0;
}

Elementy w deque takie same zasady jak w vektorze, poprawny konstruktor, k.kopiujący i operator przypisania (assignment operator) żeby działały poprawnie.

Output:
Size (d1):  10
1 2 3 4 5 6 7 8 9 10
Size (d2):  10
1 2 3 4 5 6 7 8 9 10



LISTA

również nie zajmuje ciągłej pamięci, i umożliwia wstawianie w środek bez kopiowania zawartości. Jednak Lista nie umożliwia dostępu do dowolnego elementu, trzeba przeiterować całość.
Różnica pomiędzy listą i Deque jest taka, że deque ma listę wszystkich wskaźników, więc przy dodaniu elementu w środku nie kopiuje danych ale odświeża listę.
Deque: Każde wstawienie lub usunięcie elementów innych niż na początku lub na końcu unieważnia wszystkie wskaźniki, odniesienia i iteratory odnoszące się do elementów deque.
Lista: wstawianie i usuwanie elementów nie powoduje unieważniania wskaźników, odwołań i iteratorów względem innych elementów. lack of the random access mechanism – operator[]

Lista dodatkowo zużywa trochę więcej pamięci bo każdy element ma adres następnego.

Header:

<list>

Definition:

template<

    class T,

    class Allocator = std::allocator<T> >

class list;


ITERATORS - koncept podobny do wskaźników

Kontenery danych mogą przyjmować 4 rodzaje iteratorów:


iterator – read/write iterator type;

const_iterator – read-only iterator type;

reverse_iterator – reverse iterator type (iterates from the end to the beginning)

const_reverse_iterator – as above, but read only.


Tworzymy iteroatory dla różnych kolekcji (vektora, deque, listy) Na razie są stworzone ale nie zainicjalizowane (więc nie mogą być użyte)

#include <list>
#include <vector>
#include <deque>
#include <iostream>

using namespace std;

int main()
{
    //containers
    vector<int> v;
    deque<int> d;
    list<int> l;
    //iterators
    vector<int> ::iterator it1;
    vector<int> ::const_iterator it2;
    vector<int> ::reverse_iterator it3;
    vector<int> ::const_reverse_iterator it4;

    deque<int> ::iterator it5;
    deque<int> ::const_iterator it6;
    deque<int> ::reverse_iterator it7;
    deque<int> ::const_reverse_iterator it8;

    list<int> ::iterator it9;
    list<int> ::const_iterator it10;
    list<int> ::reverse_iterator it11;
    list<int> ::const_reverse_iterator it12;

    return 0;
}

żeby zainicjować musimy pobrać wartość z kolekcji. Mamy 4 metody pobrania:


begin()
end()
rbegin()
rend()

który skąd bierze:




zielone to past-the-end element i element before the first (oznacza koniec kolekcji w odwróconym porządku)

Przykład inicjacji i użycia normal iterators

int main()
{
    //containers
    vector <int> v(10);
    deque <int> d(10);
    list <int> l(10);
    
    int i = 1;
    //vector
    vector<int>::iterator itV;
    for(itV = v.begin()  ; itV != v.end(); ++itV,++i)
    {
        *itV = i;
    }
    for(itV = v.begin();  itV != v.end(); ++itV)
    {
        cout << *itV << " ";
    }
    cout<<endl;
    //deque
    deque<int>::iterator itD = d.begin();
    for(itD = d.begin()  ; itD != d.end(); ++itD,++i)
    {
        *itD = i;
    }
    for( itD = d.begin() ; itD != d.end(); ++itD)
    {
        cout << *itD << " ";
    }
    cout<<endl;

    list<int>::iterator itL = l.begin();
    for( ; itL != l.end(); ++itL,++i)
    {
        *itL = i;
    }
    for( itL = l.begin() ; itL != l.end(); ++itL)
    {
        cout << *itL << " ";
    }
    cout<<endl;
    return 0;
}


Console output:
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


Reverse iterators:

int main()
{
    //containers
    vector <int> v(10);
    deque <int> d(10);
    list <int> l(10);
    
    int i = 1;
    //vector
    vector<int>::iterator itV;
    for(itV = v.begin()  ; itV != v.end(); ++itV,++i)
    {
        *itV = i;
    }
    
    for(vector<int>::reverse_iterator it = v.rbegin();  it != v.rend(); ++it)
    {
        cout << *it << " ";
    }
    cout<<endl;
    //deque
    i = 1;
    deque<int>::iterator itD = d.begin();
    for(itD = d.begin()  ; itD != d.end(); ++itD,++i)
    {
        *itD = i;
    }
    for( deque<int>::reverse_iterator it = d.rbegin() ; it != d.rend(); ++it)
    {
        cout << *it << " ";
    }
    cout<<endl;
    //list
    i = 1;
    list<int>::iterator itL = l.begin();
    for( ; itL != l.end(); ++itL,++i)
    {
        *itL = i;
    }
    for(list<int>::reverse_iterator it = l.rbegin() ; it != l.rend(); ++it)
    {
        cout << *it << " ";
    }
    cout<<endl; 
    return 0;
}

Console output:
10 9 8 7 6 5 4 3 2 1
10 9 8 7 6 5 4 3 2 1
10 9 8 7 6 5 4 3 2 1


Const Iterators:

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    //containers
    vector <int> v(a,a+10);
    deque <int> d(a,a+10);
    list <int> l(a,a+10);
    
    //vector
    for(vector<int>::const_iterator it = v.begin()  ; it != v.end(); ++it)
    {
        cout << *it << " ";
    }
    cout<<endl;
    //deque
    for(deque<int>::const_iterator it = d.begin(); it != d.end(); ++it)
    {
        cout << *it << " ";
    }
    cout<<endl;
    //list
    for(list<int>::const_iterator it = l.begin(); it != l.end(); ++it)
    {
        cout << *it << " ";
    }
    cout<<endl;
    return 0;
}


Console output:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10


Do const iterator nie można przypisać
Przykład błędu!

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    //containers
    vector<int>  v(a,a+10);
    deque<int>  d(a,a+10);
    list<int>  l(a,a+10);
    
    vector<int> ::const_iterator it1 = v.begin();
    *it1 = *it1+1;
    deque<int> ::const_iterator it2 = d.begin();
    *it2 = *it2+1;
    list<int> ::const_iterator it3 = l.begin();
    *it3 = *it3+1;
    return 0;
}


Iteratory innych typów pojemników działają w ten sam sposób. Najważniejszym czynnikiem właściwego używania iteratorów jest typ iteratora. Najczęściej spotykane są:

random access;
bi-directional.


Pamiętaj, że możesz zrobić więcej z pierwszym typem niż drugim.

Iteratory pozwalają ci przechodzić przez kolekcje i manipulować ich elementami bez względu na rodzaj kolekcji. Jest to popularny interfejs do korzystania z kontenerów STL.

Iteratory sprawiają, że względnie łatwo przełącza się z jednego rodzaju kontenera na inny bez znacznego wpływu na kod źródłowy. Wszystkie Kontenery STL zapewniają iteratory, dlatego tak ważne jest ich pełne zrozumienie.


1.4 METODY


size() and max_size()

Metody które pokazują bieżący rozmiar kontenera i maksymalny dozwolony rozmiar kontenera.

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    //containers
    vector <int> v(a,a+10);
    deque <int> d(a,a+10);
    list <int> l(a,a+10);
    cout<<"Size of vector, deque, list: "<<v.size() << " " << d.size() <<" " 
        << l.size()<<endl;
    cout<<"Max size of vector, deque, list: "<<v.max_size() << " " 
        << d.max_size() <<" " << l.max_size()<<endl<<endl;

    v.push_back(11);
    d.push_back(11);
    l.push_back(11);

    cout<<"Size of vector, deque, list: "<<v.size() << " " << d.size() <<" " 
        << l.size()<<endl;
    cout<<"Max size of vector, deque, list: "<<v.max_size() << " " 
        << d.max_size() <<" " << l.max_size()<<endl<<endl;

    v.pop_back();
    d.pop_back();
    l.pop_back();

    cout<<"Size of vector, deque, list: "<<v.size() << " " << d.size() <<" " 
        << l.size()<<endl;
    cout<<"Max size of vector, deque, list: "<<v.max_size() << " " 
        << d.max_size() <<" " << l.max_size()<<endl<<endl;
    return 0;
}

Console output:
Size of vector, deque, list: 10 10 10
Max size of vector, deque, list: 1073741823 1073741823 357913941

Size of vector, deque, list: 11 11 11
Max size of vector, deque, list: 1073741823 1073741823 357913941

Size of vector, deque, list: 10 10 10
Max size of vector, deque, list: 1073741823 1073741823 357913941



empty() and resize()


Metoda empty zwraca true jeśli kontener pusty.
Resize zmienia wielkość

void resize ( size_type sz, T c = T() );

Parameters:

sz – the new size of a container;
c – the value to copy in order to add new elements into a container when the new size (sz) is greater than the old size.

if (v.empty())
    {
        v.resize(10);


capacity()  - tylko vektor

ile wolnej przestrzeni bez relokacji vektora, wiekszy niż size. Jesli za mały dodanie elementu spowoduje relokacje vektora w inny obszar pamięci gdzie mamy więcej wolnego miejsca czyli większe capacity (pojemność)


reserve() - tylko vektor
rezerwuje dodatkową capacity na wypadek przyszłego dodawania elementów. Jesli większe niż obecne dostępne, zrobi relokacje w pamięci by spełnić warunki.

v.reserve(15) - ustawi capacity na 15.

1.4.5

back() and front()

Front zwraca referencję do pierwszego elementu kontenera, może być const.
Back zwraca referencję do ostatniego.

nie tylko może zwrócić wartośc
cout<<"Values at front vector "<< v.front() <<endl;

ale można i przypisać
v.front()=100;



operator[] and at() – vector and deque only

operator[n] - pozwala odczytać N-ty element kontenera, nie pozwala zapisać i nie sprawdza czy n mieści się w zakresie.

at(n) - daje referencje do Ntego obiektu, pozwala zapisać inna wartość i sprawdza czy n w zakresie.
Nie można dodać elementu tą metodą.



assign()

template <class InputIterator>
          void assign ( InputIterator first, InputIterator last );

void assign ( size_type n, const T& u );

content I w przykładzie
first, last - iteratory wejściowe, które zapewniają zbiór elementów wejściowych. Metoda assign skopiuje wszystkie elementy z tego zakresu, w tym pierwsze i wykluczające ostatnie. Ponieważ pierwszy i ostatni są typu InputIterator, praktycznie każdy typ iteratora może być użyty w wywołaniu;
content II w przykładzie
n - ile razy wartość u zostanie skopiowana w celu wypełnienia kontenera;
u - wartość do skopiowania.

Przykład użycia pierwszego assing (content I) i drugiego (content II)


template<class I>
void print (const I & start, const I & end)
{
    I it;
    for(it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    //containers
    vector <int> v(a,a+5);
    deque <int> d(a,a+5);
    list <int> l(a,a+5);

    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());

    cout<<"Assigning a new content I:\n";
    v.assign(a, a+10);
    d.assign(a, a+10);
    l.assign(a, a+10);
    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());

    cout<<"Assigning a new content II:\n";
    v.assign(3, 100);
    d.assign(3, 1000);
    l.assign(3, 10000);
    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());
    return 0;
}


Console output:
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
Assigning a new content I
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
Assigning a new content II:
100 100 100
1000 1000 1000
10000 10000 10000


insert ()          1.4.8

The method insert() performs an insertion into a container. There are three variants of this method, as stated in the signature section. Inserting an element into a container will cause the container to grow. This leads to different consequences, depending on the type of collection:

vector – when an increase in size causes it to reallocate (not enough capacity left) all iterators, references and pointers will be invalidated;
deque – all iterators will be invalidated, references also, unless insertion at the beginning or end takes place;
list – the iterators and references remain.


void insert ( iterator position, size_type n, const T& x );
template <class InputIterator>
     void insert ( iterator position, InputIterator first, InputIterator last );



Parameters:

position – the position in the container at which the insertion of an element (or elements) is to be performed. For deque and vector, this is RandomAccessIterator, while in the case of a list, BidirectionalIterator is used;
x – the value to be inserted;
n – the number of x values to be inserted;
first, last – the iterators specifying the range of elements to be inserted into the container. As usual, the range includes first and excludes last.

Przykład Vektor:

#include <vector>
#include <iostream>

using namespace std;

template <class I>
void print (const I & start, const I & end)
{
    I it;
    for(it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    vector <int> v(a,a+10);

    vector<int>::iterator it = v.insert(v.begin()+5, 100);
    print(v.begin(), v.end());
    cout<<"Inserted element: "<<*it<<endl;
    cout <<"Size: "<<v.size()<<endl;
    
    vector <int> v2;
    v2.insert(v2.begin(), v.rbegin(), v.rend());
    print(v2.begin(), v2.end());

    vector <int> v3(v.begin(), v.begin()+5);
    v3.insert(v3.end(),3,100);

    print(v3.begin(), v3.end());

    return 0;
}

Console output:
1 2 3 4 5 100 6 7 8 9 10
Inserted element: 100
Size: 11
10 9 8 7 6 100 5 4 3 2 1
1 2 3 4 5 100 100 100

Przykład deque:

#include <deque>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    I it;
    for(it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    deque <int> d1(a,a+10);

    deque<int>::iterator it = d1.insert(d1.begin()+5, 100);
    print(d1.begin(), d1.end());
    cout<<"Inserted element: "<<*it<<endl;
    cout <<"Size: "<<d1.size()<<endl;
    
    deque <int> d2;
    d2.insert(d2.begin(), d1.rbegin(), d1.rend());
    print(d2.begin(), d2.end());

    deque <int> d3(d1.begin(), d1.begin()+5);
    d3.insert(d3.end(),3,100);

    print(d3.begin(), d3.end());

    return 0;
}

Console output:
1 2 3 4 5 100 6 7 8 9 10
Inserted element: 100
Size: 11
10 9 8 7 6 100 5 4 3 2 1
1 2 3 4 5 100 100 100

Przykład Lista:

#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    I it;
    for(it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    list <int> l1(a,a+10);

    list<int>::iterator it = l1.insert(l1.begin(), 100);
    print(l1.begin(), l1.end());
    cout<<"Inserted element: "<<*it<<endl;
    cout <<"Size: "<<l1.size()<<endl;
    
    list <int> l2;
    l2.insert(l2.begin(), l1.rbegin(), l1.rend());
    print(l2.begin(), l2.end());

    list<int>::iterator it1 = l1.begin();
    //shifting 5 places
    for(int i = 0; i < 5; ++i)
    {
        ++it1;
    }
    list <int> l3(l1.begin(), it1);
    l3.insert(l3.end(),3,100);

    print(l3.begin(), l3.end());

    return 0;
}

Console output:
100 1 2 3 4 5 6 7 8 9 10
Inserted element: 100
Size: 11
10 9 8 7 6 5 4 3 2 1 100
100 1 2 3 4 100 100 100

erase()

This function removes an element or a range of elements from a collection. It’s important to note that it uses iterators, and not the index value, nor the value of the element itself. After the operation, the size of the collection is decreased. During the removal of the elements, the destructors are called.

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


Parameters:
position – the iterator pointing to the element to be erased;
first, last – the iterators which specify the range of elements to be erased. As usual, the range includes first and excludes last.


#include <list>
#include <vector>
#include <deque>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    I it;
    for(it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    vector <int> v(a,a+10);
    deque <int> d(a,a+10);
    list <int> l(a,a+10);

    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());

    cout<<"Ereasing elements:\n";
    v.erase(v.begin()+3);
    d.erase(d.begin()+3);
    //no random access iterator
    list<int>::iterator it= l.begin();
    ++it; ++it; ++it;
    it = l.erase(it);
    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());

    cout<<"Ereasing elements:\n";
    v.erase(v.begin()+3, v.end());
    d.erase(d.begin()+3, d.end());
    l.erase(it, l.end());
    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());
    return 0;
}


swap()

This method swaps the entire content between two collections of the same type (list <–> list, vector <–> vector, deque <–> deque). Although the types of collections must be the same, their sizes may differ. After the change, all the existing iterators are still valid, but they of course point to different containers. After the swap operations, the calling container will be made up of elements from the source collection, and vice versa.


Signature:
void vector::swap ( vector<T,Allocator>&   vec );
void deque::swap ( deque<T,Allocator>& dqe );
void list::swap ( list<T,Allocator>& lst );



Parameters:
vec, deq, lst – another collection of the same type as this one.


#include <list>
#include <vector>
#include <deque>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    I it;
    for(it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    vector <int> v1(a,a+5);
    deque <int> d1(a,a+5);
    list <int> l1(a,a+5);

    vector<int> v2(a+5,a+10);
    deque<int> d2(a+5,a+10);
    list<int>  l2(a+5,a+10);

    print(v1.begin(), v1.end());
    print(v2.begin(), v2.end());
    print(d1.begin(), d1.end());
    print(d2.begin(), d2.end());
    print(l1.begin(), l1.end());
    print(l2.begin(), l2.end());

    cout<<"Swapping elements:\n";
    v1.swap(v2);
    d1.swap(d2);
    l1.swap(l2);
    print(v1.begin(), v1.end());
    print(v2.begin(), v2.end());
    print(d1.begin(), d1.end());
    print(d2.begin(), d2.end());
    print(l1.begin(), l1.end());
    print(l2.begin(), l2.end());

    return 0;
}


clear()

This function removes all the elements from the collection and sets its size to 0. During the removal, the destructors are called.

Signature:
void clear ( );



Parameters:
None.
#include <list>
#include <vector>
#include <deque>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    vector <int> v(a,a+10);
    deque <int> d(a,a+10);
    list <int> l(a,a+10);

    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());

    cout<<"Clearing collections:\n";
    v.clear();
    d.clear();
    l.clear();

    v.push_back(100);
    d.push_back(100);
    l.push_back(100);
    print(v.begin(), v.end());
    print(d.begin(), d.end());
    print(l.begin(), l.end());

    return 0;
}


push_back() and pop_back()


The function push_back() adds a new value to a container. The value is added at the end (the back) of the container, and increases the size of the container by one. Different types of containers react differently:

if a vector has enough capacity, the item is just added to it, no reallocation is performed, and all obtained iterators remain valid;
if there is not enough capacity left, a reallocation is performed, which invalidates all iterators;
In the case of deque, all iterators are invalidated;
For a list container, all iterators are left unaffected.


Signature:
void push_back ( const T& x );



Parameters:
x – the value which will be used to create a new element (by copying) inside a container.


#include <vector>
#include <deque>
#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    vector <int> v;
    deque <int> d;
    list <int> l;
    
    for(unsigned i = 0; i < 10; ++i)
    {
        v.push_back(i);
        d.push_back(i);
        l.push_back(i);
    }
    cout<<"Vector: ";    print(v.begin(), v.end());
    cout<<"Deque:  ";    print(d.begin(), d.end());
    cout<<"List:   ";    print(l.begin(), l.end());
    
    for(unsigned i = 0; i < 5; ++i)
    {
        v.pop_back();
        d.pop_back();
        l.pop_back();
    }
    
    cout<<"Vector: ";    print(v.begin(), v.end());
    cout<<"Deque:  ";    print(d.begin(), d.end());
    cout<<"List:   ";    print(l.begin(), l.end());

    return 0;
}

Console output:

Vector: 0 1 2 3 4 5 6 7 8 9
Deque:  0 1 2 3 4 5 6 7 8 9
List:   0 1 2 3 4 5 6 7 8 9
Vector: 0 1 2 3 4
Deque:  0 1 2 3 4
List:   0 1 2 3 4


push_front()  

The function push_front() adds a new value to a container. The value is added at the beginning (the front) of the container, and increases the size of the container by one. Different types of containers react differently:

in the case of deque, all iterators are invalidated;
for a list container, all iterators are left unaffected.

Signature:
void push_front ( const T& x );


Parameters:
x – the value which will be used to create a new element (by copying) inside a container.


pop_front()

This function removes an element from the beginning of the container. Basically, it’s the opposite method to push_front(). During element removal, its destructor is called, and the container size is reduced by one. It’s also worth noticing that pop_front() only removes the value, and the value is not returned. This method cannot be used as the l-value. To obtain a value, you should use front() first.

Signature:
void pop_front ( );


Parameters:
None.

Przykład push, pop
#include <deque>
#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    deque <int> d;
    list <int> l;
    
    for(unsigned i = 0; i < 10; ++i)
    {
        d.push_front(i);
        l.push_front(i);
    }
    cout<<"Deque:  ";    print(d.begin(), d.end());
    cout<<"List:   ";    print(l.begin(), l.end());
    
    for(unsigned i = 0; i < 5; ++i)
    {    
        d.pop_front();
        l.pop_front();
    }
    cout<<"Deque:  ";    print(d.begin(), d.end());
    cout<<"List:   ";    print(l.begin(), l.end());

    return 0;
}

Console output:
Deque:  9 8 7 6 5 4 3 2 1 0
List:   9 8 7 6 5 4 3 2 1 0
Deque:  4 3 2 1 0
List:   4 3 2 1 0


splice() – list only

This method moves elements from a list specified as parameter x, and inserts them into the list container which calls the method. The target list size increases by the number of elements moved, while the source list size decreases accordingly. There are three versions of this method:

a method which moves the whole content of the source container;
a method which moves one, and only one, element specified by the iterator;
a method which moves a range of elements specified by the iterators.
In all cases, there’s no object destruction or construction involved during the call.

Signature:
void splice ( iterator position, list<T,Allocator>& x );
void splice ( iterator position, list<T,Allocator>& x, iterator i );
void splice ( iterator position, list<T,Allocator>& x, iterator first, iterator last );

Parameters:
position – the position in the calling list where the elements will be inserted;
x – the list from which the elements will be moved to the calling list;
i – the iterator to a single element from the source list, which will be moved to the calling list;
first, last – the iterators which define the range of elements to be moved from the source list to the destination. The range includes first and excludes last.


#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[]={1,2,3,4,5};
    int b[]={6,7,8,9,10};
    int c[]={11,12,13,14,15};
    list <int> l1(a,a+5);
    list <int> l2(b, b+5);
    list <int> l3(c, c+5);
    
    l2.splice(l2.end(),l3);
    print(l2.begin(), l2.end());
    cout<<"Size of source list l3: "<< l3.size()<<endl;

    //moving one element - '15'
    list<int>::iterator it = l2.begin();
    advance(it,9);
    l1.splice(l1.end(),l2,it);
    print(l1.begin(), l1.end());
    cout<<"Size of source list l2: "<< l2.size()<<endl;

    //moving range of elements
    it = l1.end();
    advance(it,-1);
    l1.splice(it,l2,l2.begin(), l2.end());
    print(l1.begin(), l1.end());
    cout<<"Size of source list l2: "<< l2.size()<<endl;
    
    return 0;
}


remove()

This function removes from the list all the elements equal to the values provided as the parameters. During the removal, the destructors are called. This function works in a different way in comparison to erase which uses iterators.

Signature:
void remove ( const T& value );

Parameters:
value – the value of the element to be removed from the list. It’s the same type as that used during the list declaration.


remove_if()

The function remove_if() performs a conditional object deletion. The method calls the provided predicate for every element stored inside the list. If the predicate returns true, the element is eligible for removal. During the removal, the destructors are called and the size of the list decreases.

Signature:
template <class Predicate>
   void remove_if ( Predicate pred );



Parameters:
pred – a unary predicate (one argument function, or function object) which takes an argument of the same type as the elements of the list. The predicate should return true for elements which are to be removed, and false for all others.

Przykład remove_if


#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}
struct DeleteOdd
{
    bool operator ()(int value)
    {
        if (value % 2 > 0 )
        {
            return true;
        }
        return false;
    }
};
bool deleteEven(int value)
{
    if (value % 2 == 0 )
    {
        return true;
    }
    return false;
}

int main()
{
    int a[]={1,2,1,3,2,3,4,3,4,7,8,9,6,6,5,8,9,10};
    
    list <int> l1(a,a+18);
    list <int> l2(a,a+18);
    print(l1.begin(), l1.end());
    //remove odd numbers
    l1.remove_if(DeleteOdd());
    cout<<"All odd numbers have been deleted"<<endl;
    print(l1.begin(), l1.end());
    //remove even numbers
    l2.remove_if(deleteEven);
    cout<<"All even numbers have been deleted"<<endl;
    print(l2.begin(), l2.end());
    
    return 0;
}

Console output:
1 2 1 3 2 3 4 3 4 7 8 9 6 6 5 8 9 10
All odd numbers have been deleted
2 2 4 4 8 6 6 8 10
All even numbers have been deleted
1 1 3 3 3 7 9 5 9



unique() – list only

The function unique() removes consecutive duplicates from the list. Basically, the function makes a comparison between every two directly subsequent elements (starting from the beginning of the list) and if they’re found to be alike, the second element is deleted. The second version of this function does exactly the same thing, but it uses a binary predicate to decide whether the objects are equal or not.
For every element eligible for deletion, its constructor  is called, and the size of the list is reduced. To supply the predicate to the method, you must use either the function pointer, or the functional object instance.

Signature:
void unique ( );
template <class BinaryPredicate>
    void unique ( BinaryPredicate binary_pred );


Parameters:
binary_pred – binary predicate – a two-argument function or proper functional object, which performs a comparison between two elements of the list in order to decide whether they are alike or not. If they are found to be alike, the second element is removed.



#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[]={1,2,1,3,2,3,4,3,4,7,8,9,6,6,5,8,9,10};
    
    list <int> l1(a,a+18);
    list <int> l2(a,a+18);
    print(l1.begin(), l1.end());
    
    cout<<"Deleting all subsequent duplicates"<<endl;
    l1.unique();
    print(l1.begin(), l1.end());

    cout<<"Deleting all subsequent duplicates from sorted list"<<endl;
    l2.sort();
    l2.unique();
    print(l2.begin(), l2.end());
    
    return 0;
}

Console output:
1 2 1 3 2 3 4 3 4 7 8 9 6 6 5 8 9 10
Deleting all subsequent duplicates
1 2 1 3 2 3 4 3 4 7 8 9 6 5 8 9 10
Deleting all subsequent duplicates from sorted list
1 2 3 4 5 6 7 8 9 10

merge() – list only

This method performs a merge of two sorted list. In order to do so, two iterators are used: one in the target (calling) list, and the second in the source list. The merge() method compares the objects’ pointers with those iterators, and if the source object is less than the target, it’s removed from the source and placed in the target list at the target iterator position.
If the source object is greater, the insertion iterator advances and the procedure repeats. This operation is repeated until the insertion iterator reaches the end() of the target list. At that moment, if there are any elements left in the source list, they’re all moved to the target list, and placed at the end.
The second version of the method uses an external comparator in the form of a binary predicate to perform a comparison between the elements from the target and the source. The predicate takes as its first argument an object from the target list, and as the second argument an element from the source list. It should return true if the target is less than the source, and false otherwise. It should perform the strictly weak strict  ordering.
During merge(), the objects are moved from the source to the target. Effectively, no object is created, copied, or deleted. This function requires both lists to be sorted before it can be called.

Signature:
void merge ( list<T,Allocator>& x );
template <class Compare>
   void merge ( list<T,Allocator>& x, Compare comp );


Parameters:
x – the source list, whose elements are to be merged into the list calling the function;
comp – the binary predicate used to compare the elements from the source and target lists in order to ensure the proper sequence of elements in the target list.


#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

//descending order
bool compare(int a, int b)
{
    if (a > b)
    {
        return true;
    }
    return false;
}

int main()
{
    int a[]={1,2,3,4,5};
    int b[]={6,7,8,9,10};
    list <int> l1(a,a+5);
    list <int> l2(b, b+5);
    //reversed order
    list <int> l3(l1.rbegin(), l1.rend());
    list <int> l4(l2.rbegin(), l2.rend());
    l2.merge(l1);
    print(l2.begin(), l2.end());
    cout<<"Size of source list l1: "<< l1.size()<<endl;
    l3.merge(l4,compare);
    print(l3.begin(), l3.end());
    cout<<"Size of source list l4: "<< l4.size()<<endl;
    return 0;
}

sort() – list only

This method performs the sorting of elements in a lexicographic order – from lowest to highest.
The first version uses the operator < to compare pairs of elements. This operator is available for built-in types. In order to use this function with other types, you must either create the operator for those types, or create a binary predicate which will perform basically the same role. In that case, the second variant of sort() must be used. This predicate must perform strict weak ordering. During the sort, there’s no deletion, creation, or copying of elements within the list. The objects are moved only.

Signature:
void sort ( );
template <class Compare>
   void sort ( Compare comp );


Parameters:
comp – the binary predicate used to compare pairs of elements in order to ensure a proper sort order. It takes arguments of the same type as the elements of the list.


#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

bool compare(int v1, int v2)
{
    if (v1 > v2)
    {
        return true;
    }
    return false;
}


int main()
{
    int a[]={1,2,1,3,2,3,4,7,8,9,6,5,8,9,10};
    
    list <int> l1(a,a+15);

    print(l1.begin(), l1.end());
    
    cout<<"Sorting - ascending"<<endl;
    l1.sort();
    print(l1.begin(), l1.end());

    cout<<"Sorting - descending"<<endl;
    l1.sort(compare);
    print(l1.begin(), l1.end());
    
    return 0;
}
.
Console output:
1 2 1 3 2 3 4 7 8 9 6 5 8 9 10
Sorting - ascending
1 1 2 2 3 3 4 5 6 7 8 8 9 9 10
Sorting - descending
10 9 9 8 8 7 6 5 4 3 3 2 2 1 1


reverse() – list only

This method reverses the order of the elements in the list container.

Signature:
void reverse ( );


Parameters:
None.

#include <list>
#include <iostream>

using namespace std;

template<class I>
void print (const I & start, const I & end)
{
    for(I it = start; it != end; ++it)
    {
        cout<< *it << " ";
    }
    cout<<endl;
}

int main()
{
    int a[]={1,2,1,3,2,3,4,7,8,9,6,5,8,9,10};
    
    list <int> l1(a,a+15);

    print(l1.begin(), l1.end());
    
    cout<<"Reversing order"<<endl;
    l1.reverse();
    print(l1.begin(), l1.end());
    
    return 0;
}

Console output:
1 2 1 3 2 3 4 7 8 9 6 5 8 9 10
Reversing order
10 9 8 5 6 9 8 7 4 3 2 3 1 2 1