c++ wykłady, Wyc 6 struktury przec klasy

background image

Struktury

Struktury

background image

Wskaźnik do struktury:

Jeśli

do

obiektu

strukturalnego

odwołujemy się za pomocą

wskaźnika

, to

dostęp do poszczególnych pól możemy
uzyskać przez złożenie

*

i

.

lub operator

->

( znak 

-

oraz znak

>

):

Wskaźnik_Obiektu

->

Nazwa_Składnika

(

* Wskaźnik_Obiektu

).

Nazwa_Składnika

* Wskaźnik_Obiektu

.

Nazwa_Składnika

background image

Każda definicja struktury wprowadza nowy,
unikatowy typ, np.:

struct

S1

{

int

i;

}

;

struct

S2

{

int

j;

}

;

Typy

S1

i

S2

są różnymi typami, zatem w

deklaracjach:

S1 x, y;
S2 z;

zmienne

x

oraz

y

są tego samego typu

S1

,

natomiast

x

oraz

z

są różnych typów.

background image

Wobec tego przypisania:

x = y;
y = x;

poprawne

, podczas gdy:

x = z;
z = y;

błędne

.

Dopuszczalne są natomiast przypisania
składowych o tych samych typach, np.:

x.i = z.j;

background image

Przeładowanie funkcji

background image

W jezyku C

w danym zakresie ważności może być
tylko

jedna

funkcja o danej nazwie.

Kompilator języka C++ daje nam większą
swobodę.

Przykład

:

void

pisz(

float

);

void

pisz(

char

,

int

,

char

);

pisz(‘C’, 123,
‘F’);

Czy masz

wątpliwość o

wywołanie jakiej

funkcji chodzi?

background image

Przeładowanie funkcji polega na tym, że

w danym zakresie ważności jest więcej
niż jedna funkcja o takiej samej nazwie.

To, która z nich zostaje w danym

przypadku uaktywniona zależy od typu
argumentów podanych podczas
wywołania.

Funkcje takie mają tę samą nazwę, ale

muszą się różnić liczbą lub typem
argumentów.

Przeładowujemy funkcję wówczas, gdy
wykonuje ona analogiczną akcję na różnych
zestawach obiektów
.

background image

void

pisz (

int

x);

void

pisz (

int

y);

// powtórna

deklaracja

void

pisz (

int

,

char

);

void

pisz (

float

,

char

,

int

);

Powtórna deklaracja nie jest błędem.
W przypadku deklaracji kompilator nie

zareaguje.

Zaprotestuje dopiero przy definicjach tych

funkcji

(

patrz

PROG112.CPP

)

background image

int

rysuj (

int

);

float

rysuj (

int

);

Przy przeładowaniu ważna jest tylko odmienność
argumentów.

int

rysuj (

float

,

int

);

int

rysuj (

int,

float

);

BŁĄD !

POPRAWNIE
!

background image

void

pisz (

float

);

void

pisz (

int

);

void

pisz (

int

,

int

);

pisz (123,
(

int

)45.67);

void

pisz (

int

,

int

);

void

pisz (

int

,

int

);

void

pisz (

int

,

unsigned

int

);

poprawnie
!

background image

Przeładowanie przy argumentach
domniemanych

void

pisz (

float

);

void

pisz (

char

*);

void

pisz (

int

,

float

= 0);

pisz (5.67);

//

pisz (

float

);

pisz (”2000 r.”);

//

pisz (

char

*

);

pisz (123);

//

pisz (

int

,

float

=

0);

pisz (123, 5.67);

//

pisz (

int

,

float

);

void

pisz (

int

);

background image

W rzeczywistości funkcje przeładowane mają

różne nazwy.

Kompilator zmienia nazwy wszystkich funkcji

programu.

Kompilator uzupełnia nazwę funkcji dodając typ

argumentów:

void

rys(

void

);

void

rys (

void

,

float

);

void

rys (

float

,

void

);

void

rys (

void

,

float

,

float

);

rys_Fv

rys_Fvf

rys _Ffv

rys _Fvf

Zmiana nazwy funkcji dotyczy zarówno

definicji i deklaracji funkcji, jak też i wywołań.

Informacja o typie zwracanym nie jest

doczepiana do nazwy.

background image

Rozwinięcie tej koncepcji stanowią szablony w

niektórych jezykach nazywane typami ogólnymi
(generic)

background image

Klasy

Klasy

background image

Definicja klasy:

class

identyfikator_typu

{

ciało klasy

}

;

gdzie

ciało

klasy

zawiera

deklaracje

składników

klasy.

class

Okrag

{

public

:

int

x, y, r;


}

;

background image

Deklaracja obiektu:

Okrag Zielony;
Okrag * Wskaz;
Okrag & Moj = Zielony;

Odwołanie się do składników

obiektu:

obiekt .

składnik

wskaźnik ->

składnik

referencja .

składnik

background image

Zielony . r = 100;
Wskaz = & Zielony;
Wskaz -> r = 100;
Moj . r = 100;

Przykład:

Składnikami

klasy mogą być też

funkcje

.

class

Okrag

{

public

:

int

x, y, r;

void

Inicjalizuj (

int

x1,

int

y1,

int

r1);

void

Rysuj (

int

x1,

int

y1,

int

r1);

}

;

background image

W ogólnym przypadku deklaracje funkcji w

klasie mogą być pomieszane z deklaracjami
danych.

Składnik jest znany w całej definicji klasy

,

niezależnie

od

miejsca

zdefiniowania

składnika wewnątrz klasy.

Nazwy deklarowane w klasie mają zakres

ważności równy obszarowi całej klasy.

W ciele klasy, jak w kapsule, zamknięte są

dane oraz funkcje operujące na tych danych.

Takie zamknięcie nazywamy

enkapsulacją

( od ang.

encapsulation

).

Funkcje składowe klasy mają zakres klasy.

Uwagi:

background image

Ukrywanie informacji:

class

Moj_Typ

{

private

:

int

Liczba;

// prywatne dane składowe

float

Temperatura;

char

Komunikat [80];

int

Czy_Gotowe( );

// prywatna funkcja

składowa

public

:

float

Predkosc;

// publiczna dana składowa

int

Pomiar( );

// publiczna funkcja

składowa

}

background image

Ukrywanie informacji:

3

etykiety, za pomocą których można

określać dostęp do składników klasy:

private

:

protected

:

public

:

background image

Ukrywanie informacji:

Składnik

private

: jest dostępny tylko dla

składowych danej klasy. Jeżeli zależy nam
na ukryciu informacji, to wówczas składnik
powinien być deklarowany jako prywatny.

Składnik

protected

: jest dostępny tak, jak

składnik

private

, ale dodatkowo jest

dostępny dla klas wywodzących się z danej
klasy.

Składnik

public

:

jest

dostępny

bez

ograniczeń. Zwykle składnikami takimi są
wybrane funkcje składowe. Za ich pomocą
dokonujemy z zewnątrz operacji na danych
prywatnych.

Etykiety można umieszczać w dowolnej

kolejności, mogą też się powtarzać.

Zakłada się, że dopóki w definicji klasy nie

wystąpi żadna z tych etykiet, składniki
przez domniemanie mają dostęp

private

.

background image

Klasa a obiekt :

class

Osoba

{

char

Nazwisko[40];

int

Wiek;

public

:

void

Zapamietaj (

char

*,

int

);

void

Pisz ( );

}

;

Osoba

Student1

,

Student2

,

Asystent

;

W pamięci utworzone zostały 3 różne

komplety danych składowych (

Nazwisko

i

Wiek

).

Natomiast funkcje składowe są

zapamiętane w pamięci

tylko jednokrotnie

.

background image

Funkcje składowe :

Funkcje składowe mają

pełny dostęp do

wszystkich składników swojej klasy

, to

znaczy: do danych (mogą z nich korzystać) i
do innych funkcji (mogą je wywoływać).

Do składnika swojej klasy funkcje odwołują

się przez podanie jego nazwy .

Nazwa_Obiektu .

Nazwa_Funkcji_Składowej

(

argumenty

);

Student1 .

Zapamietaj

(Kowalski, 21);

background image

Osoba * Wsk;
Wsk = &Asystent;
Osoba &Belfer = Asystent;

Wsk ->

Zapamietaj

(Kowalski, 21);

Belfer.

Zapamietaj

(Nowak, 30);

Możemy także wywołać funkcję składową
dla danego obiektu, pokazując na niego

wskaźnikiem

lub za pomocą

referencji

np.:

Funkcje składowe :

background image

Definiowanie funkcji składowych :

Pierwszy sposób

:

wewnątrz definicji klasy

.

class

Osoba

{

char

Nazwisko [80];

// składniki prywatne

int

Wiek;

public

:

// składniki publiczne

void

Zapamietaj (

char

* Napis,

int

Lata )

{

strcpy (Nazwisko, Napis);
Wiek = Lata;

}
void

Pisz ( )

{

cout

<<

Nazwisko

<<

, lat:

<<

Wiek

<<

endl

;

}

}

;

background image

Drugi sposób

: w

definicji klasy

umieszcza się

tylko

same deklaracje funkcji składowych

,

natomiast

definicje są napisane poza ciałem

klasy

.

class

Osoba

{

char

Nazwisko [80];

// składniki prywatne

int

Wiek;

public

:

// składniki publiczne

void

Zapamietaj (

char

* Napis,

int

Lata );

void

Pisz ( );

}

;

void

Osoba::Zapamietaj (

char

* Napis,

int

Lata )

{

strcpy (Nazwisko, Napis);
Wiek = Lata;

}
void

Osoba::Pisz ( )

{

cout

<<

Nazwisko

<<

, lat:

<<

Wiek

<<

endl;

}

background image

Ponieważ funkcje znajdują się teraz poza

definicją klasy, dlatego ich nazwa została
uzupełniona nazwą klasy, do której mają
należeć. Służy do tego operator zakresu

::

.

Funkcja zdefiniowana poza klasą ma dokładnie

taki sam zakres, jakby była zdefiniowana
wewnątrz klasy.

Jednakże sposób definiowania funkcji wewnątrz,

czy na zewnątrz definicji klasy stanowi różnicę
dla kompilatora.

Jeśli bowiem funkcję składową zdefiniowaliśmy

wewnątrz definicji klasy

, to kompilator uznaje,

że chcemy, aby ta funkcja była typu

inline

.

background image

inline

int

Zaokr (

float

Liczba);

{

return

(Liczba + 0.5);

}

Funkcja typu

inline

:

Jeśli ciało funkcji składowej ma nie więcej

niż dwie linijki, to funkcję tę definiujemy
wewnątrz klasy. Jest ona automatycznie

inline

.

Jeśli funkcja składowa jest dłuższa niż

dwie linijki, to definiujemy ją poza
definicją

klasy.

Nie

jest

ona

automatycznie

inline

background image

Funkcja składowa zdefiniowana poza definicją
klasy może być typu

inline

, ale trzeba to

zaznaczyć pisząc słowo

inline

, np.:

inline

void

Osoba :: Pisz ( )

{

//

ciało funkcji

}

background image

Odwołanie

się

do

publicznych

danych

składowych:

class

Liczby

{

int

L1:

public

:

float

L2;

void

Fun ( );

}

;

Są tu dwie dane składowe.

Składnik

L1

jest

prywatny

(przez

domniemanie).

Jako prywatny może być

dostępny tylko z

zakresu klasy

- czyli wewnątrz funkcji

składowej

Fun

.

background image

Składnik

publiczny

L2

oprócz tego, że może

być dostępny w funkcji składowej

Fun

,

dostępny jest

także z zewnątrz klasy

.

Pracując jednak na nim z zewnątrz musimy
podać, o który konkretnie obiekt chodzi, np.:

Liczby Temperatura, Cisnienie;

Temperatura . L2 = 18.6;
Ciscienie . L2 = 1003;
cout

<<

Temperatura

wynosi

:

<<

Temperatura . L2

<<

stopni C\n

;

cout

<<

Ciśnienie wynosi:

<<

Cisnienie . L2

<<

hPa\n

;

background image

Zasłanianie nazw

Przesyłanie do funkcji argumentów będących
obiektami

(

PROG57.CPP

)

Przez domniemanie zakłada się, że

obiekt jest przesyłany do funkcji

przez

wartość

.

Konsekwencja: jeśli obiekt jest duży, to

proces kopiowania może trwać długo.

Lepszym rozwiązaniem w takim

przypadku jest przesyłanie

przez

referencję

.

background image

void

Prezentacja (Osoba &Ktos)

{

cout

<<

Mam zaszczyt przedstawić

Państwu,\n

<<

Oto we własnej osobie:

;

cout

<<

Ktos . Pisz_Dane ();

}

background image

Konstruktor :

Definicję obiektu i nadanie mu wartości

można załatwić w jednej instrukcji.

W tym celu należy posłużyć się specjalną

funkcją składową zwaną

konstruktorem

.

Charakteryzuje się ona tym, że nazywa

się tak samo jak klasa.

class

Numer

{

int

Liczba;

public

:

Numer (

int

L )

{

Liczba = L;

}

//

konstruktor

void

Schowaj (

int

L )

{

Liczba = L;

}

int

Zwracaj ( )

{

return

Liczba;

}

}

;

(

PROG59.CPP

)

background image

Konstruktor

Klasy języka C++ wyposażone są w
specjalną funkcję zwaną

konstruktorem

;

Konstruktor

jest specjalną funkcją

składową, wywoływaną zawsze w chwili
tworzenia obiektu danej klasy;

Zadaniem konstruktora

jest inicjalizacja

danych składowych (pól) obiektu danej
klasy, przydzielenie pamięci dla jego
elementów oraz wykonanie innych
czynności niezbędnych do prawidłowego
utworzenia obiektu;

Konstruktor

nie jest obowiązkowym

elementem definicji klasy.

background image

Jeśli tworząc klasę nie zdefiniujesz jawnie

jej konstruktora, kompilator automatycznie
wygeneruje tzw.

konstruktor domyślny

;

Rozwiązanie takie, choć dość wygodne,

sprawdza się tylko dla bardzo prostych klas;

W praktyce każda definicja nietrywialnej

klasy będzie zawierała konstruktor;

Nazwa konstruktora

musi być taka sama,

jak nazwa zawierającej go klasy;

Konstruktor

nie może zwracać żadnej

wartości.

Konstruktor

background image

Klasa może posiadać

więcej niż

jeden konstruktor

;

Jest to możliwe dzięki

mechanizmowi

przeładowania

funkcji

;

Konstruktor

background image

Przypomnienie

- niezainicjalizowane

zmienne będą zawierały przypadkowe

wartości;

Reguła ta odnosi się również do klas;
Dobra praktyka wymaga inicjalizowania

wszystkich pól klasy;

Przypomnienie

- konstruktora nie można

wywołać jawnie;

Wywołanie konstruktora następuje w

chwili tworzenia obiektu danej klasy ;

W chwili powoływania obiektu wybierasz

również wersję konstruktora, jeżeli dana

klasa definiuje ich więcej.

Ciekawostki: niepubliczny konstruktor ?

Konstruktor

background image

Destruktor :

Przeciwieństwem

konstruktora

jest

destruktor

.

Destruktor

to funkcja składowa klasy.

Destruktor

nazywa się tak samo, jak

klasa z tym, że przed nazwą ma znak

~

(

tylda

).

Podobnie jak konstruktor - nie ma on

określenia typu zwracanego.

Destruktor

wywoływany jest wtedy, gdy

obiekt danej klasy ma być zlikwidowany.

background image

Destruktor

jest specjalną funkcją wywoływaną w

chwili likwidacji obiektu danej klasy;

Destruktor

jest funkcjonalnym przeciwieństwem

konstruktora;

Do jego zadań należy najczęściej

zwalnianie

zasobów

wykorzystywanych przez obiekt i inne

czynności natury porządkowej;

Destruktor

nie jest obowiązkowym elementem

klasy;

Destruktor

możesz zdefiniować tylko raz ;

Destruktor

jest funkcją bezparametrową i nie

zwracającą żadnej wartości;

Nazwa

składa się z

nazwy klasy

poprzedzonej

znakiem

~

.

Destruktor

(p.

PROG156.CPP,

PROG157a.CPP

)

background image

Destruktor

jest wywoływany w chwili

usuwania obiektu danej klasy;

Sama likwidacja obiektu może nastąpić

poprzez

usunięcie go ze stosu, jeśli jest to obiekt

lokalny, a operująca na nim funkcja właśnie
zakończyła działanie;

lub w wyniku wywołania operatora

delete

,

jeśli obiekt został utworzony dynamicznie.

W obu przypadkach wywołanie

destruktora jest ostatnią czynnością
obiektu przed jego unicestwieniem;

Destruktor

background image

Pola

klasy

to nic więcej, jak tylko jej lokalne

zmienne;

Pola klasy

funkcjonują tak samo, jak pola

struktury i różnią się od ostatnich wyłącznie

domyślną kategorią dostępu;

Wewnątrz klasy wszystkie pola są swobodnie

dostępne dla wszystkich funkcji składowych;

Natomiast ich widoczność na zewnątrz klasy

jest uwarunkowana kwalifikatorami dostępu;

Pola w sekcjach

private

i

protected

są na

zewnątrz niedostępne;

Pola

public

mogą być czytane i zapisywane

spoza klasy bez ograniczeń.

Pola

background image

Rozwiązaniem problemu dostępu do pól

prywatnych są specjalne funkcje klasy
ustawiające i pobierające wartości tych pól;

Funkcje te, zwane

funkcjami udostępniającymi

,

deklarowane są oczywiście w

sekcji publicznej

;

Fundamentaliści: ” wszystkie pola klas powinny

być prywatne, a dostęp do nich ma byś
realizowany wyłącznie za pomocą funkcji
udostępniających
”;

Radykałowie: ” wręcz przeciwnie”;
Wytyczenie granicy jest kwestią doświadczenia

i zdrowego rozsądku;

Jeśli nie wiesz, co robić z danym polem, umieść

go w sekcji prywatnej.

Pola

background image

Zestaw publicznych funkcji składowych powinien

być ograniczony do niezbędnego minimum
zapewniającego skuteczną komunikację z
obiektami i kontrolowanie ich działania;

Jeśli dana funkcja składowa nie musi być widoczna

na zewnątrz, powinna być zadeklarowana jako
prywatna;

Jeśli dana funkcja składowa nie musi być widoczna

na zewnątrz, ale powinna być dostępna dla klas
pochodnych, powinna być zadeklarowana jako
chroniona (

protected

);

Jeśli zależy Ci na szybkim wykonaniu funkcji, a

jednocześnie jest ona niewielka, możesz
zadeklarować ją jako funkcję wstawianą (

inline

);

Funkcje

background image

Deklaracja każdej klasy zawiera ukryte

pole wskaźnikowe o nazwie

this

;

Po utworzeniu obiektu, wskaźnik

this

zawiera adres obiektu w pamięci;

Oto klasa

Punkt

widziana oczami

komputera:

Wskaźnik this

Class

Punkt

{

private

:

Punkt *

this

;

int

x, y;

public

:

Punkt (

int

_x,

int

_y);

~Punkt()
....
};

background image

Czemu służy wskaźnik

this

?;

Każdy obiekt danej klasy posiada własną

kopię zestawu pól;

Natomiast funkcje składowe są

przechowywane w jednym egzemplarzu;

Wskaźnik

this

pozwala na

zidentyfikowanie właściciela danych, do
których odwołuje się funkcja składowa;

Jeśli chcesz uniknąć kłopotów, nigdy nie

zmieniaj wartości wskaźnika

this

!

Wskaźnik

this

background image

Dziedziczenie

Dziedziczeniem

nazywamy proces tworzenia

nowych klas na podstawie klas już
istniejących;

Klasa wykorzystywana jako podstawa w

procesie dziedziczenia jest

klasą bazową

;

Klasy dziedziczące po klasie bazowej są to

klasy pochodne

;

Klasa pochodna dziedziczy wszystkie

możliwości funkcjonalne klasy bazowej,
poszerzone o nowe pola i funkcje;

Niemożliwe jest usunięcie jakichkolwiek

elementów klasy bazowej.

background image

Dziedziczenie

Samo dziedziczenie symbolizowane jest

przez znajdujący się w pierwszym wierszu
deklaracji klasy

dwukropek

, po którym

występuje nazwa klasy bazowej ;

Słowo kluczowe

virtual

deklaruje

poprzedzoną nim funkcję jako

wirtualną

;

Jako przykład funkcji wirtualnych rozpatrz

funkcje o nazwie

Pokaz()

w klasach

Punkt

i

Linia_Pozioma

;

Ponieważ procedura narysowania na ekranie

linii różni się od procedury narysowania
punktu, należy w klasie

Linia_Pozioma

przedefiniować (

przesłonić

) funkcję

Pokaz()

.

background image

Przesłanianie

Przesłanianiem

nazywamy przedefiniowywanie

funkcji klasy bazowej w klasach pochodnych ;

Przesłanianie stosuje się w celu całkowitej

zmiany działania funkcji klasy bazowej lub,
znacznie częściej, jej uzupełnienia i rozszerzenia
o dodatkowe operacje;

Aby rozszerzyć pierwotną definicję

nie musisz jej

przepisywać

. W nowej definicji funkcji wystarczy

najpierw wywołać funkcję klasy bazowej, a
następnie dopisać kod realizujący rozszerzenia;

Przesłaniając funkcje klasy bazowej musisz

zapewnić

identyczność nagłówków funkcji

;

Istotne jest również aby funkcja

była dostępna

dla klas pochodnych

.

background image

Przesłanianie

Odwołując się do funkcji klasy bazowej musisz

poprzedzić ją nazwą klasy i operatorem
zakresu;

Użycie operatora zakresu jest konieczne tylko

wtedy, gdy wywoływana funkcja jest
zdefiniowana zarówno w klasie bazowej, jak i
pochodnej ;

Jeśli funkcja jest zdefiniowana tylko w sekcji

publicznej lub chronionej klasy bazowej, a nie
wchodzi w skład definicji klasy pochodnej,
możesz ją wywołać bez użycia operatora
zakresu.

background image

Funkcje wirtualne

Funkcją wirtualną

nazywamy funkcję

wywoływaną zawsze w obrębie posiadającej ją
klasy;

Poprzedzenie deklaracji słowem kluczowym

virtual

powoduje, że wszystkie odwołania do

funkcji będą zawsze wykonywane w obrębie
klasy, która ją zdefiniowała;

Jeśli podejrzewasz, że funkcja będzie
przedefiniowywana w klasach pochodnych,
warto deklarować ją jako wirtualną;

Jeśli przedefiniowywana funkcja jest
wywoływana przez inne funkcje klasy
bazowej, prawie na pewno powinieneś
zadeklarować ją jako wirtualną.

background image

Inicjalizacja obiektów

Kolejnym problemem związanym z

dziedziczeniem jest

inicjalizacja obiektów

;

W chwili utworzenia obiektu klasy pochodnej

należy zainicjalizować też pola klasy bazowej,
a w przypadku dziedziczenia „piętrowego” -
również wszystkich klas pośrednich;

Najskuteczniejszym na to sposobem jest po

prostu wywołanie konstruktora klasy bazowej
w konstruktorze klasy pochodnej;

Ponieważ nie można wywołać tego

konstruktora bezpośrednio, należy
wykorzystać w tym celu listę inicjalizującą

;

Prawidłowa inicjalizacja klasy bazowej w

konstruktorze klasy pochodnej jest sprawą
bardzo istotną.


Document Outline


Wyszukiwarka

Podobne podstrony:
c++ wykłady, Wyc 5 add wskaznki
c++ wykłady, Wyc 2
Wykład 5. Elektronowa struktura atomu, chemia, CHEMIA OGÓLNA -Walkowiak- (WPC 1002w) DOC
WYKŁAD 5. Elektronowa struktura atomu, chomikowe, WYKŁADY z Chemii
WYKŁAD 7 MODEL I STRUKTURA KOPALNI
Algorytmy wyklady, Elementarne struktury danych
wyklad 4c Struktura spoleczna Polski po transformacji
c++ wykłady, Wyc 5
Wykład 1 Ujęcie strukturalno formalne pedagogiki
wyklad 4b Struktura spoleczna Inteligencja
wykład 4a Struktura społeczna Klasa średnia
Zarządzanie Wykład VII- Struktury organizacyjne
c++ wykłady, Wyc 3

więcej podobnych podstron