C++ - Obiekt a klasa.pdf

(80 KB) Pobierz
C - Obiekt a klasa
Obiekt a klasa
Programy możemy tworzyć również z użyciem klas i obiektów, jest to wówczas
programowanie obiektowe. Obiektem w tym programowaniu nazywamy podstawowy
element programu łączący opis stanu pewnej cząstki rzeczywistości (jaką opisuje program) z
jej zachowaniem, czyli funkcjami, inaczej zwanymi metodami obiektu. Na przykład
obiektem może być samochód, który jest opisany przez takie parametry jak: marka, moc
silnika, aktualna prędkość, waga itp. Samochód ma również przyporządkowane funkcje
(akcje), które może wykonać, na przykład: jazda z przyspieszeniem, tankowanie albo sposób
zachowania się na zakręcie. Oczywiście, funkcje wykonywane przez samochód mogą, ale nic
muszą wpływać na wartości parametrów opisujących jego stan, na przykład przyspieszenie
ma wpływ na aktualną prędkość, lecz nie na markę.
Podejście obiektowe (łączące w jedną całość dane i funkcje wykonywane na tych danych)
znacznie się różni od omawianego do tej pory programowania proceduralnego, w którym
dane oraz funkcje na nich operujące są traktowane rozłącznie i programista musi pamiętać o
odpowiednim użyciu funkcji do odpowiednich danych. Obiektowe podejście do
programowania jest zgodne ze sposobem percepcji otoczenia przez mózg ludzki - zawsze
kojarzymy cechy obiektów z ich zachowaniem. Dodatkowo silna jest w człowieku potrzeba
klasyfikowania obiektów podobnych - co również ma swoje odbicie w programowaniu
obiektowym. Wszystkie obiekty są mianowicie elementami klas, które reprezentują typ
obiektów o takim samym zachowaniu. Programowanie obiektowe polega na opisie
wzajemnych interakcji obiektów, w czym jest analogiczne do tego, co obserwujemy w
realnym świecie. Programowanie obiektowe jest najbardziej naturalnym sposobem opisu
rzeczywistości w programach komputerowych, dzięki czemu programy takie są łatwe w
pisaniu i późniejszej analizie
Budowanie programów obiektowych umożliwiają wspomniane wcześniej narzędzia. Oto ich
formalna definicja
Definicja
Klasa jest to złożony typ będący opisem (definicją) pól i metod obiektów do niej
należących.
Definicja
Obiekt jest konkretyzacją danej klasy i wypełnieniem wzorca (jakim jest klasa)
określonymi danymi.
W uproszczeniu można powiedzieć, że klasa to struktura wzbogacona o definicje funkcji.
Podobnie zatem jak struktura, klasa ma zdefiniowane pola, w których są przechowywane
dane obiektów. Liczba i typy pól są definiowane przez programistę. W klasie zdefiniowane są
również metody, czyli funkcje, które może wykonywać obiekt danej klasy. Ogólnie pola i
metody klasy nazywamy składnikami klasy.
Definicja klasy nie wiąże się z utworzeniem obiektu, jest to jedynie zdefiniowanie nowego typu
obiektów (zmiennych). Klasa jest typem obiektu, a nie samym obiektem
Sposób definiowania klasy w programie pokażemy na przykładzie klasy ułamków zwykłych:
class Ułamek
{ public:
int licznik; int mianownik;
};
Definicja klasy rozpoczyna się od słowa kluczowego class. Za klamrą zamykającą definicję
klasy wymagany jest średnik, tak jak przy definiowaniu struktury. We wnętrzu klasy pojawiło
się nowe słowo: public. Oznacza ono, że składniki po nim wymienione (po dwukropku) są tak
zwanymi składnikami publicznymi klasy, czyli mamy do nich dostęp zarówno z wnętrza
klasy, jak i spoza niej. Tak napisana klasa funkcjonalnie niczym się nie różni od podobnej
struktury. Różnice te pojawią się, kiedy użyjemy składników o innym zakresie dostępu niż
218500702.002.png
public. W klasie mogą wystąpić składniki opatrzone etykietą private - są one prywatnymi
składnikami klasy, a dostęp do nich jest możliwy wyłącznie z wnętrza klasy. W wypadku
danych oznacza to, że tylko funkcje będące składnikami lej klasy mogą do nich zapisywać
lub z nich czytać, w wypadku zaś funkcji - tylko inne funkcje składowe tej klasy mogą je
wywołać. Składniki klasy mogą być również typu protected, są one wówczas dostępne
zarówno z wnętrza klasy, jak i dla klas jej pochodnych (opisanych na końcu tego rozdziału).
Domyślnie-jeśli nie ma podanego innego zakresu dostępu, dane i funkcje zadeklarowane
wewnątrz klasy są prywatne. Etykiety public, private, protected zwane są inaczej
specyfikatorami dostępu.
Deklaracja obiektu uli klasy Ułamek zdefiniowanej wcześniej może wyglądać następująco:
z nich czytać, w wypadku zaś funkcji - tylko inne funkcje składowe tej klasy mogą je
wywołać. Składniki klasy mogą być również typu protected, są one wówczas dostępne
zarówno z wnętrza klasy, jak i dla klas jej pochodnych (opisanych na końcu tego rozdziału).
Domyślnie-jeśli nie ma podanego innego zakresu dostępu, dane i funkcje zadeklarowane
wewnątrz klasy są prywatne. Etykiety public, private, protected zwane są inaczej
specyfikatorami dostępu.
Deklaracja obiektu uli klasy Ułamek zdefiniowanej wcześniej może wyglądać następująco:
Ułamek uli;
Dostęp do poszczególnych składników obiektów uzyskujemy w taki sam sposób, jak do
składowych struktur, czyli za pomocą operatora wyłuskania (kropki):
uli.licznik; uli.mianownik;
Hermetyzacja - rola metod publicznych
Wymienione specyfikatory dostępu ściśle łączą się z jedną z najważniejszych cech
programowania obiektowego, jaką jest hermetyzacja.
Definicja
Hermetyzacja pozwala na ukrycie pewnych danych i funkcji obiektu przed bezpośrednim dostępem z
zewnątrz
Dzięki temu mechanizmowi otrzymujemy obiekt, do którego ukrytych (hermetycznych) pól
i metod mamy dostęp tylko przez udostępnione metody publiczne. Jest to bardzo cenna cecha
programowania obiektowego, ponieważ znacznie ogranicza możliwość popełnienia błędu.
Wyobraź sobie samochód, w którym masz dostęp nie tylko do pedału gazu, sprzęgła i
hamulca, ale możesz także dowolnie ustawić napięcie prądnicy, fazę zapłonu, skład
mieszanki paliwowej, ciśnienie oleju - łatwo sobie wyobrazić, że taki samochód byłby bardzo
narażony na usterki.
Napiszemy zatem naszą klasę Ułamek w taki sposób, aby dostęp do danych: licznik i
mianownik był możliwy tylko przez metody, które „wiedzą", jak postępować z licznikiem i
mianownikiem. W klasie Ułamek niedopuszczalna powinna być operacja przypisania
mianownikowi wartości 0.
Wzbogaćmy zdeliniowaną klasę o metody związane z obsługą ułamków. Będą to publiczne
składowe klasy. Na początek zdefiniujemy funkcję zapisz ( ), wypełniającą obiekty klasy
Ułamek, i funkcję wypisz ( ), wyświetlającą te obiekty:
class Ułamek
{
int licznik; int mianownik;
public:
void zapisz(int 1, int m);
// deklaracja metody klasy
void wypisz()
{ cout << licznik << "/" << mianownik;
}
}; // koniec definicji klasy
void Ułamek::zapisz(int 1, int m) // definicja metody klasy
{
218500702.003.png
licznik = 1; if (m!=0)
mianownik = m; else
{
cout << "Mianownik nie moŜe mieć wartości 0 "; getchar() ; exit(
1) ; } >
Operator zasięgu
Teraz klasa Ułamek ma cztery składowe: dwie prywatne - licznik
mianownik, oraz dwie publiczne - zapisującą dane do obiektu Ułamek i wypisującą wartość
ułamka. Definicje metod klasy umieściliśmy na dwa dopuszczalne sposoby. Metoda wypisz
jako bardzo krótka została umieszczona wewnątrz definicji klasy. Natomiast metoda zapisz
została tylko zadeklarowana wewnątrz klasy, a jej definicję umieściliśmy poza definicją
klasy, dlatego jej nazwę uzupełniliśmy nazwą klasy, do której metoda należy. W tym celu
posłużyliśmy się tak zwanym operatorem zasięgu, oznaczanym : : (podwójnym
dwukropkiem).
Zauważ, że zmieniliśmy specyfikator dostępu przy polach licznik i mianownik, ponieważ
usunęliśmy specyfikator public. Jest to równoznaczne z uzyskaniem przez obydwa pola
domyślnego statusu private. Do pól licznik i mianownik zadeklarowanej w ten sposób klasy
nie można się już odwołać w programie (spoza klasy) przez operator wyłuskania. Do pól
prywatnych mają dostęp jedynie metody klasy. Tu właśnie mamy do czynienia z
mechanizmem hermetyzacji. Wartości licznika i mianownika obiektu możemy zmienić tylko
przy użyciu funkcji zapisz. Dzięki takiemu rozwiązaniu zabezpieczamy się przed
przypisaniem mianownikowi niedozwolonej wartości 0. Jeśli spróbujemy to zrobić, metoda
zapisz zareaguje wypisaniem odpowiedniego komunikatu i zakończeniem programu
(funkcja exit (1) kończy cały program, przekazując do systemu operacyjnego wartość 1
sygnalizującą błąd).
Jeśli zdefiniowaliśmy klasę, możemy już utworzyć obiekty tej klasy.
Spójrz na prosty program, który wykorzystuje klasę zdefiniowaną powyżej:
#include <iostream> #include
<cstdio> using namespaco std;
class Ułamek
{ int licznik;
int mianownik; public:
void zapisz(int 1, int m);
void wypisz()
{cout << licznik << "/" << mianownik;}
}; // koniec definicji klasy
void Ułamek::zapisz(int 1, int m) // definicja metody klasy
{ licznik = 1; if (m!=0)
mianownik = m; else
{
cout << "Mianownik nie moŜe mieć wartości 0 "; getchar();
exit(1) ; } } int main()
{ Ułamek uli, ul2; uli.zapisz(4,5);
ul2.zapisz(1,7); cout << "Pierwszy
ułamek: "; uli.wypisz ( ) ;
cout << endl << "Drugi ułamek: ";
ul2.wypisz();
getchar( ) ;
return 0;
}
Jako efekt działania tego krótkiego programu na ekranie monitora zobaczymy:
Pierwszy ułamek: 4/5
Drugi ułamek: 1/7
. Rola konstruktora i destruktora
218500702.004.png
Przyjrzyj się sposobowi deklarowania obiektów klasy Ułamek z poprzedniego przykładu i
nadawaniu im wartości:
Ułamek uli;
uli.zapisz(4,5);
Najpierw deklarujemy obiekt, a następnie uruchamiamy odpowiednia funkcję (dostęp do
metody, podobnie jak dostęp do pól danych klasy, uzyskujemy przez operator kropki). Jeśli
tworzymy klasy, chcielibyśmy móc się nimi posługiwać tak samo wygodnie jak każdym
innym typem. Jak sobie zapewne przypominasz, deklarując zmienną typu int, możemy
nadać jej wartość w jednej instrukcji:
int z = 18;
W podobny sposób możemy również zadeklarować i nadać początkowe wartości
obiektom klasy Ułamek. W tym celu użyjemy specjalnej metody zwanej
konstruktorem.
Definicja
Konstruktorem nazywamy specjalną metodę automatycznie uruchamianą w trakcie
definiowania (tworzenia) obiektu pozwalającą na nadanie początkowych wartości
danym obiektu.
Omówmy sposób deklaracji konstruktora w C++. Metoda ta nie przyjmuje żadnej wartości
(nawet void!), a jej nazwa jest taka jak nazwa zawierającej ją klasy. Przeróbmy zatem
metodę zapisz na konstruktor:
class Ułamek
{ int licznik;
int mianownik; public:
Ułamek (int 1, int m);
void wypisz()
{ cout << licznik << "/" << mianownik;
}
// deklaracja konstruktora
};
// koniec definicji klasy
{ licznik = 1; if (m!=0)
mianownik = m; else
{ cout << "Mianownik nie moŜe mieć wartości 0 ";
getchar();
exit(1 ) ; } >
Obiekt zdefiniowanej w ten sposób klasy możemy zadeklarować następująco:
Ułamek l i c z b a ( 3 , 5 ) ;
Przy tak zbudowanym konstruktorze straciliśmy jednak możliwość deklarowania obiektu bez
nadawania mu początkowych wartości (co mogliśmy zrobić w poprzednim programie).
Istnieje rozwiązanie tego problemu. Klasa może mieć kilka różnych konstruktorów
różniących się liczbą argumentów -jest to tak zwane przeciążenie konstruktora. Aby móc
deklarować obiekty typu Ułamek bez konieczności podawania ich wartości początkowych,
możemy napisać bezparametrowy konstruktor, który sam nadaje początkowe wartości
obiektom, na przykład:
class Ułamek
{ int licznik; int mianownik;
public:
Ulamek()
// definicja konstruktora
{ licznik = 1;
// definicja pierwszego konstruktora (bezparametrowego)
// mianownikowi ułamka przypisujemy wartość 1
mianownik 1;
}
Ułamek (int 1, int m); // deklaracja drugiego konstruktora (z parametrami)
void wypisz( )
Ułamek::Ułamek(int 1, int m)
// licznikowi ułamka przypisujemy wartość 1
218500702.005.png
{ cout << licznik << "/" << mianownik;}
}; // koniec definicji klasy
Ułamek::Ułamek( int 1, int m) // definicja konstruktora z parametrami
{ licznik = ul; if (m!=0)
mianownik - m; else
{ cout << "Mianownik nie moŜe mieć wartości 0 "; getchar();
exit( 1) ;
Oprócz konstruktora w klasie powinien być również zadeklarowany
destruktor.
Definicja
Destruktorem nazywamy specjalną metodę bezparametrową, która jest wywoływana zawsze w
momencie usuwania obiektu.
Destruktor ma nazwę taką jak nazwa klasy, ale poprzedzoną wężykiem -, na przyktad
destruktor klasy Rakieta będzie miał nazwę -Rakieta. Dla naszej klasy Ułamek destruktor
może wyglądać następująco:
~Ułamek()
{cout << "Usuwam obiekt ";}
Destruktor ten poza usunięciem obiektu wypisuje jeszcze odpowiedni komunikat - dzięki
niemu wiemy, kiedy jest uruchamiany.
Jeśli w swojej klasie nie zdefiniujesz destruktora, zostanie on wygenerowany przez kompilator.
Destruktor jest metodą, która uruchamia się automatycznie zwykle tuż przed zakończeniem
programu. Sami możemy spowodować wywołanie destruktora dla obiektów znajdujących się
w pamięci dynamicznej.
Jak się zapewne domyślasz, obiekty zdefiniowanej klasy mogą być argumentami funkcji, a
funkcja może przyjmować wartość obiektu zdefiniowanej klasy.
Przykład
Napiszemy program, który mnoży dwa ułamki. Wynik zostaje wyświe-tlony na ekranie
monitora
Funkcja zaprzyjaźniona
W tym celu zdefiniujemy funkcję, która pobiera dwa obiekty klasy Ułamek, mnoży je i
przyjmuje wartość obiektu tej samej klasy będący iloczynem pobranych ułamków. Funkcja
mnożąca ułamki nic będzie metodą klasy - działa jedynie na obiektach zdefiniowanej klasy.
Pojawia się tu jednak problem, ponieważ do składowych prywatnych klasy mają dostęp tylko
metody klasy. W naszym wypadku wystąpi zatem problem z dostępem funkcji zewnętrznej do
licznika i mianownika, gdyż są to składowe prywatne obiektu typu Ułamek. Aby funkcja
niebędąca elementem klasy miała dostęp do jej składników prywatnych, musimy w klasie
zapisać deklarację przyjaźni (zaprzyjaźnienia się) z tą funkcją.
Definicja
Funkcja zaprzyjaźniona to taka funkcja zewnętrzna (niebędąca składnikiem klasy), dla
której w definicji klasy umieszczona jest deklaracja przyjaźni za pomocą sfowa
kluczowego friend.
Klasa może być też zaprzyjaźniona z inną klasą, ale to zagadnienie wykracza poza ramy
tego podręcznika.
W wypadku naszej klasy Ułamek po wpisaniu:
friend Ułamek pomnoz (Ułamek, Ułamek);
> }
218500702.001.png
Zgłoś jeśli naruszono regulamin