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
first
isecond
(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 ubool
s istom ulogom; -
value()
vraća sadržanu vrijednosti ili izbacujestd::bad_optional_access
izuzetak. 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 liany
objekt sadrži vrijednost; -
type()
vraćatypeid
sadrž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 tipB
te ako vrati 2, onda sadrži tipC
. -
globalna metoda
get<n>(x)
dohvaća element s indeksomn
varijantax
(ili izbacuje izuzetak). -
globalna metoda
get_if<T>()
uzima adresu variant objekta i vraća pokazivač tipaT
na njegov sadržaj. Ukoliko sadržaj nije tipaT
vraćanullptr
. -
globalna metoda
holds_alternative<T>(x)
vraćatrue
akox
sadrži objekt tipaT
ifalse
inač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.