Programování v C++ cvičení Filip Zavoral
Povinnosti k získání zápočtu Docházka aktivní účast, znalost předchozí látky 3 nepřítomnosti OK, déledobější domluvit předem DÚ uprostřed semestru jedna větší domácí úloha Zápočtový program do 15.11. schválené zadání do 30.4. první pokus o odevzdání hotové verze do konce LS komplet hotovo vč. doc (jinak neuznáno) Zápočtový test během zimního zkouškového období v labu 3 termíny (3. termín během LS)
Můj první C++ program #include <iostream> int main() { std::cout << "Hello world" << std::endl; return 0; }
Můj první C++ program #include <iostream> int main() { std::cout << "Hello world" << std::endl; return 0; } ctrl-shift-B F5 ctrl-F5 F10 F11 F9 Debug / Window Watch, Auto, Locals Call Stack VS 2012 File / New / Project Installed / Templates / VC++ / Win32 Win32 Console Application Name, Location - OK !!! Application Settings Console Application vypnout! Precompiled header vypnout! SDL zapnout! Empty project Solution Explorer Solution / Project Source Files Add New/Existing Item Visual C++ / C++ File (.cpp) (Header Files)
Můj druhý program Násobilka funkce (třída, metoda) parametr #include <iostream> int main() { std::cout << "Hello world" << std::endl; return 0; } Násobilka funkce (třída, metoda) parametr void fnc( int x) { .... } int main() fnc( 7); return 0; Násobilka 7: 1 * 7 = 7 2 * 7 = 14 ... 10 * 7 = 70
Užitečné kousky kódu #include <iostream> #include <string> #include <vector> using namespace std; int delkaretezce( const string& s) { ... } void zpracuj( const vector<string>& a) { ... a[i] ... } int main( int argc, char ** argv) { vector<string> arg( argv, argv+argc); if ( arg.size() > 1 && arg[1] == "--help" ) { cout << "Usage: myprg [OPT]... [FILE]..." << endl; return 8; zpracuj( arg); return 0; deklarace knihovních funkcí rozbalení prostoru jmen std předávání parametrů odkazem konstantní reference ! přístup k prvkům vectoru Solution Explorer / Project Properties / Config Properties Debugging / Command Arguments vektor pro komfortnější zpracování ošetření parametrů příkazové řádky výkonná funkce / metoda v mainu nikdy nic užitečného
vypsat násobilku všech čísel z parametrů příkazové řádky Výpis parametrů #include <iostream> #include <string> #include <vector> using namespace std; void vypis( const vector<string>& a) { for( int i = 0; i < a.size(); ++i) { cout << "[" << a[ i] << "]"; } cout << endl; int main( int argc, char ** argv) vector<string> arg( argv, argv+argc); if ( arg.size() < 2) { cout << "Usage: myprg parameters" << endl; return 8; vypis( arg); return 0; vypsat násobilku všech čísel z parametrů příkazové řádky ... si naprogramujte sami
Čísla a řetězce isdigit je lepší nepovinná reference na návratový parametr první nezkonvertovaný znak if( c >= '0' && c <= '9') if( isdigit( c)) int n = c - '0'; #include <string> int stoi ( s, size_t& idxRet = nullptr, int base = 10); stol, stoul, stoll, stof, stod, ... string to_string ( val); OK - čísla jsou uspořádaná if( c >= 'a' && c <= 'z') konverze čísel a stringů písmena nejsou uspořádaná !! #include <sstream> int strtoint( const string& s) { stringstream ss(s); int n; ss >> n; return n; } #include <cctype> isalpha( c) isalnum(c) stream z řetězce num = num + c - 48; '0' ≉ 48
Třídy, objekty, metody class Pocitadlo { deklarace třídy - .hpp public: Pocitadlo( void); ~Pocitadlo(); int pocet_pismen( void); private: int pocet_; bool ve_slove_; }; deklarace třídy - .hpp konstruktor (defaultní), destruktor deklarace veřejných metod - rozhraní privátní data a metody - implementace #include <iostream> #include <cctype> #include "pocitadlo.hpp" int Pocitadlo::pocet_pismen( void) { char c; ... while( cin.get(c)) { if( ve_slove_) { if( isspace( c)) { } include deklarace definice metod - .cpp další typy, inicializace čtení po znacích typ znaku - asalpha, isdigit, isupper, ...
Tečka a čtyřtečka class Trida { int fce( int x); }; :: kvalifikátor nalevo vždy typ int Trida::fce( int x) { ... } { Trida objekt; objekt.fce( 1); } Trida objekt = new Trida(); . operátor přístupu k položce objektu nalevo vždy proměnná
Inline a ne-inline metody inline metoda rozvinutí místo volání s = ob.r s = ob.r class Trida { std::string getResult () { return r; } std::string slozitaFce( int x); int jinaFce( int x) { int y = -1; for( i = 0; i < 10; ++i) .... } }; trida.h #include "trida.h" { Trida ob; string s; s = ob.getResult(); s = ob.slozitaFce( 1); int z = jinaFce( 2); } #include "trida.h" string Trida::slozitaFce( int x) { int y; for( i = 0; i < 10; ++i) { .... } trida.cpp push 2 y = -1 i = 0 loop: if( i >= 10) goto ... ... ++i goto loop push 1 s = call ob.slozitaFce předání parametrů a volání push 1 s = call ob.slozitaFce add esp, 8
Počítání oveček Spočtěte počet znaků, řádek, slov, vět počet a součet čísel
Počítání oveček Spočtěte Upřesnění zadání Postup počet znaků, řádek, slov, vět počet a součet čísel Upřesnění zadání zdroj dat: cin, obecný istream co to je slovo, věta různá funkčnost vs. neopakovatelný vstup Postup funkční návrh objektový návrh, rozhraní ! implementace
Kousky kódu jakýkoliv vstupní stream (cin, soubor, řetězec, ...) include <iostream> fce( istream& s) { char c; for(;;) { c = s.get(); if( s.fail()) return; process( c); } include <cctype> if( isspace( c)) isalpha, isdigit if( c == '\n') .... (pokus o) načtení jednoho znaku (nemusí se povést) detekce jakékoliv chyby (např. EOF) platná načtená hodnota jaký je rozdíl? co je lepší? fce( cin); ifstream f; f.open( "file.txt"); if( ! f.good()) .... fce( f); zpracování std vstupu for( i = 0; i < 10; i++) { ... } zpracování souboru for( i = 0; i < 10; ++i) { ... }
Ovečky 1 int pocet_znaku; int pocet_slov; void spocitej( ...) { for(;;) { c = s.get(); .... if( isspace( c)) { pocet_slov = ... if( ...) ... } void spocitej( ...) { int pocet_znaku; int pocet_slov; for(;;) { c = s.get(); .... if( isspace( c)) { pocet_slov = ... if( ...) ... }
Ovečky 2 class Ovecky { public: void spocitej( ...); private: .... }; void Ovecky::spocitej( ...) { int pocet_znaku; int pocet_slov; for(;;) { c = s.get(); if( isspace( c)) { pocet_slov = ... if( ...) ... }
Ovečky 3 class Ovecky { public: void zpracuj_znak( char c); void spocitej( ...); int pocet_znaku() { return ..; } int pocet_slov() { return ..; } private: int pocet_znaku_; int pocet_slov_; }; void Ovecky::zpracuj_znak( char c) { if( isspace( c)) { pocet_slov_ = ... if( ...) ... } void Ovecky::spocitej( istream& s) for(;;) { c = s.get(); .... zpracuj_znak( c); int main() { Ovecky ov; ov.spocitej( cin); ... } int main() { Ovecky ov; for(;;) { c = s.get(); .... ov.zpracuj_znak( c); mach_etwas( c); } ...
Ovečky 4 ovecky.cpp #include "ovecky.h" guard void Ovecky::zpracuj_znak( ...) { ... } void Ovecky::spocitej( istream& s) guard ovecky.h #include <iostream> #ifndef OVECKY_H_ #define OVECKY_H_ class Ovecky { public: void zpracuj_znak( char c); void spocitej( std::istream& s); int pocet_znaku() { return ..; } int pocet_slov() { return ..; } private: int pocet_znaku_; int pocet_slov_; }; #endif main.cpp #include <iostream> #include "ovecky.h" int main() { Ovecky ov; ov.spocitej( cin); cout << ov.pocet(); }
Počítání oveček revisited Spočtěte počet znaků, řádek, slov, vět, počet a součet čísel poslední řádka nemusí být ukončená '\n' řádky jen ty, kde je (alespoň) slovo nebo číslo slovo je posloupnost alfanumerických znaků nezačínající číslicí číslo (posloupnost číslic) nesousedí bezprostředně s písmenem abc32 je slovo, !32. je číslo, 32abc není nic každá započítaná věta obsahuje alespoň 1 slovo '...' ani '31.12.2013' nejsou tři věty spočítat ze std vstupu nebo ze souborů uvedných na příkazové řádce objektově a modulárně hezky všichni poslat! kdo nestihne na cvičení, dodělat doma
Sekvenční kontejnery vector - pole prvků s přidáváním zprava celočíselně indexováno, vždy od 0 všechny prvky umístěny v paměti souvisle za sebou při přidání možná změna lokace, neplatnost iterátorů! odvozené: queue, stack deque [dek] - fronta s přidáváním a odebíráním z obou stran double-ended queue prvky nemusí být umístěny v paměti souvisle lze přidávat i doleva list - obousměrně vázaný seznam vždy zachovává umístění prvků nepodporuje přímou indexaci forward_list - jednosměrně vázaný seznam basic_string - posloupnost ukončená terminátorem string, wstring array - pole pevné velikosti [dekjú] ≈ dequeue odebrat z fronty C++11 C++11
Asociativní kontejnery setříděné setříděné podle operátoru < pro neprimitivní typy (třídy) nadefinovat operator< set<T> - množina multiset<T> - množina s opakováním map<K,T> - asociativní pole - parciální zobrazení K -> T multimap<K,T> - relace s rychlým vyhledáváním podle klíče K pair<A,B> - pomocná šablona - uspořádané dvojice položky first, second šablona funkce make_pair( f,s) nesetříděné unordered_set/multiset/map/multimap hash table - nesetříděné, vyhledávání pouze na == pro neprimitivní typy (třídy) nadefinovat hashovací funkci size_t hash<X>(const X &) C++11
polootevřený interval Struktura kontejnerů polootevřený interval
Iterátory Iterátor objekt reprezentující odkazy na prvky kontejneru operátory pro přístup k prvkům operátory pro procházení kontejneru kontejner<T>::iterator iterátor příslušného kontejneru iterátor je typovaný kontejner<T>::const_iterator konstantní iterátor - používejte! *it, it->x přístup k prvku/položce přes iterátor ++it posun na následující prvek +(int) -(int) posun iterátoru begin(), end() iterátor na začátek / za(!) konec kontejneru rbegin(), rend() reverzní iterátor vector<int> pole = { 10, 11, 20 }; vector<int>::const_iterator i; for( i = pole.begin(); i != pole.end(); ++i) cout << *i;
Základní metody kontejnerů jednotné rozhraní nezávislé na typu kontejneru ALE: ne všechny kontejnery podporují vše! push_back(T), push_front(T) přidání prvku na konec / začátek pop_front(), pop_back() odebrání ze začátku / konce nevrací hodnotu, jen odebírá! front(), back() prvek na začátku / konci operator[], at() přímý přístup k prvku bez kontroly, s kontrolou (výjimka) insert (T), (it, T) vložení prvku, před prvek insert (it, it b, it e) vložení intervalu insert(make_pair(K,T)) vložení do mapy - klíč, hodnota erase(it), erase(it,it) smazání prvku, intervalu find(T) vyhledání prvku size(), empty() velikost / neprázdost clear() smazání kontejneru upper_bound, lower_bound hledání v multisetu/mapě ... and many many others
neopisujte stále deklarace ! Základní příklady Základní práce s kontejnery Překladový slovník #include <vector> map, unordered_map, ... vector<int> pole = { 10, 11, 20 }; pole.push_back( 30); vector<int>::const_iterator i; for( i = pole.begin(); i != pole.end(); ++i) cout << "[" << *i << "]"; map<string,int> m; m.insert( make_pair( "jedna", 1)); map<string,int> mapa; map<string,int>::const_iterator it; fce( map<string,int>& mm); neopisujte stále deklarace ! typedef map<string,int> Mapka; Mapka::const_iterator it; fce( const Mapka& mm); +slovo cizi slovo cizi slovo ? slovo -> cizi cizi cizi Proč: neupíšu se změna druhu nebo typu rozlišení logicky různých typů čitelnost
Složitost operací push_front pop_front insert erase push_back pop_back přidání / odebrání na začátku přídání / odebrání na i-té pozici m prvků na konci nalezení i-tého prvku funkce push_front pop_front insert erase push_back pop_back begin()+i [i] list konst m, konst přesuny mezi sezn. (splice) neex deque min( i, n - i) m + vector n - i m + n - i asocia tivní ln (s klicem k) ln (s klicem k) ln + m nalezení podle hodnoty ln unsorted m
Vlastnosti
Vlastnosti
Q & A • Do you need to be able to insert a new element at an arbitrary position in the container? you need a sequence container: associative containers won't do • What category of iterators do you require? If they must be random access iterators... you're limited to vector, deque, and string • Is it important to avoid movement of existing container elements when insertions or erasures take place? you'll need to stay away from contiguous-memory containers
Q & A • Is lookup speed a critical consideration? you'll want to look at hashed containers (unordered_map), sorted vectors, and the standard associative containers — probably in that order • Do you need to minimize iterator, pointer, and reference invalidation? Use node-based containers, because insertions never invalidate any references (unless they point to an element you are erasing). Insertions or erasures on contiguous-memory containers may invalidate all references into the container.
Příkládky prolezeni pole 5 prvků dopředu pozpátku úkol: načíst z cin a vypsat odzadu po dvou, pak zase zepředu vector<int> v; ... vector<int>::const_iterator i; for( i = v.begin(); i != v.end(); ++i) cout << *i << " "; vector <string> pole; .... x = pole.size(); pole[x] = 0; co je zde špatně? vector<int>::reverse_iterator i; for( i = v.rbegin(); i != v.rend(); ++i) cout << *i << " "; 1 2 3 4 5 6 7 7 5 3 1 2 4 6
Odzadu a zase zepředu opatrně mnohem inteligentnější řešení pozor na korektnost mnohem inteligentnější řešení rovnou při čtení rozhazovat na strany do deque (nebo list) void vypis( vector<int> & v) { vector<int>::const_iterator i; i = v.end(); if( i == v.begin()) return; --i; for(;;) { cout << *i << ", "; if( i == v.begin() || i-1 == v.begin()) break; i -= 2; } ++i; // vytisteno [0] -> [1] else --i; // vytisteno [1] -> [0] cout << *i << "; "; if( i+1 == v.end() || i+2 == v.end()) i += 2; cout << endl;
Procházení kontejnerů vector<int> x; for( int i = 0; i < x.size(); ++i) .. x[i] .. jen pro vector/array int y[MAX]; for( int *i = y; i != y+MAX; ++i) .. *i .. historie: pointrová aritmetika vector<int>::const_iterator i; for( i = x.begin(); i != x.end(); ++i) .. *i .. jednotný průchod kontejnery type inference for( auto i = x.begin(); i != x.end(); ++i) .. *i .. C++11 for( auto i : x) .. i .. range-based for const auto& i : x chci jen prohlížet auto& i : x potřebuji měnit auto i : x opravdu potřebuji kopii explicittype i : x potřebuji přetypovat reference na prvek
Setřídit řetězce - vector, list pre, set #include <vector> #include <string> #include <iostream> #include <algorithm> using namespace std; int main() { string s; vector<string> v; for(;;) { cin >> s; if( cin.fail()) break; v.push_back(s); } sort( v.begin(),v.end()); vector<string>::const_iterator i; for( i = v.begin(); i != v.end(); ++i) cout << "[" << *i << "] "; cout << endl; list<string> v; list<string>::const_iterator i; for(;;) { cin >> s; if( cin.fail()) break; for( i = v.begin(); i != v.end() && *i <= s; ++i) ; v.insert( i, s); } string s; set<string> v; for(;;) { cin >> s; if( cin.fail()) break; v.insert(s); } jak to setřídit? vlastní třídění bool mysort( const string& s1, const string& s2) { return s1.size() < s2.size() ? true : (s1.size() > s1.size() ? false : s1 < s2) } set<string, mysort> v; ... v.insert(s);
Nejpoužívanější algoritmy #include <algorithm> it find( it first, it last, T&) int count( it first, it last, T&) for_each( it first, it last, fnc( T&)) transform( it first, it last, output_it out, fnc( T&)) copy( it first, it last, output_it out) sort( begin, end, sort_fnc(x&, y&)) find_if, count_if, remove_if( it first, it last, pred& p) funkce modifikuje argument vrací modifikovaný argument možnost jiného kontejneru predikát: bool fnc( const T&)
Algoritmy - použití #include <algorithm> vector<int> v = { 1, 3, 5, 7, 9 }; // vector<int>::const_iterator result; auto result = find( v.begin(), v.end(), 5); predikát bool greater10 ( int value ) { return value >10; } result = find_if( v.begin( ), v.end( ), &greater10 ); if ( result == v.end( ) ) cout << "Nothing" << endl; else cout << "Found: " << *result << endl; vždy otestovat! for_each( begin, end, fnc( T&)) // vynásobit všechny prvky 2 // přičíst ke všem prvkům +1, +2, +3, ... void mul2( int& x) { x *= 2; }
Algoritmy - použití #include <algorithm> vector<int> v = { 1, 3, 5, 7, 9 }; // vector<int>::const_iterator result; auto result = find( v.begin(), v.end(), 5); bool greater10 ( int value ) { return value >10; } result = find_if( v.begin( ), v.end( ), &greater10 ); if ( result == v.end( ) ) cout << "Nothing" << endl; else cout << "Found: " << *result << endl; jak parametricky? for_each( begin, end, fnc( T&)) // vynásobit všechny prvky 2 // přičíst ke všem prvkům +1, +2, +3, ... int fce( int& x) { static int qq = 0; return x += (qq +=1); } for_each( v.begin(), v.end(), fce); for_each( v.rbegin(), v.rend(), fce); jak zrestartovat? jak krok parametricky?
Funktory class ftor { public: ftor( int step) : step_(step), qq_(0) {} int operator() (int& x) { return x += (qq_ += step_); } private: int step_; int qq_; }; for_each( v.begin(), v.end(), ftor(2)); Přičíst ke všem prvkům +n, +2n, +3n, ... Funktor – třída s přetíženým operátorem () oddělení inicializace a běhového parametru it = find_if( bi, ei, fnc); class cmp { public: cmp( int cmp) : n_(cmp) {} bool operator() (int& x) { return x > n_; } private: int n_; }; auto fnd = find_if( v.begin(), v.end(), cmp(9)); cout << ( ( fnd == v.end()) ? -1 : *fnd) << endl; najít v kontejneru prvek větší než n najít prvek odlišný od předchozího alespoň o n inkrementovat čísla v zadaném rozsahu hodnot (první +1, druhé +2, ...) najít číslo za největší dírou
Návratová hodnota for_each součet všech čísel větších než parametr class scitacka { public: scitacka( int limit) : limit_(limit), vysledek_(0) {} int operator() (int& x) { if( x > limit_) vysledek += x; } int vysledek_; private: int limit_; }; scitacka s = for_each( v.begin(), v.end(), scitacka(10)); cout << s.vysledek_ << endl; jak získat výsledek? po skončení hodnota použitého funktoru pozor! nejde o identický objekt
Lambda výrazy najděte všechny prvky větší než 9 lambda výrazy od C++ 11 mnohem jednodušší zápis a syntaxe vnitřní stav funktor find_if( v.begin(), v.end(), bind2nd( greater<int>(), 9)); find_if( v.begin(), v.end(), greater_than( 9)); find_if( v.begin(), v.end(), [](int& x) { return x > 9; }); vlastní funktor lambda výraz
Nonmodyfying algorithms for_each() Performs an operation for each element count() Returns the number of elements count_if() Returns the number of elements that match a criterion min_element() Returns the element with the smallest value max_element() Returns the element with the largest value find() Searches for the first element with the passed value find_if() Searches for the first element that matches a criterion search_n() Searches for the first n consecutive elements search() Searches for the first occurrence of a subrange find_end() Searches for the last occurrence of a subrange find_first_of() Searches the first of several possible elements adjacent_find() Searches for two adjacent elements that are equal equal() Returns whether two ranges are equal mismatch() Returns the first elements of two sequences that differ lexicographical_compare() Returns whether a range is lexicogr. less
Modifying algorithms for_each() Performs an operation for each element copy() Copies a range starting with the first element copy_backward() Copies a range starting with the last element transform() Modifies and copies elements; combines two ranges merge() Merges two ranges swap_ranges() Swaps elements of two ranges fill() Replaces each element with a given value fill_n() Replaces n elements with a given value generate() Replaces each element with the result of an operation generate_n() Replaces n elements with the result of an operation replace() Replaces elements that have a special value replace_if() Replaces elements that match a criterion replace_copy() Replaces elements that have a special value while copying the whole range replace_copy_if() Replaces elements that match a criterion while copying the whole range
Removing algorithms remove() Removes elements with a given value remove_if() Removes elements that match a given criterion - does not remove anything! remove_copy() Copies elements that do not match a value remove_copy_if() Copies elements that do not match a given criterion unique() Removes adjacent duplicates unique_copy() Copies elements while removing adjacent duplicates
Mutating algorithms reverse() Reverses the order of the elements reverse_copy() Copies the elements while reversing rotate() Rotates the order of the elements rotate_copy() Copies the elements while rotating their order next_permutation() Permutes the order of the elements prev_permutation() Permutes the order of the elements random_shuffle() Brings the elements into a random order partition() Changes the order of the elements so that elements that match a criterion are at the front stable_partition() Same as partition(), but preserves the relative order of matching and nonmatching elements
Sorting algorithms sort() Sorts all elements stable_sort() Sorts while preserving order of equal elements partial_sort() Sorts until the first n elements are correct partial_sort_copy() Copies elements in sorted order nth_element() Sorts according to the nth position partition() Changes the order of the elements so that elements that match a criterion are at the front stable_partition() Same as partition(), but preserves the relative order of matching and nonmatching elements make_heap() Converts a range into a heap push_heap() Adds an element to a heap pop_heap() Removes an element from a heap sort_heap() Sorts the heap (it is no longer a heap after the call)
Algorithms for Sorted Ranges binary_search() Returns whether the range contains an element includes() Returns whether each element of a range is also an element of another range lower_bound() Finds the first element greater than or equal to a value upper_bound() Finds the first element greater than a given value equal_range() Returns the range of elements equal to a given value merge() Merges the elements of two ranges set_union() Processes the sorted union of two ranges set_intersection() Processes the sorted intersection of two ranges set_difference() Processes a sorted range that contains all elements of a range that are not part of another set_symmetric_difference() Processes a sorted range that contains all elements that are in exactly one of two ranges inplace_merge() Merges two consecutive sorted ranges
Numeric algorithms accumulate() Combines all element values (processes sum, product, ...) inner_product() Combines all elements of two ranges adjacent_difference() Combines each element with its predecessor; converts absolute values to relative values partial_sum() Combines each element with all of its predecessors; converts relative values to absolute values
pozor - v definici makra může být obsažena hodnota jiného makra Příklady na algoritmy vidle vstup: vektor čísel výstup: multiset čísel větších než X inkrementovaný o Y filmová databáze název filmu, režisér, rok, ... vypsat setříděné podle roku a názvu filmu podle režiséra a roku jednoduchý makroprocesor vstup: text #novemakro obsah makra # dalsi novemakro konec vystup: text dalsi obsah makra konec vstup: tt #m1 xx # #m2 yy m1 # pozor - v definici makra může být obsažena hodnota jiného makra
Polymorfní datové struktury Zadání: kontejner obsahující čísla libovolného typu int, double, string, complex, ... Technické upřesnění: třída Seznam operace append, print společný předek prvků AbstractNum konkrétní prvky IntNum, DoubleNum, ... stačí jednoduchá implementace polem pole objektů vs. pole odkazů S IN DN IN AN AN AN x d x
Polymorfní datové struktury - kostra tříd class AbstractNum { public: virtual void print()=0; virtual ~AbstractNum() {} }; class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; abstraktní předek umí existovat a vytisknout se virtuální destruktor! int main(int argc, char** argv){ Seznam s; s.append( new .... ); s.print(); return 0; } přidávání dynamicky vytvořených konkrétních typů
PDS - implementace metod // konstruktor Seznam::Seznam() { for(int i=0; i<MAX; ++i) pole[i]=0; n=0; } // destruktor Seznam::~Seznam() for(int i=0; i<n; ++i) delete pole[i]; // tisk seznamu void Seznam::print() pole[i]->print(); // pridani prvku do seznamu void Seznam::append(AbstractNum* p) if (n<MAX) pole[n++]=p; class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; každý prvek ví jak se vytisknout
PDS - konkrétní datové typy class IntNum : public AbstractNum { public: IntNum(int x) : x_(x) {} virtual ~IntNum() {} virtual void print() { cout << x_; } private: int x_; }; konkrétní datové typy implementují vlastní metody jednotného rozhraní konkrétní datové typy implementují vlastní metody jednotného rozhraní class IntDouble : public AbstractNum { public: IntDouble(double d) : d_(d) {} virtual ~IntDouble() {} virtual void print() { cout << d_; } private: double d_; }; Seznam s; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); kontejner obsahuje různé typy ... a všechny vytiskne
PDS - konstruktor const položek Požadavek: co když chci zakázat měnit hodnotu prvků class IntNum : public AbstractNum { public: IntNum(int x) { x_ = x; } private: const int x_; }; compiler error: x must be initialized in constructor base / member initializer list class IntNum : public AbstractNum { public: IntNum(int x) : x_(x) {} private: const int x_; }; seznam inicializátorů používejte všude, kde to lze
PDS - přiřazení Problém: přiřazení seznamů int main(int argc, char** argv){ Seznam s, s2; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); s2 = s; return 0; } Je to korektní kód?
PDS - přiřazení Problém: Spadne to! přiřazení seznamů Kde? Proč? v destruktoru ~Seznam Proč? Navíc: memory leaks int main(int argc, char** argv){ Seznam s, s2; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); s2 = s; return 0; } problém je v s2 = s; v Seznam není operator= kompilátor si ho vyrobí automaticky okopíruje datové položky a ukazatele !!! destruktor s2 dealokuje prvky destruktor s znovu odalokuje prvky bloky už ale neexistují ! Tady!
PDS - přiřazení Možné řešení: zakázání přiřazení class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: Seznam& operator=(const Seznam&); enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; operator= v sekci private znemožní přiřazení seznamů stačí pouze deklarace (bez těla) nikdo ho nemůže zavolat je to už teď konečně korektní ??
PDS - copy konstruktor Není! copy konstruktor! int main(int argc, char** argv){ Seznam s; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); Seznam s2 = s; return 0; } class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: Seznam& operator=(const Seznam&); Seznam(const Seznam&); enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; oprator= a copy konstruktor by se měly chovat stejně! je to už teď konečně korektní ??
PDS - přiřazení Pokud chceme dovolit přiřazení (kopírování), je nutné si ujasnit logiku má se změna projevit i v druhém seznamu? kopie hodnot nebo kopie datové struktury? typicky: chování jako kdyby se okopírovaly všechny prvky Každá třída s odkazy na dynamicky alokovaná data buď zakázat přiřazení operator= a copy konstruktor do sekce private nebo nadefinovat kopírování napsat vlastní duplikaci VŽDY napsat hlavičku operatoru = a copy konstruktoru! Seznam a, b; .... b = a; a[1]->x = 999; // b[1]->x ???
PDS - kopie prvků okopíruji všechny prvky je to správně ?? Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]; n = s.n; return *this } class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); Seznam& operator=(const Seznam&); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; okopíruji všechny prvky je to správně ??
PDS - úklid starého stavu Je to správně? Není !! nezruší se předchozí odkazy! memory leaks nejdříve zruším všechny prvky cílového kontejneru Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) // jako v destruktoru delete pole[i]; for(int i=0; i<s.n; ++i) // jako v copy konstr. pole[i] = s.pole[i]; n = s.n; return *this } je to už teď správně ??
PDS - generování nových prvků Je to už teď správně? Není !! okopírují se pouze ukazatele data zůstanou stejná prakticky totéž, jako kdybychom nechali automaticky vygenerovaný operator = musíme vygenerovat nové prvky dynamická alokace nového prvku Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) delete pole[i]; for(int i=0; i<s.n; ++i) pole[i] = new AbstractNum( *s.pole[i]); n = s.n; return *this } přímá konverze odvozené třídy na AbstractNum& je to už teď správně ??
PDS - zrušení abstraktnosti Je to už teď správně? Není !! AbstractNum je abstraktní třída nelze instanciovat (vytvořit objekt) neprojde kompilátorem class AbstractNum { public: virtual void print() {}; virtual ~AbstractNum() {} }; psychicky zdeptaný programátor: tak tu abstraktnost odstraníme! Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) delete pole[i]; for(int i=0; i<s.n; ++i) pole[i] = new AbstractNum( *s.pole[i]); n = s.n; return *this } je to už teď správně ??
PDS - vytvoření správných typů Je to už teď správně? Není !! vytvoří se pouze část objektu - společný předek mnohem horší chyba než předchozí případ projde kompilátorem, nespadne, ale dělá nesmysly! slicing Co s tím? když je skutečná hodnota IntNum - vytvořit IntNum když je skutečná hodnota DoubleNum - vytvořit doubleNum IN AN x AN class AbstractNum { public: enum T { T_INT, T_DOUBLE, ...}; virtual T get_t() const; virtual void print()=0; virtual ~AbstractNum() {} }; switch( s.pole[i]->get_t()) { case AbstraktNum::T_INT: pole[i] = new IntNum(*s.pole[i]); break; ... je to už teď správně ??
PDS - vytvoření správných typů Je to už teď správně? Nooo..... dělá to to, co má, ale ... je to ošklivé - těžko rozšiřitelné přidání nového typu vyžaduje zásah do impl. společného předka! navíc syntaktická chyba předka nelze automaticky konvertovat na potomka new IntNum(*s.pole[i]) - skutečný parametr typu AbstractNum& konverze new IntNum(* (IntNum*) s.pole[i]) new IntNum( (IntNum&) *s.pole[i]) Jak to udělat lépe? využít mechanismus pozdní vazby každý prvek bude umět naklonovat sám sebe rozhraní v AbstractNum, implementace v IntNum, DoubleNum, ... virtuální klonovací metoda
PDS - klonování jednotné rozhraní na klonovací funkce IN AN x IntNum class AbstractNum { public: virtual AbstractNum* clone() const =0; virtual void print()=0; virtual ~AbstractNum() {} }; starší norma: musí být typu AbstractNum* jinak by to nebyla stejná virt. metoda od 0x11: kovariantní návratový typ povolen class IntNum : public AbstractNum { public: virtual AbstractNum* clone() const { return new IntNum(*this); } IntNum(int x) : x_(x) {} private: const int x_; }; IN AN x IntNum Seznam& Seznam::operator=( const Seznam& s) { ... for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]->clone(); ... AbstractNum případné posunutí ukazatelů řeší automaticky mechanismus virt. metod je to už teď správně ??
PDS - přiřazení sebe sama Je to už teď správně? Pořád není !!!! co když někdo provede s = s ? takhle blbě to asi nikdo nenapíše, ale ... Seznam p[100]; p[i] = p[j]; nejprve se zruší všechny prvky ... a pak se kopírují dealokované bloky!!! ani vynulování ukazatelů moc nepomůže neokopírovalo by se nic nutná ochrana! this &s Seznam& Seznam::operator=( const Seznam& s) { if( this == &b) return *this; ... rovnost ukazatelů stejný objekt je to už teď správně ??
PDS - přiřazení sebe sama Seznam& Seznam::operator=( const Seznam& s) { if( this == &b) return *this; ... rovnost ukazatelů stejný objekt Je to už teď správně? .... no teď už snad ano ale co když jsou referencované objekty velké? časté kopírování neefektivní optimalizace zachování sémantiky vs. odlišná implementace reference counting, ...
Šablony scitacka.h class Scitacka hlavička šablony { public: Scitacka() : val_( 0) {} void add( int x); int result() { return val_; } private: int val_; }; void Scitacka::add( int x) { val += x } int main() Scitacka s; s.add( 1); s.add( 2); int x = s.result(); } hlavička šablony template<typename T> class Scitacka { public: Scitacka() : val_( 0) {} void add( T x); T result() { return val_; } private: T val_; }; template <typename T> void Scitacka<T>::add( T x) { val += x } hlavička i u definice těla #include "Scitacka.h" int main() { Scitacka<int> s; s.add( 1); s.add( 2); int x = s.result(); } použití instanciace šablona těla musí být při kompilaci viditelná
Šablony netypový parametr template<typename T> class Pole { public: Pole( int size) : size_(size) { array_ = new T[size]; } ~Pole() { delete[] array_; } T& operator[] (int n); private: int size_; T* array_; typename vector<T>::const_iterator vi_; }; int main() Pole<int> ip(5); ip[3] = 999; netypový parametr template<typename T, int n=100> class Pole { Pole( int size = n) .... specializace pro konkrétní typ když se sem nedá typename, tak překladač neví, že to je typ template< > class Pole <bool> { .... } Pole<bool> b; vektor s nehýbatelnými prvky x.push_back(n) x[i]
Hrábě T* T T T T T* T* T** index = new T*[k] vector<T*> T* t = new T[n] T*
potomci std::exception Výjimky / exceptions vyvolání výjimky try blok nejbližší vyhovující catch blok stack unwinding destrukce všech objektů dvojitá výjimka terminate try { if( error) throw exctype; } catch( exctype& e) { ... e. ... } catch( ...) { ... } #include <stdexcept> class exception { public: exception( ); virtual const char *what( ) const; }; bad_alloc, bad_cast, domain_error, invalid_argument, length_error, out_of_range, overflow_error, range_error, underflow_error } catch( exception& e) { cout << e.what() << endl; } potomci std::exception
vše zpracovat v konstruktoru Vlastní typ výjimky #include <stdexcept>, <cstdlib> class myexc : public std::exception { public: myexc( int ix) : ix_(ix) { strcpy( buf_, "Chyba na indexu: "); ltoa( ix, buf_+strlen( buf_), 10); } virtual const char *what( ) const { return buf_; } int getIndex() const { return ix_; } private: int ix_; char buf_[100]; }; vše zpracovat v konstruktoru kompatibilita vlastní diagnostika žádné alokace !!! anonymní instance žádné alokace ! myclass::myfnc() { whatever(); if( error_occured) throw myexc( 17); whatever_else(); } try { nejakymujkod } catch( myexc& me) { cout << "Chyba indexu: " << me.getIndex() << endl; } catch( exception& e) { cout << e.what() << endl; }
Výjimky při inicializaci a destrukci Výjimky v destruktoru nikdy! destruktory se volají při obsluze výjimek Výjimky v konstruktoru ne globální! není kde chytit Základní třída konstruktor může vyvolat výjimku Odvozená třída výjimku inicializace je vhodné zachytit objekt není vytvořen tělo konstruktoru odvozené třídy se neprovede class A { public: A( X& x) { ... throw ... } }; class B : public A { B( X& x) try : A(x) { ... } catch( ...) { } tělo try bloku je tělem konstruktoru
Výjimky Vyzkoušet Podrobnější diagnostika Gumové Pole nějak velké pole #include <vector>, <stdexcept>, <iostream>, <ctime>, <cstdlib> using namespace std; { const int max = 100; srand( (unsigned)time( NULL )); int n = rand() % max; vector<int> v; int i; for( i = 0; i < n; i++) v.push_back( i); try { for(;;) { i = rand() % max; // cout << v[i] << " "; !!! nehazi, odleti cout << v.at(i) << " "; } } catch( exception &e) { cout << endl << e.what() << endl; } catch(...) { cout << "Obscure exception!" << endl; Vyzkoušet nějak velké pole náhodně zkoušet při přetečení výjimka Podrobnější diagnostika vlastní výjimka špatný index a velikost pole Gumové Pole constr alloc push_back alloc at() out of range begin, end Guma::iterator * out of range ++, -- out of range vyvolá new
= počet ukazatelů v argv Parametry příkazové řádky C:\> myprog.exe -n -w a.txt b.txt pole řetězců (ukazatelů na char) int main( int argc, char** argv) argv 5 argc b . t x \0 a . t x \0 Počet parametrů včetně názvu programu ! = počet ukazatelů v argv - w \0 - n \0 m y p r o g . e x \0
usage: myprog [-n] [-w] fileA fileB Zpracování parametrů příkazové řádky usage: myprog [-n] [-w] fileA fileB int main( int argc, char** argv) { int n=0, w=0; while( *++argv && **argv=='-') { switch( argv[0][1]) { case 'n': n = 1; break; case 'w': w = 1; break; default: error(); } if( !argv[0] || !argv[1]) error(); doit( argv[0], argv[1], n, w); return 0; options nastavení přepínače zbývající parametry b . t x \0 a . t x \0 - w \0 výkonná funkce - n \0 argv p r g . e x \0
Streamy čtení ze souboru i std vstupu záměnnost std vstup i soubor jsou streamy lze přiřadit za běhu #include <iostream> #include <fstream> ifstream x; x.open( "file.txt"); if( ! x.good()) { "chyba" } for (;;) { x >> a; if( x.fail()) break; f( a); } x.close(); istream *in; in = & cin; if( ...) { in = new ifstream(...); } *in << "cokoliv"; stav streamu výsledek předchozí operace není třeba psát zvláštní kód pro čtení souboru lepší než test na eof
operátor << přetížení operátoru << není to metoda třídy ale friend globální funkce nemáme přístup do implementace ostream class Complex { public: Complex() : re_(0), im_(0) {} friend ostream& operator<< ( ostream& out, const Complex& x); private: double re_, im_; }; ostream& operator<< ( ostream& out, const Complex& x) { out << "[" << x.re_ << "," << x.im_ << "]" << endl; return out; } toto není metoda
Čtení vstupu - slova oddělená ws ws (mezery, ...) se automaticky přeskočí string s1, s2; int i1, i2; f >> s1 >> i1 >> s2 >> i2; if( f.fail()) ...; ... stream zůstává za posledním čtením
Čtení vstupu - celé řádky const int MaxBuf = 4095; char buffer[ MaxBuf+1]; for( ;;) { f.getline( buffer, MaxBuf); if( f.fail()) break; cout << "[" << buffer << "]" << endl; } vždy limit parsování řádku string s; for( ;;) { getline( f, s); if( f.fail()) break; cout << "[" << s << "]" << endl; } string r, s1, s2; for( ;;) { getline( f, r); if( f.fail()) break; stringstream radek(r); radek >> s1 >> s2; cout << "[" << s1 << s2 << "]" << endl; }
Čtení vstupu - oddělovače string s; string::iterator b, e; char delim = ';'; while( getline( f, s)) { b = e = s.begin(); while( e != s.end()) { e = find( b, s.end(), delim); string val( b, e); cout << "[" << val << "]"; b = e; if( e != s.end()) b++; } cout << endl; dokud přečtené slovo není na konci vrátí iterator na odělovač přečte hodnotu mezi oddělovači přeskočí oddělovač
přečte se nejbližší znak, Čtení vstupu - výhled přečte se nejbližší znak, ale nechá se ve streamu f >> ws; if( isdigit( f.peek())) { int i; f >> i; cout << "[" << i << "]" << endl; } else { string s; f >> s; cout << "{" << s << "}" << endl; }
Stream manipulátory endl vloží nový řádek setw(val) nastaví šířku výstupu setfill(c) nastaví výplňový znak dec, hex, oct čte a vypisuje v dané soustavě left, right zarovnávání fixed, scientific formát výpisu čísla precision(val) nastaví přesnost ws přeskočí bílé znaky (no)skipws nastavení/zrušení přeskakování bílých znaků při čtení (no)showpoint nastaví/zruší výpis desetinné čárky ...
Bezparametrický manipulátor speciální funkce předávané ukazatelem vrací referenci na modifikovaný stream jak to funguje přesněji: šablona cout << 1 << mriz << 2 << mriz << 3 << endl; ostream& mriz( ostream& io) { io << " ### "; return io; } ukazatel na funkci funkce zavolá ji op<< přetížená matoda na ukazatel na funkci ostream& operator<< (ostream& (* pf)(ostream&));
Parametrický manipulátor nelze předdefinovaná funkce libovolné možné parametry ošklivé řešení vlastní funkce s extra parametrem hezčí řešení zvláštní třída, zvláštní přetížení << cout << 1 << mriz(5) << 2 << mriz(3) << 3 << endl; cout << mriz(cout,5) << ...
Parametrický manipulátor vlastní třída anonymní instance parametr konstruktoru přetížení << na tuto třídu příkládek: rzlomek z( 3, 4); cout << z; 3 / IV class tecka { private: int n_; public: explicit tecka( int n) : n_( n) {} int get_n() const { return n_; } }; ostream& operator<<( ostream& io, const tecka & p) { int n = p.get_n(); while( n--) io << "."; return io; } cout << 1 << tecka(5) << 2 << tecka(3) << 3 << endl; známý trik: separace inicializace a volání zřetězení << jiná instance
Virtuální metody class A { public: virtual int f() { return 1; }; int g() { return 2; }; int h() { return f(); }; virtual int j() { return g(); }; }; class B : public A { public: virtual int f() { return 3; }; int g() { return 4; }; int h() { return g(); }; virtual int j() { return f(); }; A x; B y; A * p = &x; A * q = &y; B * r = &y; xy.fghj(); pqr -> fghj();
Konstruktory a destruktory int x = 1; class A { public: A() { x += 1; } virtual ~A() { x += 2; } }; class B : public A { public: B() { x *= 2; } virtual ~B() { x *= 3; } A *p = new B; cout << x; delete p;
101 mouder Sutter, Alexandrescu: C++ 101 programovacích technik (C++ Coding Standards) Organizational and Policy Issues 0. Don’t sweat the small stuff. (Or: Know what not to standardize.) 1. Compile cleanly at high warning levels. 2. Use an automated build system. 3. Use a version control system. 4. Invest in code reviews. Design Style 5. Give one entity one cohesive responsibility. 6. Correctness, simplicity, and clarity come first. 7. Know when and how to code for scalability. 8. Don’t optimize prematurely. 9. Don’t pessimize prematurely. 10. Minimize global and shared data. 11. Hide information. 12. Know when and how to code for concurrency. 13. Ensure resources are owned by objects. Use explicit RAII and smart pointers. Coding Style 14. Prefer compile- and link-time errors to run-time errors. 15. Use const proactively. 16. Avoid macros. 17. Avoid magic numbers. 18. Declare variables as locally as possible. 19. Always initialize variables. 20. Avoid long functions. Avoid deep nesting. 21. Avoid initialization dependencies across compilation units. 22. Minimize definitional dependencies. Avoid cyclic dependencies. 23. Make header files self-sufficient. 24. Always write internal #include guards. Never write external #include guards.
101 mouder Functions and Operators 25. Take parameters appropriately by value, (smart) pointer, or reference. 26. Preserve natural semantics for overloaded operators. 27. Prefer the canonical forms of arithmetic and assignment operators. 28. Prefer the canonical form of ++ and --. Prefer calling the prefix forms. 29. Consider overloading to avoid implicit type conversions. 30. Avoid overloading &&, ||, or , (comma) . 31. Don’t write code that depends on the order of evaluation of function arguments. Class Design and Inheritance 32. Be clear what kind of class you’re writing. 33. Prefer minimal classes to monolithic classes. 34. Prefer composition to inheritance. 35. Avoid inheriting from classes that were not designed to be base classes. 36. Prefer providing abstract interfaces. 37. Public inheritance is substitutability. Inherit, not to reuse, but to be reused. 38. Practice safe overriding. 39. Consider making virtual functions nonpublic, and public functions nonvirtual. 40. Avoid providing implicit conversions. 41. Make data members private, except in behaviorless aggregates (C-style structs). 42. Don’t give away your internals. 43. Pimpl judiciously. 44. Prefer writing nonmember nonfriend functions. 45. Always provide new and delete together. 46. If you provide any class-specific new, provide all of the standard forms (plain, in-place, and nothrow).
101 mouder Construction, Destruction, and Copying 47. Define and initialize member variables in the same order. 48. Prefer initialization to assignment in constructors. 49. Avoid calling virtual functions in constructors and destructors. 50. Make base class destructors public and virtual, or protected and nonvirtual. 51. Destructors, deallocation, and swap never fail. 52. Copy and destroy consistently. 53. Explicitly enable or disable copying. 54. Avoid slicing. Consider Clone instead of copying in base classes. 55. Prefer the canonical form of assignment. 56. Whenever it makes sense, provide a no-fail swap (and provide it correctly). Namespaces and Modules 57. Keep a type and its nonmember function interface in the same namespace. 58. Keep types and functions in separate namespaces unless they’re specifically intended to work together. 59. Don’t write namespace usings in a header file or before an #include. 60. Avoid allocating and deallocating memory in different modules. 61. Don’t define entities with linkage in a header file. 62. Don’t allow exceptions to propagate across module boundaries. 63. Use sufficiently portable types in a module’s interface. Templates and Genericity 64. Blend static and dynamic polymorphism judiciously. 65. Customize intentionally and explicitly. 66. Don’t specialize function templates. 67. Don’t write unintentionally nongeneric code. Error Handling and Exceptions 68. Assert liberally to document internal assumptions and invariants. 69. Establish a rational error handling policy, and follow it strictly. 70. Distinguish between errors and non-errors. 71. Design and write error-safe code. 72. Prefer to use exceptions to report errors. 73. Throw by value, catch by reference. 74. Report, handle, and translate errors appropriately. 75. Avoid exception specifications.
101 mouder STL: Containers 76. Use vector by default. Otherwise, choose an appropriate container. 77. Use vector and string instead of arrays. 78. Use vector (and string::c_str) to exchange data with non-C++ APIs. 79. Store only values and smart pointers in containers. 80. Prefer push_back to other ways of expanding a sequence. 81. Prefer range operations to single-element operations. 82. Use the accepted idioms to really shrink capacity and really erase elements. STL: Algorithms 83. Use a checked STL implementation. 84. Prefer algorithm calls to handwritten loops. 85. Use the right STL search algorithm. 86. Use the right STL sort algorithm. 87. Make predicates pure functions. 88. Prefer function objects over functions as algorithm and comparer arguments. 89. Write function objects correctly. Type Safety 90. Avoid type switching; prefer polymorphism. 91. Rely on types, not on representations. 92. Avoid using reinterpret_cast. 93. Avoid using static_cast on pointers. 94. Avoid casting away const. 95. Don’t use C-style casts. 96. Don’t memcpy or memcmp non-PODs. 97. Don’t use unions to reinterpret representation. 98. Don’t use varargs (ellipsis). 99. Don’t use invalid objects. Don’t use unsafe functions. 100. Don’t treat arrays polymorphically.