C i c++ wykłady, klasy

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.

W

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


Wyszukiwarka

Podobne podstrony:
Wyklad 3 klasy
Wyklad Jezyk C# klasy Wyklad2 Klasy
Wyklad 3 klasy
Klasy, Programowanie, wykłady C++
Wykład wyznaczanie klasy Lauego
Wyklady I i Sprawozdawczosc finansowa, Klasy rachunkowoÂci - skrˇt, RACHUNKOWOŚĆ JEST JEDNA
Tematy wykładów otwartych na WB 2014, sprawdziany dla klasy IV szkoła podstawowa
c++ wykłady, Wyc 6 struktury przec klasy
Standardy edukacji muzycznej klasy 1-3, STUDIA -PRYWATNE, Studia - wykłady - Dorota, studia 2014
powtórzenie klasy, Programowanie, wykłady C++
Systemy klasy ERP wyklad
Wykład 12 Chemia Organiczna, grupy funkcyjne, klasy związków 5fantastic pl
Wykład 6 1 podstawowe klasy układów adaptacyjnych
wyklad1 wprowadzenie klasy
wyklad1 wprowadzenie klasy
Napęd Elektryczny wykład

więcej podobnych podstron