II - Wyjątki
III - Dziedziczenie
I
Template
Podstawowa budowa szablonu (Template) funkcji:
template<typename T> T minmax(T a, T b, T C) { if (a > c && a > b) return a; }
wywołanie funkcji szablonowej:
minmax<int> (5,10,15); //funkcja zwraca int
Przykład szablonu klasowego
#include<iostream> #include<vector> //using namespace std; template<typename Char> class String { public: String() {} //konstruktor String(const std::string &a) //konstruktor parametrowy { std:copy( a.begin(), a.end(), back_inserter(mojvector) ); } String(String const &source) //kopiujacy { mojvector=source.mojvector; } Char& operator[](int n) //przeciazony operator [] { return String::mojvector[n]; } void operator+=(Char c) //przeciazony operator += { String::mojvector.push_back(c); } bool operator==(const String &rhs) //przeciazony operator == { if(mojvector==rhs.mojvector) return true; else return false; } private: std::vector<char> mojvector; //Pole }; int main() { String<char> a; //tworzymy obiekt a a+='a'; //przeciązonym operatorem += dodajemy elementy a+='b'; a+='c'; a+='d'; std::cout<<a[2]<<std::endl; // wyswietlamy przeciazonym operatorem [] 2 znak std::string dupa="hello"; // tworzymy string String<char> b(dupa); // tworzymy nowy obiekt z wykorzystaniem // konstruktora parametrowego if(a==dupa) // przeciazony operator == std::cout<<"rowne"<<std::endl; else std::cout<<"rozne"<<std::endl; return 0; }
Template może przyjmować jako argumenty typename i wtedy podajemy typ, ale moze przyjać zmienna.
Funkcja która pobiera wektor i inta i zwraca wektor podniesiony do potęgi w incie.
vector<int> powerof(const vector<int> &T, int T2) { vector<int> toRett = T; for(int& element : toRett) element=std::pow(element,T2); return toRett; }wywołanie
cel=powerof ( mojvektor , 2 );
to samo za pomocą template który określa tylko TYP
template<typename T> vector<T> powerof (const vector<T> &aa,int T2) { vector<T> temp=aa; for(auto& element : temp) element=pow(element,T2); return temp; }wywołanie
cel=powerof <int> (mojvektor , 2 );
albo template który określa typ i pobiera jednego inta
template<typename T,int T2> vector<T> powerof (const vector<T> &aa) { vector<T> temp=aa; for(auto& element : temp) element=pow(element,T2); return temp; }wywołanie
cel=powerof <int, 2> (mojvektor);
Nie można przekazać wektora jako argument template (musi byc przekazany w nawiasie)
ale można przekazać TYP kontenera jako template:
Przykład template który pyta o dwa typy kontenera i przerzuca pomiędzy jednym a drugim:
template<typename T,typename T2, int N > T2 powerof(const T& aa) { T2 retContainer; for(auto& element : aa) retContainer.push_back(pow(element,N)); return retContainer; }wywołanie
vector<int> mojvektor {1,2,3,4,5,6,7,8,9}; //w {} to initializer list (co innego niz initialization list)
list <int> cel;
cel=powerof <vector<int>, list<int> ,2 >( mojvektor );
Przyjmuje vektor, zwraca listę, podnosi do 2 potegi. Jak widać typ może być przekazany, wartość może być przekazana ale sam wektor już jako argument funkcji.
(w przypadku klasy kalo konstruktor argumentowy)
initializer list vs initialization list
This type is used to access the values in a C++ initialization list, which is a list of elements of type const T.
Objects of this type are automatically constructed by the compiler from initialization list declarations, which is a list of comma-separated elements enclosed in braces:
auto il = { 10, 20, 30 }; // the type of il is an initializer_list
Lokalizacja TEMPLATE w plikach
Piszemy w pliku .H czyli tam gdzie deklaracja klasy z prototypami, nie w plikach klasa.cpp czyli tam gdzie metody(funkcje)
Można wszystkie metody(funkcje) pisac w pliku H w ciele klasy, ale wtedy są inline (mają wspolną pamięć, szybsze ale wspolna pamiec przy duzej ilosci metod nie jest korzystna)
*THIS
Jeśli w klasie chcemy zwrocić referencję do niej samej to stosujemy:
*this - this to wskaźnik na klasę, zwracając referencję musimy podać jednak wartość nie wskaźnik, więc stosujemy *this.
Definicję funkcji można zdefiniować poza głównym template:
Dostęp do zmiennej zdefiniowanej w template
Zadanie:
template Array<D,T,false>
D- wielkość jesli wybrany array
true - pracuje na vektorze, false - pracuje na array
T - typ danych
dostarcza interfejsy (operator [], begin(), end(),
Implement Matrix<N, M, T>
N - number of rows
M - number of columns
has templated copy construktor ( const Matrix<N1, M1, T1>& )
N1 < N
M1 < M
T1 convertible to T
has operators:
operator<<
operator[]
template operator+ (const Matrix<N1, M1, T1>)
is specialized for N = 0 && M = 0 (cannot be created)
wynik:
Implementacja Matrix<N, M, T> gdzie N i M wielkosc, T - typ
Stworzenie Matrix<3,4,int> aaa i wypenie danymi.
Polecenie: 'cout << aaa' przeciazony << wynik:
start---------
Twoja matryca:
10,11,12,77,
13,14,15,88,
16,17,18,99,
end-----------
Polecenie: 'cout << aaa[4]' przeciazony [] wynik:
13
end-----------
Stworzenie Matrix<4,5,int> bb(aaa) za pomoca konstruktora kopiujacego.
Stworzona konstruktorem kopiujacym bb wyglada tak:
start---------
Twoja matryca:
10,11,12,77,0,
13,14,15,88,0,
16,17,18,99,0,
0,0,0,0,0,
end-----------
Dodaj bb+aaa, przeciazony + , wynik zapisz w bb.
Po dodaniu Twoje bb wyglada tak:
start---------
Twoja matryca:
20,22,24,154,0,
26,28,30,176,0,
32,34,36,198,0,
0,0,0,0,0,
end-----------
2) Jeśli chcesz stworzyć iterator na vektor ale nieznanego typu <T> (tu iterator 1 wymiaru matrycy)
dodaj typename:
jesli chcesz stworzyć vektor iteratorow (iterator dla typu vektor<T> )
stworzenie i przypisanie do tego vektora iteratorów (dlatego.begin) wewnętrznych vektorów- 2 wymiar macierzy
w konstruktorze:
3) Konstruktor kopiujący musi zawierac swoj template
4) Przeciążony operator << ma taka konstrukcje
5) Jeśli chcesz sprawdzić czy typy są konwertowalne
This type is used to access the values in a C++ initialization list, which is a list of elements of type const T.
Objects of this type are automatically constructed by the compiler from initialization list declarations, which is a list of comma-separated elements enclosed in braces:
auto il = { 10, 20, 30 }; // the type of il is an initializer_list
Lokalizacja TEMPLATE w plikach
Można wszystkie metody(funkcje) pisac w pliku H w ciele klasy, ale wtedy są inline (mają wspolną pamięć, szybsze ale wspolna pamiec przy duzej ilosci metod nie jest korzystna)
*THIS
Jeśli w klasie chcemy zwrocić referencję do niej samej to stosujemy:
*this - this to wskaźnik na klasę, zwracając referencję musimy podać jednak wartość nie wskaźnik, więc stosujemy *this.
Definicję funkcji można zdefiniować poza głównym template:
template<typename T> struct X { void mf1() {}; T mf2(); }; template<typename T> T X<T>::mf2() { /* ... */ }
Dostęp do zmiennej zdefiniowanej w template
template<typename T> struct Vector { static const int m1 = 7; static int m2 = 10; static int m3; }; template<typename T> int Vector<T>::m3 = 100;
Zadanie:
template Array<D,T,false>
D- wielkość jesli wybrany array
true - pracuje na vektorze, false - pracuje na array
T - typ danych
dostarcza interfejsy (operator [], begin(), end(),
#include<iostream> #include<vector> #include<array> using namespace std; template <int T,typename D,bool heap> struct Array { Array() { } std::vector<D> mojav; std::array<D,T> mojaa; int it=0; D& operator[] (int n) { if(heap) return mojav[n]; else return mojaa[n]; } void operator+=(D zm) { if(heap) mojav.push_back(zm); else mojaa[it++]=zm; } D& begin() { if(heap) return mojav.front(); else return mojaa.front(); } D& end() { if(heap) return mojav.back(); else return mojaa.back(); } }; int main() { Array<10,int,false> aaa; aaa+=10; aaa+=20; aaa+=30; std::cout<<aaa[2]<<std::endl; return 0; }
Zadanie:
Implement Matrix<N, M, T>
N - number of rows
M - number of columns
has templated copy construktor ( const Matrix<N1, M1, T1>& )
N1 < N
M1 < M
T1 convertible to T
has operators:
operator<<
operator[]
template operator+ (const Matrix<N1, M1, T1>)
is specialized for N = 0 && M = 0 (cannot be created)
#include<iostream> #include<vector> using namespace std; template <int N,int M,typename T> struct Matrix { std::vector<vector<T> > matryca {N,vector<T>(M)}; //macierz Vektorow o wielkosci NxM wypelniona zerami std::vector<typename vector<T>::iterator> iteratory; //vektor przechowujacy iteratory typu vector<T> typename vector<vector<T> >::iterator it=matryca.begin(); //iterator 1 wymiaru macierzy, vektor na vektor Matrix(){ //konstruktor wypwlniajacy vektor iteratorw 2 wymiaru if(N==0 && M==0) cout<<"N=0 i M=0, nie moge stworzyc"<<endl; else for(int i=0;i<N;i++) iteratory.push_back((*(it+i)).begin()); } template <int N1,int M1,typename T1> Matrix (const Matrix<N1,M1,T1>& another){ //konstruktor kopiujacy if( std::is_convertible<T,T1>::value ) { if(N1<N && M1<M ) { for(int i=0; i<N1;i++) { for( int j=0;j<M1;j++) { matryca[i][j]=another.matryca[i][j]; } } } else cout<<"za duza tablica do skopiowania"<<endl; } else cout<<"niekonwertowalny TYP danych"<<endl; } template <int N1,int M1,typename T1> void operator +(Matrix<N1,M1,T1> another) { if( std::is_convertible<T,T1>::value ) { if(N1<N && M1<M ) { for(int i=0; i<N1;i++) { for( int j=0;j<M1;j++) { matryca[i][j]+=another.matryca[i][j]; } } } else cout<<"za duza tablica do dodania"<<endl; } else cout<<"niekonwertowalny TYP danych"<<endl; } T operator[] (int n){ //przeciazony operator [],podajesz zbiorowy index zwraca wartosc int rz=(int)(n/M); int nr=n-(rz*M); return *(iteratory[rz]+nr); } friend ostream& operator<< (ostream& out, const Matrix& obj) { //przeciazony operator << out<<"start---------"<<endl; out<<"Twoja matryca: "<<endl; for(int i=0; i<N;i++) { for( int j=0;j<M;j++) { out<<obj.matryca[i][j]; out<<","; } out<<endl; } out<<"end-----------"<<endl; return out; } }; int main() { cout<<endl<<"Implementacja Matrix<N, M, T> gdzie N i M wielkosc, T - typ"<<endl<<endl; cout<<"Stworzenie Matrix<3,4,int> aaa i wypenie danymi."<<endl<<endl; Matrix<3,4,int> aaa; //stworz macierz vektorw o wielkosci 3x3 aaa.matryca[0][0]=10; aaa.matryca[0][1]=11; aaa.matryca[0][2]=12; aaa.matryca[0][3]=77; aaa.matryca[1][0]=13; aaa.matryca[1][1]=14; aaa.matryca[1][2]=15; aaa.matryca[1][3]=88; aaa.matryca[2][0]=16; aaa.matryca[2][1]=17; aaa.matryca[2][2]=18; aaa.matryca[2][3]=99; cout<<"Polecenie: 'cout << aaa' przeciazony << wynik:"<<endl; // operator << cout<<aaa<<endl; cout<<"Polecenie: 'cout << aaa[4]' przeciazony [] wynik:"<<endl; //operator [] cout<<(aaa[4])<<endl;; cout<<"end-----------"<<endl<<endl; cout<<"Stworzenie Matrix<4,5,int> bb(aaa) za pomoca konstruktora kopiujacego."<<endl<<endl;; Matrix<4,5,double> bb(aaa); //konstruktor kopiujacy (tylko do wiekszej) cout<<"Stworzona konstruktorem kopiujacym bb wyglada tak:"<<endl; cout<<bb<<endl<<endl; //wyswietl skopiowana cout<<"Dodaj bb+aaa, przeciazony + , wynik zapisz w bb."<<endl<<endl; bb+aaa; cout<<"Po dodaniu Twoje bb wyglada tak:"<<endl; cout<<bb<<endl<<endl; //wyswietl skopiowana return 0; }
wynik:
Implementacja Matrix<N, M, T> gdzie N i M wielkosc, T - typ
Stworzenie Matrix<3,4,int> aaa i wypenie danymi.
Polecenie: 'cout << aaa' przeciazony << wynik:
start---------
Twoja matryca:
10,11,12,77,
13,14,15,88,
16,17,18,99,
end-----------
Polecenie: 'cout << aaa[4]' przeciazony [] wynik:
13
end-----------
Stworzenie Matrix<4,5,int> bb(aaa) za pomoca konstruktora kopiujacego.
Stworzona konstruktorem kopiujacym bb wyglada tak:
start---------
Twoja matryca:
10,11,12,77,0,
13,14,15,88,0,
16,17,18,99,0,
0,0,0,0,0,
end-----------
Dodaj bb+aaa, przeciazony + , wynik zapisz w bb.
Po dodaniu Twoje bb wyglada tak:
start---------
Twoja matryca:
20,22,24,154,0,
26,28,30,176,0,
32,34,36,198,0,
0,0,0,0,0,
end-----------
Uwagi:
1) Skrócony sposób tworzenia vektora vektorówstd::vector<vector<T> > matryca {N,vector<T>(M)};
2) Jeśli chcesz stworzyć iterator na vektor ale nieznanego typu <T> (tu iterator 1 wymiaru matrycy)
dodaj typename:
typename vector<vector<T> >::iterator it=matryca.begin();
jesli chcesz stworzyć vektor iteratorow (iterator dla typu vektor<T> )
std::vector<typename vector<T>::iterator> iteratory;
stworzenie i przypisanie do tego vektora iteratorów (dlatego.begin) wewnętrznych vektorów- 2 wymiar macierzy
w konstruktorze:
for(int i=0;i<N;i++) iteratory.push_back( (*(it+i)).begin() );
3) Konstruktor kopiujący musi zawierac swoj template
template <int N1,int M1,typename T1> Matrix (const Matrix<N1,M1,T1>& another){
4) Przeciążony operator << ma taka konstrukcje
friend ostream& operator<< (ostream& out, const Matrix& obj)
5) Jeśli chcesz sprawdzić czy typy są konwertowalne
std::is_convertible<T,T1>::value
SPECJALIZACJA templatów
jeśli mamy zwykłego template:
TEMPLATE<typname>
void print (T t){
cout<<t;}
to jeśli dodamy template bez typu
TEMPLATE<>
void print(K k) to jeśli K jest konkretnym typem np struktura
{
cout<<k.wartosc;
} to zostanie wybrane najlepsze dopasowanie i w przypadku typu K
zostanie uruchomiona ta funkcja.
Zablokowanie typu wskaźnikowego
template<typename T>
class CustomString {
};
template<> Specjalizacja
class CustomString<int*> { typ wskaźnikowy
CustomString() {
static_assert("Pointer are not supported"); } rzuci błąd podczas kompilacji
};
SFINALE - linijka funkcji, jeśli ma błąd nie jest traktowana jak błąd, tylko funkcja jest nieotwierana
to zjawisko wykorzystuje Type-Traits
Link
przykład:
jak odrzucić wszystkie wskaźniki
#include <iostream> #include <type_traits> #include <vector> template<typename T, typename W = void> struct CustomString{ }; template<typename T> struct CustomString<T, typename std::enable_if<std::is_pointer<T>::value>::type> { CustomString() { static_assert(true, "nope");} }; int main(){ CustomString<int> asd; CustomString<int*> asd1; }
sprawdza czy pointer, jeśli tak, daje TRUE wtedy robi
::TYPE - co jest błędem, zgodnie z SFINALE nie zgłasza błędu ale odrzuca linijkęfunkcji.
II
Wyjątki
oddzielają logikę od zarządzania problemem
if elsy zabierają czas procesora.
w konstruktorze tylko wyjątki
kiedyś:
Error number, zwracane przez funkcję, lub zapisywane do zmiennej globalnej.
otwarcie pliku w funkcji - jak się wykrzaczy funkcji to nie zamknie pliku.
dlatego w C++ robimy obsługę plików w konstruktorze (otwarcie) i zamknięcie w destruktorze.
Exception to OBiekt który możemy rzucać. (w c++ możemy rzucać każdy obiekt nie tylko dziedziczący po EXCEPTION)
THROW - rzuca
TRY - obszar ktory sprawdza
CATCH - łapie
Jeśli rzucamy wyjątek w konstruktorze, to musimy sami pozamykać zaalokowane obszary bo destruktor nie zostanie uruchomiony.
Nie rzucać exception w Destruktorze, bo przerwie jego pracę!
Co rzucać? najlepiej po STD::exception, hierarchia klas
Możemy stworzyć własną klasę która dziedziczy po exception i dopisać
RETHROW
rzucanie jeszcze raz, żeby przekazać obsługę błędu dalej.
(można nawet ponownie wywołać main :-)
catch - od szczegółu do ogółu
try { // ... } catch (user_defined_speciall_exception& e) { } catch (bad_exception& e) { } catch (exception& e) { } catch (...) { }
NOEXCEPT
od c++11 - wtedy funkcja nie rzuca wyjątków
static exception ptr nazwa - i można rzucić z jednego do drugiego wątku.
III
DZIEDZICZENIE
visibility specifier
virtual functions()visibility specifier
virtual base classes (aka interfaces)
dynamic_cast, upcasting, downcasting, factory concept
vptr
generalnie mamy klasę bazową OSOBA:
struct Person { std::string name() const; std::uint16_t age() const; std::uint_16_t height() const; virtual void fix() = 0; };
pola imię, wiek wzrost są wspólne doa kobiet i mężczyzn:
struct Men : public Person { std::uint32_t beardLength() const; void fix() override; };
struct Women : public Person { std::pair<std::uint16_t, char> breastSize() const; void fix() override; };
wiec dokładają tylko swoje indywidualne zarost i biust.
Jest też metoda czysto wiertualna ( ta z =0 ) ktorą trzeba nadpisać.
Dodany na końcu override nie jest obowiązkowy, ale informuje nas to tym, że nadpisujemy.
Klasa z metodą czysto wirtualną jest klasą abstrakcyjną (nie ma obiektów)
Jak w klasie bazowej mamy konstruktor to w klasie dziedziczącej też MUSI być konstruktor (bo domyślny nie powstanie)
struct Car { Car(EngineType et) : _et(et) {} }; struct Sport : public Car { Sport(EngineType sport_et) : Car(sport_et) {} };
Jeśli kolejne klasy dziedziczą od siebie to przy tworzeniu obiektów Konstruktory są od środka (od bazowej)
Lista INICJALIZATORÓW { }
jak nie ma konstruktora to wypełnia po kolei pola klasy, ale silnie typowane (nie jak konstruktor)
PROBLEM ROMBU:
Kiedy mamy dwie klasy dziedziczące z bazowej a potem trzeci dziedziczący po tych dwóch następuje redundancja danych.
Rozwiązanie to Virtualne dziedziczenie:
struct Car : Vehicle { Weels w; MobilityType mt; // not needed virtual void move() noexcept override; }; struct ElectricCar : virtual Car { Batteries b; }; struct FuelCar : virtual Car { FuelType f; }; struct Hybrid : FuelCar, ElectricCar { };
Virtual function dynamic binding
struct Base { virtual void f() { cout << "Base" << endl; } virtual ~Base() { } }; struct Derived: Base { void f() override { cout << "Derived" << endl; } virtual ~Derived() { } };
Base b; Derived d; b.f(); d.f(); Base bb = d; bb.f();.
powyżej widać, że do stworzonego obiektu bb jest tylko PRZYRÓWNANY
więc wynik będzie
Base
Derived
Base
czyli obiekt bb wyświetli base bo on jest tego typu tylko pobiera dane z d
Przykład z referencję
Base b; Derived d; Base& rb = b; Base& rd = d; rb.f(); rd.f(); rb.Base::f(); rd.Base::f();wynik powyzszego będzie:
Base
Derived
Base
Base
czyli wywołanie metody referencji na obiekt derived rd z dopisanym typem klasy bazowej
czyli rd.Base::f() wywoła matodę bazową.
to samo wskazniki
Base b; Derived d; Base* pb = &b; Base* pd = &d; pb->f(); pd->f(); pb->Base::f(); pd->Base::f();
wynik powyzszego będzie:
Base
Derived
Base
Base
Dynamiczne castowanie
make_unique = new
struct Base { virtual ~Base() { } }; struct Derived: Base { virtual void method() { cout << "."; } }; void function() { vector<shared_ptr<Base>> v; v.push_back(make_shared<Base>(Base())); v.push_back(make_shared<Derived>(Derived())); for (auto e : v) { if (Derived* d = dynamic_cast<Derived*>(e.get())) { // can we call it // or downcast from shared_ptr to d successful d->method(); } } }
w vektorze obiekty typu bazowego i obiekty typu pochodnego
w warunku if dynamicznie castujemy, jeśli obiekt typu pochodnego przypisuje do wskaznika.
to samo ale range for na wyjątkach (i referencjach, referencja nie moze dostac stanu 0)
for (auto e : v) { try { Derived& d = dynamic_cast<Derived&>(*e.get()); d.method(); } catch (exception& e) { cout << "Exception: " << e.what() << endl; }
Przykład
struct Base { int a; Base(int _a) : a(_a) { } }; struct Derived: Base { int b; Derived(int _a, int _b) : Base(_a), b(_b) { } }; void foo() { Derived d1 { 1, 1 }; Derived d2 { 2, 2 }; Base& b_ref = d2; b_ref = d1; cout << d2.a << d2.b << endl; }
co ważne b_ref=d1; to nie przypisanie nowej referencji ale przypisanie pól jednego obiektu do drugiego
wynik 1 2
czyli b_ref jest referencją do d2
do b_ref przyrównany (przepisze pola) d1 (z dwoma jedynkami )
ale zmieni tylko pierwszą jednykę !!! bo typ Base ma tylko to pole i nie ma dostępu do zmiennej b
Funkcje virtualne - późne wiązanie, realizowane za pomocą V-table. Tylko jedna v-table per klasa nie per obiekt.
Nie da się wywołać virtualnych nadpisanych funkcji w konstruktorze i destruktorze bo v-table są tworzone po stworzeniu konst/destr.
Można to zrobić, jeśli rzutujemy swój typ na THIS.
Jeśli do statycznej metody (istniejącej na przestrzeni obiektów) dodamy this to odwołujemy się do metody konkretnego obiektu (obejście statyczności metody - patrz sposób wywoływania wątków POSIX w obiektach c++)
pthread musi wywoływać istniejącą metodę (statyczną , istnieje przed obiektem) dlatego używamy posiłkowej funkcji statycznej która w środku za pomocą THIS wywołuje konkretną funkcje/metodę konkretnego obiektu
void *drv::Ethernetdriverserver::work() //docelowa funkcja do uruchomienia void *drv::Ethernetdriverserver::RunWork(void *context) //statyczna (static w hpp) { return ( reinterpret_cast<drv::Ethernetdriverserver *>(context))->work(); } //uruchomienie wątku za pomocą statycznej RunWork, // ostatni parametr "this" to argument przekazany do funkcji pthread_create(&m_Thread_id,0,&RunWork,this)
czyli this, adres bierzącego obiektu jest przekazywany do RunWork jako parametr, tam kastowany jest na niego typ i uruchamiana docelowa, niestatyczna metoda docelowa work.
Typ sposobem za pomocą pomocnicznej statycznej metody uruchomiliśmy metode niestatyczną.
Wadą polimorfizmu jest wywoływanie funkcji virtualnych co powoduje skakanie po v-table i utratę trafności Cache.
FINAL - uniemożliwia dalsze dziedziczenie, zarówno metod jak i klas:
class Animal{ public: virtual void eat() const noexcept =0; }; class Cat : public Animal{ public: virtual void eat() const noexcept final { cout << "I'm a cat and eating a rat." << endl; } }; class HouseCat final: public Cat { public: virtual void eat() const noexcept { cout << "I'm a house cat and eating Whiscas." << endl; } };
OVERRIDE - nieobowiązkowe , ale jeśli dodamy to przypilnuje, że funkcja będzie musiała być nadpisana. Gdy ktoś zmieni nazwę funkcji w klasie bazowej to override zagwarantuje, że brak nadpisanej funkcji spowoduje, że się wywali:
class Animal{ public: virtual void eat() const noexcept =0; }; class Cat : public Animal{ public: virtual void eat() const noexcept override { cout << "I'm a cat and eating a rat." << endl; } }; class HouseCat: public Cat { public: //virtual void eat() override { // cout << "I'm a house cat and eating Whiscas." << endl; //} };
WHY Virtual Destructor:
jeśli ktoś używa hierarchii klas polimorficznie to musi dodać virtualny destruktor.
Bez virtualnego, będzie dla klas pochodnych (typowanych klasą bazową) uruchamiać destruktor bazowy, czyli bez niszczenia pól dodanych.
git zadania:
https://github.com/JanosikOpryszek/kod/tree/master/ZUT/template
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/
