assert makro
assert je preprocesorska naredba (makro) koja služi za ispitivanje da li je
neki uvjet zadovoljen ili nije. Koristi se kao funkcija koja uzima jedan argument
tipa bool ili int te ne čini ništa ako se argument izračunava na true ili
broj različit od nule, a zaustavlja izvršavanje programa ako se izračunava na false
ili nulu.
Na primjer,
#include <vector>
#include <cassert> // Nužno za assert makro!
using namespace std;
int main()
{
vector<int> a(3);
a.push_back(1.0);
assert(a.size() == 4);
return 0;
}
Ovdje se ispituje da li je a.size() jednako 4. Ako jeste program se nastavlja,
a ako nije bit će zaustavljen s porukom o grešci. Na primjer, ako u assert uvjetu
zamijenimo 4 sa 5 dobit ćemo sljedeću poruku o grešci:
main: /.../Assert/main.cpp:13: int main(): Assertion `a.size() == 5' failed.
Aborted
Vidimo da nam assert daje uvjet koji nije ispunjen
i liniju koda u kojoj se nalazi assert koji je zaustavio
program.
Assert makro je sredstvo za provjeru ispravnosti koda i koristi se u fazi razvoja koda.
Koristimo ga provjeru ispravnosti različitih invarijanti u kodu. Kada je kod završen
sve assert naredbe mogu se jednostavno eliminirati iz programa. To se postiže time što
preprocesor definira assert makro u ovisnosti o simbolu NDEBUG: ako simbol
NDEBUG nije definiran tada cassert makro radi na opisan način; ako je NDEBUG definiran
onda se assert naredba pretvara u trivijalnu naredbu koju prevodilac eliminira iz koda.
Da bismo eliminirali assert naredbe NDEBUG treba definirati prije točke uključivanja
<cassert> zaglavlja. Na primjer,
#include <vector>
#define NDEBUG 1
#include <cassert> // Nužno za assert makro!
using namespace std;
int main()
{
vector<int> a(3);
a.push_back(1.0);
assert(a.size() == 5);
return 0;
}
sada assert naredba ne zaustavlja program.
Ubacivanje #define NDEBUG linije nije najpraktičniji način za eliminaciju assert
naredbi pogotovo ako program ima puno datoteka koje koriste assert.
Bolja opcija je da se simbol NDEBUG definira na komandnoj liniji pri kompilaciji pomoću -D opcije:
g++ -Wall -O3 -DNDEBUG main.cpp
Opcija -DNDEBUG učinit će simbol NDEBUG definiran.
Ako koristimo CMake tada treba program kompilirati u Release načinu i Cmake će ubaciti
-DNDEBUG opciju prevodiocu za nas. Na primjer, u build direktoriju naredbe
cmake -DCMAKE_BUILD_TYPE=Release ..
make VERBOSE=1
će kompilirati program u Release načinu. Opcija VERBOSE=1 nam omogućava da vidimo koje su opcije poslane prevodiocu. Dobivamo:
[50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
/usr/bin/c++ -O3 -DNDEBUG -o CMakeFiles/main.dir/main.cpp.o -c /.../Assert/main.cpp
assert je nužno koristiti u što većoj mjeri za provjeru ispravnosti funkcioniranja programa.
Pri tome treba testirati uvjete koji vode na logičke greške unutar programa, a ne greške pri
izvršavanju koje dolaze od pogrešnog unosa podataka, pogrešnog korištenja programa, iscrpljivanja
resursa i slično. Te greške treba tretirati korištenjem izuzetaka.
Pomoćna klasa std::pair
Par (std::pair) je pomoćni tip deklariran je u zaglavlju <utility>.
Tip std::pair sadrži dva podatka (dvije varijable) općenito različitih tipova koji čine (uređeni) par.
#include <utility> // Za pair
#include <string>
std::pair<std::string, int> ime_i_broj("XCmp", 12345000);
std::cout << "Ime = " << ime_i_broj.first;
std::cout << ", broj = " << ime_i_broj.second << std::endl;
-
Konstruktor uzima dva argumenta kojima inicijalizira par.
-
Konstruktor bez argumenata vrši incijalizaciju nulama, odnosno pozivom konstruktora bez argumenata na elementima para.
-
Elementi se dohvaćaju kao
firstisecond(javne varijable u klasistd::pair<T1,T2>). -
Parove je moguće kopirati, pridruživati i uspoređivati ako su kompatiblinih tipova.
Radi uniformnosti pristupa elementi se mogu dohvatiti istom sintaksom kao i kod tuple spremnika:
auto x = std::get<0>(ime_i_broj); // isto što i x = ime_i_broj.first
auto y = std::get<1>(ime_i_broj); // isto što i y = ime_i_broj.second
Funcija std::make_pair kreira par iz zadane dvije vrijednosti i pri tome deducira tipove
elemenata para iz inicijalizacijskih vrijednosti.
auto par = std::make_pair("ggg", 1.2);
// par je tipa std::pair<const char*, double>
Tipovi podataka koje std::pair drži mogu biti i reference te konstantne reference. Na primjer,
int i = 0;
std::pair<int&,int&> par3{i,i};
par3.first++;
par3.second++;
assert(i == 2);
Funcija std::make_pair će prepoznati tip podataka kao referencu (konstantnu referencu) ako podatak
zamotamo u std::ref() (odnosno std::cref()) koji su definirani u zaglavlju <functional>. Na primjer,
#include <functional>
...
// deducira std::pair<int const &, int const &>
auto par4 = std::make_pair(std::cref(i), std::cref(i));
par4.first--; // greška, referenca je konstantna
Konstrukcija po dijelovima
Konačno, postoji i konstruktor koji elemente para ne kopira iz inicijalizacijskih vrijednosti
već ih konstruira pozivajući odgovarajućih konstruktora. Argumenti tog konstruktora para se interpretiraju
kao argumenti konstruktora prvog, odnosno drugog člana para. Argumenti ovog konstruktora imaju
drukčije značenja od argumenata ostalih konstruktora pa stoga on dobiva dodatni argument
std::piecewise_construct koji služi samo da ga razlikuje od ostalih konstruktora.
|
Konstrukcija para u kojem se prvi član para konstruira pozivom
njegovog konstruktora s argumentima u |
U varijabli t1 se nalaze argumenti konstruktora za tip T1, a u varijabli t2 se nalaze argumenti konstruktora za tip T2.
Tip tuple koji se ovdje koristi je generalizacija para i može čuvati proizvoljan broj vrijednosti različitih tipova.
Na primjer,
#include <tuple>
struct X{
X(int i, char c, float f) : mi(i), mc(c), mf(f) {}
int mi;
char mc;
float mf;
};
...
std::tuple<int,char,float> a(1,'a',2.0f), b(2,'b',3.0f); // argumenti konstruktora za tip X
std::pair<X,X> par5(std::piecewise_construct, a,b);
std::cout << par5.first.mc << std::endl; // 'a'
Pomoćna klasa std::tuple
Tip std::tuple je generaliracija tipa std::pair i predstavlja uređenu n-torku. On može držati proizvoljan ali fiksan broj
vrijednosti različitih tipova koji se zadaju kao parametri predloška. Na primjer,
#include <tuple>
std::tuple<double,int,std::string> t1; // inicijalizacijama "nulama"
Objekt t1 čuva tri vrijednosti redom tipa double, int i std::string; konstruktor bez argumenata vrši vrijednosnu inicijalizaciju
(inicijalizacija nulama).
U sljedećem primjeru std::tuple drži dvije vrijednosti inicijalizirane u konstruktoru:
#include <complex>
using namespace std::complex_literals;
std::tuple<double, std::complex<double>> t2(1.0, 1.0i);
Vrijednosti se mogu dohvatiti pomoću funkcije std::get koja uzima indeks pozicije komponente:
std::get<1>(t2) = 3.0+4i;
Funkcija std::make_tuple kreira tuple deducirajući tipove iz argumenata kojima se inicijaliziraju.
auto t3 = std::make_tuple(1,2.0,'v'); // kreira std::tuple<int, double, char>
Kao i kod tipa std::pair pojedini tipovi u std::tuple mogu biti reference. Funkcija
std::make_tuple će deducirati referencu na tip (konstantnu referencu na tip) ako argument
zamotamo u std::ref (u std::cref) iz zaglavlja <functional>.
std::optional<>
Parametrizirana klasa std::optional<T> (zaglavlje <optional>)
može sadržavati vrijednost tipa T ali i ne mora,
odnosno može biti prazna. Koristi se za signaliziranje greške kada tražena vrijednost nije dostupna.
Metode članice:
-
has_value()provjerava da li objekt sadrži vrijednost. Postoji i operator konverzije ubools istom ulogom; -
value()vraća sadržanu vrijednosti ili izbacujestd::bad_optional_accessizuzetak. Istu ulogu ima operator dereferenciranja; -
value_or(n)vraća vrijednost ili (ako je nema)n.
Dodijeljeni konstruktor konstruira prazan objekt.
// Funkcija vraća int ali može i ne uspjeti.
optional<int> read_int(){
int n;
if(cin >> n)
return n;
else{
cin.clear(); // resetiraj stream -- poredak je važan
cin.ignore(numeric_limits<int>::max(), '\n');
return {};
}
}
optional<int> add(optional<int> n, optional<int> m){
if(n and m)
return *n + *m;
else
return {};
}
int main()
{
auto n = read_int();
if(n.has_value())
cout << "Pročitao " << *n << endl;
else
cout << "Neuspješno čitanje.\n";
auto m = read_int();
if(m)
cout << "Pročitao " << m.value() << endl;
else
cout << "Neuspješno čitanje.\n";
if(auto z = add(n,m); z)
cout << "n+m = " << z.value() << endl;
else
cout << "Greška. " << z.value_or(123) << "\n"; // value_or(n) vrati vrijednost ili n.
return 0;
}
std::any
U zaglavlju <any> jedefiirana klasa any koja može sadržavati vrijednost bilo kojeg tipa.
Pored toga any objekt može biti prazan.
Metode članice:
-
has_value()provjerava da lianyobjekt sadrži vrijednost; -
type()vraćatypeidsadržane vrijednosti.
Globalna funkcija any_cast<T>(x) vraća kopiju u x sadržanog objekta kao objekt tipa T.
Ako sadržani objekt nije tipa T baca std::bad_any_cast izuzetak. Ako rebamo referencu
koristimo any_cast<T&>(x).
Primjer:
void print(any const & a){
if(!a.has_value())
cout << "Ništa.\n";
else if(a.type() == typeid(int)){
cout << "Tip je int, vrijednost = "
<< any_cast<int>(a) << endl;
}
else if(a.type() == typeid(string))
cout << "Tip je string, vrijednost = "
<< any_cast<string const&>(a) << endl;
else
cout << "Nepoznati tip.\n";
return;
}
std::variant<>
Unija u programskom jeziku C (i C++) je struktura koja u istoj memorijskoj lokaciji može držati više varijabli različitih tipova. Služi optimizaciji memorije. Odgovornost je programera da dohvati tip varijable koji je spremljen u uniju.
std::variant<> (zaglavlje <variant>)
je moderna verzija unije. Tipovi koje može pohraniti su zadani kao parametri
predloška i svi dijele istu memorijsku lokaciju. Objekt tipa std::variant<> uvijek sadrži samo jednu
vrijednost i ne može biti prazan.
Metode i globalne funkcije:
-
metoda
index()vraća indeks tipa kojeg variant sadrži. Na primjer, akovariant<A,B,C>::index()vrati nulu onda sadrži tipA, ako vrati 1 onda sadrži tipBte ako vrati 2, onda sadrži tipC. -
globalna metoda
get<n>(x)dohvaća element s indeksomnvarijantax(ili izbacuje izuzetak). -
globalna metoda
get_if<T>()uzima adresu variant objekta i vraća pokazivač tipaTna njegov sadržaj. Ukoliko sadržaj nije tipaTvraćanullptr. -
globalna metoda
holds_alternative<T>(x)vraćatrueakoxsadrži objekt tipaTifalseinače.
Primjer.
list< variant<int, double> > lV{ 1, 2.0, 3, 4.0};
for(auto const & x : lV){
switch(x.index()){
case 0: cout << " Tip = int : val = " << get<0>(x) << endl; break;
case 1: cout << " Tip = double: val = " << get<1>(x) << endl; break;
}
}
Isti primjer pomoću get_if<>:
list< variant<int, double> > lV{ 1, 2.0, 3, 4.0};
for(auto const & x : lV){
if(auto p=get_if<int>(&x); p)
cout << " Tip = int : val = " << *p << endl;
else if(auto q=get_if<double>(&x); q)
cout << " Tip = double: val = " << *q << endl;
}
Primjer sa holds_alternative<T>():
template <typename T>
bool is_type(variant<int,double> const &a){
return holds_alternative<T>(a);
}
// ..
// ne može direktno holds_alternative jer on deducira i tip varianta
cout << "Imamo "
<< count_if(lV.begin(), lV.end(), is_type<int>)
<< " objekata tipa int.\n";
Primjer pomoću globalne funkcije std::visit():
// Funkcijski objekt koji može proći kroz sve tipove u variantu
struct Visitor{
void operator()(int const & x)
{ cout << "Visitor int: val = " << x << endl; }
void operator()(double const & x)
{ cout << "Visitor double: val = " << x << endl; }
};
// ....
for(auto const & x : lV)
visit(Visitor{}, x); // primijeni visitora na variant
Generiranje slučajnih brojeva
Biblioteka slučajnih brojeva je dana u zaglavlju <random> i uključuje:
-
Generatore (pseudo) slučajnih brojeva s uniformnom distribucijom ("Random number engines");
-
Različite distribucije — uniformna, normalna, Poissonova itd. Svaka distribucija uzima generator slučajnih brojeva i generira brojeve sa zadanom statističkom distribucijom.
Detalji se mogu naći na en.cppreference.com/w/cpp/numeric/random.
I generatori slučajnih brojeva (SB) i sve distribucije djeluju kao
funkcijski objekti. Pozivom generatora SB (bez argumenata) generira se
sljedeći SB. Isto tako, pozivom distribucije (sa generatorom SB kao
argumentom) dobiva se novi SB po toj distribuciji. Na generatoru je
nužno pozvati metodu seed() s nekim promjenjivim brojem (na primjer
trenutnim vremenom) kako se ne bi pri svakom pozivanju programa
generirao isti slučajni niz.
Primjer
Primjer korištenja dodijeljenog generatora:
#include <iostream>
#include <vector>
#include <random> // Uključi biblioteku slučajnih brojeva
#include <ctime> // Za trenutno vrijeme time(0)
// ...
std::default_random_engine r_engine; // Generator slučajnih brojeva
r_engine.seed( std::time(nullptr) ); // seed = trenutno vrijeme,osigurava da
// ne generiramo uvijek isti niz brojeva
Iskoristimo normalnu distribuciju sa očekivanjem 0 i standardnom devijacijom 2:
// Normalna distribucija sa očekivanjem 0 i standardnom devijacijom 2.
std::normal_distribution<> nd(0.0, 2.0);
// Svaka distribucija funkcionira kao funkcija koja uzima generator SB
// kao argument i vraća sljedeću SB dane distribucije.
std::vector<double> rand(20);
for(auto & x : rand) x = nd(r_engine);
std::cout << "N(0,2) distribucija.\n";
for(auto & x : rand) std::cout << x << "\n";
Zadaci
Zadatak 1. Generirati vektor od 20 cijelih brojeva po binomnoj distribuciji (npr. t=26, p=0.4). Na generiranom vektoru napraviti ove operacije i ispisati rezultat.
-
Sortirati ga pomoću algoritma sort.
-
Sortirati prvih 5 elemenata niza pomoću partial_sort.
-
Particionirati niz ako da peti element bude na svom mjestu, ispred njega manji elementi, a iza njega veći elementi. Koristiti nth_element.
-
Particionirati elemente tako da svi elementi manji od 10 budu ispred elemenata većih ili jednakih 10. Prvi dio izdvojiti u novi vektor i ispisati ga. Koristiti algoritam partition i lambda izraz.
Zadatak 2. Učitajte iz datoteke stringovi.txt niz stringova u vektor
pomoću copy algoritma. Zatim instancirajte
set pokazivača na
stringove i ubacite u njega adrese učitanih stringova. Ukoliko bismo
koristili spremnik set<string *> uređaj bi se odnosio na adrese
stringova, a ne na same stringove. Stoga treba setu dati dodatni
template parametar koji predstavlja tip binarnog operatora uspoređivanja
koji će uspoređivati stringove umjesto pokazivača na stringove. Dakle,
treba koristiti set<string *, Cmp> gdje je Cmp klasa koja
implementira binarni operator uspoređivanja. Nakon toga ispišite sadržaj
set-a na izlazni stream koristeći algoritam
transform te
ostream_iterator
i lambda funkciju koja će dereferencirati pokazivač na string (to je
transformacija koja se radi na svakom elementu). Stringovi moraju biti
ispisani u leksikografskom poretku.
Zadatak 3. Za normalnu distribuciju N(mean,sigma) ispišite
histogram - izračunati relativne frekvencije nalaženja broja u sljedećim
intervalima : [-3,-2),[-2,-1),[-1,0),[0,1),[1,2),[2,3). Srednja
vrijednost mean i standardna devijacija sigma neka su argumenti
kondne linije. Povećajte broj generiranih SB, mijenjajte parametre
distribucije. Napravite ispis oblika:
[-3,-2) : xx
[-2,-1) : xxxxxxxxxxxxx
[-1,-0) : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[0,1) : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[1,2) : xxxxxxxxxxxx
[2,3) : xx
gdje svaki x predstavlja 1 % relativne frekvencije za dani interval.
Za račun relativnih frekvencija koristite algoritam std::count_if s predikatom zadanim lambda izrazom.