programowanie obektowe - opracowania do kolosa, PWR, Programowanie obiektowe


Wykład 1

Podstawowe pojęcia związane z programowaniem obiektowym

obiekt i klasa

abstrakcja

hermetyzacja

polimorfizm

dziedziczenie

Prosta klasa:

pola klasy (dane)

funkcje składowe klasy (metody operujące na danych obiektu)

prawa dostępu do składowych klasy - publiczne i prywatne

Klasa

Klasa - definicja Klasa to zdefiniowanie własnego typu danych - wymyślony na potrzeby danego programu. Ten typ, to nie tylko jedna lub kilka zebranych liczb, ale to również sposób ich zachowania jako całości.

class Klasa
{
};

Klasa, tak jak struktura, może zawierać dane. Klasa pozwala na ustawianie praw dostępu do składowych przy pomocy trzech słów kluczowych private, protected i public, zwanych specyfikatorami dostępu.
0x01 graphic

Obiekt

Obiektem nazywamy egzemplarz klasy. Tworzymy go jak zmienną, ponieważ, w istocie, to jest zmienna.

Klasa obiekt;

Do składowych odwołujemy się tak, jak do składowych struktury.

int main()
{
    Klasa obiekt
;
    obiekt
.publiczne1 = 1; //Składowa publiczna = jest dostęp = wyrażenie jest poprawne
    obiek
t.chronione1 = 1; //Składowa chroniona = brak dostępu = błąd kompilacji
    obiekt
.prywatne1 = 1; //Składowa prywatna = brak dostępu = błąd kompilacji
}

Klasa - dostęp do danych składowych klasy

• obiekt - do danej składowej klasy można odwołać się poprzez operator . obiekt.skladnik

• wskaźnik - odniesienie się do danej składowej obiektu pokazywanego wskaźnikiem można odwołać się poprzez operator -> wskaznik->składnik

• referencja - do danej składowej obiektu znanego z referencji można odwołać się poprzez operator . referencja.skladnik Klasa - dostęp do funkcji składowych klasy

Abstrakcja

Każdy obiekt w systemie służy jako model abstrakcyjnego "wykonawcy", który może wykonywać pracę, opisywać i zmieniać swój stan oraz komunikować się z innymi obiektami w systemie bez ujawniania, w jaki sposób zaimplementowano dane cechy. Procesy, funkcje lub metody mogą być również abstrahowane

Hermetyzacja

Czyli ukrywanie implementacji, enkapsulacja. Zapewnia, że obiekt nie może zmieniać stanu wewnętrznego innych obiektów w nieoczekiwany sposób. Tylko własne metody obiektu są uprawnione do zmiany jego stanu. Każdy typ obiektu prezentuje innym obiektom swój interfejs, który określa dopuszczalne metody współpracy

Dziedziczenie. Ogólnie mówiąc polega ono na tworzeniu nowych klas na podstawie już istniejących.. Tak więc zdefiniujmy sobie taką klasę.

  1. class pojazd

  2. {

  3. public:

  4. int predkosc;

  5. int przyspieszenie;

  6. int ilosc_kol;

  7. int kolor;

  8. };

Znajdują się w niej zaledwie cztery składniki opisujące pojazd. Dla ułatwienia wszystkie one są publiczne. Jeden pojazd to jednak zbyt mało. Chcesz dać graczowi możliwość wyboru i tworzysz pojazd z dopalaczem

  1. class super_pojazd

  2. {

  3. public:

  4. int predkosc;

  5. int przyspieszenie;

  6. int ilosc_kol;

  7. int kolor;

  8. int dopalacz;

  9. };

klasy są niemal jednakowe. Odróżnia je zaledwie jeden detal. Jest to dopalacz w klasie drugiej. Zatem klasa super_pojazd jest jakby rozbudowaną klasą pojazd. Zamiast pisać na piechotę całą klasę super_pojazd wystarczy poinformować kompilator, że ta klasa jest rozwiniętą wersją klasy pojazd.

  1. class super_pojazd : public pojazd

  2. {

  3. public:

  4. int dopalacz;

  5. };

W wyniku takiego zapisu otrzymaliśmy klasę super_pojazd, która jest pochodną od klasy pojazd. Oznacza to, że zawiera ona wszystkie wady i zalety klasy swojego przodka. Składniowo chyba wszystko jest jasne. Tuż na nazwą klasy pochodnej [u nas super_pojazd] stawiamy dwukropek. Teraz trzeba jeszcze określić sposób dziedziczenia. Ustala się to za pomocą etykiet, które już poznałeś .Dziedziczenie prywatne oznacza, że wszystkie składniki klasy podstawowej staną się niedostępne w klasie pochodnej. Niezależnie od sposobu dziedziczenia, prywatne składniki klasy podstawowej zawsze pozostaną niedostępne w klasie pochodnej! Podczas dziedziczenia chronionego, składniki prywatne klasy podstawowej, staną się niedostępne w klasie pochodnej. Zmienią się składniki publiczne i chronione. W klasie pochodnej będą chronione. Dziedziczenie publiczne jest najprostsze i chyba najczęściej stosowane. Praktycznie nie powoduje żadnych zmian.

Polimorfizm (wielopostaciowość) jest to cecha programowania obiektowego, umożliwiająca różne zachowanie tych samych metod wirtualnych (funkcji wirtualnych) w czasie wykonywania programu

Metoda wirtualna jest to funkcja składowa klasy poprzedzona słowem kluczowym virtual, której sposób wywołania zależy od typu dynamicznego wskaźnika, a nie od typu statycznego

Aby funkcje zostały przesłonięte muszą mieć taką samą nazwę, argumenty oraz typ zwracany:

Zmienne w klasie nazywamy polami

class osoba {

private:

    string imie;    //pole prywatne

    int wiek;       //pole prywatne

public:

    void UstawImie(string imie, int wiek) {

        this->imie = imie;

        this->wiek = wiek;  //deklaracja metody wewnątrz klasy

     }

    void WypiszImie();      //tylko definicja metody

Słówko this oznacza, że funkcja odwołuje się do pól klasy(a konkretniej jej przyszłej instancji), w której została zdefiniowana.

0x01 graphic

Konstruktor to specjalna funkcja o nazwie takiej jak klasa. Deklaruje się (i definiuje) go podobnie do zwykłej funkcji, nie jest konieczna jego obecność, nie mogą zwracać wartości w deklaracji ale mogą mieć domyślne argumenty lub używać list argumentów inicjujących dane obiektów. Konstruktor określa jak obiekt jest inicjowany i kopiowany.

Konstruktor - Specjalna funkcja składowa, która nazywa się tak samo jak klasa

W ciele możemy zamieścić instrukcje nadające składnikom obiektu wartości początkowe

Konstruktor sam nie przydziela pamięci na obiekt

Konstruktor jest automatycznie uruchamiany przy definiowaniu każdego obiektu danej klasy

Konstruktor można przeładowywać - może być kilka w jednej klasie

Konstruktor nic nie zwraca - ale nie jest typu void !!!

Konstruktor może być jawnie wywołany - nazwa_klasy (argumenty )

Konstruktor domniemany (można go wywołać bez żadnych argumentów) - nazwa klasy(void)

Konstruktor może być nie publiczny

Kon struktur kopiujący - inicjalizator kopiujący

Wywołanie : klasa::klasa( klasa &) -Argumentem jest referencja

Konstruktor kopiujący służy do skonstruowania obiektu który jest kopią innego już istniejącego obiektu tej klasy

Konstruktor kopiujący może być wywołany z jednym argumentem lub zwiększą ich ilością pod warunkiem ze pozostałe są domniemane

Konstruktor kopiujący nie jest obowiązkowy jeżeli go nie zdefiniujemy kompilator zrobi to sam

Konstruktor kopiujący może zostać wywołany jawie lecz jest on również wywoływany bez naszej wiedzy

Deklaracja: klasa( klasa & kl)

class TPunkt

{

private:

int x;

int y;

public:

int getx(){return x;}

int gety(){return x;}

void putx(int xx){x=xx;}

void puty(int yy){y=yy;}

TPunkt(){} //konstruktor - nazwa konstruktora jest taka sama jak nazwa klasy

};

Konstruktor jest funkcją, przy której najczęściej spotyka się przeładowanie nazw. Inaczej mówiąc konstruktor może mieć różne zestawy argumentów inicjujących obiekt. To, który konstruktor zostanie wywołany wynika z kolejności i typu wrgumentów wywołania.

class TPunkt

{

public:

TPunkt(){} //konstruktor bez argumentów

TPunkt(int xx, int yy){x=xx;y=yy;} //konstruktor z dwoma argumentami typu int

};

Teraz w zależności od sposobu tworzenia obiektu zostanie wywołany odpowiedni konstruktor.

void main(void)

{

TPunkt p1; //zostanie wywołany konstruktor TPunkt(){} klasy TPunkt

TPunkt p2(10,20); //zostanie wywołany konstruktor TPunkt(int xx, int yy){x=xx;y=yy;} klasy TPunkt

}

Konstruktor TPunkt(){} bez argumentów jest nazywany konstruktorem bezparametrowym. Przykładowo można wprowadzić jego modyfikację w celu zerowania pól x i y.

class TPunkt

{

public:

TPunkt(){ x = y = 0;} //konstruktor bezparametrowy

};

Konstruktor TPunkt(int xx, int yy){x=xx;y=yy;} z argumentami jest nazywany konstruktorem parametrowym z inicjalizacją.

class TPunkt

{

public:

TPunkt(int xx, int yy){x=xx;y=yy;} //konstruktor parametrowy z inicjalizacją

};

Konstruktor jest niejawnie wywoływany zawsze wtedy, gdy tworzony jest obiekt jego klasy. Szczególnym rodzajem konstruktora jest konstruktor kopiujący. Służy on do tworzenia nowego obiektu w oparciu o już istniejący.

Teraz podczas tworzenia obiektów można stworzyć obiekt klasy na podstawie już istnejącego obiektu.

Podsumowując. Konstruktory mają taką samą nazwę co klasa, nie posiadają typu, parametrami konstruktora nie mogą być obiekty do której należy (mogą być referencje).

Konstruktor

• Konstruktor może być przeładowany. Jest to bardzo częsta praktyka, w definicjach klas widzi się zwykle kilka wersji konstruktora (różnią się listą argumentów)

•Konstruktor nie ma wyspecyfikowanego żadnego typu wartości zwracanej.

Nie zwraca nic - nawet typu void!

• Konstruktor może być wywoływany dla tworzenia obiektów z przydomkami const i volatile, ale sam nie może być funkcją typu const i volatile.

• Konstruktor nie może być typu static - między innymi dlatego, że ma pracować na niestatycznych składnikach klasy.

• Osobliwością konstruktora jest to, że jest on wywoływany automatycznie ilekroć powołujemy do życia nowy obiekt danej klasy.

• Konstruktor nie może być także typu virtual.

• Nie można posłużyć się adresem konstruktora

• Jeśli obiekt ma być składnikiem unii, to jego klasa nie może mieć żadnego konstruktora.

Konstruktor -jawne wywołanie Obiekt może być też stworzony przez jawne wywołanie konstruktora. W efekcie otrzymujemy obiekt, który nie ma nazwy, a czas jego życia ogranicza się do wyrażenia, w którym go użyto. nazwa_klasy( argumenty ) Zauważ, że wywołujemy konstruktor - czyli funkcję składową, a nie stosujemy notacji

obiekt.funkcja_skladowa( argumenty )

Konstruktor domniemany to taki konstruktor, który można wywołać bez żadnego argumentu. Zauważmy, że nie mówimy „konstruktor bez argumentów”, tylko „konstruktor, który można wywołać bez żadnych argumentów”.

W świetle tej definicji konstruktorem, który można wywołać bez żadnych argumentów jest konstruktor ze wszystkimi argumentami domniemanymi.

Klasa może mieć tylko jeden konstruktor domniemany.Jeśli klasa nie ma w ogóle żadnego konstruktora, wówczas sam kompilator wygeneruje dla tej klasy konstruktor domniemany

Konstruktor może być nie - publiczny.

Jest on składnikiem klasy i jako takiego - obowiązują go również zwykłe reguły dostępu ustalane za pomocą słów: public/protected/private.

Klasa, która nie ma publicznych konstruktorów nazywana jest klasą prywatną. Pomimo tego, iż konstruktor jest niedostępny dla tzw. szerokiej publiczności, jest dostępny dla obiektów tej klasy.

Funkcja zaprzyjaźniona czy też klasa zaprzyjaźniona ma również dostęp do prywatnych składników klasy więc mogłaby uruchomić prywatny konstruktor.

Konstruktor kopiujący Konstruktorem kopiującym w danej klasie klasa nazywamy konstruktor, który można wywołać z jednym argumentem poniższego typu: klasa::klasa(klasa&);

• Argumentem jest, jak widać, referencja (przezwisko) obiektu danej klasy.

• Konstruktor ten służy do konstruowania obiektu, który jest kopią innego, już istniejącego obiektu tej klasy.

• Konstruktor kopiujący nie jest obowiązkowy. Jeśli go nie zdefiniujemy wówczas kompilator wygeneruje go sobie sam.

• Konstruktor kopiujący inaczej można nazwać inicjalizatorem kopiującym.

• Konstruktor kopiujący jest wywoływany w kilku sytuacjach, które można najogólniej podzielić na:

• Gdy tego jawnie zażądamy, • Bez naszej wiedzy.

• Wywołanie konstruktora kopiującego na nasze życzenie następuje wtedy, gdy tego jawnie zażądamy i definiujemy nowy obiekt w następujący sposób: NowyTyp ObiektWzorcowy; NowyTyp NowyObiekt = NowyTyp( ObiektWzorcowy ); Niejawne wywołanie konstruktora kopiującego klasy NowyTyp następuje w kilku sytuacjach:

• Podczas przesyłania argumentów do funkcji - jeśli argumentem funkcji jest obiekt klasy NowyTyp, a przesyłanie odbywa się przez wartość.

• Podczas, gdy funkcja jako swój rezultat zwraca przez wartość obiekt klasy NowyTyp

Konstruktor -wady

• Nie można zdefiniować konstruktora dla typu wbudowanego.

• Nie można napisać konstruktora dla klasy, która nie jest naszą własnością -

• Przy konstruktorze konwertującym argument musi pasować dokładnie do typu argumentu deklarowanego w konstruktorze. Nie możemy polegać na żadnych - tak zwanych konwersjach standardowych.

• Nawet jeśli klasa jest naszą własnością, to konstruktor, który chcemy napisać, musi oprzeć się na informacjach z tej obcej klasy. Tamta obca klasa musi zapewnić sposoby dotarcia do tych informacji. (Robi się to: albo przez publiczne dane składowe, albo przez deklarację przyjaźni). Jeśli ta obca klasa nie zapewnia nam tych informacji, to musimy ją zmodyfikować.

• Konstruktora służącego do konwersji nie dziedziczy się (bo nie dziedziczy się żadnych konstruktorów).

Destruktor

• Destruktor jest przeciwieństwem konstruktora, czyli funkcja składowa wywoływana wtedy, gdy obiekt danej klasy ma być likwidowany.

• Destruktor to funkcja składowa klasy. Nazywa się tak samo, jak klasa z tym, że przed nazwą ma znak ~ (tylda). Podobnie jak konstruktor - nie ma on określenia typu zwracanego.

• Destruktorem klasy K jest funkcja składowa o nazwie ~NowyTyp (wężyk i nazwa klasy). Funkcja ta jest wywoływana automatycznie zawsze, gdy obiekt jest likwidowany.

• Klasa nie musi mieć obowiązkowo destruktora. Destruktor nie likwiduje obiektu, ani nie zwalnia obszaru pamięci, który obiekt zajmował. Destruktor przydaje się wtedy, gdy przed zniszczeniem obiektu trzeba jeszcze dokonać jakichś działań. Po prostu trzeba posprzątać

• Jeśli na przykład obiekt reprezentował okienko na ekranie, to możemy chcieć, by w momencie likwidacji tego obiektu okienko zostało zamknięte, a ekran wyglądał jak dawniej.

• Destruktor jest potrzebny, gdy konstruktor danej klasy dokonał na swój użytek rezerwacji dodatkowej pamięci (operatorem new). Wtedy w destruktorze umieszcza się instrukcję delete zwalniającą ten już nie potrzebny obszar pamięci.

• Destruktor może się też przydać, gdy liczymy obiekty danej klasy. • Niemożliwe jest pobranie adresu destruktora.

• Obiekt klasy mającej destruktor nie może być składnikiem unii.

• Destruktor nie jest wywoływany z żadnymi argumentami. W związku z tym nie może być przeładowany. Destruktor jest automatycznie wywoływany, gdy obiekt automatyczny lub chwilowy wychodzi ze swojego zakresu ważności.

• Jeśli obiekt lokalny jest statyczny, to mimo, że kończy się jego zakres ważności - nie jest likwidowany - więc także nie uruchamia się jego destruktora. Likwidacja następuje dopiero przy zakończeniu programu i wtedy też rusza do pracy destruktor.

• Jeśli kończy się zakres ważności referencji (przezwiska) obiektu - destruktor nie jest wywoływany. Analogicznie nie jest automatycznie wywoływany, gdy wskaźnik do jakiegoś obiektu wychodzi ze swojego zakresu.

• Destruktor nie może być ani const ani volatile, ale może pracować na obiektach swojej klasy z takimi przydomkami.

Wywołanie nieistniejącego destruktora

Może się zdarzyć, że klasa, którą się posługujemy, nie ma destruktora. Jeśli mimo to jawnie go wywołamy, to wywołanie takie zostanie zignorowane. Można również wywołać destruktor dla typu wbudowanego. Także i takie wywołanie jest dopuszczalne, ale ignorowane.

 Metody stałe

Metody klasy mogą być zdeklarowane jako metody stałe. Oznacza to „obietnicę”, że dana metoda nie zmienia stanu obiektu, na rzecz którego została wywołana, czyli nie zmienia żadnej jego składowej. Deklarujemy metodę jako stałą umieszczając słowo kluczowe const tuż za nawiasem zamykającym listę parametrów, a przed

Oczywiście, deklarujemy tę stałość tylko raz, jeśli metodę definiujemy bezpośrednio wewnątrz klasy. Jeśli natomiast, jak to zwykle czynimy, w klasie tylko metodę deklarujemy, a definicję podajemy poza klasą, być może w ogóle w innym pliku, to pamiętać trzeba, że dwie metody - nawet o tej samej sygnaturze - z których jedna jest stała, a druga nie są różnych typów! Zatem

deklaracja stałości musi wystąpić zarówno w definicji, jak i w deklaracji metody.

Metoda stała nie może zmienić składowych obiektu, na rzecz którego została wywołana, ale może zmienić składowe innych obiektów tej samej klasy, do których ma dostęp.

Wykład 2

Prosta klasa. Podział programu na kilka plików

Prawa dostępu - przypomnienie z poprzedniego wykładu

Wywołanie funkcji składowej. "Zmienna" this

Inicjalizacja obiektów - konstruktory

inicjalizacja pól klasy w ciele konstruktora

inicjalizacja pól klasy za pomocą listy inicjalizacyjnej konstruktora

kiedy trzeba stosować ten sposób ?

Likwidowanie obiektów - destruktor

Składowe stałe klasy (const)

stałe pola (np. const int wartosc_stala; )

stałe "metody" (np. void f() const; )

Składowe statyczne klasy - pola i funkcje

Składowe stałe i statyczne (jednocześnie)

Składowe volatile

0x01 graphic

Lista inicjalizacyjna jest rozszerzeniem możliwości zwykłego konstruktora. Jej zadaniem jest inicjalizacja składowych nowego obiektu. Ważnym jest fakt, że wykonuje się ona jeszcze zanim obiekt zacznie istnieć.

Na pierwszy rzut oka ciężko znaleźć różnicę pomiędzy konstruktorem a listą inicjalizacyjną. Obydwa mechanizmy są ze sobą bardzo ściśle związane. Sama lista stanowi poszerzenie możliwości konstruktora, a więc nie można zdefiniować listy inicjalizacyjnej nie definiując konstruktora w danej klasie.

Cechą konstruktorów jest to, że wykonują się one w momencie kiedy obiekt klasy już istnieje. Co za tym idzie, konstruktory mogą modyfikować wartości składowych klas jednak w niektórych przypadkach staje się to niemożliwe. Jeżeli składową klasy jest zmienna const wtedy nie będziemy mieli możliwości nadania jej wartości poprzez konstruktor.

Lista inicjalizacyjna  znajduje się w definicji konstruktora i poprzedzona jest dwukropkiem. Argumenty w liście inicjalizacyjnej przypisywane są składowym klasy w specyficzny sposób

Funkcje statyczne

Deklaracja funkcji składowej static jest bardzo podobna do deklaracji pola static. Funkcja, podobnie jak pole statyczne może być użyta bez wskazania określonego obiektu, stosując jedynie nazwę klasy. Co więcej, funkcja może być wywołana wtedy, gdy nie istnieje jeszcze żaden obiekt danej klasy.

class TPunkt

{

private:

int x;

int y;

public:

static int x; //definicja pola statycznego

static void fun(int xx) //deklaracja funkcji statycznej

{

x=xx;

}

}

int TPunkt::x = 1; //deklaracja pola statycznego klasy TPunkt

Istnienie w klasie funkcji statycznej wynika z istnienia pól statycznych tej klasy. Jeżeli w klasie jest zdefiniowana funkcja, która operuje tylko na polach statycznych, to taką funkcję można zadeklarować jako statyczną. 

Zadeklarowanie funkcji składowej jako statycznej sprawia, że nie zawiera ona wskaźnika this. Z tego względu nie dotyczy ona konkretnego obiekt, ale klasy obiektów. Nie jest możliwe odwołanie się do pola nie-statycznego danej klasy.

Funkcje składowe klasy mogą być zadeklarowane jako statyczne. Takie funkcje można wywołać nawet wtedy, gdy nie istnieje jeszcze żaden obiekt klasy.

Do jej nazwy z zewnątrz klasy odwołujemy się poprzez nazwę klasy za pomocą operatora zasięgu, czyli „czterokropka” (' ::'), albo za pomocą operatora wyboru składowej (kropka) - nie ma wtedy znaczenia, jakiego obiektu tej klasy użyjemy.

Ponieważ funkcja statyczna nie jest wywoływana na rzecz obiektu, ale jak funkcja globalna, nie można w niej odwoływać się do this ani do żadnych składowych niestatycznych - te bowiem istnieją tylko wewnątrz konkretnych obiektów i w każdym z nich mogą być różne. Można natomiast w funkcjach statycznych klasy odwoływać się do składowych statycznych tej klasy: innych funkcji statycznych i zmiennych klasowych (określanych przez statyczne pola klasy).

Funkcje statyczne klasy od funkcji zadeklarowanych w zasięgu globalnym różni to, że należą do zakresu (przestrzeni nazw) klasy. Mają zatem bezpośredni dostęp do nazw z zakresu tej klasy (również prywatnych).

Modyfikator static sprawia, że obiekt w danej funkcji jest umieszczany w tej samej pamięci, co zmienna globalna i nie jest usuwany wraz z zakończeniem funkcji.

Zmienna lokalne statyczna nie jest usuwana. Będzie cały czas pamiętała swoją poprzednią wartość.
Zmienne lokalne statyczne są automatycznie inicjalizowane wartością 0.

Słowem kluczowym const deklaruje się tzw. stałe. Obiekty z modyfikatorem const mogą być inicjalizowane, natomiast nie mogą być przypisywane poza ich inicjalizacją

Możliwe jest użycie funkcji składowych z przydomkiem const, dzięki temu funkcja nie będzie modyfikować jego danych składowych

Podczas modyfikowania deklaracji danych, słowo kluczowe const określa, że nie można modyfikować obiektu lub zmiennej.

Karty CRC

Przykładowa implementacja wybranej karty CRC w postaci klasy

Testowanie karty w sytuacji "izolowanej"

Ciąg dalszy na temat tworzenia obiektów

typy obiektów (statyczne, automatyczne, tymczasowe, dynamiczne, składowe, tablice obiektów)

kolejność tworzenia obiektów

inicjalizacja

Metoda projektowania z użyciem kart CRC została zaproponowana przez jednego z propagatorów tzw. zwinnych metodyk Jest ona szczególnie przydatna na etapie definiowania odpowiedzialności poszczególnych klas i określania sposobu współpracy. Co waŜne, pomija ona całkowicie wewnętrzną strukturę klas, skupiając się wyłącznie na ich zachowaniu i odpowiedzialności. Dzięki temu złożoność procesu projektowania schematu klas jest ograniczona do minimum. Karty CRC są kartkami papieru podzielonymi na trzy części, opisującymi następujące własności klasy: nazwę klasy intuicyjnie opisującą jej odpowiedzialność.. odpowiedzialność zawierającą dłuższy opis zadań, jakie będą powierzone klasie, oraz współdziałanie, przedstawiające interakcje obiektu z innymi klasami.

1. Jakie zasady ("techniczne") obowiązują przy przeciążaniu operatorów ?

2 Jakie zasady ("zdroworozsądkowe") powinny obowiązywać przy przeciążaniu operatorów ?

3 Których operatorów nie można przeciążać ?

4 Których operatorów nie można przeciążyć jako funkcji globalnych ?

5 W jakiej sytuacji jesteśmy zmuszeni (w sensownej praktyce) do przeciążenia operatora jako funkcji globalnej ?

6 Załóżmy, że przeciążamy dwuargumentowy operator mnożenia. Ile argumentów będzie miała funkcja operator*(...) jeśli przeciążymy ją jako funkcję składową ?

7 Załóżmy, że przeciążamy jednoargumentowy operator negacji. Ile argumentów będzie miała funkcja operator!(...) jeśli przeciążymy ją jako funkcję globalną ?

8 Jeśli efektem działania operatora jest powstanie nowego obiektu, to zwracamy ...... ?

9 Jeśli wynikiem działania operatora jest jeden z jego argumentów (zmodyfikowany lub nie), to zwracamy ..... ?

10 W jaki sposób definiujemy konwersję z obcego typu do "naszej klasy" ? Jak uniknąć przypadkowego zdefiniowania takiej konwersji ?

11 W jaki sposób definiujemy operację konwersji z naszej klasy do obcego typu ?

12 Jakie problemy mogą się pojawić po zdefiniowaniu operatorów konwersji "w obie strony" (tzn np. double->Zespolona i Zespolona->double) ?

1 Przeładowanie operatorów - pozwala na zmianę działania operatorów np. + przeładowujemy aby dodawał nam dwa składniki naszej klasy ( domyślnie potrafi on dodawać tylko liczby które są typu wbudowanego

Parę uwag co do operatorów: Nie można zmienić ich priorytetów, argumentowości, argumenty operatorów nie mogą być domniemane, redefiniować operatory można gdy co najmniej jeden argument jest typu zdefiniowanego przez użytkownika. operatory =, [], (), -> muszą być niestatycznymi funkcjami składowymi w danej klasie.


Przeładowywanie operatorów, zamiennie można używać słowa przeciążanie, wykorzystuje się dla struktur lub klas. Nie można ich przeciążyć gdzie inaczej niż tam.

3 Operatory które nie mogą być przeładowane :

.

.*

::

4 Jeśli operator sięga po obiekt, aby jedynie na nim pracować bez jego modyfikacji, to lepiej jest zdefiniować go jako globalną funkcję. Jeśli ma pracować na polach prywatnych danej klasy, to należy przeciążany operator zaprzyjaźnić z klasą. nie wolno jest jednocześnie przeładować danego operatora zarówno jako funkcji składowej klasy oraz funkcji globalnej

5 Niektóre operatory są nieco „specjalne” i muszą być przeciążane jako metody, a nie jako funkcje globalne. Należą do nich operatory:

Co to jest konstruktor kopiujący? Jak jest zadeklarowany?

W jakich sytuacjach może zostać stworzona kopia obiektu?

Jak zachowuje się "standardowy" konstruktor kopiujący?

Jak zachowuje się "standardowy" operator przypisania?

* Konstruktor kopiujący służy do skonstruowania obiektu który jest kopią innego już istniejącego obiektu tej klasy

* Konstruktor kopiujący może być wywołany z jednym argumentem lub zwiększą ich ilością pod warunkiem ze pozostałe są domniemane

* Konstruktor kopiujący nie jest obowiązkowy jeżeli go nie zdefiniujemy kompilator zrobi to sam

* Konstruktor kopiujący może zostać wywołany jawie lecz jest on również wywoływany bez naszej wiedzy

Kompozycja i dziedziczenie, wprowadzenie do UML-a. Relacje: IS-A, HAS-A

Dziedziczenie. Tryby dziedziczenia.

Przesłanianie funkcji.

Konstruktory i destruktory w klasie pochodnej. Przekazywanie argumentów do konstruktora klasy bazowej.

Kolejność inicjalizacji (klasa bazowa, klasa pochodna, obiekty składowe)

Przy tworzeniu nowych klas można wykorzystywać już istniejące klasy za pomocą:

kompozycji, dziedziczenia.

Kompozycję stosuje się wtedy, gdy między klasami zachodzi relacja typu „całość ↔ część” tzn. nowa klasa zawiera w sobie istniejącą klasę.

Dziedziczenie stosuje się wtedy, gdy między klasami zachodzi relacja „generalizacja ↔ specjalizacja” tzn. nowa klasa jest szczególnym rodzajem juz istniejącej klasy

Kompozycję uzyskujemy poprzez definiowanie w nowej klasie pól, które są obiektami istniejących klas.

Przykład:

Klasa Osoba zawiera: pola nazwisko i imie, które należą do klasy String.

Klasa Ksiązka zawiera:

pole autor należące do klasy osoba,

pole tytul należące do klasy String,

pole cena typu double.

Dziedziczenie polega na przejęciu właściwości i funkcjonalności obiektów innej klasy i ewentualnej modyfikacji tych właściwości i funkcjonalności w taki sposób, by były one bardziej wyspecjalizowane.

UML- zunifikowany język modelowania do tworzenia systemów obiektowo zorientowanych. Diagram klas pokazuje klasy i zachodzące między nimi relacje.

Przesłanianie to właściwość polegająca na tym, iż deklarowany i definiowany w pewnym bloku o nazwie (identyfikatorze) identycznym z pewnym obiektem zewnętrznym (globalnym), staje się w tym bloku widoczny i wszystkie odwołania występujące w kodzie źródłowym realizowane za pomocą wybranej nazwy w obrębie tego bloku, są kierowane do obiektu lokalnego (element przesłaniający), natomiast obiekt zewnętrzny staje się w danym bloku niewidoczny, tzn. nie można się do niego odwołać wprost, za pomocą jego nazwy (element przesłaniany).

  Konstruktory i destruktory klas pochodnych

Jak wspomnieliśmy, konstruktory nie są dziedziczone. Jeśli w klasie pochodnej nie zdefiniowaliśmy konstruktora, to zostanie użyty konstruktor domyślny. Aby jednak powstał obiekt klasy pochodnej, musi być najpierw utworzony podobiekt klasy nadrzędnej wchodzący w jego skład. On również zostanie utworzony za pomocą konstruktora domyślnego, który zatem musi istnieć!

Podobiekt klasy bazowej jest tworzony jeszcze przed wykonaniem konstruktora klasy pochodnej.

Co w takim razie zrobić, aby do konstrukcji podobiektu klasy bazowej zawartego w tworzonym właśnie obiekcie klasy pochodnej użyć konstruktora innego niż domyślny? Wówczas musimy w klasie pochodnej zdefiniować konstruktor, a wywołanie właściwego konstruktora dla podobiektu klasy bazowej musi nastąpić poprzez listę inicjalizacyjną - wewnątrz konstruktora byłoby już za późno. Na liście tej umieszczamy jawne wywołanie konstruktora dla podobiektu. Tak więc, jeśli klasą bazową jest klasa  A i chcemy wywołać jej konstruktor, aby „zagospodarował” podobiekt tej klasy dziedziczony w klasie B, to na liście inicjalizacyjnej konstruktora klasy pochodnej  B umieszczamy wywołanie A(...), gdzie w miejsce kropek wstawiamy oczywiście argumenty dla wywoływanego konstruktora. Wywołuje się tylko konstruktory bezpośredniej klasy bazowej („ojca”, ale nie „dziadka”; oczywiście konstruktor ojca poprzez swoją listę inicjalizacyjną może wywołać konstruktor swojego ojca...).

 kolejność inicjalizacji zmiennych jest taka, w jakiej zostały one zadeklarowane w klasie, i jest niezależna od kolejności na liście inizjalizacyjnej. To, oraz inne standardy związane z kolejnością inicjalizacji w klasie, argumentują tym, że dzięki temu można zapewnić, iż składowe klasy (oraz klasy bazowe) będą niszczone w kolejności odwrotnej do tworzenia

Funkcje wirtualne

Wczesne i późne wiązanie (early/late binding)

Zastosowania funkcji wirtualnych

Wirtualny destruktor ?

Wirtualny konstruktor ?

Funkcje czysto wirtualne

Klasy abstrakcyjne

Funkcje wirtualne to specjalne funkcje składowe, które przydają się szczególnie, gdy używamy obiektów posługując się wskaźnikami lub referencjami do nich. Dla zwykłych funkcji z identycznymi nazwami to, czy zostanie wywołana funkcja z klasy podstawowej, czy pochodnej, zależy od typu wskaźnika, a nie tego, na co faktycznie on wskazuje. Dysponując funkcjami wirtualnymi będziemy mogli użyć prawdziwego polimorfizmu - używać metod klasy pochodnej wszędzie tam, gdzie spodziewana jest klasa podstawowa. W ten sposób będziemy mogli korzystać z metod klasy pochodnej korzystając ze wskaźnika, którego typ odnosi się do klasy podstawowej. W tej chwili może się to wydawać niepraktyczne, lecz za chwilę przekonasz się, że funkcje wirtualne niosą naprawdę sporo nowych możliwości.

Proces wczesnego wiązania polega na tym, że kompilator wywołuje identyfikatory funkcji na podstawie kodu źródłowego. Następnie linker pobiera te identyfikatory i zamienia je na adres fizyczny. W ten sposób identyfikatory funkcji łączone są z adresami fizycznymi przed wykonaniem programu w procesie kompilacji i konsolidacji programu. Problem z wczesnym wiązaniem polega na tym, że programista musi przewidzieć, jakie obiekty będą używane we wszystkich wywołaniach funkcji w każdej sytuacji. Daje to w wyniku dużą szybkość, ale brak elastyczności. 

Proces późnego wiązania jest bardziej złożony. Kod programu sam musi decydować w czasie swojego wykonania, którą funkcję należy wywołać. Wymusza to, aby kod wykonawczy sortował powiązania identyfikatorów i adresów funkcji. Daje to w wyniku skuteczny język, lecz stosowanie późnego wiązania spowalnia działanie programu. 

Do programisty należy decyzja, kiedy należy użyć wczesnego, a kiedy późnego wiązania.

Wirtualne destruktory należy stosować w klasach bazowych, które mogą być dziedziczone przez inne klasy. Destruktor wirtualny w klasie bazowej zapewnia wywołanie wszystkich destruktorów klas potomnych podczas niszczenia obiektu. Należy pamiętać, że ideą destruktorów jest zwalnianie zasobów, które zostały przydzielone w czasie pracy z obiektem. Niewywołanie destruktora może prowadzić więc do wycieków pamięci, a w konsekwencji do krytycznego błędu pisanej aplikacji. Słowo kluczowe virtual przy destruktorze klasy bazowej zapewnia wywołanie wszystkich destruktorów klas pochodnych, które dziedziczą po danej klasie bazowej. 

Nie ma czegoś takiego jak wirtualny konstruktor. Wynika to z tego, że zawsze trzeba określić klasę obiektu na rzecz którego wykonywany jest konstruktor, a więc nie ma tu zastosowania v-tables.

Funkcje czysto wirtualne

Określa to, że metoda z klasy bazowej deklarująca metodę wirtualną nigdy nie powinna się wykonać. W efekcie klasa taka staje się klasą abstrakcyjną. Oznacza to tyle, iż nie jest możliwe stworzenie obiektu tej klasy. Klasa taka służy jedynie temu, by zdefiniować pewnego rodzaju interfejs i jest przeznaczona jedynie po to, by od niej dziedziczyć.

W C++ klasą abstrakcyjną jest klasa, która posiada zadeklarowaną co najmniej jedną metodę czysto wirtualną. Każda klasa, która dziedziczy po klasie abstrakcyjnej i sama nie chce być abstrakcyjną, musi implementować wszystkie odziedziczone metody czysto wirtualne Idea klasy abstrakcyjnej

Klasa abstrakcyjna jest pewnym uogólnieniem innych klas (na przykład dla występujących w rzeczywistości obiektów), lecz sama jako taka nie istnieje. Ustalmy, że przez "figurę" będziemy rozumieć "koło", "kwadrat" lub "trójkąt". Te obiekty matematyczne mogą być reprezentowane przez pewne klasy. Obiekty te posiadają już konkretne właściwości takie jak promień (dla konkretnego koła) czy długość boku (dla konkretnego kwadratu). Klasy tych obiektów wywodzą się z pewnej uogólnionej klasy określanej jako po prostu figura. Jednak nie jesteśmy w stanie określić jaką konstrukcję miałby obiekt klasy figura, ponieważ figura geometryczna jako taka nie istnieje. Istnieją natomiast wywodzące się od niej klasy koło czy kwadrat. Dodatkowo oczywistym jest, że figura nie posiada konkretnej wartości pola czy obwodu, jednak już na tym etapie wiemy, że każda figura tak zdefiniowana (koło, kwadrat czy trójkąt) posiada pole i obwód, które będzie różnie obliczane dla różnych figur. Dzięki temu figura definiuje pewien interfejs dla klas wywodzących się od niej.

Obsługa błędów, wyjątków

Sposoby reagowania na błędne sytuacje:

Bardzo często zdarza się, że pisząc jakąś operację (funkcję), zauważamy, że ta operacja nie zawsze musi się dać poprawnie wykonać. Nasza funkcja powinna jakoś zareagować w takiej sytuacji, kłopot polega na tym, że nie wiemy jak. Może:

W celu rozwiązania takich problemów włączono do języka C++ mechanizm obsługi wyjątków. W C++ wyjątek oznacza błąd, zaś obsługa wyjątków oznacza reakcję programu na błędy wykryte podczas działania programu. Idea obsługi wyjątków polega na tym, że funkcja, która napotkała problem, z którym nie potrafi sobie poradzić zgłasza wyjątek. Wyjątek jest przesyłany do miejsca wywołania funkcji. Tam może być wyłapany i obsłużony lub może być przesłany dalej (wyżej). Podczas tego przechodzenia, przy wychodzeniu z funkcji i bloków następuje automatyczne usuwanie automatycznych obiektów stworzonych w tych funkcjach i blokach (to bardzo ważne). W C++ nie możliwości powrotu z obsługi wyjątku, do miejsca jego wystąpienia, w celu ponownego wykonania akcji, która spowodowała błąd.

Uwaga: Mechanizm obsługi wyjątków w innych językach może być zrealizowany zupełnie inaczej (np. wyjątek nie musi być utożsamiany z błędem, może być możliwe wznowienie wykonywania programu w miejscu wystąpienia wyjątku itp.). W szczególności nie ma ogólnej zgody czym powinien być wyjątek.

Zgłoszenie wyjątku:

throw <wyrażenie>;

Obsługa wyjątków może być podzielona między wiele funkcji:

void f1(){

try{ f2(w); }

catch (Wektor::Rozmiar) { /* ... */ }

}

void f2(Wektor& w){

try{ /* używanie wektora w */ }

catch (Wektor::Zakres) { /* ... */ }

}

W instrukcjach obsługujących wyjątek może się pojawić instrukcja throw. W szczególności może się też pojawić instrukcja throw zgłaszająca taki sam wyjątek, jak ten właśnie wywoływany. Nie spowoduje to zapętlenia ani nie będzie błędem. Z punktu widzenia języka C++ wyjątek jest obsłużony z chwilą wejścia do procedury obsługi wyjątku, zaś wyjątki zgłaszane w procedurach obsługi są obsługiwane przez funkcje wywołujące blok try. Można także zagnieżdżać bloki try-catch w instrukcjach catch (nie wydaje się to jednak celowe).

Mechanizm obsługi wyjątków

Nowoczesne obiektowe języki programowania mają wbudowany specjalny mechanizm ułatwiający i upraszczający radzenie sobie z obsługą systuacji wyjątkowych. W chwili wykrycia takiej sytuacji można stworzyć specjalny obiekt nazywany wyjątkiem (ang. exception), zawrzeć w nim wszystkie informacje na temat tego, co się stało i przy pomocy specjalnej instrukcji throw (w niektórych językach raise) zgłosić ten wyjątek do obsłużenia. Zgłoszenie wyjątku wymusza przerwanie normalnego trybu wykonywania programu i rozwinięcie stosu wywołań, aż do napotkania kontekstu zawierającego kod obsługi dla wyjątków tego rodzaju. Jeżeli cały stos zostanie rozwinięty, a wyjątku nie obsłużono, program jest przerywany.

Nieobsłużone wyjątki

Metoda głębiej() w pokazanej poniżej klasie NieobsługiwanyWyjątek zgłasza wyjątek klasy Exception (podstawowa klasa reprezentująca wyjątki w Javie), gdy przekazany jej parametr ma wartość null.

Kontrolowanie obsługi wyjątków przez kompilator

Warto zwrócić uwagę, że w deklaracjach wszystkich metod klasy NieobsługiwanyWyjątek występuje klauzula throws, która informuje, jakich wyjątków można się spodziewać w wyniku wywołania danej metody. W tym wypadku klauzula ta jest wymuszona przez kompilator, gdyż z metody mogą wydostać sie nieobsłużone wyjątki. Dzięki temu programista nie przeoczy żadnego wyjątku i jeżeli nie chce wszystkich obsłużyć musi świadomie wymienić je (bądź ich nadklasy) w deklaracji metody. Jeżeli wymienianych jest kilka klas, ich nazwy oddziela się przecinkiem.

W klauzuli throws można również wymienić wyjątki, które w aktualnej wersji metody nie mogą wystąpić. W ten sposób można zawczasu wymusić na innych programistach, aby ich kod używający naszej metody był na te wyjątki przygotowany. Gdy w przyszłej wersji będą już się mogły pojawić, nie spowoduje to żadnych problemów.

Różnice w obsłudze wyjątków.

Istnieją dwa mechanizmy strukturalnej obsługi wyjątków:

Powyższe dwa rodzaje programów obsługi różnią się od siebie, ale są ściśle powiązane przez proces znany jako "rozwijanie stosu". Gdy wystąpi wyjątek, system Windows wyszuka ostatnio zainstalowany program obsługi wyjątków, który jest aktualnie aktywny. Program obsługi może zachować się na trzy sposoby:

Program obsługi wyjątków, który rozpoznaje wyjątek może nie znajdować się w funkcji, która była uruchomiona podczas wystąpienia wyjątku. W niektórych przypadkach może znajdować się w funkcji będącej znacznie wyższej na stosie. Aktualnie uruchomiona funkcja i wszystkie inne funkcje na ramce stosu zostają zakończone. Podczas tego procesu, stos jest "rozwijany", tzn. zmienne lokalne zakończonych funkcji - o ile nie mają modyfikatora dostępu static - są usuwane ze stosu.

Podczas rozwijania stosu, system operacyjny nie wywołuje żadnych programów obsługi zakończeń, które zostały napisane dla każdej funkcji. Za pomocą programu obsługi zakończeń, można oczyścić zasoby, które inaczej pozostałyby otwarte ze względu na nieprawidłowe zakończenie. Jeśli została wprowadzona sekcja krytyczna, można zakończyć ten tryb w programie obsługi rozwiązań. Jeśli program będzie wkrótce zamknięty, użytkownik może wykonać inne zadania porządkowe, takie jak zamykanie oraz usuwanie plików tymczasowych.

Rzucanie i łapanie wyjątków

Technikę obsługi wyjątków można streścić w trzech punktach, które od razu wskażą nam jej najważniejsze elementy. Tak więc, te trzy założenia wyjątków są następujące:

       jeżeli piszemy kod, w którym może zdarzyć się coś wyjątkowego i niecodziennego, czyli po prostu sytuacja wyjątkowa, oznaczamy go odpowiednio. Tym oznaczeniem jest ujęcie kodu w blok try (`spróbuj'). To całkiem obrazowa nazwa: kod wewnątrz tego bloku nie zawsze może być poprawnie wykonany, dlatego lepiej jest mówić o próbie jego wykonania: jeżeli się ona powiedzie, to bardzo dobrze; jeżeli nie, będziemy musieli coś z tym fantem zrobić…

       załóżmy, że wykonuje się nasz kod wewnątrz bloku try i stwierdzamy w nim, że zachodzi sytuacja wyjątkowa, którą należy zgłosić. Co robimy? Otóż używamy instrukcji throw (`rzuć'), podając jej jednocześnie tzw. obiekt wyjątku (ang. exception object). Ten obiekt, mogący być dowolnym typem danych, jest zwykle informacją o rodzaju i miejscu zainstniałego błędu

       rzucenie obiektu wyjątku powoduje przerwanie wykonywania bloku try, zaś nasz rzucony obiekt „leci” sobie przez chwilę - aż zostanie przez kogoś złapany. Tym zaś zajmuje się blokcatch (`złap'), następujący bezpośrednio po bloku try. Jego zadaniem jest reakcja na sytuację wyjątkową, co zazwyczaj wiąże się z odczytaniem obiektu wyjątku (rzuconego przez throw) i podjęciem jakiejś sensownej akcji

Programowanie generyczne


Metodyka ta, zwana również programowaniem uogólnionym, jest oparta o założenie, że wiele fragmentów kodu bywa niepotrzebnie powtarzanych tylko dlatego, że służą do przetwarzania w identyczny sposób danych różnych typów. Podejście generyczne pozwala na tworzenie uogólnionych wzorców konstrukcji kodu (zwanych zależnie od języka: szablonami, funktorami, abstraktami, widmami lub paczkami typowanymi). Wzorzec taki (na przykład w postaci wzorca struktury, klasy, funkcji, procedury lub typu złożonego - zależnie od danego języka) jest deklarowany przy wstępnie nieokreślonych typach, wykorzystanych zmiennych czy pól. Dopiero podczas definiowania konkretnej instancji dla wzorca należy podać podstawienia typów dla wcześniejszych typów nieokreślonych.

Częstym przykładem idei programowania generycznego jest deklaracja wzorca klasy, implementującego działanie listy. Jako, że do opisu działania klasy, obsługującej listę, nie jest konieczne znanie typów danych w jej węzłach, można działanie opisać dla dowolnego typu T. Dopiero przy deklarowaniu lub tworzeniu obiektu listy zmiennych typu float za T podstawiony powinien zostać ten typ; z kolei obiekt listy zmiennych typu string wymaga podczas deklarowania lub tworzenia podstawienia string za T. Utworzenie zatem obiektów, obsługujących listy float oraz string (jak również każdego innego typu), wymaga tylko jednego, wspólnego i uniwersalnego opisu klasy: dla abstrakcyjnego typu T.

Programowanie generyczne, ponieważ oferuje uniezależnienie konstrukcji kodu od przetwarzanych typów zmiennych, a zatem pozwala na uogólnianie rozwiązań określonych problemów, stało się podstawą rozwoju wzorców projektowych.

Programowanie generyczne jest najczęściej zaliczane do metodyk typu imperatywnego, mimo że często występuje również w językach, wykorzystujących metodykę typu funkcyjnego.

Zalety:

Standardowa biblioteka szablonów (STL) Rdzeniem standardowej biblioteki C++ jest tzw. standardowa biblioteka szablonów

*STL umożliwia zarządzanie kolekcjami danych przy użyciu wydajnych algorytmów, bez konieczności dogłębnego poznawania ich sposobu działania

*STL oferuje grupę klas kontenerowych zaspokajających rozmaite potrzeby wraz z algorytmami, które na nich operują

*STL wzbogaca język C++ o nowy poziom abstrakcji Możemy zapomnieć o programowaniu dynamicznych tablic czy drzew oraz algorytmów do ich przeszukiwania

Składniki STL

Kontenery - służą do zarządzania kolekcjami obiektów określonego typu Poszczególne kontenery mają róśne zalety oraz wady i odzwierciedlają zróżnicowane potrzeby wobec kolekcji w tworzonych programach

Iteratory - służą do poruszania się po kolekcjach Oferują one interfejs wspólny dla każdego dowolnego typu kontenerowego Interfejs iteratorów jest bardzo podobny operacji na wskaźnikach (możemy np. używać ++, *, ->)

Algorytmy - służą do przetwarzania elementów kolekcji Mogą one wyszukiwać, sortować, modyfikować lub po prostu wykorzystywać elementy Algorytmy korzystają z iteratorów przez co mogą być używane do dowolnego typu kolekcji

Kontenery sekwencyjne - reprezentują kolekcje uporządkowane, w których każdy element posiada określoną pozycję Pozycja zależy od momentu i miejsca wstawienia, ale nie zalezy od samej wartości elementu Należą do nich Vector - wektor

Deque - kolejka dwustronna

List - lista

Łańcuchy - string, basic_string<> Bardzo zbliżone do wektorów, ale ich elementami są znaki

Kontenery asocjacyjne - będące kolekcjami sortowanymi Położenie elementu zależy od jego wartości zgodnie z określonym kryterium sortowania Należą do nich

Set - zbiór

Mulitset - wielozbiór

Map - mapa

Multimap - multimapa

Nie ma w standardzie hash...

Iteratory Iteratory są obiektami, które potrafią nawigować po elemexntach kontenerów

Podstawowe operacje definiowane dla iteratorów

operator* - zwraca element z aktualnej pozycji

operator++ - przesuwa iterator na pozycję następną

operator== i != zwracają wartość logiczną czy iteratory reprezentują tą samą (inną) pozycję

operator= - przypisanie

Każdy kontener definiuje co najmniej dwa typy iteratorów

kontener::iterator - przeznaczony do nawigowania w trybie odczytu i zapisu

kontener::const_iterator - przeznaczony do nawigowania w trybie tylko do odczytu Zrealizowane jest to za pomocą instrukcji typedef



Wyszukiwarka