background image

KLASY

background image

WAŻNE – zanim o obiektach

Przypomnienie idei programowania 

strukturalnego

Promowanie uporządkowania kodu i 

struktury danych

Standardowo – zstępująco: wymaga 

podzielenia zadania na niezależne procedury

Na ogół manipulowanie danymi odbywa się 

na niskim poziomie zadań

Ważniejsze w duecie kod-dane są procedury 

– stąd inna nazwa programowania 

strukturalnego – podejście proceduralne

background image

Pisanie programu 
strukturalnego

Wymaga doświadczenia

Trzeba cały czas mieć na uwadze jednocześnie 

dane i działania na nich

Główny powód utworzenia programu, to 

zamierzone przekształcenie danych w wyniki

Tymczasem, przekształcenia dokonują się na 

odległym od sterowania poziomie, a dostęp do 

danych przez konwersje i referencje może 

odbiegać od założonego

Pisanie programu przypomina organizację biura 

projektowego, w którym każdy z pracowników 

ma mało-limitowany dostęp do projektu

background image

Zanim o obiektach..

Strukturalizację programu (poznaną dotychczas)
a więc wyodrębnianie elementów wspólnych 
i jednokrotne ich opracowanie (co przynosi 
niewątpliwy zysk czasowo/objętościowy) można 
przyrównać do realizacji algorytmicznej idei dziel i 
zwyciężaj na polu „pragmatyki biurowej”  - 
zdefiniowania i rozdzielenia zadań do wykonania. 

background image

Dotychczas

realizowany jest ciąg zadań 
o cząstkowych znaczeniach niejasno 
związanych z celem ogólnym, a wszystko 
trzeba powiązać mając „w głowie całość” 
tj. 

algorytm

 i 

struktury danych

 na których 

ten algorytm działa 

Korzystanie z „gotowców” obcego 
autorstwa nie jest łatwe, wymaga 
„wejścia” w sposób myślenia innego 
twórcy

background image

Dotychczas proceduralnie

Co gorsze użytkownik jest traktowany jak idiota, 
który może sterować programem tylko w jeden 
określony przez programistę sposób (i musi się 
tego sposobu nauczyć)

Oczywiście MY wiemy, że jest jeden procesor 
wykonujący sekwencję rozkazów

A tymczasem użytkownik (współcześnie coraz 
mniej obeznany z ograniczeniami wynikającymi z 
idei sekwencyjnej realizacji przetwarzania danych) 
żyje w świecie OBIEKTÓW o określonych cechach 
oraz funkcjonalności i tego oczekuje od programu

background image

Dlaczego to takie 
ważne

Przez komplikację

background image

Samo się napędza

Sprzedaż komputeropodobnej techniki jest 

możliwa dzięki:

Masowej produkcji i niskiej cenie

Łatwości (intuicyjności) obsługi, rosnącej idioto-

odporności oprogramowania

Efektywnym algorytmom

Atrakcyjnej, nawiązującej do rzeczywistości szacie 

graficznej

Jest oczywiste, że przy tak dużych pieniądzach, 

odpowiednio zasilane są zarówno techniczne jak 

i programistyczne badania warunkujące rozwój (i 

kasę dla milionów ludzi zatrudnionych w branży)

background image

A że rozwój jest szalony

Jesteśmy uzależnieni od tej techniki, marzymy 

aby inne dziedziny tak się rozwijały

30 lat temu

R-32U – masa ok. 4 t, zużycie energii 38 kW, 11 osób 

obsługi, cena ~0,5 mln $, RAM 1 MB, 1 MFlop/s

Dziś 

Laptop – masa ok. 1 kg, zużycie energii – 100 W, RAM 

4GB, 2 GFlop/s, cena 500$

W samochodach taki rozwój oznaczałby dziś 

masę 0,5 kg, zużycie paliwa 20ml/100 km, cenę 

2$ (taniej kupić nowy niż zaparkować)

Nic dziwnego, że z chwilą pojawienia się PC 

mnóstwo ludzi zafascynowało się tą techniką  

background image

Brak standardów użytkowych

Brak unormowania interfejsu użytkownika i 

pisanie programów przez zafascynowanych 

amatorów skutkowały pojawieniem się 

mnóstwa programów (ok. 40% softwaru ‘80) z 

bardzo skomplikowaną obsługą.

Rozwój Windows (a wcześniej Maców)

Zastopował programy amatorskie – tak znaczny jest 

stopień komplikacji systemu, że amatorzy „wymiękli”

Ujednolicił obsługę – autorzy „rzucili” się na 

„gotowce” (API Windows)

Rozwój sprzętu umożliwił realizację opracowań 

softwarowych bogatych graficznie, 

zorientowanych na UŻYTKOWNIKA i jego 

postrzeganie funkcjonalności narzędzi      

background image

Prawdopodobnie

Przejście do techniki obiektowej (OOP-
Object Oriented Programming) jest być 
może ważniejszą pojedynczą zmianą niż:

przejście od języka maszynowego do assemblera

przejście od assemblera do języków 
proceduralnych

A każda z nich „odsuwa o krok” programistę 
od  nieprzyjaznego i obcego świata binariów 
w stronę rzeczywistości

background image

Jednolite podejście do Analizy-
Projektowania-Realizacji

Przez tworzenie struktur autonomicznych wiążących 

zarówno dane jak i sposoby oddziaływania na nie 

z ukryciem szczegółów realizacyjnych.

Niezmiernie wartościowe przy tworzeniu programów w 

środowisku wieloakcyjnym oraz zmianie podejścia do 

sterowania pracą programów z „prowokowanej” 

(Ja daję Ci możliwość i czekam aż ją zaakceptujesz) 

na obsługę zdarzeń

 - rób co chcesz 

(myszą, klawiaturą itp.) - jestem przygotowany na 

prawie wszystkie twoje wymysły

background image

Wymyślono podejście 
obiektowe

Jesteśmy otoczeni przez obiekty 
działające zgodnie ze swoją naturą (nie 
można wydrukować samochodem czy 
jeździć żarówką – wymienione operacje 
są niewłaściwe, bo nie stanowią 
elementu funkcjonowania obiektu)

Struktury danych awansowano do roli 
niemal pierwszoplanowej

background image

Obiekty (Smalltalk, Simula, C++, 
Eiffel, Ada ... a od v. 5.5 też TP - 
sama nazwa object z TP)

Podstawowe pojęcie programowania 

zorientowanego obiektowo – OBIEKT TO:

Złożona struktura o ustalonej liczbie 
elementów

pola - określające dane obiektu

metody - określające funkcjonowanie

Zdefiniowane jako

typ niezależny

typ potomny od już istniejącego

background image

Przykład z zegarem 
cyfrowym

Stan wyświetlany jest na wyświetlaczu

Regulacji możemy dokonać przyciskając H/M

W terminologii OOP (object oriented 

programming) przyciski odpowiadają metodom

Klasa – model zegara a obiekt to konkretny 

zegar zbudowany wg modelu, może mieć 

własne stany czasu (pola)

Klasy enkapsulują pola i metody (łączą w 

jedno tworząc funkcjonalną całość)

11:22

H M

background image

OOP - definicje

Klasa jest wzorcem dla swoich zmiennych 
– obiektów (opisem – dokumentacją)

Obiekt jest zmienną swojej klasy. 
Pomimo, że różne zmienne mają wspólne 
właściwości i charakterystyki, każdy 
obiekt może znajdować się we własnym 
stanie

Komunikat reprezentuje operację 
wykonywaną na obiekcie. Metoda określa 
sposób wykonania komunikatu

background image

OOP - dziedziczenie

Jest możliwością tworzenia klas jako 
potomnych od klasy rodzicielskiej

Klasy pochodne dziedziczą charakterystyki 
i działanie klasy rodzicielskiej

Klasy pochodne definiują własne 
(dodatkowe) operacje i nowe (dodatkowe) 
właściwości, mogą 
zmieniać/modyfikować/uzupełniać 
operacje odziedziczone 

background image

Dziedziczenie - uwagi

NIE trzeba od początku definiować całej 
rzeczywistości (wirtualnej), można oprzeć 
się na klasach już zdefiniowanych, 
modyfikując ich właściwości, czy 
zmieniając/dodając funkcjonalność

Wspaniałe rozwiązanie w rozbudowanych 
aplikacjach

Sukces wielu (np. Windows) oparty jest 
na obiektach

background image

OOP - polimorfizm

Cenna właściwość programowania 
obiektowego

Umożliwia generowanie własnych 
odpowiedzi na identyczne komunikaty

Pozwala każdej klasie na posiadanie 
własnych wersji metod dostosowanych do 
aktualnych potrzeb

(w literaturze określa się też jako opóźnione 

wiązanie lub komunikat abstrakcyjny)

background image

Siła techniki obiektowej

Enkapsulacja (obudowanie hermetyczne) - 
integrowanie danych i operującego na nich 
kodu

Dziedziczenie - tworzenie obiektów pochodnych 
na bazie już istniejących z możliwością 
zróżnicowania/wzbogacenia potomków

Polimorfizm (wielopostaciowość)- wykorzystanie 
wspólnych elementów funkcjonalności klas 
macierzystej i pochodnej -  

każdy z „rodziny” 

wie jak zapiąć płaszcz - jedni na guziki 
(lewo/prawo zależnie od płci) inni na suwak

background image

Klasy w C++

C++ rozszerza C dodając 

możliwości programowania 
zorientowanego obiektowo 
(pierwotna różnica C-C++)

background image

Deklarowanie klas bazowych

Jeśli deklarujemy klasę bazową (praprzodka)

 class NazwaKlasy
 {
 private:
 <prywatne dane składowe>
 <prywatne funkcje składowe>
 protected:
 <chronione dane składowe>
 <chronione funkcje składowe>
 public:
 <publiczne dane składowe>
 <publiczne funkcje składowe>
};

background image

Trzy poziomy widzialności 
swoich składowych

Część prywatna: tylko funkcje składowe 
danej klasy mają dostęp do składowych. 
Zmienne (obiekty) nie mają dostępu

Część chroniona: tylko funkcje składowe 
i klas pochodnych mają dostęp. 
Zmienne nie mają dostępu

Cześć publiczna: widoczne dla 
składowych, ich pochodnych i 
zmiennych klasowych 

background image

Poziomy prywatności – uwagi

Części klas mogą występować w dowolnej 
kolejności, także więcej niż jeden raz

Jeśli nie użyto etykiet sekcji private, 
protected, public – kompilator traktuje 
jako chronione (protected)

Należy unikać danych (pól/własności) w 
części publicznej, aby ich odczyt/zmiana 
mogła być realizowana tylko przez użycie 
funkcji składowych

background image

Deklarowanie hierarchii klas

class NazwaKlasy: [public] NazwaKlasyRodzicielskiej
 {
 private:
 <prywatne dane składowe>
 <prywatne funkcje składowe>
 protected:
 <chronione dane składowe>
 <chronione funkcje składowe>
 public:
 <publiczne dane składowe>
 <publiczne funkcje składowe>
};

background image

Konstruktory i destruktory

Konstruktor to metoda klasy, której rolą jest 
utworzenie obiektu danej klasy

W językach C/C++, C#, Java – ma nazwę taką jak 
nazwa klasy, w Pascalu to metoda proceduralna 
poprzedzona słowem constructor zamiast procedure

Destruktor to metoda klasy, której rolą jest 
usunięcie obiektu

W C i Java brak konstruktora/destruktora powoduje 
automatyczne ich utworzenie; w Pascalu jest 
obowiązkowy wyłącznie przy polimorfizmie

background image

Zadania konstruktora (bliżej)

obliczenie rozmiaru obiektu 

alokacja obiektu w pamięci 

wyczyszczenie (zerowanie) obszaru pamięci 

zarezerwowanej dla obiektu 

wpisanie do obiektu informacji łączącej go z 

odpowiadajacą mu klasą (połączenie z 

metodami klasy) 

wykonanie kodu klasy bazowej

wykonanie kodu wywołanego konstruktora 

Uwaga: istnieją różnice w językach OOP oraz/lub 

ich implementacjach

background image

O klasach i obiektach

Klasy służą do tworzenia opisu formalnego typu danych 

(taka super struktura). 

W przypadku klas wiadomo jednak "z definicji", że będzie 

to bardziej złożony typ (tzw. agregat) zawierający 

praktycznie zawsze i dane "tradycyjnych" typów i funkcje 

(nazywane "metodami"). 

Podobnie jak definiując strukturę tworzy się nowy formalny 

typ danych, tak i tu - definiując klasę tworzy się nowy typ .

Jeśli zadeklaruje się użycie zmiennych danego typu 

formalnego, to takie zmienne to właśnie obiekty. 

Innymi słowy, klasy stanowią definicje formalnego typu, 

natomiast obiekty - to zmienne danego typu (danej klasy).

Nie ma przeszkód, by obiekty były składowymi innych 

strukturalnych typów (tablice, rekordy, obiekty itp) 

background image

Klasy i obiekty

class Klasa

{

int p_tab[80]

public:

int dane;

void Inicjuj(void)

int Funkcja(int 
our_param);

} Obiekt; //

OD RAZU

Zawiera:

Chronionąną tablicę 
p_tab

Publicznie dostępną 
całkowitą dane

Publicznie dostępne 
metody Inicjuj i Funkcja

Mamy do czynienia 
jednocześnie z 
definicją i deklaracją 
(globalną)

background image

Można zadeklarować nasz 
obiekt w bloku, gdzie będzie 
potrzebny

class Klasa

{

int prywatna_tab[80]

public:

int dane;

void Inicjuj(void)

int Funkcja(int argument);

};

main()

{

...

Klasa Obiekt;      //tu tylko deklaracja – definicja klasy globalnie!!!

... 

background image

Przypisanie wartości polom 
obiektu, użycie metody

main()
{
...
Klasa Obiekt;
Obiekt.dane = 13; 
Obiekt.Funkcja(44);
...

Widać podobieństwo 
do korzystania ze 
struktur:

Operator kropki (dot) 
„wyłuskuje” 
składową klasy 

background image

[!!!] UWAGA!

W C++ nie możemy zainicjować danych 

wewnątrz deklaracji klasy:

class Klasa
{
private:
int p_tab[80] = { 1, 2, 3 }; //ŹLE !
public:
int dane = 123; //ŹLE !
... 

background image

Inicjowanie danych

Inicjowanie danych odbywa się w 
programie:

przy pomocy przypisania (dane publiczne), 
bądź 

za pośrednictwem funkcji (zazwyczaj 
publicznej) należącej do danej klasy i mającej 
dostęp do wewnętrznych danych klasy/obiektu 
(np. dane prywatne). 

Inicjowania danych mogą dokonać także 
specjalne funkcje - tzw. konstruktory. 

background image

Statusy

Dane znajdujące się wewnątrz deklaracji klasy mogą 

mieć status: public, private, bądź protected. 

Jeżeli część obiektu jest prywatna, to oznacza, że żaden 

element programu spoza obiektu nie ma do niej dostępu. 

W naszej Klasie prywatną część stanowi tablica złożona z 

liczb całkowitych:

int p_tab[80];

Do (prywatnych) elementów tablicy dostęp mogą 

uzyskać tylko funkcje związane (ang. associated) z 

obiektem danej klasy.

Funkcje takie muszą zostać zadeklarowane wewnątrz 

definicji danej klasy i są nazywane członkami klasy - ang. 

Member functions.

background image

Nie przesadzić

Funkcje mogą mieć status private i stać się dzięki temu 

wewnętrznymi funkcjami danej klasy (a w konsekwencji 

również prywatnymi funkcjami obiektów danej klasy). 

Jest to jedna z najważniejszych cech nowoczesnego stylu 

programowania w C++. Na tym polega idea hermetyzacji 

danych i funkcji wewnątrz klas i obiektów. 

Gdyby jednak cała zawartość (i dane i funkcje) 

znajdujące się w obiekcie zostały dokładnie 

"zakapsułkowane", to okazałoby się, że obiekt stał się 

"ślepy i głuchy", a w konsekwencji - niedostępny i 

(prawie) kompletnie nieużyteczny dla programu i 

programisty. 

Po co nam obiekt, do którego nie możemy odwołać się z 

zewnątrz żadną metodą? 

background image

Pamiętać o public

Dane zawarte w obiekcie, podobnie jak 
zwykłe zmienne wymagają 
zainicjowania. Funkcja inicjująca dane - 
zawartość obiektu musi zawsze 
posiadać status public aby mogła być 
dostępna z zewnątrz i zostać wywołana 

Funkcje i dane dostępne z zewnątrz 
stanowią tzw. INTERFEJS OBIEKTU. 

background image

Na początek – prosta klasa 
zliczająca wybrane znaki

Definicja klasy:

class Licznik
{
private:
char znak;
int ile;
public:
void Inicjuj(char);
void PlusJeden(void);
}; 

Wewnętrzne pola 

znak i ile służą do 

przechowywania 

informacji

Funkcje są tylko 

zapowiedziane (to 

prototypy)

background image

void Licznik::Inicjuj(char 

x)

{

znak = x;

ile = 0;

}

void 

Licznik::PlusJeden(void)

{

ile++;

funkcje nie są definiowane 

"niezależnie", lecz w 

stosunku do własnej klasy

Aby wskazać, że funkcje 

są członkami klasy Licznik 

stosujemy

operator :: (oper. 

widoczności/przesłaniania 

- ang. Scope resolution 

operator). Taki sposób 

zapisu definicji funkcji 

oznacza dla C++, że 

funkcja jest członkiem 

klasy (ang. Member 

function). 

background image

A main? 

(na początku odpowiednie 

includy)

void main()

{

char znak_we;                       //zwykła zmienna znakowa

Licznik licznik;                       //deklaracja obiektu

Licznik.Inicjuj('A');

cout << "\nWpisz tekst zawierajacy litery A";

cout << "\nPierwsze wystąpienie kropki";

cout << "\n - oznacza Koniec zliczania: ";

for(;;)                                            //pętla bez końca

{

cin >> znak_we;

if (znak_we == ‘.') break;              //jednak koniec jest       

if(licznik.znak == toupper(znak_we)) licznik.PlusJeden();

}

cout << "\nLitera " << licznik.znak

<< " wystapila " << licznik.ile

<< " razy.";

background image

Ten pomysł prowadzi do błędu – 
pola znak i ile są niedostępne

Potrzebne są dwie metody dostępu do 

nich:

char Licznik::Pokaz(void)
{
return znak;
}
int Licznik::Efekt(void)
{
return ile;

I zmiana w main()

….
if(licznik.Pokaz() == toupper(znak_we)) 

licznik.PlusJeden();

}
cout << "\nLitera " << licznik.Pokaz()
<< " wystapila " << licznik.Efekt()
<< " razy.";

Pierwsza metoda „Pokaz” zwraca znak
Ten sam problem wystąpi przy próbie 

pobrania od obiektu efektów jego pracy 

- stanu pola licznik.ile. Do tego też 

niezbędna jest autoryzowana do 

dostępu metoda. Nazwiemy ją Efekt()

OCZYWIŚCIE potrzebne są zapowiedzi tych 

metod w części publicznej definicji 

klasy

background image

Podobna klasa - nowocześniej 
Inicjowanie za pomocą 
konstruktora

class Licznik
{private:
char znak;
int ile;
public:
Licznik(char);                  //Konstruktor
void PlusJeden(void);
char Pokaz(void);
int Efekt(void);
};

Licznik::Licznik(char x) //Def. konstruktora
{
znak = x;
ile = 0;
}

void main()
{
Licznik licznik('A'); //Zainicjowanie obiektu 

licznik 

UWAGA Konstruktor nie ma typu

Jego nazwa jest identyczna z nazwą 

klasy

Domyślnie tworzony konstruktor jest 

bezparametryczny – tutaj jego 

pokrycie mógłby tylko posłużyć do 

wyzerowania pola ile

background image

Rodzaje konstruktorów

Konstruktor domyślny – generowany automatycznie przez 

kompilator, gdy autor nie zamieścił własnego

Konstruktor zwykły – zamieszczony przez autora, zwykle inicjuje 

(także z parametrami domyślnymi) ukryte wartości obiektu

Konstruktor kopiujący -konstruktor, którego jedynym 

argumentem niedomyślnym jest referencja do obiektu swojej 

klasy. Jest on używany niejawnie wtedy, gdy działanie programu 

wymaga skopiowania obiektu (np.: przy przekazywaniu obiektu 

do funkcji przez wartość). Gdy konstruktor kopiujący nie został 

zdefiniowany, jest on generowany niejawnie (nawet gdy są 

zdefiniowane inne konstruktory) i domyślnie powoduje 

kopiowanie wszystkich składników po kolei. Zablokowanie tego 

konstruktora (np. przez umieszczenie go w sekcji prywatnej lub 

chronionej) oznacza brak zezwolenia na kopiowanie obiektu.

Konstruktor konwertujący (C++) -konstruktor, którego jedynym 

argumentem niedomyślnym jest obiekt dowolnej klasy lub typ 

wbudowany. Powoduje niejawną konwersję z typu argumentu 

na typ klasy własnej konstruktora.  

background image

Różnie można dziedziczyć

class NazwaKlasy: [

public/private

] NazwaKlasyRodzicielskiej

 {
 private:
 <prywatne dane składowe>
 <prywatne funkcje składowe>
 protected:
 <chronione dane składowe>
 <chronione funkcje składowe>
 public:
 <publiczne dane składowe>
 <publiczne funkcje składowe>
};

public

 – u potomka tak jak u przodka, co widać to widać

private

 – wszystko co odziedziczone staje się prywatną sprawą 

potomka

background image

Kolejność wywołań 
konstruktorów
jak jest kaskada potomstwa?

Kolejność wywołań konstruktorów klasy bazowej, czy też 

obiektów składowych danej klasy, jest określona kolejnością:

Konstruktory klas bazowych w kolejności w jakiej znajdują się 

w sekcji dziedziczenia w deklaracji klasy pochodnej (bo 

można w C dziedziczyć po kilku przodkach równolegle – nie 

tylko w linii dziadek/ojciec ale ojciec1/ojciec2 – i inne 

kombinacje). 

Konstruktory obiektów składowych klasy w kolejności, w 

jakiej obiekty te zostały zadeklarowane w ciele klasy. 

Konstruktor klasy. 

Object Pascalu

 konstruktor może być dziedziczony i 

wirtualny, 

ze względu na brak dziedziczenia wielokrotnego oraz 

konieczność dziedziczenia od klasy bazowej (TObject) nie 

istnieje problem kolejności wywołań konstruktorów

background image

Lista inicjalizacyjna 
konstruktora - rola

Nadanie wartości danym podczas konstrukcji 

obiektu

Dla danej stałej (oznaczonej jako 

const

) jest to jedyna 

możliwość nadania jej wartości początkowej, podczas 

gdy 

zmienne

 nie-const mogą zostać zainicjalizowane 

zarówno na liście inicjalizacyjnej jak też w formie 

zwykłego przypisania im wartości. 

Wywołanie 

konstruktorów

 obiektów składowych - jest to 

jedyny sposób na wywołanie takich konstruktorów. 

Wywołania konstruktora klas bazowych - jest to jedyny 

sposób na wywołanie takich konstruktorów. 

Gdy klasa bazowa lub obiekt składowy klasy posiada 

konstruktor domniemany, jego wywołanie nie musi się 

pojawić na liście inicjalizacyjnej

background image

Destruktor

obowiązkowa w C nazwa ~NazwaKlasy
w Pascalu – słowo kluczowe destructor

Destruktor - w 

obiektowych językach programowania

 

specjalna 

metoda

, wywoływana przed usunięciem 

obiektu

Pod względem funkcjonalnym jest to przeciwieństwo 

konstruktora

. Jest pojedynczy, niedozwolone przeciążanie bo 

brak parametrów

Destruktor ma za zadanie wykonać czynności składające się 

na jego "zniszczenie", inne niż zwolnienie zadeklarowanej 

pamięci, przygotowujące obiekt do fizycznego usunięcia. Po 

jego wykonaniu obiekt znajduje się w 

stanie osobliwym

 i nie 

można już wtedy z tym obiektem zrobić nic poza fizycznym 

usunięciem lub ponownym wywołaniem konstruktora. 

Destruktor zwykle wykonuje takie czynności, jak zamknięcie 

połączenia z 

plikiem

/

gniazdem

/

potokiem

, odrejestrowanie 

się z innych obiektów, czasem również zanotowanie faktu 

usunięcia, a także usunięcie obiektów podległych, które 

obiekt utworzył lub zostały mu przydzielone jako podległe 

(jeśli jest ich jedynym właścicielem) lub wyrejestrowanie się z 

jego użytkowania (jeśli jest to obiekt przezeń współdzielony).

background image

Składowe statyczne

W C/C++ składowa klasy (pole lub funkcja) może być 

deklarowana jako statyczna

Publiczna metoda statyczna może być wywołana, nawet 

wtedy gdy nie istnieje obiekt danej klasy 

(nazwa_klasy::składowa)

Statyczność deklarujemy przy pomocy słowa static, np. 

static int liczbaObiektów;

Po utworzeniu obiektu danej klasy, dysponuje on zestawem 

pól tej klasy (każdy obiekt tej klasy własnym zestawem) 

natomiast w przypadku pól statycznych występują 

one jednoinstancyjnie 

Pola statyczne muszą być deklarowane na zewnątrz jako 

globalne

Cel użycia: 

Komunikacja między obiektami

Umieszczenie informacji wspólnej dla wszystkich obiektów

background image

Funkcje i klasy 
zaprzyjaźnione

Istnieją sytuacje, w których funkcja nie będąca metodą 

danej klasy powinna mieć dostęp do składowych 

prywatnych lub chronionych tej klasy

Zwykła funkcja zaprzyjaźniona

friend void Spr( const Wekt3&);

Zaprzyjaźniona metoda innej klasy

friend void T4::Spr(const Wekt3&);

Np. porównanie identyczności danych pochodzących z 

dwóch baz

Zwykle deklarację zaprzyjaźnienia umieszcza się 

na początku definicji klasy, a funkcja 

zaprzyjaźniona ma dostęp do danych chronionych i 

prywatnych, tak jak gdyby były one publiczne (tzn. 

potrzebna jest nazwa obiektu, w którego klasie ją 

„zaprzyjaźniono” i „.” nazwa składowej)

background image

przykład – klasa liczb 
zespolonych

class – klasycznie

class lZespolone
{
protected: 
double Re; double Im;
public:
lZespolone (double re=0, double im=0)
 {przypisz(re,im);}//zwykły konstruktor
lZespolone(lZespolone& zrodlo);//konstr. kopujący
void przypisz(double re=0,double im=0);//wpisanie
void dajRe(){return Re;} //zwraca Re
void dajIm(){return Im;}   //zwraca Im
friend lZespolone dodaj(lZespolone& a1, lZespolone& a2); 
}

background image

Zespolone – brakujące 
metody

lZespolone::lZespolone(lZespolone& zrodlo)
{Re=zrodlo.Re; Im=zrodlo.Im;}
lZespolone::przypisz(double re, double im)
{Re=re; Im=im;}
//i w końcu zaprzyjaźnione dodawanie
lZespolone dodaj(lZespolone& a1,lZespolone& a2)
{
 lZespolone wynik(a1); //pomocniczy wynik skopiowany
 wynik.Re+=a2.Re;
 wynik.Im+=a2.Im;// dodane składowe drugiego argumentu
 return wynik;
}

Pozostaje banalny main

background image

Zespolone – poprawka z 
operatorem

Wygodnie by było, gdyby:

Można użyć znaku podstawienia (np. 
c3=c2;)

Można użyć znaku dodawania (np. 
c3=c1+c2;)

Lub w „porozumieniu” z iostream 
wyprowadzać w prosty sposób (np. 
cout<<c;), w jakiejś ustalonej formie … 

background image

Z ostream (przydatny)

 friend ostream& operator <<(ostream & os, 

lZespolone& z); // to w klasie

ostream&  operator <<(ostream& os, lZespolone& 

z)

{os<<"("<<z.Re<<"+j"<<z.Im<<")";
 return os;}
// to jako definicja zaprzyjaźnionego operatora

background image

Przeciążanie operatorów

Język C++ umożliwia przeciążanie operatora tzn. 
zmianę jego znaczenia na potrzeby danej klasy

W tym celu definiujemy funkcję o nazwie
operator op
gdzie op to nazwa (symbol/symbole) 
konkretnego operatora

Taka funkcja (zwykła lub metoda) musi posiadać 
przynajmniej jeden argument danej klasy, co 
uniemożliwia zamianę typowych (wbudowanych) 
operatorów dla takich typów jak int, float itp.

background image

Prosty przykład przeciążania

Powiedzmy, że istnieje klasa TWektor z 
metodą Dodaj dodawania do własnego pola 
zawierającego składowe n-elementowego 
wektora p, składowych innego wektora:
 void TWektor::Dodaj(TWektor x)
 { int i; 
   for (i=0;i<n;i++) p[i]+=x.p[i];
 }
//metodę wywołujemy np. a.Dodaj(b);
//gdzie a i b są obiektami klasy TWektor
//na pewno wygodniej byłoby zapisać a+=b;

background image

Przeciążanie operatora +=

W klasie pochodnej np. Wektor definiujemy:

 void Wektor::operator += (Wektor b)

{  for (int i=0;i<n;i++) p[i]+=b.p[i]; }

oczywiście, wcześniej w klasie Wektor nie możemy 

zapomnieć o dodaniu wiersza

 void operator += (Wektor);

Jeżeli w treści funkcji korzystamy z pól prywatnych 

(lub chronionych) to funkcja operator musi mieć 

statut zaprzyjaźnionej

W C++ można przeciążać większość operatorów, 

poza (czterema): ::  *

(jednoargumentowy adresu)

   ? :  

Po przeciążeniu zachowane są pierwotne 

reguły pierwszeństwa, łączności, liczby 

argumentów

background image

Przeciążanie cd

Funkcja definiująca przeciążenie operatora 

nie musi być metodą

*

:

 void operator += (Wektor &a, Wektor &b)

 {  for (int i=0;i<a.n;i++) a.p[i]+=b.p[i]; }

Dzięki przekazywaniu referencyjnemu 

parametrów, możliwa jest zmiana 

argumentu!

Jeśli operator jest metodą, ma o jeden 

parametr mniej (domyślnie działa na klasie)

*

Cztery operatory: =  [ ]  ( )  -> muszą być 

zdefiniowane jako metody

background image

Przeciążanie =

Operacja przypisania jednego obiektu drugiemu 

jest zawsze wykonalna, odpowiednią 

implementację podstawienia zapewnia kompilator

Jeśli w klasie istnieją pola dynamiczne, 

wygenerowany przez kompilator operator = 

będzie działał nieprawidłowo (jak automatyczny 

konstruktor kopiujący)

Jeśli chcemy, by w takim przypadku prawidłowo 

działało:

a=b;

funkcję przeciążającą podstawienie musimy 

zaprojektować sami (jako metodę klasy – bo = 

jest jednym z czterech operatorów tego 

wymagających)

background image

Przeciążanie = cd

Trzymając się dalej klasy Wektor (z dynamicznym 
zestawem n składowych wektora p
 void Wektor :: operator = (Wektor& b)
// najpierw usuń część dynamiczną
{ delete [n] p; 
// utwórz własne pole p
 p=new int[n=b.n];
// przepisz do nowego z b
 for (int i=0;i<b.n;i++) p[i]=b.p[i];
} // wyżej p po lewej to nasze własne, 
  //po prawej z argumentu

background image

Przeciążanie = cd

Przykład poprawnie działa dla
b=a; // a i b są typu wektor

UWAGA źle działa dla banalnego a=a; 
ponieważ najpierw usunie źródło 
danych a potem je przepisze skąd?!

Poprawa pierwsza: warunkowe 
usunięcie, gdy to są różne obiekty
if (this != &b) {
delete [n]p; p=new int [n=b.n];}

background image

Przeciążanie = cd

Uwaga ! Źle działa dla 

a=b=c; // a,b,c typu wektor

Ponieważ powyższe działa jak a=(b=c); a 

przecież metoda operator = jest typu void

Pomaga użycie dla niej typu Wektor& 

(koniecznie z referencją &) i dodanie wiersza

return *this;

Występujący tu wskaźnik this oznacza 

samego siebie i często występuje niejawnie 

(kompilator go używa do wyrażeń po lewej 

stronie operacji podstawienia)

background image

Przeciążanie = cd – 
ostateczna wersja

Wektor& Wektor:: operator = (Wektor& b)

{ if (this!=&b) { //gdy są różne

 if (n!=b.n){     // i mają różne długości  

   delete [n] p;  p=new int[n=b.n];  //twórz 

swoje

}

// przepisz do nowego z b gdy różne 

for (int i=0;i<b.n;i++) p[i]=b.p[i];

}

return *this; //zwróć samego siebie

}

Warto pamiętać, że przeciążanie nie jest 

dziedziczone i w klasach pochodnych trzeba 

je definiować oddzielnie, a korzystające z pól 

chronionych i prywatnych muszą być 

zaprzyjaźnione

background image

Przeciążanie uwagi

Intuicyjnie pożądane przeciążanie znacznie 

utrudnia czytanie tekstu. Jest techniką z wyższej 

półki, której towarzyszą częste manieryczne 

„ściemniania” treści 

Analiza takiego programu jest trudna, choć 

autorowi ułatwia pracę, a „tajne” zwykle wtyczki 

zaprzyjaźnione są normą (niepublikowaną)

Dodatkową trudność stanowi przecież 

standardowe rozbicie definicji i deklaracji klasy na 

dwa pliki:

*.h i *.cpp (o ile mamy dostęp do prawdziwych 

źródeł)

Gdy dodać do tego ciągłe udoskonalanie młodego 

przecież języka i zmiany standardów, różnice 

implementacyjne – to analiza i poprawianie C jest 

super krzyżówką 

background image

Przeciążanie - wnioski

Trzeba dokładnie się zastanowić, które 
operatory przeciążać budując nową klasę

Bezwzględnie wymagane jest przeciążanie = 
(jak i budowa własnego konstruktora 
kopiującego) przy polach dynamicznych

Przeciążanie należy stosować, gdy poprawi to 
czytelność programu – w innych przypadkach 
lepiej zbudować odpowiednie metody

I pamiętać – przeciążane operatory nie są 
dziedziczne


Document Outline