PARADYGMATY PROGRAMOWANIA Wykład 2

Transkrypt

PARADYGMATY PROGRAMOWANIA Wykład 2
PARADYGMATY PROGRAMOWANIA
Wykład 2
Definiowanie klas w C++ - ciąg dalszy
Lista inicjalizująca konstruktora
Przeznaczenie - do inicjalizacji pól klasy z kwalifikatorem const i inicjalizacji
obiektów składowych
class Stack
{
public:
const int size;
Stack( int max_size );
. . .
}
void Stack::Stack( int max_size )
{
size = max_size; // Blad - proba przypisania do zmiennej const
}
Postać listy inicjalizującej
<def_konstruktora_z_lista_init> :: =
<nazwa_klasy>::<nazwa_konstruktora>
(<parametry>):<lista_inicjalizująca>
<lista_inicjalizująca> ::= <nazwa_pola>(<wartosc>) || ,
Lista inicjalizująca jest umieszczana w definicji (xxx.cpp) a nie w prototypie
(xxx.h).
Przykład:
Stack::Stack( int max_size ): size( max_size )
{
. . .
}
Obiekt stały
class String
{
public:
String( char *str);
char *get();
private
char contents[MAX_LEN];
};
const String s1("To jest lancuch");
cout << s1.get();
// BŁĄD
Jeśli obiekt jest zadeklarowany jako const to metody użyte wobec niego też
muszą być oznaczone jako nie zmieniające obiektu.
const char *get() const;
Funkcje udostępniające
Przeznaczenie - pozwalają udostępnić dane klasy z sekcji public w trybie tylko
do odczytu.
Cele:
• ochrona danej przed nieuprawnioną modyfikacją,
• skupienie wszystkich operacji odstępu do zmiennej w jednym miejscu (np.
możliwość sprawdzania poprawności przy podstawianiu, przebudowa struktury
danych przy podstawianiu, konstruowanie wartości (wydobywanie ze złozonej
struktury danych, konwersja) przy odczycie),
• hermetyzacja - możliwość zmiany typu danych lub implementacji zmiennej bez
konieczności wykonywania zmian u użytkowników klasy.
Składanie klas
Umieszczanie jako pola jednej klasy obiektu innej klasy nazywamy składaniem
(zagnieżdżaniem) klas.
class Nazwisko
{
public:
Nazwisko( char *);
private:
char nazw[MAX_LEN];
};
class Data
{
public:
Data (int d, int m, int r);
private:
int dd, mm, rr;
};
class NazwiskoData
{
public:
NazwiskoData( char *n, int d, int m, int r)
private:
Nazwisko nazw;
Data
data;
}
Inicjalizacja obiektów składowych za pomocą konstruktora innego niż domyślny tylko z listy inicjalizującej
NazwiskoData::NazwiskoData( char *n, int d, int m, int r ):
nazw( n ), data( d, m, r )
Jeśli brak inicjalizacji na liście - użyty będzie konstruktor domyślny.
Kiedy automatycznie wywoływane są konstruktory i destruktory:
• dla zmiennych lokalnych - przy wejściu i wyjściu z bloku, w którym
zadeklarowany jest obiekt,
• przy obiektach dynamicznych - w momencie użycia operatora new i delete,
• dla obiektów statycznych - w momencie rozpoczynania i kończenia programu,
• dla obiektów składowych innych klas - w momencie wywoływania konstruktora
(ewentualnie z listy inicjalizującej) i destruktora; kolejność przy inicjalizacji zgodna z kolejnością deklaracji, przy destrukcji - kolejność odwrotna.
Wskaźnik this
Dla pewnej klasy X typem wskaźnika this jest
X const* this;
this jest wskaźnikiem stałym (tzn. nie można zmieniać jego wartości) na obiekt,
którego dotyczy wywołanie metody.
Zastosowania:
• zwracanie wskaźnika do obiektu, na którym wykonywana jest operacja,
• zwracanie referencji do obiektu, na którym wykonywana jest operacja,
• przy operowaniu na strukturach wskaźnikowych zawierających wskaźniki do
obiektów (np. samowstawianie się obiektu do wskazanej listy),
• dla uniknięcia przesłaniania identyfikatorów pół klasy (np. parametrami
metody)
Operator zakresu ::
Zastosowanie:
A. typowo - do uniknięcia niejednoznaczności przy definiowaniu ciała metod
klasy,
B. do do dostępu z kodu poza klasą do obiektów zdefiniowanych w tej klasie, np.
stałej zdefiniowanej w klasie, definicji klasy wewnętrznej,
C. do wskazywania obiektów globalnych, których nazwy zostały przysłonięte
nazwami lokalnymi zdefiniowanymi w klasie.
ad B)
class Stack
{
public:
enum StackState(OK, FULL, EMPTY);
StrackState current_state;
int get();
}
Stack s1;
...
if ( s1.current_state == Stack::OK )
s1.Get;
ad B)
int get()
class Stack
{
}
// Funkcja globalna
Stack.get()
{
if (::get() == 10)
}
// Odwołanie do funkcji globalnej
Pola statyczne klasy
• są wspólne dla wszystkich obiektów danej klasy,
• muszą być zdefiniowane poza ciałem metod klasy.
• mogą być inicjalizowane poza ciałem metod klasy.
class Osoba
{
public:
String nazwisko,
String imie;
Osoba ( char *imie, char *nazwisko);
private:
static int liczba_osob;
};
Osoba::Osoba(char *imie, char *nazwisko )
{
...
liczba_osob++;
}
...
int
Osoba::liczba_osob = 0;
//definicja i inicjalizacja
Reguły dotyczące dobrego stylu tworzenia klas:
• umieszczać deklarację klasy i jej elementów (interfejs klasy) w pliku
nagłówkowym xxx.h a implementację - w pliku z kodem xxx.cpp;
• możliwie dużo danych klasy ukrywać deklarować w sekcji private;
• jako public deklarować tylko funkcje udostępniające oraz funkcje
reprezentujące abstrakcyjne zachowania klasy (wynikające z modelowania za jej
pomocą rzeczywistości);
• do dostępu do danych stosować funkcje udostępniające definiowane wraz z
deklaracją (co spowoduje ich klasyfikację jako inline);
• porządkować kolejność deklaracji elementów klasy - zachowywać kolejność
sekcji: public, protected, private.
Dziedziczenie (derywacja)
Dziedziczenie - tworzenie klasy pochodnej w oparciu o już
istniejącą klasę (klasę bazową)
Klas bazowa - model pojęcia ogólnego
Klasa pochodna - model przypadku szczególnego pojęcia bazowego
modelowanego przez klasę bazową.
Przykłady:
pojazd
samochód,
samochód osobowy/ciężarowy
osoba
pracownik/student
pracownik umysłowy/fizyczny/wolny zawód
sprzęt
sprzęt elektroniczny/maszyny
komputer/monitor
<--
hierarchie obiektów !
Składnia dziedziczenia:
class <nazwa_klasy_pochodnej> :
{
<deklaracja elementów klasy>
};
public <nazwa_klasy bazowej>
Każdy obiekt klasy pochodnej ma dostęp do elementów sekcji public i
protected klasy bazowej.
Wywoływanie funkcji klasy bazowej
class Pracownik
{
public:
void print();
. . .
};
class Kasjer : public Pracownik
{
public:
void print();
int
nr_kasy;
};
void Kasjer::print()
{
Pracownik::print();
cout << "Nr kasy " << nr_kasy;
}
{
Kasjer prac;
...
prac.print();
// wywołuje metodę print klasy Kasjer
}
Użycie konstruktora klasy bazowej
Kasjer::Kasjer( char *nazwisko, int stawka, int _nr_kasy ) :
Pracownik( nazwisko, stawka ), nr_kasy(_nr_kasy )
{
}
Konwersja typów obiektów
klasa pochodna -> klasa bazowa TAK
klasa bazowa -> klasa pochodna NIE
Pracownik p1;
Kasjer
k1;
...
p1 = k1;
k1 = p1;
// poprawne
// niepoprawne
Kolejność wywoływania konstruktorów (dla dziedziczenia):
• Najpierw wywoływany jest konstruktor Rodzica,
• Następnie wykonywany jest konstruktor Dziecka
Kolejność wywoływania destruktorów (dla dziedziczenia):
• Najpierw wywoływany jest destruktor Dziecka, a potem destruktor Rodzica
Rodzaje dziedziczenia (public, private, protected)
• dziedziczenie publiczne (public): klasa dziecka nie wprowadza ograniczeń na
widzialność składowych rodzica
• dziedziczenie chronione (protected): klasa dziecka chroni (tryb protected)
składowe publiczne i chronione odziedziczone od rodzica
• dziedziczenie prywatne (private): klasa dziecka ukrywa (tryb private) składowe
publiczne i chronione odziedziczone od rodzica
Kwalifikator dostępu protected przed składowymi klasy
• Składowe te są niedostępne na zewnątrz klasy (zachowują się jak prywatne)
• Składowe te są dostępne u potomków danej klasy (zachowują się jak publiczne)
Kwalifikator dostępu private przed składowymi klasy
• Składowe te są niedostępne na zewnątrz klasy
• Składowe te są niedostępne u potomków danej klasy
Kwalifikator dostępu public przed składowymi klasy sprawia,
• Składowe te są dostępne na zewnątrz klasy
• Składowe te są dostępne u potomków danej klasy
class Odcinek {
public:
Odcinek(int,int,int,int);
void dane();
protected:
int x1,y1,x2,y2;
private:
double odleglosc();
};
class Prosta : protected Odcinek {
public:
Prosta(int,int,int,int); // przechodząca przez odcinek
void rownanie(); // wypisuje równanie prostej
};
void main()
{
Prosta p(0,0,10,10);
// p.odleglosc(); // skladowa prywatna
// p.dane(); //skladowa już chroniona
p.rownanie(); //skladowa publiczna
}
Dziedziczenie wielokrotne:
Klasa potomna ma wiele klas bazowych:
class Prostokat {
public:
doublepole();
Prostokat(int a=100, int b=100);
~Prostokat();
protected:
int a, b;
}; // definicja klasy Prostokąt
class Kolo {
public:
doublepole();
Kolo(int r=10);
~Kolo();
protected:
int r;
}; // definicja klasy Kolo
class Walec : public Kolo, public Prostokat {
public:
// deklaracja publicznych składowych
};
Dostęp do składowych odziedziczonych z klas bazowych
jest możliwy dzięki operatorowi zakresu
nazwa_klasy::nazwa_składowej
double
return
}
double
return
}
Walec::objetosc() {
Kolo::pole()*Prostokat::pole();
Walec::pole_pow_bocznej() {
2*Kolo::pole()+Prostokat::pole();
Zalety dziedziczenia:
• Oszczędność pracy:
o Definiujemy tylko różnice
o Oddzielmy od siebie dwie sprawy: jak klasa jest zrealizowana oraz jak się
nią posługiwać
• Możliwość wprowadzenia hierarchii klas – klasy tworzymy w zależności
logicznej
• Możliwość zdefiniowania klas ogólnych – przeznaczone wyłącznie do
dziedziczenia
Przykład
Sprzet.h
#define DL 20
class Sprzet
{
protected:
char opis[DL], nazwa[DL], producent[DL], model[DL];
public:
Sprzet(char *aOpis, char *aNazwa, char *aProducent,
char *aModel);
void Informuj_sprzet();
};
class Komputer: public Sprzet
{
protected:
char procesor[DL], plyta[DL], bios[DL];
int pamiec;
public:
Komputer(char *aOpis, char *aNazwa, char *aProducent,
char *aModel, char *aProcesor, int aPamiec, char *aPlyta,
char *aBios);
void Informuj_komputer();
};
class Monitor: public Sprzet
{
protected:
int rozdzielczosc;
public:
Monitor(char *aOpis, char *aNazwa, char *aProducent,
char *aModel, int aRozdzielczosc);
void Informuj_monitor();
};
Sprzet.cpp
#include "k_sprzet.h"
#include <iostream.h>
#include <string.h>
Sprzet::Sprzet(char *aOpis, char *aNazwa, char *aProducent,
char *aModel)
{
strcpy(opis,aOpis);
strcpy(nazwa,aNazwa);
strcpy(producent,aProducent);
strcpy(model,aModel);
}
void Sprzet::Informuj_sprzet()
{
cout << endl<< "OPIS:" << endl << opis << endl;
cout << "NAZWA:" << endl << nazwa << endl;
cout << "PRODUCENT:" << endl << producent << endl;
cout << "MODEL:" << endl << model << endl;
}
Komputer::Komputer(char *aOpis, char *aNazwa, char *aProducent,
char *aModel, char *aProcesor, int aPamiec, char *aPlyta,
char *aBios) : Sprzet(aOpis, aNazwa, aProducent, aModel)
{
strcpy(procesor, aProcesor);
pamiec = aPamiec;
strcpy(plyta,aPlyta);
strcpy(bios,aBios);
}
void Komputer::Informuj_komputer()
{
Informuj_sprzet();
cout << "PROCESOR:" << endl << procesor << endl;
cout << "PAMIEC:" << endl << pamiec << endl;
cout << "PLYTA:" << endl << plyta << endl;
cout << "BIOS:" << endl << bios << endl;
}
Monitor::Monitor(char *aOpis, char *aNazwa, char *aProducent,
char *aModel, int aRozdzielczosc):Sprzet(aOpis, aNazwa, aProducent,
aModel)
{
rozdzielczosc = aRozdzielczosc;
}
void Monitor::Informuj_monitor()
{
Informuj_sprzet();
cout << "ROZDZIELCZOSC:" << endl << rozdzielczosc << endl;
}