I - Kontenery
vector
array
lista
deque
map
array
lista
deque
map
II - Iteratory
input
output
forward
bidirectional
random
std::advance
std::distance
back inserter (front)
output
forward
bidirectional
random
std::advance
std::distance
back inserter (front)
III - Algorithms
for_each (niemodyfikujace)
transform
count/ if
min/max
find
equal/mismatch
copy (modyfikjacy)
transform v2
fill_n
generate_n
replace
remove
unique
reverse
rotate
next_permutation
random_shuff;e
sort (sortujace)
accumulate
transform
count/ if
min/max
find
equal/mismatch
copy (modyfikjacy)
transform v2
fill_n
generate_n
replace
remove
unique
reverse
rotate
next_permutation
random_shuff;e
sort (sortujace)
accumulate
IV - I/O STREAM
stream
file i/o
stream
file i/o
V - Utility
std::pair
std::tuple
smart pointers
std::pair
std::tuple
smart pointers
I
Kontenery
dostęp sekwencyjny:
array - zwykła tablica z C, statyczna, dodane metodyvector - najczęsciej używany, zwany dynamiczną tablicą z metodami
lista - elementy powiązane(linked) latwo wstawić w srodek, trzeba iterowac iterator zeby znalezc
deque - wektor wektorów, spis vektorów, tylko podvektory są w ciągłej pamięci
szybkie przeszukiwanie:
map - zbiór par(x1-y1,x2-y2)-podajemy klucz, otrzymujemy daneset - drzewo (kolekcja wartości) wolno wklada-szybko zwraca (odwrotnie niż vektor)
multiset - kolekcja kluczy
multimap - kolekcja par klucz-wartosc
Adaptors
stack - adapts a container to provide stack (LIFO ) (stos książek)
queue - adapts a container to provide queue (FIFO) (kolejka)
priority_queue - heap
kontenery end
Iteratory
Kontenery używają iteratorów żeby uzyskać dostęp do elementów
std::vector<int>::iterator it; //obsluguje wszystkie vektory int;
it=mojvektor.begin();
albo
auto it=mojvektor.begin();
*it - zwraca wartość na jaką wskazuje iterator
int a=std::distance(moj.begin(), it ); - pokaże który z kolei jest dany iterator
Iteracja po kontenerze;
for (auto element : container) { iteruje kontener elementem }
powyższe jednak tworzy kopie, lepiej iterować po referencji
for (auto& element : container )
Rodzaje iteratorów:
InputIterator
OutputIterator
ForwardIterator
BidirectionalIterator
RandomAccesIterator
Ważne, jak z powodu zmiany rozmiaru vektor zostanie przeniesiony, iterator traci wskazanie.
dostęp sekwencyjny:
Vektor
Tworzenie:
std::vector<int> v1 (10, 15); //wielkosc 10 w kazdym wstawi 15 std::vector<int> v2 (10); //wielkosc - opcjonalne std::vector<int> v3 (v1.begin(), v1.end()-1);
std::vector<string> v4 ( 20, "przykladowy tekst" );std::vector<float> f1 { 1.0, 2.3, 3.3, 4.4}; //konkretne wartosci
Gdzie alokuje:
vector<Type> vect;
samą zmienna na stosie, ale elementy vektora na stercievector<Type> *vect = new vector<Type>;
wszystko na stercie
Tworzenie w klasie:
template<typename T,int N> class Ringbuffer { std::vector<T> myvec; //vektor deklaracja typename std::vector<T>::iterator it; //iterator deklaracja public: Ringbuffer() //konstruktor { myvec.assign(N,0); //vektor inicjalizacja it=myvec.begin(); // iterator inicjalizacja } template <typename T1,int N1> //konstruktor kopiujacy musi miec Ringbuffer (const Ringbuffer<T1,N1>& another){} //wlasny template };
w obszarze zmiennych deklaracja, w konstruktorze inicjalizacja
przy okazji jeśli klasa templatowa to konstruktor kopiujący musi mieć wlasny template jak powyzej.
typ przypisany do vektora musi być: kopiowalny, przenoszalny, automatycznie konstruowalny, usuwalny
Inicjalizacja vectora w C++ (5 different ways)
vector<int> vect;
vect.push_back(10);
vect.push_back(20);
-Specifying size and initializing all values :
vector<int> vect(n, 10);
-Initializing like arrays :
vector<int> vect{ 10, 20, 30 };
-Initializing from array :
int arr[] = { 10, 20, 30 };
int n = sizeof(arr) / sizeof(arr[0]);
vector<int> vect(arr, arr + n);
-Initializing from another vector :
vector<int> vect1{ 10, 20, 30 };
vector<int> vect2(vect1.begin(), vect1.end());
Dodawanie elementów:
można dodawać na początek i na konieć vektora (co go poszerza i zmienia iteratory)
mojv.push_back(10); lub push_front
ale moża zastosować insert lub emplace , w miejsce iteratora wstawi wartość, ale nie zastąpi tylko przesunie resztę, jeśli it był początkiem, to wstawi na początek a dotychczasowy pierwszy będzie drugi (jeden i drugi)
mojv.insert(it,10);
mojv.emplace(it,10);
jeśli vektor był pusty nie zadziała, jeśli był stworzony tylko jako
vector<int> mojv ;
to musimy albo resize (i wypełni zerami lub przypadkowymi wartosciami
mojv.resize(10); tym można też zmniejszyć
albo
myvec.assign(10,0); (rozmiar, czym ma wypełnić ) też może zmniejszyć
CO WAŻNE OPERACJE MOGĄ UNIEWAŻNIĆ ITERATORY
Czym się różnią?
Emplace takes the arguments necessary to construct an object in place, whereas insert takes (a reference to) an object.
insert copies objects into the vector.emplace construct them inside of the vector.
Można również zrobić dereferencję iteroatora i zamienić wpisaną tam wartość
it=mojv.begin();*(it+1)=100;
GENERALNIE JEŚLI DO ITERATORA DODAMY WARTOŚĆ WIĘKSZA NIŻ ROZMIAR ITERATORA I SPROBUJEMY ZAPISAC *(it) TO NADPISZEMY PAMIĘĆ
DLATEGO LEPIEJ UZYWAĆ DOSTĘPU PRZEZ AT
mojvector.at(liczba);
#include<iostream> #include<vector> using namespace std; vector<int> mojv {1,2,3,4,5,6,7,8}; int main () { vector<int>::iterator it; it=mojv.begin(); cout<<endl<<"vektor po stworzeniu 1-8"<<endl; for (auto element : mojv) cout<<element<<endl; cout<<endl<<"proba std::advance iteratora poza zakres"<<endl; std::advance(it,20); if(it>mojv.end()) cout<<"za duzy"<<endl; else cout<<"poprawny"<<endl; cout<<"wyświetlam"<<endl; cout<<*(it)<<endl; int aa= std::distance(mojv.begin(),it); cout<<"dystans= "<<aa<<" rozmiar vectora byl= "<<mojv.size()<<endl; //insert copies objects into the vector. //emplace construct them inside of the vector. it=mojv.begin(); //std::advance(it,7); it=mojv.emplace(it+2,98); //emplace niszczy wskazanie iteratora ALE zwraca iterator na nowo wstawiony cout<<endl<<"iterator+2 po emplace 98= "<<*(it)<<" ,a vector:"<<endl; for (auto element : mojv) cout<<element<<endl; int bb= std::distance(mojv.begin(),it); cout<<"dystans= "<<bb<<" rozmiar vectora byl= "<<mojv.size()<<endl; it=mojv.begin(); mojv.insert(it+2,99); //insert nie zmienia wskazanie iteratora cout<<endl<<"iterator+2 po insert 99= "<<*(it)<<" ,a vector:"<<endl; for (auto element : mojv) cout<<element<<endl; //it=mojv.begin(); //*(it+1)=100; mojv.at(9)=100; cout<<endl<<"po vector.at(9)=100"<<endl; for (auto element : mojv) cout<<element<<endl; //TYLKO vector.at(liczba) nie pozwala na dostęp POZA ZAKRESEM return 0; }
Przykład 3 konstruktorów i ich wywoływania:
#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)
Metody
Array
Tworzenie
array<typ,wielkosc> nazwa ;
Metody:
Iterators
- begin
- Return iterator to beginning (public member function )
- end
- Return iterator to end (public member function )
- rbegin
- Return reverse iterator to reverse beginning (public member function )
- rend
- Return reverse iterator to reverse end (public member function )
- cbegin
- Return const_iterator to beginning (public member function )
- cend
- Return const_iterator to end (public member function )
- crbegin
- Return const_reverse_iterator to reverse beginning (public member function )
- crend
- Return const_reverse_iterator to reverse end (public member function )
Capacity
- size
- Return size (public member function )
- max_size
- Return maximum size (public member function )
- empty
- Test whether array is empty (public member function )
Element access
- operator[]
- Access element (public member function )
- at
- Access element (public member function )
- front
- Access first element (public member function )
- back
- Access last element (public member function )
- data
- Get pointer to data (public member function )
Modifiers
- fill
- Fill array with value (public member function )
- swap
- Swap content (public member function ) c++11
Non-member functions
| lexicographically compares the values in the array (function template) | |
accesses an element of an array(function template) | |
(C++11)
| specializes the std::swap algorithm (function template) |
Lista
Tworzenie
list<typ> nazwa;
niemożliwy dostęp przez []
iterator trzeba iterować, żeby dostać się do kolejnych elementów.
te same metody co Vektor
przykład porownania prędkości vektor - lista i metody push back - find - lista
LINK git
Deque
Jeśli wsadzamy vektory jako elementy, to tylko one będą zajmowały ciągłą pamięć, całe deque będzie się składać z sumy ciągłych elementów
szybkie przeszukiwanie:
Map inaczej słownik
zbiór par, klucz wartość, wstawianie wolne ale szybkie szukanie (drzewo się samo balansuje)
tylko 1 klucz na mapę i musi być porównywalny, dane jak w vektorze
złożoność obliczeniowa szukania lepsza niżliniowa (log N)
Tworzenie
map<typklucza,typdanych> nazwa={{1,"slowo"} , {2,"slowo2"},...
trzecim elementem w < > jest komparator do porównywania większe mniejsze, można go zmienić jak potrzeba, ale jak inny do nie pasuje do innych map bez takiego.
przy wyświetlaniu rangeforem:
for(auto element : MyMap ) cout<< element.second;
.second - wyświetli tylko dane bez klucza
Klasy składowe
| porównuje obiekty typu value_type (klasa) |
Metody
| Konstruuje map (publiczna metoda) | |
| Niszczy map (publiczna metoda) | |
| przypisuje wartości do kontenera (publiczna metoda) | |
| zwraca skojarzony alokator (publiczna metoda) | |
Dostęp do elementów | |
(C++11)
| dostęp do wskazanego elementu, ze sprawdzeniem zakresów (publiczna metoda) |
| dostęp do wskazanego elementu (publiczna metoda) | |
Iteratory | |
| zwraca iterator na początek kontenera (publiczna metoda) | |
| zwraca iterator za koniec kontenera (publiczna metoda) | |
| zwraca odwrócony iterator na początek (publiczna metoda) | |
| zwraca odwrócony iterator za koniec kontenera (publiczna metoda) | |
Pojemność | |
| sprawdza, czy kontener jest pusty (publiczna metoda) | |
| zwraca liczbę elementów (publiczna metoda) | |
| zwraca maksymalną możliwą liczbę elementów (publiczna metoda) | |
Modyfikatory | |
| czyści zawartość (publiczna metoda) | |
| wstawia elementy (publiczna metoda) | |
(C++11)
| konstruuje element "w miejscu" (publiczna metoda) |
(C++11)
| tworzy elementy "w miejscu", korzystając z podpowiedzi (publiczna metoda) |
| usuwa elementy (publiczna metoda) | |
| zamienia zawartość (publiczna metoda) | |
Przeszukiwanie | |
| zwraca liczbę elementów o podanym kluczu (publiczna metoda) | |
| znajduje element ze wskazanym kluczem (publiczna metoda) | |
| zwraca przedział elementów o podanym kluczu (publiczna metoda) | |
| zwraca iterator na pierwszy element nie mniejszy niż dany klucz (publiczna metoda) | |
| zwraca iterator na pierwszy element większy niż dany klucz (publiczna metoda) | |
Obserwatory | |
| zwraca funkcję porównującą klucze (publiczna metoda) | |
| zwraca funkcję porównującą klucze obiektów typu value_type (publiczna metoda) | |
Przykład:
Stwórz słownik który zapisuje słowa a jako klucz daje hashe do nich. Wyszukiwanie następuje po odtworzeniu hasha i szukaniu po nim (bo słowa są tylko danymi a szukamy kluczem:
#include<iostream> #include<map> using namespace std; struct Dictionary { std::map<int,string> MyMap; void insert(const std::string& word) { int temphash=Hash(word); MyMap.emplace(temphash,word); } bool find(const std::string& word) const { int temphash=Hash(word); if( MyMap.find(temphash)!=MyMap.end() ) return true; else return false; } bool empty() const; int Hash(const std::string& key) const { int _value = std::hash<std::string>{}(key); return _value; } }; int main () { Dictionary mojslownik; mojslownik.insert("dupa"); for(auto element : mojslownik.MyMap ) cout<< element.second; cout<<endl; cout<<mojslownik.find("dupa")<<endl; return 0; }
Set
Kolekcja kluczy, sortowana po kluczach, wolno dodaje, szybko szuka
#include<iostream> #include <map> #include <set> #include <iterator> #include <algorithm> #include <string> using namespace std; int main() { set<int> s1; //set<int>::iterator it=s1.begin(); //niepoprawne umiejscowienie iteratora s1.insert(2); //set<int>::iterator it=s1.begin(); //jeśli tu, to insert(1) sprawi, że iterator s1.insert(5); //dalej bedzie pokazywal na 2, ale widzi dalsze pozycje!!! s1.insert(3); s1.insert(6); s1.insert(4); s1.insert(8); s1.insert(7); s1.insert(1); set<int>::iterator it=s1.begin(); //najlepiej na koncu cout << "Elements in set:\n"; for (auto el : s1) cout << el << " "; // Sorted it++; //sposob zwiekszania iteratora (nie dziala it+5) ale dziala advance it++; it++; it++; it++; it++; cout<<endl; cout<<"iterator:"<<*(it)<<endl; std::advance(it,-5); cout<<"iterator po advance:"<<*(it)<<endl; return 0; }
wyswielta:
Elements in set:
1 2 3 4 5 6 7 8
iterator:7
iterator po advance:2
co ciekawe operacje robione po iteratorze, np sl.erase(2) , czyli usunięcie elementu '2', sprawi, ze iterator będzie aktualny i bedzie potrafil wyswietlic
1 3 4 5 6 7 8
iterator:8
iterator po advance:3
II
Iteratory
Input iterator: can read from the pointed-to element (DO ODCZYTU ELEMENTOW)
*
->
++iter/iter++
==, !=
TYPE(iter)
ma zdefiniowaną POST i PRE INKREMENTACJĘ
Output Iterator is an Iterator that can write to the pointed-to element (DO ZAPISU)
*
++iter/iter++
TYPE(iter)
std::ostream_iterator
std::ostreambuf_iterator
std::insert_iterator
std::back_insert_iterator
std::front_insert_iterator
Forward Iterator, like input&output but it can be used in multipass algorithms.
Bidirectional like Forward but: --iter/--iter
Random access iterator(only vector, String, Array)
[]
+=n, -=n
iter = iter + n, iter = iter -n
iter-iter, iter > iter, <, <=, >=
polecenia:
std::advance - przesuwa iterator listy np o 50, szybkoalbo np:
std:advance (std::end(kontener),-5) i mamy iterator 5 przed koncem
std::distance - distance between two iterators
int a=std::distance(moj.begin(), it ); - pokaże który z kolei jest dany iterator
Zad:
Znajduje element w vektorze i drukuje index pod jakim się znajduje:
#include<iostream> #include<vector> using namespace std; vector<int> mojv {1,2,3,4,5,6,7,8}; int main () { vector<int>::iterator it; it=mojv.begin(); int aa; for (unsigned int i=0;i<=mojv.size();i++) { if(*(it++)==6) aa= std::distance(mojv.begin(),it); } cout<<aa<<endl; return 0; }
(back|front)_insert iterators
front inserter inserts a value at front of the container (list, deque)
back inserter inserts a value at the end o the container (vector, deque, lists, strings)
przypisany do iteratora ustawia nas na końcu.
"sprytny" iterator który po przypisaniu sam rozszerza kontener.
przykład poniżej w algorytmie transform, gdzie przypisuje z jednego do drugiego kontenera i ten drugi (nowy jest pusty, zwykły iterator problem, back inserter sprawi, ze jak przypisze to rozszerzy,
wstęp do algorytmów:
Predicates
-Free function
-template function
-Function object
-lambda
do predykatów stosujemy praktycznie tylko lambdę
LAMBDA -funkcja zdefiniowana w miejscu jej wywołania, zaczyna się capture list czyli []
[&] - referencje do wszystkich objektow
[=] - kopie do wszystkich obiektow
[ ] - lapie kontekst (closure costam)
[] - niczego nie lapie
jeśli w Capture List [ ] mamy &s, przekazuje przez referencję zmienną s która jest poza linią w której definiujemy lambdę
ale równie dobrze mogło by być samo [ &] ale wtedy daje dostęp do wszystkich obiektów
lambdas!
VERY easy to implement
Performs almost always better!
Should be used by default!
przykłady lambdy w algorytmach, gdzie ostatni argument - predykat - zawsze załatwiany lambdą.
EXPLICIT Przy oznaczeniu konstruktora jako explicit, nie będzie przeprowadzana automatyczna konwersja przy pomocy tego konstruktora czyli Explicit nie pozwala na wywolanie takie:
A a = 5;
(dla jednego argumentu)
można jednak tak:
A a(5);
III
Algorithms
Typy algorithms:
nonmodifying
modifying
removing
mutating
sorting
numeric
niemodyfikujące, niektóre mogą modyfikować, ale nie zaleca się
FOR_EACH (niemodyfikowalny) iteruje i zmienia predykatem (zwraca unaryfunction)
for_each(InputIt first, InputIt last, UnaryFunction f)
inputIt first i last to iteratory początku i konca zakresu ktory ma for_eachować
UnaryFunction to predykat (funkcja która wykona dzialanie "dla każdego"
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> mojv{1,2,3,4,5}; auto it=mojv.begin(); void print( const std::vector<int>& v) { std::cout<<"["; std::for_each(v.begin(),v.end()-1, [](int vv) {std::cout<<vv; std::cout<<","; }); std::cout<<(*v.end()-1); std::cout<<"]"; } int main () { print(mojv); return 0; }
w przykladzie powyżej zakres end jest zmiejszony o 1 v.end()-1 ponieważ nie chcemy wyświetlić ostatniego elementu (za nim ma nie być przecinka).
co robi nasz predykat (tu LAMBDA)
przyjmuje z for_eacha inty vv i po kolei coutuje je z dodanym przecinkiem
następnie za for_eachem, wyswietlamy ostatnią wartość i zamknięcie nawiasu ]
Funkcja uper która zamienia na duże litery, co ważne funkcja zwraca nowy string (można zrobić wersję która nic nie zwraca ale za pomocą referencji zmienia oryginalny)
tutaj tworzymy wewnątrz funkcji nowy string ss, ale żeby przekazać ss do predykatu (tu lambdy) musimy w Capture List [ ] podać zmienna ss z referencją
w ciele Lambdy { } dodaje do ss kolejne elementy iterowane for_eachem przez string s (tu mojs)
include<iostream> #include<vector> #include<algorithm> #include<string> using namespace std; std::string mojs="moj napis"; string upper(const std::string &s) { std::string ss; std::for_each(begin(s),end(s), [&ss](char c) { ss+=::toupper(c); }); return ss; } int main () { std::cout<<mojs<<std::endl; std::cout<<upper(mojs)<<std::endl; return 0; }
wersja która modyfikuje bezposrednio dostarczany string:
#include<iostream> #include<vector> #include<algorithm> #include<string> using namespace std; std::string mojs="moj napis"; void upper(std::string &s) { std::for_each(begin(s),end(s), [&s](char c) { s+=::toupper(c); }); } int main () { std::cout<<mojs<<std::endl; upper(mojs); std::cout<<mojs<<std::endl; return 0; }
jak widać w Capture List [ ] mamy &s, ale równie dobrze mogło by być samo [ &]
Jeśli chcemy przekazać do lambdy wszystkie zmienne przez referencję ale jedną przez kopię to
[&,i]
TRANSFORM zmienia jeden w drugi (zwraca OutputIt)
transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op)
InpitIT first last - zakres po ktorym iteruje (iteratory np begin - end
można pisać v.begin() v.end() co zwraca iterator początku i konca, ale i begin(v) albo end(v) )
OutputIt to iterator nowego kontenera (zawsze innego) innego (albo tego samego) TYPU
przykład transformacja z jednego stringa do drugiego (czyli inne stringi ten sam typ)
przykład ze zmiana na duże litery, ale tym razem za pomocą transform
#include<iostream> #include<vector> #include<algorithm> #include<string> using namespace std; std::string mojs="moj napis"; string upper(const std::string &s) { std::string nowy; auto it=begin(nowy); std::transform(begin(s),end(s), std::back_inserter(nowy),[](char c) { return ::toupper(c); }); return nowy; } int main () { std::cout<<mojs<<std::endl; std::cout<<upper(mojs)<<std::endl; return 0; }
Co ważne w tym przykładzie predykat jak widać zwraca (ma return) w for_each nie miał returna
(albo w 1 przykłądzie tylko cout, albo w 2 przykłądzie zmieniał przez referencję to co dostał)
tu musi zwrócić żeby przekazać do OutputIT, można dodać informację o zwracanym typie, albo zostawić samo return ze zwracanym typem (wystarczy).
ponieważ zwraca do outputiteratora, a stworzony string (nowy) jest pusty stosujemy back_inserter który dodaje na koniec i powiększa (gdybyśmy chcieli stosować sam iterator, kontener musiał by mieć już określoną wielkość)
przykład:
string upper(const std::string &s) { std::string nowy(10,' '); auto it=begin(nowy); std::transform(begin(s),end(s), it,[](char c) { return toupper(c); }); return nowy; }
COUNT/ COUNT_IF mogą modyfikować, ale z zasady nie powinny. Pierwszy zlicza jak określona wartość jest spełniona, drugi ma predykat który daje warunki
count(InputIt first, InputIt last, const T& value)
count_if(InputIt first, InputIt last, UnaryPredicate p)
przykład z predykatem który zwraca TRUE jak parzyste, wynik to zliczona ilość true (parzystych)
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> mojva={1,2,3,4,5,6,7,8,9,10,11}; int odd(const std::vector<int> &v) { return count_if(begin(v),end(v), [](int ii){ return ii%2 == 0; } ); } int main () { cout<<odd(mojva)<<endl; return 0; }
MIN/MAX
wylicza najmniejsza lub największą wartość
może mieć wersję z predykatem (funkcją z warunkami) ale wtedy nie nazywa się ...._IF tylko
dodaje się tą funkcję
min_element(ForwardIt first, ForwardIt last, Compare comp)
przykład bez predykatu:
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<float> mojva={-1,-2,-3,4,3,2}; float minimum(std::vector<float> v) { auto it=std::min_element(begin(v),end(v) ); return *it; } int main () { cout<<minimum(mojva)<<endl; return 0; }
wersja z predykatem , elementy w nawiasie ( ) pierwszy ostatni najmniejszy, tu st, drugi aktualnie porównywany, tu ch.
Trzeba dać return żeby wiedział na jaki warunek ma znajdować minimum
tu wstawione szukanie wartości bezwzględnej czyli najmniejsze w abs jest -1 a nie -3.
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<float> mojva={-1,-2,-3,4,3,2}; float minimum(std::vector<float> v) { auto it=std::min_element(begin(v),end(v),[](float st,float ch) { return abs(st)<abs(ch); } ); return *it; } int main () { cout<<minimum(mojva)<<endl; return 0; }.
FIND
szuka, zwraca podany iterator InputIT last jeśli NIE znalazł
(i zwraca jak znajdzie, iterator na następny za znalezionym!!!!)
find(InputIt first, InputIt last, const T& value)
Implement a function that
works on std::vector<int>
finds all occurrences of N
return a vector of indexes of N's
Rozwiązanie:
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> mojva={1,2,3,4,3,2,10,3,55,3,128}; std::vector<int> znajdz(std::vector<int> &v,int ii) { unsigned int licz=0; std::vector<int> tempv; while(licz<(v.end()-v.begin())) { auto it=std::find_if(v.begin()+licz,end(v),[ii](int i ){ return i==ii; } ); licz=it-v.begin(); if(licz<(v.end()-v.begin())) // check if not las tempv.push_back(licz); licz+=1; } return tempv; } int main () { for(auto element : znajdz(mojva,3)) cout<<element<<endl; return 0; }.
equal/mismatch
mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2)
equal - zwraca bool
mismatch - zwraca strukturę z 2 wartościami std::pair (ma 2 pola, dostęp do pierwszego p.first do drugiego p.second
przykład equal:
#include<iostream> #include<vector> #include<list> #include<algorithm> using namespace std; vector<int> mojva={1,2,3,4,5,6,7,8,9,10,11}; list<int> mojlist={1,2,3,4,5,6,7,8,9,10,11}; bool rowne(std::vector<int> &v,std::list<int> &l ) { return std::equal(v.begin(),end(v),l.begin() ); } int main () { cout<<rowne(mojva,mojlist); return 0; }
mismatch - zwraca strukturę z 2 wartościami std::pair (ma 2 pola, dostęp do pierwszego p.first do drugiego p.second
pierwsze pole zawiera iterator pierwszego kontenera do którego jest taki sam jak drugi
drugie pole zwraca iterator drugiego kontenera do którego jest takie same.
Poniżej przykład na jednym kontenerze, ale sprawdzający czy wyraz jest palindromem, dlatego trzeci argument to nie iterator na drugi kontener ale iterator na pierwszy tylko cofający się (z literką r)
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> mojva={1,2,3,4,4,3,2,1}; bool pali(std::vector<int> &v ) { const auto p= std::mismatch(v.begin(),end(v),v.rbegin() ); return p.first == v.end() && p.second == v.rbegin(); } int main () { cout<<pali(mojva)<<endl;; return 0; }
modyfikujące
COPY/COPY_BACKWARP/COPY_IP
copy(InputIt first, InputIt last, OutputIt d_first)
zwraca iterator na ostatni+1 w docelowym
przykład, który kopiuje tylko parzyste:
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> mojva={1,2,3,4,4,3,2,1}; void copiuj(std::vector<int> &v ) { std::vector<int> temv; std::copy_if(v.begin(),end(v),std::back_inserter(temv), [](int i) {return i%2 == 0; } ); for (auto element : temv) cout<<element<<endl;; } int main () { copiuj(mojva); return 0; }
predykat działa tak, że zwraca bool, jeśli zwraca true (tu jest parzysta) wtedy kopiuje.
w copy muszą być przypisywalne.
TRANSFORM
transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op)
przykład transform, z intów na string. iterator docelowego to back_inserter, żeby go powiekszał po dodaniu.
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> mojva={1,2,3,4,4,3,2,1}; void transformuj(std::vector<int> &v ) { std::vector<string> taemv; std::transform(v.begin(),end(v),std::back_inserter(taemv), [](int i) {return std::to_string(i); } ); for (auto element : taemv) cout<<element<<endl;; } int main () { transformuj(mojva); return 0; }
predykat przyjmuje typ pierwszego konerera a zwraca typ drugiego
TRANSFORM ver 2
to wersja która zmienia 2 kontenery w 3 typ, przyjmuje 5 argumentów.
transform(InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOperation binary_op)
przykład:
#include<iostream> #include<vector> #include<algorithm> #include<cmath> //potega pow(x,y) using namespace std; vector<int> mojva={1,2,3,4,4,3,2,1}; vector<int> mojva2={1,2,3,4,5,6,7,8}; void transformuj(std::vector<int> &v,std::vector<int> &v2 ) { std::vector<float> tempv; std::transform(v.begin(),end(v),begin(v2),std::back_inserter(tempv), [](int i,int ii) { return pow(i,ii); } ); for (auto element : tempv) cout<<element<<endl;; } int main () { transformuj(mojva,mojva2); return 0; }
FILL_N
wypełnia wartością, (iterator, ile elementów, wartość)
fill_n (OutputIterator first, Size n, const T& val)
GENERATE_N
wypełnia kontener wartością którą może określić predkat.
generate_n( OutputIt first, Size count, Generator g )
REPLACE
zamienia wartości w kontenerze
replace(ForwardIt first, ForwardIt last, const T& old_value, const T& new_value)
REPLACE_IF
zamienia te które spełniają warunek predykatu
replace_if(ForwardIt first, ForwardIt last, UnaryPredicate p, const T& new_value)
jeśli predykat zwróci true zamieni na wartość ostatniego argumentu
Implement a function that
works on std::vector<std::string>
replaces all lowercase string with "bummer"
#include<iostream> #include<vector> #include<algorithm> #include<cmath> using namespace std; vector<string> mojva={"ala","Ala","ALa","ala","ALA"}; void replace1(std::vector<string> &v ) { std::replace_if(v.begin(),end(v), [](string ss) { for( auto element : ss) { if ((char)element<122 && (char)element >97) return true; } return false; },std::string ("bummer") ); } int main () { replace1(mojva); for (auto element : mojva) cout<<element<<endl;; return 0; }
w rangefor iteruje dostarczonego stringa ss i jesli trafi na choć jedną dużą wysyła true i zamienia ten string na "bummer"
jesli nie znajdzie ani jednej dużej wysyła false i nie zmienia.
REMOVE
nie usuwa, ale przestawia usunięte na koniec
dlatego trzeba zastosować dodatkowo erase (to nie algorytm ale funkcja składowa kontenerów)
remove(ForwardIt first, ForwardIt last, const T& value)
przykład z erase:
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<int> mojva={1,2,3,4,3,2,10,3,55,3,128}; void remove(std::vector<int> &v,int ii ) { auto it=std::remove(v.begin(),end(v),ii ); v.erase(it, v.end()); } int main () { remove(mojva,3); for(auto element : mojva) cout<<element<<endl; return 0; }
remove zwraca iterator na niejsce gdzie wstawił usunięte elementy.
Dlatego wykorzystujemy ten zwrocony iterator i za pomocą erase kasujemy od niego do końca
pozostaje sam kontener z usuniętymi elementami.
REMOVE_IF
przykład:
#include<iostream> #include<vector> #include<algorithm> #include<cctype> using namespace std; vector<string> mojva={"ala1","Ala","ALa","ala2","ALA"}; void remove(std::vector<string> &v ) { auto it=std::remove_if(v.begin(),end(v),[](std::string ss){ for( auto element : ss) { if (isdigit(element)) return true; } return false; } ); v.erase(it, v.end()); } int main () { remove(mojva); for(auto element : mojva) cout<<element<<endl; return 0; }
UNIQUE
usuwa zdublowane
unique(ForwardIt first, ForwardIt last)
działa tylko na posortowanym kontenerze.
mutujące zmieniają, ale tylko przez przestawianie, nie usuwanie
REVERSE
reverse(BidirIt first, BidirIt last)
odwraca w tym samym kontenerze, iteratory bidirectional, więc na liście jednokierunkowej nie zadziała. nic nie zwraca.
REVERSE_COPY
reverse_copy(BidirIt first, BidirIt last, OutputIt d_first)
odwraca i kopiuje do nowego
ROTATE
wykorzystywany w Circular buffer z zaliczenia
NEXT_PERMUTATION
generuje kolejne mozliwe permutacje
RANDOM_SHUFFLE
potasuje kontener
SHUFFLE
potasuje używając funkcji sortującej
SORT
posortuje
REVERSE_COPY
reverse_copy(BidirIt first, BidirIt last, OutputIt d_first)
odwraca i kopiuje do nowego
ROTATE
wykorzystywany w Circular buffer z zaliczenia
// simple rotation to the left std::rotate(v.begin(), v.begin() + 1, v.end()); std::cout << "simple rotate left : "; for (int n: v) std::cout << n << ' '; std::cout << '\n'; // simple rotation to the right std::rotate(v.rbegin(), v.rbegin() + 1, v.rend()); std::cout << "simple rotate right : "; for (int n: v) std::cout << n << ' '; std::cout << '\n';
NEXT_PERMUTATION
generuje kolejne mozliwe permutacje
RANDOM_SHUFFLE
potasuje kontener
SHUFFLE
potasuje używając funkcji sortującej
sortujące
SORT
posortuje
std::partial_sort - posortuje, ale te same pozostawi w takiej kolejnosci jak były (tyle, że koło siebie) jesli będzie sortowanie np po długości stringu, to zwykły sort ustawi koło siebie ale moze poprzestawiać oryginalną kolejność, partial_sort gwarantuje zachowanie.
Implement a function that
works on std::vector<std::string>
sort based on string length
#include<iostream> #include<vector> #include<algorithm> using namespace std; vector<string> mojva={"alaaaaaaaa","Alaaa","ALaaaaa","ala","ALAaa"}; void sortuj(std::vector<string> &v ) { std::sort(v.begin(),end(v), [](string ss,string ss2) { return ss.length()<ss2.length(); } ); } int main () { sortuj(mojva); for (auto element : mojva) cout<<element<<endl;; return 0; }
ACCUMULATE (wymaga include numeric)
suma wartości kontenera (np predykatem można sumować długości stringów w kontenerze)
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int sum = std::accumulate(v.begin(), v.end(), 0); int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<int>());
Przykład
Implement a program that
reads N values from command line
reads N floating point values
checks if values are given in ascending order
if ascending multiply
#include<iostream> #include<vector> #include<list> #include<algorithm> #include<numeric> using namespace std; void licz( ) { int licz; float licz2; vector<float> mojva; cout<<"podaj ilosc liczb do przemnozenia:"<<endl; cin>>licz; cout<<"podawaj kolejne liczby float"<<endl; std::cin.exceptions(std::ios::failbit | std::ios::badbit); for(int i=0;i<licz;i++) { cin>>licz2; mojva.push_back(licz2); } if (std::is_sorted( mojva.begin(),mojva.end() ) ) { cout<< std::accumulate (mojva.begin(),end(mojva),1.0f,std::multiplies<float>() ) << endl; } } int main () { licz(); return 0; }
IV
I/O STREAM
wystarczy raz zmienić wyświetlany typ i taki pozostaje:
zad 11.16
works on std::vector<std::uint8_t>
displays all values as 0x7f____ 0xff____
#include<iostream> #include<vector> #include<algorithm> #include<cstdio> using namespace std; void copiuj( ) { vector<uint8_t> mojv={10,20,30,40}; cout<<std::hex<<endl; for (auto element :mojv ) cout<<(int)element<<endl;; } int main () { copiuj(); return 0; }
będzie cały czas wyświetlał w hexadecymalnym.
chyba, że zapamiętamy poprzedni typ OLD i go po operacji przywrócimy:
#include<iostream> #include<vector> #include<algorithm> #include<cstdio> using namespace std; void copiuj( ) { vector<uint8_t> mojv={10,20,30,40}; std::ios::fmtflags old = std::cout.flags(); cout<<std::hex<<endl; for (auto element :mojv ) cout<<(int)element<<endl;; std::cout.setf(old); cout<<"powrot do OLD"<<endl; for (auto element :mojv ) cout<<(int)element<<endl;;
ENDL
nie nadużywać, oprócz końca lini ma flush, przez co flushuje strumien i pozbawia procesor możliwości pracy na cache (wydajność) (proces z Shedulera przenoszony jest do kolejki)
Dopóki poprawnie czyta:
while(std::cin) {}
metody strumienia:
goodbit - ios_base::good() - sprawdza czy dobry
failbit - ios_base::fail() - wzraca TRUE jeśli uszkodzony
badbit - ios_base::fail() - zwraca true jeśli źle wpisany
throws exceptions on failbit and badbit:
w Przykładzie z ACCUMULATE mamy linię:
std::cin.exceptions(std::ios::failbit | std::ios::badbit);
FILE I/O
std::ifstream czyta
std::ofstream zapisuje
std::fstream zapis i odczyt
#include<iostream> #include<vector> #include<fstream> using namespace std; int main (int argc,char**argv) { if(argc<2) { cout<<"arg < 2" <<endl; } else { std::string arg=argv[1]; cout<<arg<<endl; std::ifstream file { arg}; file.unsetf(std::ios_base::skipws); if(file.is_open()) { char c; while(file >> c) { cout<<c; } } else cout<<"failed to open"<<endl; } cout<<endl; return 0; }
std::ios_base::skipws
When the skipws format flag is set, as many whitespace characters as necessary are read and discarded from the stream until a non-whitespace character is found before.
Przekaże białe znaki, dzięki czemu znak końca linii zostanie uwzględniony.
Strumienie jak kontenery, dzięki czemu można stosować algorytmy na strumieniach.
Jak widać w powyższym przykładzie czytać plik strumieniem też można.
V
UTILITY
STD::PAIR - para, prosty typ, przydatny gdy chcemy zwrócić 2 argumenty z funkcji
przykład inicjalizacji:
#include <utility> #include <string> #include <complex> #include <tuple> #include <iostream> int main() { std::pair<int, float> p1; std::cout << "Value-initialized: " << p1.first << ", " << p1.second << '\n'; std::pair<int, double> p2(42, 0.123); std::cout << "Initialized with two values: " << p2.first << ", " << p2.second << '\n'; std::pair<char, int> p4(p2); std::cout << "Implicitly converted: " << p4.first << ", " << p4.second << '\n'; }
Przykład użycia jeśli mamy funkcję która przyjmuje string i doubla i mapę o takich samych arg.
void foo(const std::string& ss, const std::complex<double>& d); std::map<std::string, std::complex<double>> m;
możemy dzięki parze zastosować ją w rangeforze jako TYP
for( typ a : kontener) // zamiast typu może być auto
for(std::pair<std::string, std::complex<double>> p: m) { foo(p.first, p.second); }
Przykład zwracania przez funkcję 2 argumentów w parze:
std::pair<std::string, std::complex<double>> foo(const std::string& s, const std::complex<double>& d) { return std::pair<std::string, std::complex<double>> ( s, d); }
Przykład zastosowania make_pair który automatycznie rozpozna typ:
std::pair<std::string, std::complex<double>> foo(const std::string& s, const std::complex<double>& d) { return std::make_pair(s, d); }
std::tuple krotka
zmienna liczba parametrów szablonowych
Przykład zastosowania:
#include <tuple> #include <iostream> #include <string> #include <stdexcept> using student_t = std::tuple<double, char, std::string>; student_t get_student(int id) { if (id == 0) return std::make_tuple(3.8, 'A', "Lisa Simpson"); if (id == 1) return std::make_tuple(2.9, 'C', "Milhouse Van Houten"); if (id == 2) return std::make_tuple(1.7, 'D', "Ralph Wiggum"); throw std::invalid_argument("id"); } int main() { auto student0 = get_student(0); std::cout << "ID: 0, " << "GPA: " << std::get<0>(student0) << ", " << "grade: " << std::get<1>(student0) << ", " << "name: " << std::get<2>(student0) << '\n'; }
smart pointers
http://slides.com/uczelnia_bt_kw/stl-3-14#/3Klasa shared_ptr
język C++ udostępnia co prawda wskaźniki i referencje, ale same w sobie są one niewystarczające, ponieważ niejednokrotnie zachodzi potrzeba zagwarantowania usunięcia obiektu wskazywanego wraz z usunięciem ostatniego „odwołania", co ma zapewniać wykonanie operacji porządkujących stan programu (zwolnienie pamięci, zwolnienie zasobów skojarzonych z obiektem).
Potrzebna jest więc semantyka „sprzątania po obiekcie, kiedy ten nie jest już nigdzie używany". Semantykę tę, w wydaniu ze współdzieleniem własności obiektu, realizuje klasa shared_ptr. Daje ona możliwość używania wielu wskaźników shared_ptr odnoszących się do tego samego obiektu wskazywanego i biorących go w „posiadanie". Ostatni istniejący wskaźnik współdzielony, będący właścicielem obiektu wskazywanego, jest odpowiedzialny za zwolnienie obiektu i jego zasobów.
Domyślnie zwolnienie obiektu polega na wywołaniu na jego rzecz operatora delete (przy założeniu, że obiekt został utworzony wywołaniem new).
Możliwe jest jednak (a czasem konieczne) definiowanie własnych operacji porządkowania stanu obiektu w postaci własnych wytycznych zwalniania obiektu wskazywanego. Jeśli na przykład obiekt wskazywany jest tablicą przydzielaną przez new [], zwolnienie obiektu powinno odbywać się przez wywołanie del ete []. Inne wytyczne zwalniania będą dotyczyły obiektów przechowujących dodatkowe zasoby, takie jak pliki, uchwyty systemowe, blokady i tym podobne.
Podsumowując, zadaniem wskaźników typu shared_ptr jest automatyzacja usuwania zasobów skojarzonych z obiektem wskazywanym w momencie, kiedy obiekt ten nie jest już więcej używany (ale nie wcześniej).
Stosowanie wskaźników współdzielonych shared_ptr
Klasę shared_ptr stosuje się bardzo podobnie jak zwyczajny wskaźnik: wskaźniki współdzielone można przypisywać, kopiować i porównywać, a także stosować z operatorami wyłuskania * i ->, udostępniającymi obiekt wskazywany i jego składowe.
Weźmy następujący przykład:
#include <iostream> #include <string> #include <vector> #include <memory> using namespace std; int main() { // dwa współdzielone wskaźniki reprezentujące dwa imiona shared_ptr<string> pNico(new string ("nico") ); shared_ptr<string> pJutta(new string ("jutta") ); // popraw zapis imion zoielkę literę (*pNico )[0] = 'N'; pJutta -> replace (0,1,"J"); // umieść wskaźniki w kontenerze (wielokrotnie) vector<shared_ptr<string>> whoMadeCoffee; whoMadeCoffee.push_back(pJutta); whoMadeCoffee.push_back(pJutta); whoMadeCoffee.push_back(pNico); whoMadeCoffee.push_back(pJutta); whoMadeCoffee.push_back(pNico); // wypisz wszystkie elementy for (auto ptr : whoMadeCoffee) { cout << *ptr << " "; } cout << endl ; // jeszcze raz zmień pisowmię jednego z imion *pNico = "Nicolai"; // ponownie wypisz wszystkie elementy for (auto ptr : whoMadeCoffee) { cout << *ptr << " "; } cout << endl ; // wypisz wewnętrzne dane wskaźnika cout << "use_count: " << whoMadeCoffee[0].use_count () << endl; }
Po dołączeniu do programu pliku nagłówkowego <memory> zawierającego definicję klasy shared_ptr deklarujemy i iniqalizujemy dwa wskaźniki współdzielone typu shared_ptr, ujmujące wskaźniki do dwóch łańcuchów znakowych:
shared_ptr<string> pNico(new string ("nico") ) ;
shared_ptr<string> pJutta(new string("jutta") ) ;
Ponieważ konstruktor wskaźnika typu shared_ptr z pojedynczym argumentem wywołania jest deklarowany ze słowem explicit, nie można tu zastosować składni inicjalizacji przez przypisanie, ponieważ jest ona traktowana jako niejawna konwersja typu.
Można za to użyć nowej dla C++11 składni inicjalizacji listą inicjalizującą:
shared_ptr<string> pNico = new string ("nico") ; / /B Ł Ą D
shared_ptr<string> pNico{new string("nico") } ; / /O K
Można też skorzystać z funkcji pomocniczej make_shared ():
shared_ptr<string> pNico = make_shared<string>("nico");
shared_ptr<string> pJutta = make_shared<string>("jutta") ;
Ten sposób tworzenia i inicjalizacji wskaźnika shared_ptr jest szybszy i prostszy, ponieważ wykorzystuje jedną alokację w miejsce dwóch: jednej dla obiektu wskazywanego i jednej dla danych wewnętrznych wskaźnika współdzielonego (patrzpunkt 5.2.4).
Alternatywnie wskaźnik współdzielony można zadeklarować osobno i dopiero
później przypisać do niego nowy wskaźnik. Nie można jednak wykonać tego za pomocą
operatora przypisania, a tylko za pośrednictwem funkcji składowej reset () wskaźnika:
shared_ptr<string> pNico4;
pNico4 = new string ( "nico"); // BŁĄD: nie ma przypisania zwykłego wskaźnika
pNi co4. reset(new string ( "nico")); //O K
Następne dwa wiersze programu pokazują, że używanie wskaźników shared_ptr
nie różni się od stosowania zwyczajnych wskaźników:
(*pNico) [0] = ' N';
pJutta->replace(0, l ,"J") ;
komunikaty wypisywane przez sam program:
Jutta Jutta Nico Jutta Nico
Jutta Jutta Nicolai Jutta Nicolai
use count: 4
Klasa unique_ptr (od c++11)
jest odmianą inteligentnego wskaźnika wyłącznego, pomocną w unikaniu
wycieku zasobów w sytuacjach potencjalnego występowania wyjątków. Ogólnie
rzecz biorąc, klasa unique_ptr implementuje koncepcję wyłącznego posiadania
obiektu wskazywanego, co oznacza, że obiekt wskazywany jest w danym momencie
w posiadaniu dokładnie jednego wskaźnika wyłącznego. Kiedy wskaźnik wyłączny
jest usuwany albo staje się wskaźnikiem pustym bądź zaczyna wskazywać do innego
obiektu, dotychczasowy obiekt wskazywany również jest usuwany. Klasa zastąpiła
wprowadzoną w c++98 auto_ptr.
standardowo należy obsłużyć wyjątki, żeby w przypadku ich wystąpienia nie nastąpiło wyjście z funkcji bez usunięcia dynamicznie stworzonych obiektów.
void f ( ) { ClassA* ptr = new ClassA; // utwórz jawnie obiekt zasobu try { ... // wykonaj jakieś operacje } catch ( ... ) { // dla wszelkich wyjątków delete ptr; // - posprzątaj throw; // - przerzuć wyjętek dalej } delete ptr; // sprzątanie przy zwykłym wyjściu z funkcji }
Sytuacja byłaby jeszcze gorsza, gdyby funkqa miała zarządzać
w ten sposób nie jednym, ale kilkoma obiektami, albo posiadała więcej niż jedną
klauzulę catch. Takie radzenie sobie z zasobami należy uznać za niepożądane, bo
mimo wszystko niebezpieczne (łatwo o błąd, pominięcie wyjątku, przeoczenie
zasobu).
W tym kontekście bardzo pomocne okazują się inteligentne wskaźniki, potrafiące
zwolnić obiekty wskazywane, kiedy same są usuwane. Co więcej, jeśli takie
wskaźniki są obiektami lokalnymi funkcji, zostaną usunięte automatycznie przy
wyjściu z funkcji, niezależnie od tego, czy jest to wyjście normalne, czy wymuszone
wyjątkiem. Implementacją takiego inteligentnego wskaźnika jest klasa
unique_ptr.
Poprzedni przykład z użyciem wskaźnika wyłącznego wyglądałby tak:
// plik nagłówkowy klasy unique_ptr #include <memory> void f () { // utwórz i zainicjalizuj loskaźnik wyłączny do zasobu std::unique<ClassA> ptr(new ClassA); ... // wykonaj jakieś operacje }
nie potrzeba już jawnego wywołania delete ani klauzul przechwytywania
wyjątków.
Stosowanie wskaźników wyłącznych unique_ptr
Klasa unique_ptr udostępnia interfejs wzorowany na interfejsie zwyczajnych wskaźników,
to znaczy definiuje operator wyłuskania * zwracający obiekt wskazywany
oraz operator dostępu do składowej obiektu wskazywanego -> (jeśli obiekt wskazywany
jest klasą albo strukturą):
// utwórz i zainicjalizuj łańcuch znaków (przez wskaźnik wyłączny) std::unique_ptr<std::strig> up(new std::string ( "nico") ) ; (*up) [0] = 'N' ; // zastęp pierwszy znak łańcucha up->append ("lai"); // dołącz kilka znaków do łańcucha std::cout <<*up<< std::endl; // wypisz cały łańcuch
Wskaźniki wyłączne nie implementują jednak arytmetyki wskaźnikowej (nie udostępniają
więc np. operacji ++); należy to zaliczyć do zalet, ponieważ arytmetyka
wskaźników bywa uznawana za źródło problemów programistycznych.
Zauważ, że klasa unique_ptr<> nie pozwala zainicalizowac obiektu wskaźnika
wyłącznego za pomocą składni inicjalizacji przez przypisanie; wskaźnik wyłączny
trzeba zainiqalizować bezpośrednim wywołaniem konstruktora z adresem obiektu
wskazywanego:
std::unique_ptr<int> up = new int; //B Ł Ą D std::unique_ptr<int> up(new int); // OK
Wskaźnik wyłączny unique_ptr może być wskaźnikiem pustym13. Z takim wskaźnikiem
mamy do czynienia po wywołaniu konstruktora domyślnego uni que_ptr:
std::unique_ptr<std::string> up;
Do wskaźnika wyłącznego można przypisać wskaźnik pusty nul1ptr albo wyzerować
go wywołaniem funkcji składowej reset ():
up = nullptr;
up.reset();
Ponadto na rzecz wskaźnika wyłącznego można wywołać release (), co powoduje
zwrócenie adresu obiektu wskazywanego z równoczesnym oddaniem odpowiedzialności
za zwolnienie tego obiektu:
std::unique_ptr<std::string> up(new std::string("nico") ) ;
std::string* sp = up.release( ) ; / / up nie jest już właścicielem obiektu
Fakt posiadania obiektu wskazywanego można określić za pośrednictwem operatora
bool ():
if (up) { / / jeśli up nie jest wskaźnikiem pustym
std::cout << *up << std::endl ;
}
Transfer obiektu wskazywanego
Wskaźnik wyłączny unique_ptr implementuje semantykę wyłącznego posiadania
obiektu wskazywanego, ale to programista musi zapewnić, że nie istnieją inne
wskaźniki wyłączne inicjalizowane tym samym obiektem wskazywanym:
std::string *sp = new std::string ( "witaj "); std::unique_ptr<std::string> upl ( sp ); std::unique_ptr<std::string> up2( sp ); //BŁĄD : upl i up2 posiadają ten sam obiekt
Niestety, taki błąd jest błędem czasu wykonania, a nie kompilacji.
Prowadzi to do pytania o działanie konstruktora kopiującego i kopiującego
operatora przypisania klasy unique_ptr. Odpowiedź jest natomiast prosta: wskaźnik
wyłączny nie może być kopiowany ani przypisywany do innego wskaźnika
wyłącznego w znaczeniu klasycznej semantyki kopiowania. Dozwolony jest za to
transfer odpowiedzialności za obiekt wskazywany w ramach semantyki przenoszenia,
zdefiniowanej w C++11 . W takim przypadku konstruktor
przenoszący i przenoszący operator przypisania dokonują transferu obiektu
wskazywanego między wskaźnikami wyłącznymi.
// zainicjalizuj wskaźnik wyłączny nowo utworzonym obiektem std::unique_ptr<ClassA> upl(new ClassA); // skopiuj wskaźnik wyłączny do innego wskaźnika wyłącznego std::unique_ptr<ClassA> up2 (upl ); // BŁĄD: niedozwolone kopiowanie // przekaż obiekt wskazywany do innego wskaźnika wyłącznego std::unique_ptr<ClassA> up3(std::move(upl)); //O K
up3 jest posiadaczem obiektu utworzonego przez new, a upl staje się wskaźnikiem pustym.
Obiekt utworzony wyrażeniem new Cl assA zostanie usunięty dokładnie raz,
w momencie usuwania wskaźnika wyłącznego up3.
Podobnie zachowuje się operator przypisania:
// zainicjalizuj wskaźnik wyłączny nowo utworzonym obiektem std::unique_ptr<ClassA> upl(new Cl as sA) ; std::unique_ptr<ClassA> up2; // utwórz drugi wskaźnik wyłączny up2 = upl ; // BŁĄD: niedozwolone przypisanie kopiujące up2 = std::move (upl ); // przeniesienie wskaźnika wyłącznego z upl do up2
Przypisanie przenoszące powoduje transfer obiektu wskazywanego z upl do up2;
w efekcie up2 jest nowym właścicielem obiektu wskazywanego uprzednio przez upl,
który staje się wskaźnikiem pustym.
Gdyby wskaźnik up2 był przed transferem w posiadaniu innego obiektu, obiekt
ten zostałby zwolniony wywołaniem delete.
Przypisanie nowej wartości do wskaźnika unique_ptr wymaga, aby nowa
wartość była również typu uni que_ptr. Nie można użyć w przypisaniu zwyczajnego
wskaźnika:
std::unique_ptr<ClassA> ptr; // utwórz (pusty) wskaźnik wyłączny ptr=new ClassA; //BŁĄD ptr=std::unique_ptr<ClassA>(new ClassA); //OK, usuń obecnie wskazywany obiekt // i weź nowy
Do wskaźnika wyłącznego można za to przypisać wskaźnik pusty nuli ptr, co
ma efekt identyczny z wywołaniem funkcji składowej reset ():
Weak_ptr (125 stl book)
Wskaźnik pomocniczy dla shared_ptr, pozwalający na współdzielenie dostępu do obiektu wskazywanego, bez współwłasności i współodpowiedzialności za obiekt.
Można go traktować jak wskaźnik na shared_ptr, co pozwala bezpiecznie przekazywać go do dowolnej klasy bez mnożenia shared_ptr. (i ryzyka mnożenia licznika shared pointera)
Mając weak_ptr, jesteśmy niezależni od shared_pointera. Jeśli shared pointer jest pełny dostaniemy adres obiektu, jeśli został usunięty, dostaniemy null.
Jesli chcemy pracować na obiekcie na który wskazuje weak_ptr zawsze powinnismy za pomocą locka stworzyć shared_ptr i po zakończeniu pracy usunąć go ,chyba, że stworzony lokalnie, sam zostanie usunięty po } .
Funkcja use_count() zwraca liczbę faktycznych współwłaścicieli obiektu (shared_ptr)
Weak_ptr może być pusty, jeśli nie został zainicjalizowany pełnym shared_pointerem, (domyslnie konstruktor tworzy pusty) albo jeśli po zainicjalizowaniu doszło do usunięcia ostatniego shared_pointera.
zm.expired() na pustym zwróci true
zm.lock() zwraca nowy wskaźnik shared_ptr do wskazywanego obiektu podbijając licznik.
Stworzenie w ten sposób wskaźnika shared_ptr jest jedynym bezpiecznym sposobem korzystania z obiektu na który wskazuje weak_ptr.
zm.reset() resetuje wskaźnik (zostaje wyzerowany)
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
Metody smart pointerów
.get() - zwraca przechowywany wskaźnik
.reset() - resetuje wskaźnik – zwalnia prawo
wlasności, można od razu przypisac nowy
.release() -
zwraca zwykły wskaźnik a obecny smart przestaje być właścicielem –
zwalnia prawo własności
.use_count() – zwraca wartość
licznika obiektów shared
.unique() – na shared jeśli jedyny właściciel zwróci
true
.expired() - na pustym
weak_ptr zwróci true
.lock() - na
weak_ptr zwraca nowy wskaźnik shared_ptr do wskazywanego obiektu podbijając
licznik (nie operować na weak)
Tworzenie:
shared_ptr<string> pNico(new string ("nico") ) ; // =new string(„nico”); nie zadziała
shared_ptr<string> pNico =
make_shared<string>("nico");
pNico. reset(new string ( "nico2"));
tworzenie weak prosto z shared:
std::weak_ptr<int> weak1 = sptr;
(potem żeby działać na nim zwracamy lock shared, weak tylko do
przekazywania)
Transfer:
// skopiuj wskaźnik wyłączny up1 do innego wskaźnika wyłącznego
std::unique_ptr<ClassA> up2 (upl ); // BŁĄD: niedozwolone kopiowanie
// przekaż obiekt wskazywany do innego wskaźnika wyłącznego
std::unique_ptr<ClassA> up3(std::move(upl)); //O K
Podobnie zachowuje się operator przypisania, też trzeba użyć move.
Przykłady unikania wycieków.
132. (stl book)
smart pointers jako argumenty funkcji:
Link1
Link2
git z zadaniami:
https://github.com/JanosikOpryszek/kod/tree/master/ZUT/stl
slajdy:
http://slides.com/uczelnia_bt_kw/
taczala git:
https://github.com/Super-Teachers
wilk git:
https://github.com/kwilkx/advc
syntex:
https://tohtml.com/cpp/
https://tohtml.com
Bred3



