07 - Programowanie obiektowe - klasy, obiekty, ochrona danych.pdf

(233 KB) Pobierz
Lekcja 7: Programowanie obiektowe - klasy, obiekty, ochrona
danych.
Wst ħ p
Umiecie ju Ň pisa ę programy strukturalne (przynajmniej mamy tak Ģ nadziej ħ ). Prawdopodobnie słyszeli Ļ cie te Ň o obiektach - mo Ň e
jeszcze nie w naszym podr ħ czniku, ale gdzie Ļ na pewno. Aby w pełni skorzysta ę z mo Ň liwo Ļ ci programowania wizualno-obiektowego
w BCB, musicie pozna ę cho ę by podstawy programowania zorientowanego obiektowo, b ħ d Ģ cego istot Ģ programowania w C++. To
przede wszystkim bezpo Ļ rednim wsparciem dla takiego programowania ró Ň ni si ħ j ħ zyk C++ od "zwykłego", czyli strukturalnego j ħ zyka
C.
Oprócz zwykłej dawki teorii zamie Ļ cili Ļ my wi ħ c w tej lekcji przykład obiektowej analizy problemu. Natomiast nie b ħ dzie tu ju Ň
kalkulatora. Zamiast niego - zamieszczamy przykład przydatnej klasy, która realizuje rotacyjny bufor na liczby o wysokiej wydajno Ļ ci.
Obiekty i klasy
W trakcie dotychczasowego kursu programowania poznali Ļ cie (mamy tak Ģ nadziej ħ ) podstawy programowania strukturalnego. Jest to
historycznie najstarsza metodologia programowania. Mimo tego jest do tej pory znana i cz ħ sto stosowana, szczególnie wsz ħ dzie tam,
gdzie nie mamy dost ħ pu do nowoczesnych j ħ zyków obiektowych lub gdzie nale Ň y szybko rozwi Ģ za ę niezbyt skomplikowany problem.
Ta lekcja po Ļ wi ħ cona b ħ dzie innemu podej Ļ ciu do programowania, tzn. programowaniu zorientowanemu obiektowo . Aby pokaza ę
Ň nic ħ pomi ħ dzy obiektowym a strukturalnym podej Ļ ciem do programowania, spróbujmy jeszcze raz sprecyzowa ę , czym
charakteryzuje si ħ programowanie strukturalne. W tego typu podej Ļ ciu do programowania najwa Ň niejszy jest algorytm, natomiast
sposób zapisu i pami ħ tania danych, na jakich operuje, jest drugorz ħ dny.
Wady programowania strukturalnego
Analizuj Ģ c problem strukturalnie, zawsze układamy pewn Ģ kolejno Ļę działa ı , operacji wykonywanych przez komputer. Efektem tego
jest
powstawanie
funkcji.
Ka Ň dy
z
algorytmów
wymaga
pewnych
danych
wej Ļ ciowych,
które
przekształca,
produkuj Ģ c
dane
wyj Ļ ciowe. Tak wi ħ c wybór czy te Ň opracowanie
algorytmu determinuje, jakich
danych b ħ dzie potrzebował i
jak maj Ģ by ę one
zapami ħ tywane. W ten sposób dostajemy list ħ parametrów i zmiennych przekazywanych do procedury.
ņ eby nie by ę gołosłownym, przeanalizujmy proces powstawania prostego programiku rysuj Ģ cego np. prostok Ģ t na ekranie komputera i
dokonuj Ģ cego na nim podstawowych przekształce ı , tzn. obrotu i przesuni ħ cia. Celem jest otrzymanie rysunku i jego pó Ņ niejsze
przekształcenia. Najpierw zastanówmy si ħ , jak rysuje si ħ prostok Ģ t. Wystarczy, Ň e narysujemy cztery odcinki ł Ģ cz Ģ ce jego wierzchołki.
Potrzebujemy wi ħ c zapami ħ ta ę poło Ň enia czterech punktów stanowi Ģ cych wierzchołki prostok Ģ ta. Oczywi Ļ cie nie jest to jedyny sposób
opisu, jaki mo Ň emy zastosowa ę , lecz wydaje si ħ , Ň e jest najbardziej naturalny. Je Ļ li chcemy przesun Ģę prostok Ģ t o wektor (x,y),
wystarczy Ň e do odpowiednich współrz ħ dnych dodamy współrz ħ dne wektora. Inaczej z obrotem - aby go dokona ę , musimy zna ę
Ļ rodek i k Ģ t, o jaki nale Ň y prostok Ģ t obróci ę . W tym przypadku wygodniej byłoby pami ħ ta ę prostok Ģ t jako poło Ň enie jego Ļ rodka,
długo Ļę przek Ģ tnej oraz dwa k Ģ ty: pomi ħ dzy przek Ģ tnymi i pomi ħ dzy jedn Ģ z nich a osi Ģ układu współrz ħ dnych. W wyniku
przedstawionej analizy otrzymujemy kilka procedur oraz struktur ħ danych, w jakiej pami ħ tamy prostok Ģ t. Przykładowo, mogłoby to
wygl Ģ da ę tak:
struct SPr1
{
int x1, y1, x2, y2, x3, y3, x4, y4;
};
struct SPr2
{
int xs, ys;
double dl_p;
double kat_p, kat_ox;
};
void rysuj(SPr1 p);
SPr1 przesun (SPr1 p, int x, int y);
SPr2 obroc(SPr2 p, double kat);
899923668.048.png 899923668.050.png 899923668.051.png 899923668.052.png 899923668.001.png 899923668.002.png 899923668.003.png 899923668.004.png 899923668.005.png 899923668.006.png 899923668.007.png 899923668.008.png
 
I jeszcze dwie procedury pomocnicze, wykorzystywane przez procedur ħ obracaj Ģ c Ģ , a słu ŇĢ ce do przekształcenia opisu opartego na
czterech punktach w opis oparty na Ļ rodku, przek Ģ tnej i dwu k Ģ tach - i na odwrót:
SPr2 wierzcholki_na_katy(SPr1 p);
SPr1 katy_na_wierzcholki(SPr2 p);
Dobrym zwyczajem w programowaniu jest przewidywanie sytuacji bł ħ dnych i reagowanie na nie, tak wi ħ c ka Ň d Ģ z wymienionych
procedur nale Ň ałoby poszerzy ę o
sprawdzanie
ħ dów (np. czy
dane
cztery punkty rzeczywi Ļ cie tworz Ģ prostok Ģ t, czy
długo Ļę
przek Ģ tnej nie jest ujemna, itp ...).
Je Ň eli uwa Ň nie przeczytali Ļ cie powy Ň szy tekst, zauwa Ň yli Ļ cie zapewne, Ň e teraz stosunkowo proste zadanie narysowania prostok Ģ ta, po
analizie i rozpisaniu na procedury, nie jest ju Ň takie przejrzyste. Musimy pami ħ ta ę , która procedura potrzebuje jakich danych, mamy
dwa niezale Ň ne opisy prostok Ģ ta i musimy dba ę o synchronizacj ħ pomi ħ dzy nimi; przy tym zadania wykonywane przez procedury
cz ħĻ ciowo si ħ pokrywaj Ģ (kontrola poprawno Ļ ci danych wej Ļ ciowych powinna znale Ņę si ħ w ka Ň dej procedurze) itd. Je Ļ li chcieliby Ļ my
dalej rozbudowywa ę ten program o nowe figury, bardzo szybko dostaniemy olbrzymi Ģ liczb ħ procedur i zmiennych w lu Ņ ny sposób
powi Ģ zanych ze sob Ģ .
Zaprezentowane powy Ň ej podej Ļ cie do problemu nie jest do ko ı ca typowe dla człowieka. Na ogół jak my Ļ limy o prostok Ģ cie, to
my Ļ limy o nim jako o cało Ļ ci. Prostok Ģ t jest figur Ģ geometryczn Ģ składaj Ģ c Ģ si ħ z czterech boków parami równoległych, o wszystkich
k Ģ tach prostych. Prostok Ģ t mo Ň na opisa ę na kilka sposobów, mo Ň na narysowa ę , przemie Ļ ci ę , obróci ę , zetrze ę . W sposób naturalny
traktujemy prostok Ģ t jako obiekt. Podobnie traktujemy np. samochód - blaszane pudełko na kółkach, okre Ļ lone kolorem, mark Ģ ,
kształtem, które przemieszcza si ħ , skr ħ ca, ma pewne osi Ģ gi itd... Powstaje wi ħ c pytanie - dlaczego takiego wła Ļ nie postrzegania Ļ wiata
nie przenie Ļę do programowania? Odpowiedzi Ģ na nie jest wła Ļ nie programowanie zorientowane obiektowo .
Co to jest obiekt i czym jest klasa
Skoro mówimy o programowaniu zorientowanym obiektowo, potrzebna jest jaka Ļ definicja obiektu. Podczas tego kursu obiektem
b ħ dziemy nazywali dane i algorytmy na nich operuj Ģ ce. Zatem w powy Ň szym przykładzie w skład obiektu nazywanego prostok Ģ tem
b ħ d Ģ wchodzi ę nie tylko dane stanowi Ģ ce jego opis, ale i wszystkie procedury słu ŇĢ ce zarówno do zmiany czy przekształcania tych
danych, czy te Ň do wykonywania odpowiednich działa ı ŇĢ danych przez u Ň ytkownika (np. narysowania obiektu na ekranie). W dalszym
opisie dane b ħ dziemy cz ħ sto
nazywali
polami lub
wła Ļ ciwo Ļ ciami obiektu, natomiast algorytmy zapisane w postaci funkcji lub
procedur - metodami .
Na pocz Ģ tek mo Ň ecie sobie wyobrazi ę obiekty jako troch ħ bardziej zło Ň one rekordy, które poznali Ļ cie ju Ň wcze Ļ niej. Oprócz pól, do
których ju Ň jeste Ļ my przyzwyczajeni, dokładamy jeszcze metody, definiowane w sposób zbli Ň ony do definiowania funkcji.
rekordy składaj Ģ si ħ z pól
obiekty składaj Ģ si ħ z pól i metod działaj Ģ cych na tych polach
I podobnie jak definiowali Ļ my typ rekordowy , by opisa ę struktur ħ rekordu, mo Ň emy teraz zdefiniowa ę typ obiektowy , by opisa ę
struktur ħ obiektu. Przy czym zamiast poj ħ cia typu obiektowego w C++ u Ň ywa si ħ poj ħ cia klasa (klasa obiektów).
Zamiast mówi ę typ obiektowy mo Ň emy mówi ę po prostu: klasa .
Definicja klasy jest podobna do definicji typu rekordowego (struktur), musi tylko uwzgl ħ dnia ę metody. W opisie typu rekordowego
u Ň ywali Ļ my słowa kluczowego struct . W opisie typu obiektowego (klasy) b ħ dziemy u Ň ywa ę słowa kluczowego class .
W przypadku najprostszych klas, w których nie jest potrzebne definiowanie widoczno Ļ ci poszczególnych pól czy te Ň metod,
mo Ň emy pozosta ę przy słowie struct lub u Ň ywa ę go zamiennie ze słowem class , lecz lepiej od razu przyzwyczaja ę si ħ do dobrego
standardu u Ň ywania słowa class , którego wymaga prawdziwe programowanie obiektowe.
Załó Ň my, Ň e chcemy zdefiniowac klas ħ b ħ d Ģ c Ģ bardzo prost Ģ komputerow Ģ reprezentacj ħ diody. Przyjmijmy, Ň e dioda, traktowana jako
ogólne poj ħ cie, b ħ dzie okre Ļ lona przez dwie wła Ļ ciwo Ļ ci (dwa pola):
ma jaki Ļ kolor (pole typu string)
jest zapalona albo nie (pole typu bool)
Na polach tych chcemy móc wykonywa ę nast ħ puj Ģ ce operacje (metody):
"zapalenie diody"
"zgaszenie" diody
obejrzenie jej stanu: czy Ļ wieci i w jakim kolorze.
899923668.009.png 899923668.010.png 899923668.011.png 899923668.012.png 899923668.013.png 899923668.014.png 899923668.015.png 899923668.016.png 899923668.017.png 899923668.018.png 899923668.019.png
Nasz Ģ klas ħ opisuj Ģ c Ģ poj ħ cie diody nazwiemy CDioda - zaczynaj Ģ c jej nazw ħ od du Ň ego C, by od razu było wida ę , Ň e jest to nazwa
klasy. To nieobowi Ģ zuj Ģ ca, ale wygodna konwencja, analogiczna do zapisu struktur - typów rekordowych, których nazwy zaczynali Ļ my
od du Ň ego S (lub T).
Mo Ň emy wi ħ c ju Ň zaproponowa ę definicj ħ klasy CDioda:
class CDioda {
public :
string kolor;
bool zapalona;
void zapal() {
zapalona = true ;
}
void zgas() {
zapalona = false ;
}
void pokaz() {
if (zapalona)
cout << "Swieci w kolorze " << kolor << endl;
else
cout << "Nie swieci\n" ;
}
};
W ten sposób pokazali Ļ my Wam, jak mo Ň na zdefiniowa ę nowy typ obiektowy - klas ħ . Lecz typ obiektowy, czyli klasa obiektów, albo
krócej klasa, to jeszcze nie obiekt - pami ħ tajcie o tym. Definicja klasy jest opisem wspólnych metod i pól, specyficznych dla
wszystkich obiektów tej klasy, bez podawania ich konkretnych warto Ļ ci. Przykładowo klasa obiektów znana pod poj ħ ciem człowiek
definiuje, i Ň ka Ň dy człowiek ma kolor oczu. Nie jest powiedziane jaki - wiadomo tylko, Ň e ma. Natomiast doprecyzowanie (okre Ļ lenie,
jaki to jest kolor) - odbywa si ħ w odniesieniu do konkretnego obiektu - takiego lub innego człowieka. Podobnie
klasa CDioda
definiuje, Ň e ka Ň dy obiekt tej klasy (dioda) ma jaki Ļ kolor. A jaki - to ju Ň zale Ň y od konkretnej diody.
Tutaj mamy wi ħ c podobn Ģ sytuacj ħ jak w przypadku rekordów. Najpierw definiowali Ļ my typ rekordowy (struktur ħ ), a dopiero potem
rekordy - zmienne danego typu. I dopiero z tych zmiennych mogli Ļ my zacz Ģę korzysta ę . Podobnie wygl Ģ da sytuacja w przypadku
obiektów. ņ eby mo Ň na było ró Ň ne obiekty - diody wykorzysta ę w naszym programie, musimy najpierw zdefiniowa ę klas ħ CDioda, a
potem utworzy ę jakie Ļ zmienne klasy CDioda (zmienne typu obiektowego CDioda).
Zobaczcie to na przykładzie:
1. #include <iostream>
2. #include <cstdlib>
3.
4. using namespace std;
5.
6. /* Definicja typu obiektowego - klasy CDioda*/
7.
class CDioda {
8.
public :
9.
// definicje pól o nazwach kolor i zapalona
10.
string kolor;
11.
bool zapalona;
12.
// definicja metody zapal
13.
void zapal() {
14.
zapalona = true ;
15.
}
16.
// definicja metody zgas
17.
void zgas() {
18.
zapalona = false ;
19.
}
20.
// definicja metody pokaz
21.
void pokaz() {
22.
if (zapalona)
23.
cout << "Swieci w kolorze " << kolor << endl;
24.
else
25.
cout << "Nie swieci\n" ;
26.
}
899923668.020.png 899923668.021.png 899923668.022.png 899923668.023.png 899923668.024.png 899923668.025.png
 
27. };
28.
29. int main( int argc, char *argv[])
30. {
31. cout << "Diody ..." << endl;
32.
33. // utworzenie dwóch obiektów typu CDdioda, o nazwach d1 i d2
34. CDioda d1, d2;
35.
36. // ustawienie warto Ļ ci ich pól
37. d1.kolor = "zielony" ;
38. d2.kolor = "czerwony" ;
39. d1.zapalona = false ;
40. d2.zapalona = false ;
41.
42. // a teraz główna p ħ tla progamu - b ħ dzie prosi ę u Ň ytkownika
43. // o podanie działania - i po ka Ň dym poleceniu wy Ļ wietla ę
44. // stan diod
45. char zn;
46. do {
47. cout << "Stan diod:\n" ;
48. d1.pokaz();
49. d2.pokaz();
50. cout << "\nCo chcesz zrobic?\n1 - zapal diode 1\n" ;
51. cout << "2 - zgas diode 1\n" ;
52. cout << "3 - zapal diode 2\n" ;
53. cout << "4 - zgas diode 2\n" ;
54. cout << "0 - zakoncz program\n" ;
55. cin >> zn;
56. switch (zn) {
57. case '1' : d1.zapal(); break ;
58. case '2' : d1.zgas(); break ;
59. case '3' : d2.zapal(); break ;
60. case '4' : d2.zgas(); break ;
61. };
62. } while (zn != '0' );
63.
64. return 0;
65.
}
No tak ... uwa Ň ny czytelnik mo Ň e stwierdzi ę - tyle pisania, tyle teorii, tyle hałasu, a jedyny zysk to fakt, Ň e zamiast pisa ę pokaz(d1)
piszemy d1.pokaz() . I b ħ dzie miał racj ħ - jak na razie ... przynajmniej dopóki nie poka Ň emy mo Ň liwo Ļ ci ochrony pól i metod w
obiekcie. A prawdziw Ģ pot ħ g ħ programowania
obiekowego zobaczycie dopiero w nast ħ pnej lekcji - jak omówimy zupełnie nowe
poj ħ cia - dziedziczenie i polimorfizm.
Troch ħ składni
Po tym prostym przykładzie z diod Ģ mo Ň emy ju Ň poda ę uproszczon Ģ , ogóln Ģ posta ę deklaracji klasy:
class nazwa_klasy
{
public : // co znaczy public - w nast ħ pnym segmencie
// najpierw definiujemy pola ró Ň nych typów
typ_pola nazwa_pola, ...nazwa_pola ;
...
// potem metody (z parametrami lub bez) operuj Ģ ce na tych polach
typ_zwracany nazwa_metody ( lista_parametrów );
...
// potem znowu mog Ģ by ę inne pola
typ_pola nazwa_pola, ...nazwa_pola ;
...
// i inne metody
typ_zwracany nazwa_metody ( lista_parametrów );
899923668.026.png 899923668.027.png 899923668.028.png 899923668.029.png 899923668.030.png 899923668.031.png 899923668.032.png
 
// i tak dalej...
};
Wyst ħ puj Ģ cym tutaj słowem kluczowym public na razie si ħ nie przejmujcie - tylko przyjmijcie, Ň e by ę musi. Co ono znaczy, i dlaczego
by ę musi - wyja Ļ nimy w dalszej cz ħĻ ci tej lekcji.
W C++, mimo Ň e mo Ň liwa jest jednoczesna definicja i deklaracja klasy (tak zrobili Ļ my w przykładzie wprowadzaj Ģ cym - diody) -
zwykle post ħ puje si ħ inaczej. Ka Ň d Ģ klas ħ rozbija si ħ na dwa pliki:
plik z deklaracj Ģ (a nie definicj Ģ ) klasy
plik zawieraj Ģ cy implementacj ħ klasy, czyli definicje jej metod.
Klas ħ deklaruje si ħ w pliku nagłówka (*.h), natomiast definicje metod wchodz Ģ cych w jej skład umieszcza w implementacji (*.cpp).
Tak wi ħ c klasa CDioda po poprawkach powinna wygl Ģ da ę nast ħ puj Ģ co: najpierw tworzymy nowy moduł, nast ħ pnie w jego pliku
nagłówkowym umieszczamy deklaracj ħ klasy:
1. #ifndef cdiodaH
2. #define cdiodaH
3.
4. #include <string>
5. using namespace std;
6.
7. /* Deklaracja klasy CDioda*/
8. class CDioda {
9. public :
10. // pole pami ħ taj Ģ ce stan diody
11. bool zapalona;
12. // kolor diody
13. string kolor;
14. // informacja, Ň e dioda ma metod ħ zapal
15. void zapal();
16. // informacja, Ň e dioda ma metod ħ zgas
17. void zgas();
18. // informacja, Ň e dioda ma metod ħ pokaz
19. void pokaz();
20. };
21.
22. #endif
W pliku z implementacj Ģ podajemy natomiast tre Ļę (definicj ħ ) metod - to tam piszemy, co nale Ň y zrobi ę . Kierujemy si ħ przy tym
zasadami takimi samymi, jak w przypadku tworzenia klasycznych funkcji, z dwoma istotnymi ró Ň nicami:
1.
Nazwa metody podczas implementacji składa si ħ z dwóch cz ħĻ ci. Implementacje (definicje) kolejnych metod tworz Ģ tzw.
wn ħ trze obiektu , bo nale ŇĢ w cało Ļ ci do obiektów danej klasy. W implementacji metody konieczne jest wi ħ c zastosowanie
desygnatora (oznacznika), okre Ļ laj Ģ cego, do jakiej klasy nale Ň y dana metoda. Za
desygnatorem umieszcza si ħ podwójny
dwukropek, a dopiero po nim nazw ħ metody:
typ_zwracany nazwa_klasy :: nazwa_metody ( lista_parametrów )
{
...
};
2.
Pisz Ģ c ciało (tre Ļę ) funkcji b ħ d Ģ cej metod Ģ jakiej Ļ klasy, mo Ň emy odwoływa ę si ħ do pól tej klasy bezpo Ļ rednio, nie podaj Ģ c
nazwy klasy, do której dane pole nale Ň y - bo przecie Ň wewn Ģ trz klasy wszystkie pola s Ģ znane i bezpo Ļ rednio dost ħ pne (to tak
jak my - na polecenie "rusz swoj Ģ r ħ k Ģ " - wiemy, która r ħ ka jest nasza). Inaczej mówi Ģ c - wewn Ģ trz metody wszystkie pola klasy
mo Ň na traktowa ę jak zdefiniowane zmienne lokalne.
Przyjrzyjmy si ħ wi ħ c przykładowej implementacji:
1. #include <iostream>
2. #include <cstdlib>
3.
4. #include "cdioda.h"
5.
899923668.033.png 899923668.034.png 899923668.035.png 899923668.036.png 899923668.037.png 899923668.038.png 899923668.039.png 899923668.040.png 899923668.041.png 899923668.042.png 899923668.043.png 899923668.044.png 899923668.045.png 899923668.046.png 899923668.047.png 899923668.049.png
Zgłoś jeśli naruszono regulamin