Strona główna

Dynamiczne tworzenie tablic np


Pobieranie 78.62 Kb.
Data19.06.2016
Rozmiar78.62 Kb.




1. Dynamiczne tworzenie tablic (zarówno typów prostych jak i struktur). Tablice wielowymiarowe. Zależność pomiędzy tablicami a wskaźnikami.

Dynamiczne tworzenie tablic np.

//wskaźnik do tablicy

int *wsk;

wsk=new int [rozm]

wsk[3]=1;

*(wsk+3)=1;

delete [] wsk;

//tablica wskaźników

char *tab[3];

*tab[0]=”pupa”;

//wskaźnik do tablicy wskaźników

int **tab;

tab=new (int *)[n];

for(int i=0;i

tab[i]=new int [n];

tab[i][j]=a;

*(*(tab+i)+j)=a

for(i=0;i

delete [] tab[i];

delete [] tab;
3. Operacje na bitach (operatory |, &, <<, >>; zapis i odczytywanie konkretnego bitu; maska; wyświetlanie liczby w postaci binarnej).

Operatory:

a) <> przesunięcie w lewo/prawo;

zmienna <> ile_miejsc

b) &, |, ~, ^(xor),

Zapis na 3. bicie 1

int m=1;

int l=0;


m<<3;

l=m | l ;

Sprawdzanie czy na 2. bicie jest 0

int m=(1<<2)

l=m&l;

if(!l) cout<<0



else cout <<1;

Wyświetlanie binarnie liczby

int l;

for(int i=15;i>=0;i--)



{

maska=1<

cyfra=maska&l;

if(l) cout<<1

else cout<<0;

}
4. Dostęp do składników klasy (etykiety public:, protected:, private:). Definiowanie funkcji składowych w ciele klasy lub poza nim (różnice). Wskaźnik this.

Private – jest dostępny tylko dla funkcji składowych danej klasy. (także dla funkcji zaprzyjaźnionych z ta klasą) jeśli zależy nam na ukryciu informacji, to wówczas składnik powinien być deklarowany jako prywatny.

Protected – jest dostępny tak jak składnik private, ale dodatkowo jest jeszcze dostępny dla klas wywodzących się z tej klasy .

Public – jest dostępny bez ograniczeń. Zwykle składnikami takimi są jakieś wybrane funkcje składowe. To za ich pomocą dokonuje się z zewnątrz operacji na danych prywatnych.

Funkcje składowe – funkcje zdeklarowane wewnątrz definicji klasy.

:: - operator zakresu

A różnica jest taka że jak funkcja definiowana jest poza klasą to nie jest inline i wszystko jasne!!

This – wskaźnik adresu obiektu na rzecz którego ma być wywołana funkcja
5. Zasłanianie nazw (zmienne globalne, zmienne będące składnikami klasy, zmienne lokalne). Równoczesne przeładowanie i zasłonięcie.

:: coś_tam globalnie

nazwa_klasy::cos_tam składnik klasy

cos_tam lokalnie

Z funkcjami to samo.

Kilka funkcji o tej samej nazwie może mieć ten sam zakres pod warunkiem że mają różne argumenty.


6. Przesyłanie do funkcji argumentów będącymi obiektami (przez wartość, przez referencję, przez wskaźnik).

Przez wartość – tworzona jest kopia obiektu

Przez referencje – przez przezwisko, nie jest kopiowany , funkcja ma dostęp do oryginału

Prze wskaźnik - zmienna przechowuje adres komórki pamięci gdzie się znajduje dany obiekt


7. Konstruktor. Przeładowanie konstruktorów. Kiedy wywoływany jest konstruktor (obiekty lokalne, obiekty lokalne statyczne, obiekty globalne, obiekty tworzone operatorem new)? Konstruktor domyślny. Czy konstruktor domyślny jest automatycznie generowany (jeżeli tak to kiedy ma to miejsce)? Czy konstruktor może być nie-publiczny?

Konstruktor – funkcja składowa która się nazywa tak samo jak klasa.

Wywoływany jest on automatycznie ilekroć powołujemy do życia nowy obiekt danej klasy.

Przeładowanie - kilka funkcji o tej samej nazwie może mieć ten sam zakres pod warunkiem że mają różne argumenty.

Wywoływanie konstruktorów :


  1. obiekty lokalne – konstruktor jest wywoływany gdy program napotka definicje obiektu np. {…. ; klasa obiekt1; … }

  2. obiekty lokalne statyczne – istnieją cały czas ale dostęp do nich jest tylko w zakresie ważności, konstruktor rozpoczyna prace przed wywołaniem funkcji main np. {…; static klasa obiekt2;…}

  3. obiekty globalne konstruktor rozpoczyna prace przed wywołaniem funkcji main

  4. obiekty tworzone operatorem new - konstruktor działa w momencie przydzielania pamięci{…… ; klasa *wsk; wsk= new klasa; ……..}

konstruktor domyślny (domniemany) to taki który można wywołać bez parametrów. Jest automatycznie generowany tylko wtedy kiedy klasa nie ma żadnego konstruktora.

Konstruktor może być prywatny wtedy klasa nazywa się prywatną.


8. Lista inicjalizacyjna konstruktora. Gdzie umieszczamy listę inicjalizacyjna (przy defnicji, deklaracji czy w obu miejscach)? Jakie składniki klasy możemy inicjalizować na liście inicjalizacyjnej? W jaki sposób inicjalizować obiekt jakiejś klasy, będący składnikiem innej klasy? Czy możemy użyć w tym celu konstruktor

domyślny? Kolejność wywoływania konstruktorów i destruktorów.

Lista inicjalizacyjna


  1. umieszczamy ją tylko przy definicji

  2. na liście tej mogą być umieszczone składniki nie const i const, nie może być umieszczony składnik static

  3. obiektu jakieś klasy będącej składnikiem innej klasy inicjalizujemy poprzez umieszczenie na tej liście ich konstruktora

    1. aby zainicjalizować konstruktorem domyślnym to nie trzeba pisać go na tej liście

  4. najpierw tworzymy starszych potem gości następnie siebie a destrukcja obywa się w odwrotnej kolejności

9. Konstruktor kopiujący. Czy konstruktor kopiujący jest automatycznie generowany? Jeżeli tak, to kiedy ma to miejsce oraz czy wygenerowany w ten sposób konstruktor zawsze działa poprawnie? Kiedy wywoływany jest konstruktor kopiujący? Konstruktor kopiujący gwarantujący nietykalność.

Konstruktor kopiujący jest generowany automatycznie ale wtedy kopiuje składnik po składniku wszystko działa poprawnie dopóki składnikiem klasy nie jest wskaźnik do tablicy bo kopiuje tylko ten wskaźnik.

Wywoływany jest podczas inicjalizacji obiektu a także podczas przesyłania do/z funkcji argumentów przez wartość.

Nietykalność gwarantuje nam słówko const przed typem argumentu np. klasa (const klasa &wzor);
10. Destruktor. Kiedy wywoływany jest destruktor (obiekty lokalne, obiekty lokalne statyczne, obiekty globalne, obiekty tworzone operatorem new)? Czy można jawnie uruchomić destruktor (jeżeli tak, to czy jest to jednoznaczne z likwidacją obiektu)?

Wywołanie destruktorów:



  1. obiekty lokalne automatyczne – {..; klasa obiekt1; … } zamknięcie bloku i likwidacja obiektu

  2. obiekty lokalne statyczne istnieją aż do zakończenia programu

  3. obiekty globalne - -------||-------

  4. obiekty tworzone operatorem new – istnieją aż do usunięcia czyli do wywołania operatora delete

Destruktor można wywołać jawnie np. obiekt.~klasa(); wywołanie nieistniejącego destruktora ostanie zignorowane, jawne wywołanie destruktora nie jest jednoznaczne z likwidacja obiektu
11. Składnik statyczny w klasie, jego deklaracja i definicja. Sposoby odwołań do składnika statycznego. Kiedy używamy zmiennych statycznych? Funkcja statyczna.

Deklaracja zmiennej statycznej umieszczamy w ciele klasy natomiast jego definicje umieszczamy jak definicje zmiennej globalnej.

Sposoby odwoływania się do składnika:


  1. za pomocą nazwy klasy i operatora zakresu np. klasa::składnik

  2. jeśli istnieją już jakieś egzemplarze obiektów klasy to możemy posługiwać się operatorem ‘.’ Np. obiekt.składnik

  3. jeśli jest zdefiniowany taki wskaźnik do obiektów klasy klasa *wsk to stosujemy operator -> np. wsk->składnik

Zmiennych statycznych używamy wtedy kiedy chcemy aby poszczególne obiekty danej klasy posługiwały się ta sama daną, zliczanie liczby obiektów, do porozumiewania się między obiektami.

Funkcja statyczna to funkcja która pracuje tylko na składnikach statycznych. static void funkcja ()

Zdeklarowanie funkcji składowej jako statycznej sprawia że nie zawiera ona wskaźnika this. Nie dotyczy ona konkretnego obiektu tylko kasy obiektów.

Funkcje ta można wywołać nawet wtedy kiedy nie istnieje jeszcze żaden obiekt tej klasy. klasa::funkcja()


12. Funkcje składowe typu const i volatile. Przeładowywanie tych funkcji.

Funkcja const to funkcja, która obiecuje że jeśli się ją wywala na rzecz jakiegoś obiektu to nie będzie modyfikował jego danych składowych. Jest to ważne w sytuacjach gdy zamierzamy w danej klasie definiować sobie obiekty typu const.

Słówko const umieszczamy zarówno po deklaracji jak i po definicji, po argumentach np. void funkcja1(int a)const

Funkcja volatile zrezygnowała z optymalizacji na rzecz rzetelnej pracy z danymi.

Obiekty typu volatile mogą być przekazywane tylko do funkcji volatile.

Funkcja może być jednocześnie const i volatile, ale obiekt nie.

Funkcji o tych samych argumentach ale rożnych przydomkach (const .. ) nie da się przeładować.

Obecność przydomku ma znaczenie przy dopasowywaniu funkcji.


13. Funkcje zaprzyjaźnione. W którym miejscu deklarujemy przyjaźń? Czy funkcja może być przyjacielem kilku klas? Czy możemy umieścić definicją funkcji zaprzyjaźnionej bezpośrednio w klasie z którą funkcja ta się przyjaźni (jeżeli tak to co to zmienia)? Co w przypadku funkcji przeładowanych? Czy funkcja zaprzyjaźniona może być funkcją składową zupełnie innej klasy? Przyjaźń klas. Czy przyjaźń klas jest symetryczna/przechodnia/dziedziczna?

Przyjaźń deklarujemy w klasie w której się zaprzyjaźniamy poprzez dodanie słówka friend do nazwy funkcji.

Funkcja może być przyjacielem kilku klasami.

Definicje funkcji zaprzyjaźnionej można umieścić w klasie Nie będzie a składnikiem klasy ale będzie inline(a tak pyzatym to nic nie zmienia ).

Przyjacielem jest tylko ta funkcja której lista argumentów odpowiada liście widocznej w deklaracji przyjaźni.

Funkcja składowa innej klasy może być przyjacielem jeszcze innej klasy np.

friend void klasa::funkcja();

Funkcja zaprzyjaźniona nie ma wskaźnika this.

Nie ma możliwości zadeklarowania w jednej klasie że przyjaźni się ona z funkcjami innej klasy a w tej innej klasie że przyjaźni się z wybranymi funkcjami klasy pierwszej

class 2;


c1ss 1

{

fun1;



friend fun2; //błąd

friend class 2;

}

class 2


{

fun2;


friend fun1;

}

Przyjaźń nie jest symetryczna, nie jest przechodnia ani dziedziczna


14. Tablice obiektów. Inicjalizacja tablic obiektów (w przypadkach gdy mamy do czynienia: z agregatem (skupiskiem danych), z tablicą nie będącą agregatem, z tablicą tworzona dynamicznie).

Tablice obiektów:


a) będące agregatami. Cechy agregatu:

  1. wszystkie składniki są publiczne,

  2. nie ma konstruktorów

  3. nie ma klas podstawowych (nie może być to klasa pochodna)

  4. nie ma funkcji wirtualnych

Przykład uzupełnienia tej tablicy:

klasa tab[4]={0,1,”Pozań”, ….} //nie może przekroczyć czterech obiektów ale może być ich mniej niż 4(resztę uzupełni odpowiednimi zerami)

b) nie będące agregatami np.

class klasa

{

..

public:



klasa(int a,float b)

klasa ();

….
};

klasa tab[4]={klasa(7,0.5),klasa(),klasa(1,1.2)}//czwarty element zostanie uzupełniony konstruktorem domyślnym

c) tworzenie tablic dynamicznych np.

klasa *wsk=new klasa[5]; //dla wszystkich elemetnmów wykorzysta konstruktor domyślny, nie da inaczej zainicjalizować

klasa **wsk=new klasa*[5];

for(int i=0;i<5;i++)

wsk[i]=new kalsa(i,i+1);
15. Konwersje. Konstruktor jako konwerter (problemy z dostępem do składników gdy dokonujemy konwersji innej klasy). Funkcja konwertująca (operator konwersji). Który wariant konwersji wybrać? Sytuacje, w których zachodzi konwersja (niejawnie, jawnie). Standardowe konwersje.

Konwersje:



  1. przy pomocy konstruktora konwertującego

-przyjmuje jeden argument i określa konwersje od tego typu do typu klasy, do której sam należy;

np.


1)

zespol::zespol(float r)

{

rzeczywista=r;



}

2)

class figura



{

public:


figura (kwadrat a)

{



}

};

class kwadrat



{

friend figura::figura(kwadrat a); //należy się zaprzyjaźnić

};
Cechy:


  1. nie można zbudować konstruktora dla konwersji do typów wbudowanych;

  2. nie można napisać go nie do naszej klasy;

  3. trzeba obcą klasę zaprzyjaźnić z konstruktorem;

  4. argument konstruktora musi dokładnie pasować do tego na liście- nie można polegać na standardowej konwersji;

  5. konstruktorów nie dziedziczy się




  1. operator konwersji

-jest skąłdnikiem klasy z której dokonujemy konwersji;

np.


class zespol

{



public:

operator int()

{

return cz_rzeczy;



}

};

Cechy:



  1. jest składnikiem klasy, ma przekazywany this;

  2. typ, który zwraca nie jest określony ponieważ musi zwrócić to na co konwertuje;

  3. ma pustą listę argumentów;

  4. jest dziedziczony;

  5. może być wirtualny;

Niejawna konwersja:



  1. niezgodność argumentów aktualnych z formalnymi podczas wywołania funkcji(funkcja spodziewa się int a dostanie obiekt typu naszej klasy no ) ;

  2. przy zwracaniu przez funkcję;

  3. w obecności operatorów

  4. W wyrażeniach :

A) if (obiekt)

B) switch (obiekt), while, for

Jawne wywołanie konwersji np.

klasa z;


inny n;

n=inny(z); //forma funkcji

n=(inny) z; //forma rzutowania

Wszystkie konwersje standardowe dla typów wbudowanych są możliwe, tzn. char->int, int->char, int->float, float->inr, char->float, float->char, … mój kompilator łyka wszystko, i mój tez hehe .


16. Przeładowanie operatorów. Operatory, które mogą być przeładowane (operatory jedno-, dwu- oraz wieloargumenowe). Jakie wartości mogą zwracać operatory? Priorytety wykonywania. Czy można zmieniać "argumentowość” albo ”łączność" operatorów? Funkcja operaotorowa jako funkcja składowa. Operatory

predefiniowane.

Przeładowanie operatorów to pisanie operatorów dla typów zdefiniowanych przez NAS (suma, iloczyn, …)

Które operatory mogą być przeładowane str 425

Operatory mogą zwracać typ jaki sobie chcemy. Operator przypisania musi zwracać wskaźnik this.

Nie można zmieniać priorytetów i nie da się zmienić argumentowości oraz nie można zmienić łączności operatorów(czyli tego czy operator łączy się z argumentem z lewej czy prawej strony).

Operator można napisać w klasie albo poza klasą.

Jak się pisze operator globalnie to zawsze operator ma tyle argumentów ile argumentowy jest operator, a jak w klasie go piszemy to ma jeden argument mniej.

Funkcja operatorowa jako funkcja składowa jest to operator napisany w klasie.

Operatory predefiniowane : ‘=’, ‘&’, ‘,’, ‘new’, delete’


17. Operatory, które muszą by¢ niestatycznymi funkcjami składowymi. Kiedy działa konstruktor kopiujący, a kiedy operator przypisania? Czym zazwyczaj różnią się te funkcje? Jakie wartości może zwraca¢ operator przypisania? Kiedy operator przypisania jest generowany automatycznie? Przeładowanie operatorów [] , () , <<

oraz >> . Jak przeładować operatory post- i pre- inkrementacji/dekrementacji?

Operatory: =, [], (),-> muszą być nieestetycznymi składowymi klasy czyli zawsze musza być definiowane w klasie i nie może być przed tym operatorem słówka static

Operator przypisania



  1. jest generowany automatycznie

  2. składa się z:

    1. likwidacja (destrukcja) starego obiektu

    2. konstrukcja (przypisanie nowych wartości) – konstruktor kopiujący tutaj działa

  3. zawsze zwraca wartość *this(zabezpieczenie przed wodotryskami)

  4. a=a if (*this==&wzór) return *this; else cała_reszta :P

  5. kiedy operator = nie jest generowany automatycznie jeżeli

    1. klasa ma składnik const

    2. składnikiem klasy jest referencja

    3. składnikiem klasy jest obiekt innej klasy którego operator = jest prywatny

    4. klasa podstawowa ma operator = prywatny

Wszystkie operatory z wyjątkiem operatora =są dziedziczone.

Konstruktor kopiujący działa przy inicjalizacji obiektu innym obiektem np.

samochod s;

samochod s1(s) // działa konstruktor kopiujący

Operator przypisania działa przy przypisywaniu wartości np.

samochod s;

samochod s1;

s1=s // działa operator

Funkcje te różnią się:


  1. miejscem wywołania

  2. budową (konstruktor kopiujący składa się z części konstrukcyjnej a operator przypisania składa się z części destrukcyjnej i części konstrukcyjnej)

Operator przypisania zawsze zwraca *this.
Operator [] np.

int & klasa::operator [](int który)

{

Return a[ktory]; // tablica Intów zdeklarowana w klasie klasa



}

void main()

{



int x;



klasa w;

x=w[2];


w[3]=x; /błąd dużymi literami!! dobrze

}
Operator ()

Może być przeładowany bo jest wieloargumentowy np.

int operator () (int a, int d)

{

return tab[a][d];



}

Preinkrementacja klasa operator++ (klasa a)

Postinkrementacja klasa operator++ (klasa a, int )

Wypisania << musi być zdefiniowany zawsze globalnie bo gdyby był w klasie musiałby być w klasie ostream a to jest nie możliwe (tak mówi Piotruś !!)

Przykład do operatorów

int operator +(int a, samochod s)

{

..

}



Int operator + (samochod s, int a )

{



}

void main ()

{



int z=1;



samochod s;

z=z+s; //pierwszy operator

z=s+z; // drugi operator

}
18. Mechanizm dziedziczenia. Rodzaje dziedziczenia (private, protected oraz public), domniemane dziedziczenie. Dziedziczenie po znajomości_. Czy wszystkie składniki dziedziczy klasa pochodna? Przypisanie i inicjalizacja obiektów w warunkach dziedziczenia. Dziedziczenie kilkupokoleniowe. Kolejność

wywoływania konstruktorów i destruktorów. Ryzyko wieloznaczności przy dziedziczeniu, sposoby rozwiązania problemu.

Mechanizm dziedziczenia:

a) w klasie pochodnej możemy zdefiniować:


  • dodatkowe dane składowe

  • dodatkowe funkcje składowe

  • składniki (najczęściej funkcje) które istnieją już w klasie podstawowej (zasłonięcie)

b) dziedziczenie to jakby zagnieżdżenie się zakresu np.

{

klasa podstawowa



{

klasa pochodna

}

}

Rodzaje dziedziczenia



private

private private

protected private

public private



protected

private private

protected protected

public protected



public

private private

protected protected

public public


Domniemane dziedziczenie – brak sposobu dziedziczenia domyślnie jest przyjmowany jako private

Dziedziczenie po znajomości – po odpowiedniej etykiecie piszmy nazwe przodka :: i nazwę składnika(tylko) np.

class podstawowa

{

int x;



protected:

void fun1();

public:

float z;


fun2();

};

class pochodna: private podstawowa



{

public:



podstawowa::z;

podstawowa::fun2;

}

Nie dziedziczymy:



- konstruktorów

- operatorów przypisania

- destruktora

Przypisanie i inicjalizacja obiektów w warunkach dziedziczenia



  1. gdy nie napiszemy operatora ‘=’ wygeneruje się sam (z wyjątkiem patrz punkt 17.) przypisanie będzie się obywać składnik po składniku (Dla części odziedziczonej z klasy pojazd uruchomi operator=, który tam napisaliśmy, a elementy dodatkowe klasy automobil skopiuje składnik po składniku (dokładnie skopiuje). Mówiąc krótko kompilator pomyśli i wykorzysta to co my napisaliśmy.);

  2. gdy nie napiszemy konstruktora kopiującego wygeneruje się sam i będzie kopiował składnik po składniku

  3. gdy obiekty są const np.

tak to powinno wyglądać

klasa::operator= (const klasa &wzor); //operator przypisania

klasa::klasa(const klasa &wzor) //konstuktor kopiujący;


  1. sami piszemy konstruktor kopiujący i operator przypisania np.

class pojazd

{

..



public;

pojazd(const pojazd &wzor);

pojazd &operator = (const pojazd &wzor);

..

};



class auto: public pojazd

{



public:

auto (const auto &wzor):pojazd(wzor)

{

//inicjalizacja pozostałych składników z auta



}

auto & operator = (const auto & wzor)

{

//3 sposoby



//1. sposób

(*this).pojazd::operator= (wzor);

// 2. sposób

pojazd *wsk=this;

(*wsk)=wzor;

//3 sposób

pojazd & ref =*this;

ref=wzor;

//przypisanie pozostałych składników z auta

}

};



Dziedziczenie kilkupokoleniowe np.

Class klasa1

{



};



class klasa2: public klasa1

{



};

class klasa3: public klasa 2

{



};



Klasa1 jest dla klasa2 klasą podstawową bezpośrednią, a dla klasy 3 pośrednio.

Kolejność wywoływania konstruktorów. Najpierw uszanuje starszych potem gości a na końcu samego siebie, destruktory są wywoływane w odwrotnej kolejności.

Wieloznaczność:

class samochod

{

..

int x,a;



};

class lodz

{

..

int x,b;



};

class amfibia: public samochod, public lodz

{

..

//operator zakresu



samochod::x;

lodz::x;


//zasłonięcie

int x ()


{

return samochod::x;

};



};



Najpierw sprawdzana jest jednoznaczność a potem ewentualny dostep.

Bliższe pokrewieństwo usuwa wieloznaczność(jest dziadek ojciec matka i dziecko, jak ojciec dziedziczy coś od dziadka i dziecko dziedziczy od matki i ojca, a matka ma to coś co miał dzidek i teraz ma ojciec po dziadku to u dziecka nie występuje wieloznaczność)


19. Konwersje standardowe przy dziedziczeniu. Czy sposób dziedziczenia (private, protected oraz public) wpływa na standardową konwersją? W jakich sytuacjach następuje konwersja? Co jest automatycznie konwertowane (wskaźniki do obiektów, referencje czy same obiekty)?

Można przekształcić klasę pochodną na podstawową ale nie odwrotnie. Konwersja może zajść niejawnie tylko dla klas dziedziczonych publicznie w innych przypadkach należy dokonać jawnej konwersji np. funkcja((typ_podst &)zmienna_typu_pochodnego)

Konwersja niejawna zachodzi:


  1. przesyłanie do funkcji

  2. zwracanie funkcji

  3. przeładownie operatorów

  4. wyrażenia inicjalizujące np. trabant t; samochod s(t);

Konwersji poddawany jest tylko wskaźnik lub referencja.

Nie można skonwertować wskaźnika do wskaźnika klasy pochodnej na wskaźnik do wskaźnika klasy podstawowej


20. Wirtualne klasy podstawowe (klasa podstawowa dziedziczona wirtualnie). Publiczne i prywatne dziedziczenie tej samej klasy wirtualnej. Funkcje wirtualne. Dlaczego wszystkie funkcje nie są wirtualne (skoro to taki świetny mechanizm)? Wczesne i późne wiązanie. Czy dla wywołań funkcji wirtualnych może zachodzić

wczesne wiązanie? Czy funkcja wirtualna może być inline? Klasy abstrakcyjne. Funkcje czysto wirtualne. Wirtualny destruktor, _wirtualny destruktor_.


Dziedziczenie wirtualne przydaje się wtedy, gdy chcemy by w hierarchii był lokalny punkt dzielenia się wspólną informacją.

class srodek_transportu

{



};



class samochod: public virtual srodek_transportu

{



};

class lodz:public virtual srodek_transportu

{

...


};

class amfibia: public samochod,public lodz

{

....


};

Jeśli na dwa sposoby możemy dotrzeć do tej samej funkcji, typu, obiektu lub typu wyliczeniowego to nie występuje wieloznaczność.

!!!Do składników srodka_transportu dotrzeć możemy albo przez lodz, albo przez samochod.

***************************************************************************

!!!Odpowiedzi na pytanie Justunki:

1) Powyższy przykład bez słowek virtual, gdy jedna klasa jest dziedziczona public a druga private.

Najpierw jest sprawdzana wieloznaczność, a dopiero potem ewentualny dostęp.

Dlatego on najpierw zauważ , że w klasie amfibia są dwa takie same składniki, a potem będzie sprawdzał jaki jest do nich dostęp (do jednych public, a do drugich private).

Czyli i tak będzie wieloznaczność.

2) Czym różni się przypisanie bit po bicie od składnik po składniku?

Rozważmy to na przykładzie (str. 518, ale nieco zmodyfikowany): Mamy klasę podstawową pojazd, w której mamy napisany nasz operator= przypisanie (ważne jest on publiczny). Następnie mamy klasę automobil, która dziedziczy z klasy pojazd. Jak wiadomo operator= z pojazdu nie zostanie odziedziczony przez automobil (tylko tego operatora się nie dziedziczy). Teraz ta mała modyfikacja: w klasie pochodnej (czyli tej automobil) nie piszemy operatora=. Co się stanie, gdy przypiszemy dwa obiekty typu automobil? Komputer sam wygeneruje sobie operator= (bo jak pamiętacie jest to 1 z 5 operatorów predefiniowanych) i … (właśnie co dalej):


  1. W starszych wersjach C++ stosowano metodę bit po bicie, czyli kompilator kopiował dokładnie bit po bicie każdy składnik klasy automobil, zarówno ten odziedziczony z klasy pojazd, jak i ten dodatkowy. Identyczne kopia.

  2. W nowszym C++ kopiuje składnik po składniku. Co to oznacza? Dla części odziedziczonej z klasy pojazd uruchomi operator=, który tam napisaliśmy, a elementy dodatkowe klasy automobil skopiuje składnik po składniku (dokładnie skopiuje). Mówiąc krótko kompilator pomyśli i wykorzysta to co my napisaliśmy.

Ta sama zasada dotyczy konstruktora kopiującego w klasie podstawowej i tego automatycznego w klasie pochodnej.

***************************************************************************

Publiczne i prywatne dziedziczeni tej samej klasy wirtualnej:

class srodek_transportu

{



};



class samochod: public virtual srodek_transportu

{



};

class lodz:private virtual srodek_transportu

{

...


};

class amfibia: public samochod,public lodz

{

....


};
Składniki w klasie amfibia będą odziedziczone publicznie ponieważ wystarczy by choć jedno dziedziczenie wirtualnej klasy podstawowej było publiczne, a efekt jest taki, jakby wszystkie pozostałe dziedziczenia tej klasy były także publiczne.
Funkcje wirtualne:

Przykład to program 44

Jak myśli kompilator?

1)

Kompilator widzi, że ma do czynienia ze wskaźnikiem do pokazywania na instrument. Obok tego wskaźnika stoi wywołanie jakieś funkcji składowej. Sięga więc na ślepo do klasy instrument i uruchamia tę funkcję.



To był głupi kompilator i uruchomił funkcję właściwą typowi wskaźnika, czyli bez słówka virtual przed nazwą funkcji.

2)

Kompilator widzi, że ma do czynienia ze wskaźnikiem do instrumentu. Nie sięga na ślepo do klasy instrument, tylko używa swojej inteligencji: nie daje się zwieść typem wskaźnika, tylko sprawdza na co on pokazuje. Orientuje się natychmiast, że wskaźnik pokazuje aktualnie na obiekt klasy trąbka (w innej linijce jest to fortepian, albo bębenek) zatem: orientując się według typu obiektu uruchamia funkcję właściwą pokazywanemu obiektowi. (A nie właściwą typowi wskaźnika).



Czyli nie z klasy instrument tylko z klasy trąbka (bębenek, fortepian,…)

To jest te inteligentny kompilator, sytuacja ze słówkiem virtual prze funkcji.

Obydwa rozumowania są poprawne, a o wyborze jednego z nich decyduje właśnie to cudowne słówko virtual.

Referencja, czyli przezwisko, które widzi jest przezwiskiem nadawanym instrumentowi. Obok tego przezwiska stoi wywołanie funkcji składowej. Gdyby nie słówko virtual, to znów bezmyślnie poleciał by do klasy instrument i wywołał funkcję wydaj_dzwiek. Ale tam jest to słówka, więc on se mówi- teraz jestem inteligentny- więc patrzy na obiekt przezwany tym przezwiskiem stwierdza, że to obiekt klasy pochodnej trąbka (bębenek, …) . Więc uruchamia funkcję z klasy trąbka.

Polimorfizm: funkcja wirtualna nie jest polimorficzna (czyli wielość-form0 tylko ten fragment programu, w którym wywołuje się funkcję wirtualną jest polimorficzny.

Funkcja składowa jest wirtualna gdy w definicji klasy przy jej deklaracji stoi słówko virtual, lub gdy w jednej z klas podstawowych tej klasy identyczne funkcja zadeklarowana jest jako virtual. Identyczna tzn. z identyczną nazwą, argumentami argumentami typem rezultatu.

Słówko virtual może wystąpić tylko raz w klasie podstawowej i nie musi już powtarzać się przy analogicznych funkcjach w klasach pochodnych (ale może).

Jeśli klasa pochodna nie zdefiniuje swojej wersji funkcji wirtualnej to zostanie wywołana funkcja z klasy podstawowej. Czyli gdyby w trąbce nie było funkcji wydaj dźwięk to na rzecz trąbki (trabka.wydaj_dziwek()) wypisywało by „Nieokreślony dziwię”.

Gdy w klasie instrument funkcja wydając dźwięk była by public, a w trąbce private to o dostępie do funkcji decyduje jakiego typu wskaźnik wskazuje na tą funkcję. Gdy wywołamy przy pomocy wskaźnika do obiektu instrument to będzie ok., a przy pomocy wskaźnika do trąbki już nie.

Funkcja wirtualna nie może być stanic. Czemu? Bo o tym jaka wersja funkcji zostanie wywołana decyduje się na podstawie typu obiektu, a funkcje static nie są wywoływane na rzecz obiektu, tylko na rzecz klasy.

Klasa zawierająca funkcję wirtualne ma większy rozmiar, a wywołanie funkcji wirtualnej trwa dłużej.

Wczesne wiązanie: wszystkie decyzje o tym jaką funkcję w danym miejscu wywołamy podejmowane są w czasie kompilacji. Jeśli decyzja o rodzaju wykonywanej funkcji wirtualnej jest podejmowana w trakcie działania programu to wiązanie jest poźne.

Wczesne wiązanie dla funkcji wirtualnych (mimo wszystko):



  1. wywołanie na rzecz obiektu

obiekt.funkcja();

  1. jawne użycie kwalifikatora zakresu:

wskaźnikk->klasa::funkcje();

  1. wywołanie z konstruktora klasy podstawowej:

jeżeli podczas tworzenia obiektu potomnego rusz do akcji konstruktor klasy podstawowej (do tworzenia części odziedziczonej) i w tym konstruktorze wywołano funkcje wirtualną, to uruchomiona zostanie wersja z klasy podstawowej.

Funkcja wirtualna może być inline, ale:



  1. gdy wywołanie funkcji odbywa się dla obiektu klasy pochodnej pokazywanego wskaźnikiem(referencją) do klasy podstawowej to przydomek inline jest ignorowany;

  2. jednak gdy na etapie kompilacji wiadomo dla jakiego obiektu jest wywołanie funkcji to będzie ona inline.

Klasa abstrakcyjna: to klasa, która nie reprezentuje żadnego konkretnego obiektu. Czyli to taka klasa, która przechowuje jakieś wspólne cechy klasy pochodnych. To jakby niedokończona klasa, a jej dokończenie jest zrealizowane dopiero w klasie pochodnej.

  1. Jeżeli w klasie jest funkcja wirtualna to klasa jest abstrakcyjna, ale:

  1. można wtedy zdefiniować obiekt typu klasy abstrakcyjnej (ale zazwyczaj nie ma to sensu);

  2. jeżeli w klasie pochodnej nie zdefiniujemy funkcji wirtualnej (tego odpowiednika) to wtedy w momencie wywołanie funkcji na rzecz tego obiektu zostanie uruchomiona właśnie funkcja wirtualna z klasy podstawowej;

  1. Jeżeli w klasie jest funkcja czysto wirtualna (klasa też jest abstrakcyjna):

  1. Jak wygląda taka funkcja void virtual funkcja()=0; //brak definicji;

  2. Wtedy już na pewno nie można zdefiniować obiektu typu klasy, w której ta funkcja cysto wirtualna jest.

    • Jeśli ta funkcja jest w klasie figura to napisanie takiej linii jest błędem: figura f1;

    • Nie można też zdefiniować funkcji (np. gdzieś globalnie), która odbierała by przez wartość (pamiętacie bez * lub &) obiekt typu figura; np. void funkcja(figura x);

    • Funkcja nie może też zwracać przez wartość obiektu typu abstrakcyjnego (figura);

    • Klasa abstrakcyjna nie może być typem w jawnej konwersji, czyli nie da się zamienić np. kwadratu na figurę.

3)Jeżeli w klasie jest funkcja czysto wirtualna i zdefiniowaliśmy ją:

np. void virtual funkcja()=0

{

…..


}

To jakby przypadek 2. dodatkowo wyposażony w funkcję, której prawie nigdy nie da się uruchomić. A kiedy się da? Ano wtedy gdy jawnie wywołamy ją z operatorem zakresu, np. wskaźnik-> figura::funkcja();. Można ją też wywołać z wnętrza konstruktora klasy podstawowej (czyli tego figura()).



Wirtualny destruktor:

Dodajemy przy deklaracji słówko virtual: virtual ~instrument();

Dobra rada: Jeśli klasa deklaruje jedną ze swoich funkcji jako virtual jej destruktor także powinien być virtual.

To jedyny wyjątek, gdzie funkcje wirtualne mają różne nazwa.

Trzymajmy się ciągle przykładu z instrumentami.

Skoro wskaźnikiem typu instrument możemy wskazywać na trąbkę to możemy powiedzieć usuń ten instrument, a będziemy mieli na myśli trąbkę. Gdy destruktor nie był wirtualny to co nasz kompilator by zrobił? Odpalił by destruktor dla takiego typu jaki ma wskaźnik (pamiętaj że to samo tyczy się referencji) nie patrząc na co on wskazuje (czyli destruktor instrumentu). Efekt może być fatalny, ponieważ klasa podstawowa instrument miała mniej składników, a destruktor z instrumentu ”usunie” (pamiętajcie że destruktor nie niszczy, tylko sprząta) tylko te odziedziczone z instrumentu. Pozostałe dodatkowe składniki w trąbce pozostaną. Od razu widać dlaczego destruktor musi być wirtualny. Wtedy kompilator nie odpali destruktora dla takiego typu jakim jest wskaźnik, ale dla takiego typu, na jaki wskazuje ten wskaźnik.

Przykład str. 579.

Uwaga. Zagadka ze strony 582 (u dołu)

Konstruktor nie wirtualny, ale jednak…:

Chcemy by postał obiekt, ale w trakcie pisania nie wiemy jeszcze jaki (dowiemy się dopiero w trakcie wykonywanie programu). Utworzenie konstruktora wirtualnego jest niemożliwe. Czemu? Ponieważ każda funkcja wirtualna była wywoływana na rzecz obiektu na który wskazywał wskaźnik lub referencja. A konstruktor nie jest wywoływany na rzecz obiektu, bo w tym momencie obiekt jeszcze nie istnieje (dopiero go tworzymy). Można to obejść tworząc dodatkowe wirtualne funkcje składowe. Patrz przykład str. 584. lub program 47.


Koniec omawiania definicji języka C++. Znamy język C++. 

ZA WSZELKIE BŁEDY ORTOGRAFICZNE INTERPUNKCYJNE I INNE NIE BIORE ŻADNEJ ODPOWIEDZIALNOŚCI 

NO I TO BY BYŁO NA TYLE POWODZENIA !!!!! HEHE

AAA I ZAPOMNIAŁBYM KOPIOWANIE I ROZPOWSZECHNIANIE JEST SÓROWO ZABRONIONE POD KARA NIEZDANIA EGZMINU :P







©snauka.pl 2016
wyślij wiadomość