Programowanie obiektowe
Andrzej Walczak
awalczak@wat.edu.pl
WAT
Pierwsza edycja 2006
Temat: ogólny opis techniki obiektowej
Uwagi wprowadzające
• Jest nowy syllabus przedmiotu.
Studenci powinni się z nim zapoznać.
• Literatura podstawowa:
wazniak.mimuw.edu.pl; Bruce Eckel –
Thinking in C++; Bruce Eckel –
Thinking in Java; Nicolai Josuttis – C+
+ Biblioteka Standardowa
Ogólne cechy obiektowej techniki programowania
Zarządzanie informacją w programie –
współdzielenie danych
Większość wysiłku usprawniającego pisanie dużych
programów koncentrujemy na dzieleniu procedur (etapów
integralnych wykonania kodu) na moduły. Istnieje jednak
inny składnik programu nie mniej ważny niż kod napisany
w wybranym języku. Tym składnikiem są dane czyli
zestaw informacji, na których funkcjonują procedury
programu.
Ogólne cechy obiektowej techniki programowania
Zarządzanie informacją w programie –
współdzielenie
danych
Jeśli program do swojego działania potrzebuje tylko kilka
kawałków danych, to bez utraty bezpieczeństwa te kawałki
mogą być udostępniane wszystkim podprogramom
tworzącym kod.
Taka organizacja danych jest wygodna dla programistów
ponieważ współdzielony zestaw danych jest czymś w
rodzaju tablicy ogłoszeniowej na której podprogramy mogą
wymieniać się informacjami. Jeśli jednak tych kawałków
danych i podprogramów są tysiące takie rozwiązanie często
prowadzi do nieprzewidywalnych zachowań programu.
Ogólne cechy obiektowej techniki programowania
Zarządzanie informacją w programie –
współdzielenie danych
Problem polega na tym, że współdzielenie danych
jest
pogwałceniem
modułowej
konstrukcji
programowania, której podstawą jest całkowita
niezależność
modułów.
Dopuszczając
swobodne współdziałanie modułów poprzez
wymianę
informacji
powodujemy,
że
zachowanie jednego modułu wpływa na
zachowanie wszystkich pozostałych.
Ogólne cechy obiektowej techniki programowania
• Rozwiązaniem jest
podzielenie danych
na części odpowiadające
modułom programu.
Podprogramy otrzymują dane, którymi
tylko
one mogą się posługiwać
. Taki zabieg nazywa się przesłanianiem
danych.
Wiele programów musi korzystać z tych samych danych
wielokrotnie np. obliczenia inżynierskie, programy księgowe etc.
Często z programów tego rodzaju korzysta jednocześnie wiele
osób. Zawsze istnieje możliwość, że jedna z nich zmieni dane,
któreych inne osoby właśnie używają.
• Dlatego utworzono tak zwane systemy dostępowe rozbudowane
aktualnie do Systemów Zarządzania Bazami Danych (Data Base
Management System). Kierują one wielodostępem do danych i
chronią je przed zmianami.
Okazało się , że idea DBMS jest niezwykle skuteczna w tworzeniu
kodu o podniesionej niezawodności. Ma jednak ten mechanizm
szereg ograniczeń . Kłopoty ze współdzieleniem danych
doprowadziły do utworzenia tak zwanego programowania
obiektowego.
Ogólne cechy obiektowej techniki programowania
• Pierwszy język obiektowy – Simula 67 – powstał już w latach
sześćdziesiątych ubiegłego stulecia. Jego twórcami byli Ole-Johan
Dahl i Kristen Nygaard z Norsk Regnesentral w Oslo. Podczas
swoich prac nad symulacją statków musieli dla każdego rodzaju
statku
uwzględniać
wiele
atrybutów.
Ponieważ
liczba
modelowanych rodzajów statków była duża, uwzględnienie
wszystkich możliwych zależności między atrybutami stało się
problematyczne. Pojawił się pomysł, aby pogrupować różne
rodzaje statków w klasy obiektów. Każda klasa obiektów sama
miała być odpowiedzialna za definiowanie swoich danych i
zachowania. Simula była pierwszym językiem programowania, w
którym wprowadzono pojęcie klasy i jej egzemplarza. Warto tu
zwrócić uwagę, że zgodnie z nazwą języka takie odwzorowanie
obiektów spotykanych w świecie rzeczywistym na obiekty
programowe można nazwać symulacją. Niedługo potem w
laboratorium badawczym Xerox's Palo Alto stworzono Smalltalk.
Jego głównym pomysłodawcą był Alan Kay. Smalltalk zawierał
wiele rewolucyjnych pomysłów - m.in. dziedziczenie - i zyskał
sobie sporą popularność. W pewnej skali był również z
powodzeniem stosowany w praktyce. Standardem przemysłowym
programowanie obiektowe stało się jednak dopiero w latach
dziewięćdziesiątych za sprawą języka C++, który jest obiektowym
rozszerzeniem C. Obecnie jednym z najpopularniejszych języków
obiektowych i równocześnie języków programowania w ogóle jest
Java. *
•
*wazniak.mimuw.pl
Trzy (albo dwa) klucze do techniki obiektowej:
obiekty, klasy, komunikaty
Jedną z barier w zrozumieniu techniki obiektowej
jest specjalistyczne słownictwo, którym ta technika
obrosła.
W rzeczywistości można tym żargonem posługiwać
się skutecznie stosując zaledwie kilka pojęć:
1. obiekt, instancja (konkret)
,
2. metoda, (funkcja składowa klasy)
3. komunikat, [ pojęcie od dawna zaniedbane ]
4. klasa,
5. podklasa,
6. dziedziczenie,
7. hermetyzacja (enkapsulacja),
8. abstrahowanie,
9. polimorfizm
.
O obiektach
Koncepcje programowania obiektowego zostały pokazane w języku
Simula. Zbudowano go do symulacji złożonych systemów i jako
podstawę konstrukcji języka przyjęto, że
każdy obiekt rzeczywisty
charakteryzują jego zachowania
.
Spróbowano więc tak układać komunikacje pomiędzy
procedurami i danymi aby były podporządkowane
zachowaniom symulowanych obiektów
.
Obiekt był „pakunkiem” zawierającym procedury i dane powiązane
razem tak, aby określały jego możliwe zachowania. Procedury w
podejściu obiektowym nazywa się metodami. W odniesieniu do
danych stosujemy nazwę zmienne bo ich wartość może się zmieniać
w czasie. Nazywamy metody i zmienne atrybutami obiektu.
Rozważmy dla ilustracji jak moglibyśmy programować
przenośnik
fabryczny
. Może on wykonywać rozmaite czynności jak
przemieszczanie się, ładowanie, rozładowanie. Musi przechowywać
informacje o nośności, rozmiarach palety, prędkości, aktualnego
położenia, aktualnego ładunku, itp.
Tak więc realny obiekt „coś robi”
i „coś wie”.
Czynności obiektu opiszemy jako metody, a wielkości
charakterystyczne, którymi obiekt się posługuje
wykonując czynności, jako zmienne.
CZYNNOŚCI OBIEKTU = METODY
DANE DO METOD=ZMIENNE OBIEKTU
Obiekt jest idealnym modułem programu. Tworzy własny,
zamknięty świat. Wszystko co „wie” wyrażają jego
zmienne. Wszystko co może robić wyrażają jego metody.
Klasy obiektów
Wykład 2
Pojęcie klasy obiektów
Klasa jest nowym typem zmiennej w programie . Definiujemy ją
jako:
class nasza_klasa {
//
//
...
//ciało klasy
};
Jeśli chcemy stworzyć konkretny element czyli obiekt tej klasy to
zapisujemy :
nasza_klasa
nasz_obiekt
;
Wtedy w pamięci operacyjnej
powstanie
obiekt klasy
nasza_klasa, który się nazywa nasz_obiekt.
Kiedy już mamy typ nasza_klasa, to możemy utworzyć obiekt pochodny, na
przykład wskaźnik do obiektu z naszej_klasy:
nasza_klasa *wsk;
(czy mogę określić referencję na nazwę klasy?)
albo:
nasza_klasa &name = obiekcik;
Utworzy to wskaźnik do obiektów klasy nasza_klasa albo referencje do
wybranego obiektu klasy nasza_klasa.
Przykład: program w C++, który przechowuje informację o kolorze i wartości
punktowej karty do gry. Wykorzystuje klasę Karta. W kodzie ponumerowano
wiersze do dalszej analizy.
Kod programu do przykładu
1. #include <iostream>
2. #include <conio.h>
3. #include <string.h>
4. #include <dos.h>
5. class Karta
6. {
7. public:
8. char kol[80];
9. int wym;
10. Karta(char*, int);
11. void Druk();
12. };
void main()
{
Karta k1(“czarna”,5),k2(“czerwona”,8); //
powolanie do zycia
obiektow k1 oraz k2 klasy Karta
k1.Druk();
//
wykonanie funkcji Druk() na rzecz obiektu k1
k2.Druk();
strcpy(k1.kol,”czarno-czerwona”); //
zmiana zmiennej kol na rzecz
obiektu k1
k2.wym=28;
//
zmiana zmiennej wym na rzecz obiektu k2
cout<<endl;
k1.Druk();
k2.Druk();
getch();
}
Karta::Karta(char* s,int w): wym(w)
{
strcpy(kol,s);
}
Definicja
konstruktora
Inicjalizacja
argumentu
konstruktora
Definicja
funkcji
składowej klasy
void Karta::Druk()
{
cout<<”\nkolor=”<<kol<<”,wymiar=”<<wym;
}
omówienie:
uwagi o strukturze kodu programu:
• opis klasy Karta znajduje się przed blokiem main() czyli w tej części
kodu programu, która przeznaczona jest do umieszczania deklaracji,
definicji i inicjalizowania typów zmiennych. To także wskazuje, że
klasa jest typem zmiennej
.
• Opis klasy zawsze umieszczamy w nawiasie klamrowym, po którym
jest średnik, tak, jak w normalnych deklaracjach typów zmiennej.
• Konstruktor obiektu Karta, o ZAWSZE nazwie takiej samej jak klasa
jest w obszarze opisu klasy tylko deklarowany i podawana jest lista
jego argumentów. Nie podajemy typu pomimo, że jest funkcją.
• Ciało konstruktora, który jest funkcją, jest opisywane
poza
blokiem
main() jeśli konstruktor jest podany jawnie
• W bloku definiowania klasy deklarowane są także funkcje
składowe klasy, które dalej będą metodami obiektów klasy. W
przykładzie jest to funkcja Druk().
• Opis funkcji klasy umieszczamy po bloku main() tak, jak opis
konstruktora ale może być podany także wewnątrz bloku deklaracji
klasy.
Opis kodu:
Wiersze 5-11: początek i koniec opisu klasy Karta zawierający
deklaracje konstruktora Karta i funkcji Druk() oraz wykorzystywanych
zmiennych, tablicy char[80] oraz zmiennej całkowitej wym.
class Karta
{
public:
char kol[80];
int wym;
Karta(char*, int);
void Druk();
};
Wiersze 12-25: blok main(), w którym powołane są dwa obiekty klasy Karta o
nazwach k1 i k2 w wierszu 14ym. Obiekty mają argumenty takie, jakie
zadeklarowano w konstruktorze, czyli zmienna typu char oraz zmienna
integer. WNIOSEK: deklaracja konstruktora musi zawierać to, co potem
jest potrzebne w funkcjonowaniu obiektów konstruowanych w klasie.
W każdym obiekcie k1 oraz k2 wartości zmiennych deklarowanych są
zainicjowane poprzez podanie konkretnych wartości.
void main()
{
Karta
k1(“czarna”,5),k2(“czerwona”,8);
k1.Druk();
k2.Druk();
strcpy(k1.kol,”czarno-czerwona”);
k2.wym=28;
cout<<endl;
k1.Druk();
k2.Druk();
getch();}
Wiersze 26-33: definicja konstruktora klasy. Najpierw nazwa Karta, a
potem operator :: czyli operator zakresu. Teraz już na liście
argumentów formalnych podane są nazwy argumentów s oraz w. Po
liście argumentów formalnych może ( ale nie musi) pojawić się
dwukropek i lista inicjalizująca wartości argumentów, czyli argumenty
początkowe. Następnie w nawiasie klamrowym, po którym nie ma
średnika umieszczamy ciało konstruktora. W ciele konstruktora, w
wierszu 27 mamy kopiowanie łańcucha nazwy koloru karty z s do kol.
Karta::Karta(char* s,int w): wym(w)
{
strcpy(kol,s);
}
Wiersze 34-36: definicja funkcji własnej klasy Karta o nazwie Druk(). Jest ona
wykonywana w bloku main() na rzecz obiektów k1 oraz k2. To, że jest ona
funkcją własną klasy Karta wskazuje nazwa klasy rozpoczynająca definicję i
operator zakresu :: , który wskazuje, że funkcja działa w całym zakresie
ważności klasy. Funkcja Druk() wykonuje na ekranie wypisanie nazwy koloru
karty i jej wartości punktowej.
void Karta::Druk()
{
cout<<”\nkolor=”<<kol<<”,wymiar=”<<wym;
}
k1.Druk();
k2.Druk();
Kolor obiektu k1 jest modyfikowany w wierszu 18 funkcją strcpy. W
składni wymienia się nazwę obiektu, a po kropce nazwę argumentu
zmienianego. W wierszu 19 modyfikowany jest argument wym obiektu
k2 poprzez zwykłą operację przypisania nowej wartości.
k1.Druk();
k2.Druk();
strcpy(k1.kol,”czarno-czerwona”);
k2.wym=28;
WNIOSEK:
Klasa a obiekt
Widać, że
klasa nie definiuje konkretnych
obiektów tylko ich typy
!!! Jest ona typem
obiektu jako abstrakcyjnej zmiennej, a nie
obiektem lub zbiorem obiektów
.
Uwagi ogólne do konstruktorów:
1. Konstruktor
NIE MUSI
wystąpić w opisie klasy, czyli obiekty nie muszą być
wprowadzane konstruktorem.
2. Nazwa konstruktora może być przeładowana
, czyli stosowana
wielokrotnie w opisie klasy z różnymi listami argumentów. Wtedy
kompilator odróżnia konstruktory po listach argumentów, tak, jak w
przypadku przeładowanych nazw funkcji. Konstruktorów może wiec być
wiele.
3. Konstruktor
może być wywoływany ( a nie deklarowany!!) bez żadnych
argumentów
. Jest to tak zwany konstruktor domniemany. Czasem
nazywamy go domyślnym albo standardowym. Ze względu na istotę
przeładowania nazwy konstruktor domniemany czyli bezargumentowy
może wystąpić tylko raz. Jeśli nie deklarujemy w klasie żadnego
konstruktora, to kompilator sam ustanawia właśnie konstruktor
domniemany do obsługi obiektów w programie. Każdy konstruktor z
argumentami, którym nadamy wartości domyślne czyli niedefiniowalne
jest także konstruktorem domniemanym.
4. Co właściwie robi konstruktor? On INICJALIZUJE obiekty. Kompilator
automatycznie wywołuje konstruktor w miejscu tworzenia obiektu zanim
jeszcze obiekt podejmie jakiekolwiek działanie. Czyli po pierwszym
komunikacie staruje konstruktor. Nazwa konstruktora taka sama jak
nazwa klasy (pomysł Stroustrupa) pozwala na jednoznaczne powiązanie
konstruktora z typem zmiennej obiektowej (wykorzystano operator
zakresu).
•
Konstruktor jest zwykle deklarowany jako
publiczny, bo przecież wprowadzane nim
obiekty mogą być używane przez klasy
zewnętrzne. Możemy jednak dla
konstruktora przewidzieć ochronę tak, jak
dla klas za pomocą etykiet private lub
protected. Wówczas jednak także
konstruowane obiekty będą dostępne tylko
w obrębie klasy z tym konstruktorem jako
private albo jako protected tylko w zakresie
klas dziedziczących.
Konstruktor może zamiast definiować
obiekty podawać kopie obiektów zawartych
w innej klasie lub tworzyc kopie obiektów
istniejących. Wtedy jest to tak zwany
konstruktor kopiujący
.
Konstruktor może dokonywać konwersji typu
obiekty z jednego w drugi. Nazywamy go
wtedy
konstruktorem konwertującym
.
Przykład: Konstruktor domniemany
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <dos.h>
class X
{
public:
char kol[80];
int wym;
X(char* s=“?”, int w=-1);
void Druk();
};
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <dos.h>
class Karta
{
public:
char kol[80];
int wym;
Karta(char*, int);
void Druk();
};
void main()
{
clsscr();
X k1,k2(“fiolet”);
k1.Druk();
k2.Druk();
strcpy(k1.kol,”biala”);
strcpy(k2.kol, k1.kol);
k1.wym+=10;
k2.wym=k1.wym*5;
cout<<endl;
k1.Druk();
k2.Druk();
cout<<”\n\n”<,”size=”<<si
zeof(k1);
cout<<”\n\n”<,”size=”<<si
zeof(X);
getch();
}
void main()
{
clsscr();
Karta
k1(“czarna”,5),k2(“czerwona”,8);
k1.Druk();
k2.Druk();
strcpy(k1.kol,”czarno-
czerwona”);
k2.wym=28;
cout<<endl;
k1.Druk();
k2.Druk();
getch();
}
X::X(char* s,int w)
{
wym=w;
strcpy(kol,s);
}
void X::Druk()
{
cout<<”\nkolor=”<<kol<<”,w
ymiar=”<<wym;
}
Karta::Karta(char* s,int w):
wym(w)
{
strcpy(kol,s);
}
void Karta::Druk()
{
cout<<”\nkolor=”<<kol<<”,wym
iar=”<<wym;
}
Cechy obiektów
• Poprzez sposób definiowania obiektu decydujemy o
zakresie ważności jego nazwy czyli także o czasie jego
życia.
• Jeśli obiekt jest definiowany w dostępie publicznym to
rozumiemy, że jest dostępny globalnie (uwaga na Grębosza
i pomyłkę pomiędzy pojęciem zmiennej typu wbudowanego
i obiektem) czyli mogą z niego korzystać wszystkie funkcje i
obiekty innych klas w programie.
• Obiekt może funkcjonować lokalnie (obiekt prywatny) i
wówczas automatycznie kończy się jego zakres ważności
wtedy, kiedy fragment programu (klasa, blok) pozostaje
zakończona faktycznie. Taki obiekt –podobnie jak zmienna
lokalna – traci swoje cechy (pomimo hermetyzacji) w
zakresie wartości jego zmiennych i metod. Taki obiekt,
podobnie jak zmienną lokalną, będziemy uważać za
zapisywany automatycznie.
Cechy obiektów
• Obiekt globalny jest inicjalizowany inaczej niż lokalny bo
wstępnie (zanim zacznie funkcjonować jako konkret) jest
inicjowany zerami.
• Obiekt mogę powołać do życia jako obdarzony atrybutami.
W szczególności może to być atrybut static. Taki obiekt,
nawet jeśli jest lokalny, zachowa swoje wartości zmiennych
i metod takie, jak przy ostatnim komunikacie. Inicjalizacja
jest tu podobna jak obiektu globalnego – wartościami
zerowymi.
• Jeśli atrybutu static użyjemy do nazwy globalnej, to może
ona być dostępna TYLKO W SWOIM PLIKU. Oznacza to, że
nie mogę uzyskać dostępu do takiego obiektu wtedy, kiedy
jest on w pliku dołączonym dyrektywą preprocesora include
jako plik nagłówkowy.
this - WSKAŹNIK SPECJALNY
• Każdej funkcji - metodzie zadeklarowanej wewnątrz klasy
zostaje
w momencie wywołania w niejawny sposób (ang. implicitly)
przekazany wskaźnik do obiektu (w stosunku do którego
funkcja ma
zadziałać). Pointer wskazuje funkcji w pamięci ten obiekt,
którego członkiem jest dana funkcja. Bez istnienia takiego
właśnie wskaźnika nie moglibyśmy stosować spokojnie
funkcji, nie
moglibyśmy odwoływać się do pola obiektu, gdybyśmy nie
wiedzieli
jednoznacznie, o który obiekt chodzi.
• Wskaźnik this jest pierwszym argumentem konstruktora
obiektu. W konstruktorze wskazuje on na nie
zainicjalizowany fragment pamięci, a rolą konstruktora jest
właśnie inicjalizacja na rzecz konkretnego obiektu
(konkretu).
this - WSKAŹNIK SPECJALNY, c.d.
• Program posługuje się automatycznie niejawnym
wskaźnikiem do obiektu (ang. implicit pointer). Możemy
wykorzystać ten istniejący, choć do tej pory nie widoczny
dla nas pointer posługując się słowem kluczowym this (ten).
This pointer wskazuje na obiekt, do którego należy
funkcja. Korzystając z tego wskaźnika funkcja może bez
cienia
wątpliwości zidentyfikować właśnie ten obiekt, z którym
pracuje
a nie obiekt przypadkowy.
[!!!] FUNKCJE KATEGORII static NIE OTRZYMUJĄ POINTERA
this.
Należy pamiętać, że wskaźnik this istnieje wyłącznie
podczas
wykonywania metod (ang. class member function
execution), za
wyjątkiem funkcji statycznych.
Tworzenie obiektów
•
Kiedy w C++ tworzony jest obiekt zachodzą dwa procesy:
1.
przydział pamięci do obiektu
2.
Wywołanie konstruktora inicjalizującego tę pamięć.
Pierwszy proces może być wykonany na różne sposoby i w różnym
czasie:
a.
Pamięć może zostać przydzielona, zanim zacznie się praca
programu –w obrębie obszaru danych statycznych. Obszar ten
istnieje przez cały czas działania programu.
b.
Pamięć może zostać przydzielona na stosie kiedy zostanie
osiągnięty określony punkt realizacji programu (klamrowy
nawias otwierający). Jest ona zwalniana po pojawieniu się
klamrowego nawiasu zamykającego. Tutaj potrzebna jest wiedza
o liczbie i rozmiarze wykorzystywanych zmiennych aby nie
przekroczyć rozmiaru stosu.
c.
Pamięć jest przydzielana na stercie. Jest to proces dynamiczny.
Jest on obsługiwany odpowiednia funkcją. Czyli przydziela ją i
zwalnia program (czytaj programista) [malloc(), free() w
<cstdlib>, new, delete w standardowej bibliotece poprzez
przestrzeń nazw]