Wykład 7
C++:
Przeciążanie operatorów, Wzorce
projektowe, Zmienne dynamiczne
Wskaźniki i referencje,
Operator przypisania
Relacje, Dziedziczenie, Polimorfizm
itm / MVLab (c) 2007-2011
Przeciążanie operatorów
Powód:
l
Dla standardowych typów w C istnieje szereg zdefiniowanych
operatorów
l
Dla nowo tworzonych typów (klas) powinna być także
możliwość zdefiniowania analogicznych, intuicyjnych
operatorów
#include "Ulamek.h"
void main()
{
CUlamek ulamekA(1,2),
ulamekB = 3,
ulamekC;
ulamekC = ulamekA + ulamekB;
ulamekC *= ulamekA;
int c;
if(c<=0.2)
{ //...
};
TestUlamek.cpp(22):
error C2676:
Operator binarny '+':
'CUlamek' nie definiuje
takiego operatora lub konwersji
do operatora predefiniowanego.
itm / MVLab (c) 2007-2011
Zły przykład: operatory
#include "Ulamek.h"
// ...
CUlamek Add (CUlamek U2) {
m_iLicznik =
m_iLicznik * U2.m_iLicznik +
m_iMianownik *
U2.m_iLicznik;
m_iMianownik = U2.m_iMianownik;
return *this;
};
CUlamek Mul (CUlamek U2) {
m_iLicznik *= U2.m_iLicznik;
m_iMianownik *= U2.m_iMianownik;
return *this;
};
class CUlamek
{
private:
int m_iLicznik;
int m_iMianownik;
public:
CUlamek(
const CUlamek& kUlamek
);
CUlamek(int iLicznik);
CUlamek(int iLicznik,
int iMianownik);
CUlamek Add (CUlamek U2);
CUlamek Sub (CUlamek U2);
CUlamek Mul (CUlamek U2);
CUlamek Div (CUlamek U2);
};
#include "Ulamek.h"
void main()
{
CUlamek ulamekA(1,2), ulamekB(3), ulamekC;
ulamekC = (ulamekA.Add(ulamekB)).Mul(ulamekC);
// ...
};
Nigdy
w ten
sposób
itm / MVLab (c) 2007-2011
Przeciążanie operatorów –
Przykład '~'
#include "Ulamek.h"
// ...
CUlamek operator~() const {
if(!m_iMianownik) exit(1);
CUlamek tmp(m_iMianownik, m_iLicznik);
return tmp;
};
CUlamek operator*(const CUlamek&
aU) const {
CUlamek tmp(
m_iLicznik*aU.m_iLicznik,
m_iMianownik*aU.m_iMianownik);
return tmp;
};
class CUlamek
{
private:
int m_iLicznik;
int m_iMianownik;
public:
// ...
CUlamek operator~() const;
CUlamek operator*(
const CUlamek& aU)
const;
CUlamek operator=(const Culamek& aU);
// ...
};
#include "Ulamek.h"
void main()
{
CUlamek ulamekA(1,2), ulamekB(3);
Culamek ulamekC = ~ulamekA;
(ulamekA * ulamekB).Wypisz();
};
itm / MVLab (c) 2007-2011
Przeciążanie operatorów
Przykład
#include "Ulamek.h"
// ...
CUlamek operator~() const {
if(!m_iMianownik) exit(1);
CUlamek tmp (m_iLicznik,
m_iMianownik);
return tmp;
};
CUlamek operator*(const CUlamek&
aU) const {
CUlamek tmp(
m_iLicznik*aU.m_iLicznik,
m_iMianownik*
aU.m_iMianownik);
return tmp;
};
Zwracanie wartości
bez użycia referencji:
po przetworzeniu obiektu
przez metodę,
lokalny obiekt jest niedostępny.
Kompilator zwykle nie dostrzega
tego problemu.
itm / MVLab (c) 2007-2011
Przeciążanie operatorów
Przykład '='
#include "Ulamek.h"
// ...
CUlamek& operator=(const CUlamek& aU)
{ //sprawdzenei konieczne w przypadku
//komponentów dynamicznych
//np. ulamekA = ulamekA
if(this == &aU)
return *this;
m_iLicznik = aU.m_iLicznik;
m_iMianownik = aU.m_iMianownik;
return *this;
class CUlamek
{
private:
int m_iLicznik;
int m_iMianownik;
public:
// ...
CUlamek operator~() const;
CUlamek operator*(
const CUlamek& aU)
const;
CUlamek operator=(const CUlamek& aU);
// ...
};
#include "Ulamek.h"
void main()
{
CUlamek ulamekA(1,2), ulamekB(3), ulamekC;
ulamekC = ulamekA * (~ulamekB);
ulamekC.Wypisz();
};
Obiekt którego
metoda została wywołana
(a nie jego kopia!!)
itm / MVLab (c) 2007-2011
Wzorce klas
l
Używanie funkcji jest zależne od typów danych jakie one
obsługują. Powoduje to m.in. konieczność ich przeciążania.
l
Zasadniczo ta sama funkcjonalność musi być często udostępniana
dla różnych typów danych. Przeciążanie funkcji celem umożliwienia
współpracy z różnymi typami powoduje:
l
większy nakład programistyczny,
l
trudności w analizie kodu,
l
większe prawdopodobieństwo błędów,
l
problemy z ich znalezieniem w gąszczu podobnych funkcji.
l
Przykładem jest bufor FIFO który działa tak samo dla wszystkich
możliwych typów danych,
l
Rozwiązaniem jest parametryzacja funkcji i klas pod względem
typów w postaci tzw. wzorców (szablonów -ang. templates).
→ programowanie generyczne
l
wzorce klas
l
wzorce funkcji
itm / MVLab (c) 2007-2011
Implementacja wzorców
przykład
// Wzorzec funkcji zwracającej większą wartość z dwóch podanych
template<class T> T Max (T wart1, T wart2) {
if(wart1>wart2) return wart1;
else return wart2; }
// ...
CUlamek ulamekA(2,3), ulamekB(3,4), ulamekC;
ulamekC = Max(ulamekA,ulamekB);
int i = Max(4,5);
// Wzorzec klasy CLista
template<class T> class CLista {
public:
Clista();
~Clista();
void dolacz(T);
T podajOstatni(T);
void usunOstatni();
//...
};
Clista<CUlamek> mojaLista;
CUlamek ulamekA(2,3);
mojaLista.dolacz(ulamekA);
Typ T zostanie określony dalej,
podczas wywoływania funkcji;
można podać np. int -wówczas
zarówno argumenty, jak
i typ zwracany też zostaną
zamienione na int
Tu również na tej samej
zasadzie zadziała typ
parametryczny T.
mojaLista (typu CLista<CUlamek>)
będzie działała dla typu Clista
-
wszędzie tam,
gdzie w definicji wpisany był typ T.
itm / MVLab (c) 2007-2011
Zasady postępowania z wzorcami
l
Wzorce nie mogą być (pre-)kompilowane oddzielnie.
W tym celu muszą istnieć odpowiednie zależności,
parametryzowane przez sam wzorzec -
zwykle nie powoduje to błędów.
l
Wzorce muszą być zadeklarowane i zdefiniowane w jednym pliku.
Są one łączone wg zapotrzebowań.
Kompilator generuje sprecyzowane wersje klas opartych na wzorcach
dopiero w przypadku odpowiedniego wywołania.
Ulamek
.h
Ulamek
.cpp
TestUlame
k
.cpp
Kompil
ator
Kompil
ator
Ulamek
.obj
TestUlamek
.obj
Linker
TestUlamek
.exe
Wzorzec
.t
Wzorzec
.t
itm / MVLab (c) 2007-2011
Pamięć dynamiczna
l
Zmienne i tablice są statyczne i nieelastyczne
-
nie można zmieniać ich rozmiaru.
l
Co zrobić gdy ilość dostępnej pamięci
jest w czasie programowania nieznana? np.:
l
przetwarzanie tekstu -ile liter?
l
obsługa MP3 -ile piosenek?
l
Rozwiązaniem jest zastosowanie pamięci dynamicznej
o modyfikowalnym rozmiarze. Jeśli trzeba alokuje się dodatkową
przestrzeń w pamięci, jeśli nie -można ją zwolnić dla innych potrzeb.
l
Cele:
l
efektywne gospodarowanie pamięcią,
l
elastyczność,
l
dopasowanie do zasobów systemowych.
itm / MVLab (c) 2007-2011
Pamięć dynamiczna w C++
Dlaczego nie tak jak w C?
l
Wady malloc() i free()
l
wysokie wymagania od programisty
l
niebezpieczeństwo naruszenia zasad ochrony
pamięci
l
brak dopasowania do filozofii obiektowej
l
Rozwiązanie: operatory new() i delete()
l
dynamiczna obsługa pamięci w C++ za pomocą operatorów new i delete
l
zastosowanie zarówno do typów standardowych jak i do klas
l
automatyczne (niejawne) wywołania konstruktora w przypadku klas
l
operatory new i delete można także przeładowywać w obrębie klasy
l
new i delete istnieją zawsze razem
-
każdy dynamicznie powołany obiekt musi być w C++ niejawnie usunięty.
itm / MVLab (c) 2007-2011
new i delete -
przykłady
int* piMojaLiczba = new int;
*piMojaLiczba = 4711;
delete piMojaLiczba;
int iRozmiar = 5;
int* piPole = new int [iRozmiar];
for(int j=0; j<iRozmiar; j++)
piPole[j]=j*11;
delete[] piPole;
CUlamek *pUlamekA = new CUlamek;
CUlamek *pUlamekB = new CUlamek;
CUlamek *pUlamekC = new CUlamek;
pUlamekC = pUlamekB;
//...
delete pUlamekA;
delete pUlamekB;
delete pUlamekC;
4711
piMojaLiczba
44
piPole
33
22
11
0
26
1
L
M
5
8
L
M
0
1
L
M
pUlamekC
pUlamekB
pUlamekA
wywołanie konstruktorów
pUlamekC
„wisi w powietrzu”
„wysypanie” programu
dynamiczna alokacja tablicy
dynamiczna dealokacja tablicy
(delete bez nawiasów klamrowych
spowoduje usunięcie wyłącznie
pierwszego elementu)
brak konieczności
konwersji typów
(typ jest ten sam)
itm / MVLab (c) 2007-2011
Przykład:
konstruktor konwertujący
// MyString.h
class CMyString
{
public:
CMyString();
CMyString(const char*);
~CMyString();
void pokaz();
private:
int m_iDlugosc;
char* m_pszBuf;
};
#include "MyString.h"
#include <cstring>
#include <iostream>
using namespace std;
CmyString::CMyString() {
m_iDlugosc = 0;
m_pszBuf = NULL;
};
CMyString::CMyString(const char* pszName {
m_iDlugosc = strlen(pszName);
m_pszBuf = new char[m_iDlugosc+1];
strcpy(m_pszBuf, pszName);
};
CmyString::~CMyString() {
delete[] m_pszBuf;
};
void CmyString::pokaz() {
cout << "STRING-Objekt: " << m_pszBuf;
};
#include "MyString.h"
void main() { CMyString mojString("To jest String");}
m_iDlugosc
m_pszBuf
14
T o
j e s t
S t r i n g
\0
mojString
konstruktor
korzysta z pamięci
podręcznej
dealokacja
w obrębie destruktora
automatyczne
wywołanie
destruktora
na końcu bloku
itm / MVLab (c) 2007-2011
Przykład:
konstruktor konwertujący
#include "MyString.h"
#include <cstring>
#include <iostream>
using namespace std;
CmyString::CMyString() {
m_iDlugosc = 0;
m_pszBuf = NULL;
};
CMyString::CMyString(const char* pszName {
m_iDlugosc = strlen(pszName);
m_pszBuf = new char[m_iDlugosc+1];
strcpy(m_pszBuf, pszName);
};
CmyString::~CMyString() {
delete[] m_pszBuf;
};
void CmyString::pokaz() {
cout << "STRING-Objekt: " << m_pszBuf;
};
#include "MyString.h"
void main() { CMyString mojString("To jest String");}
UWAGA!
W przypadku
pamięci dynamicznej
ostrożności nigdy za wiele
if(m_pszBuf!=NULL)
delete[] m_pszBuf;
m_pszBuf = NULL;
itm / MVLab (c) 2007-2011
Dynamiczne
komponenty i kopie
Płaskie kopie
l
Standardowo kopiowanie ani przypisanie nie powiela
zawartości dynamicznej, przez co dynamiczna zawartość
staje się przynależna do wielu obiektów
l
Ważne w przypadku konstruktorów kopiujących
oraz operatorów przypisania.
CMyString String1("Pierwszy String");
//Niejawne wyw. konstr. kopującego:
CMyString String2 = String1;
CMyString String3;
//Niejawne wyw. operat. przypisania:
String3 = String1;
//String4 pozostawiony "w powietrzu":
CMyString String4("Czwarty String");
String4 = String1;
15
P i e r w s z y
S t r i n g
\
0
15
15
15
String4
String3
String2
String1
C z w a r t y
S t r i n g
\
0
Zamiany dokonane
w którymkolwiek stringu
mają wpływ na inne!
„Wisi
w powietrzu!”
itm / MVLab (c) 2007-2011
Dynamiczne
komponenty i kopie
Głębokie kopie
l
Dla każdej operacji kopiowania
zostaje zaalokowana pamięć do której kopiowana jest ta sama zawartość,
jednak w innym, indywidualnie przydzielonym miejscu w pamięci.
CMyString String1("Pierwszy String");
//Niejawne wyw. konstr. kopującego:
CMyString String2 = String1;
CMyString String3;
//Niejawne wyw. operat. przypisania:
String3 = String1;
//Przygotowanie pamięci dla String4
CMyString String4("Czwarty String");
String4 = String1;
15
P i e r w s z y
S t r i n g \0
15
15
15
String4
String3
String2
String1
C z w a r t y
S t r i n g
\0
Usunięty
P i e r w s z y
S t r i n g \0
P i e r w s z y
S t r i n g \0
P i e r w s z y
S t r i n g \0
itm / MVLab (c) 2007-2011
Przykład: głębokie kopiowanie
własny konstruktor kopiujący i przeciążenie operat.
// MyString.h
class CMyString
{
public:
//...
CmyString(const
CMyString&);
CMyString& operator=
(const CMyString&);
private:
int m_iDlugosc;
char* m_pszBuf;
};
CMyString::CMyString(const CMyString&
argString)
{
m_iDlugosc = argString.m_iDlugosc;
if(m_iDlugosc == 0) m_pszBuf = NULL;
else {
m_pszBuf = new char[m_iDlugosc+1];
strcpy(m_pszBuf, argString.m_pszBuf);
}
}
CMyString& CMyString::operator= (const
CMyString& argString)
{ if(this == &argString) return *this;
m_iDlugosc = argString.m_iDlugosc;
if(m_pszBuf != NULL) delete[] m_pszBuf;
if(m_iLaenge == 0) m_pszBuf = NULL;
else {
m_pszBuf = new char[m_iDlugosc+1];
strcpy(m_pszBuf, einString.m_pszBuf);
}
return *this;
}
zabezpieczenie
przed
samozniszczeniem
itm / MVLab (c) 2007-2011
Podsumowanie rozdz. 4.3
•Konstruktory
(standardowe, kopiujące,
•
konwertujące, inne)
•Destruktory
•Inicjalizacja podczas
• tworzenia instancji
•usuwanie obiektów
•dopasowywanie funkcji
•
obsługa innych obiektów
•operatory
•
pozostałe funkcje
Metody
obsługa pamięci
statyczna/dynamiczna
jawne i niejawne
wywołania
przeciążanie
wzorce
przestrzenie
nazw
płytkie
i głębokie
kopiowanie,
głęboki
operator
przypisania
zwracanie wartości
przez referencję
typ metody
(czy zmienia stan?)
itm / MVLab (c) 2007-2011
Rozróżnianie wskaźników
i referencji
CUlamek* p_ulamek = &ulamek1;
if(p_ulamek!=NULL) //p_iPole!=NULL;
delete p_ulamek; //delete[] p_iPole;
p_ulamek=NULL; //p_iPole=NULL;
5
12
L
M
11
45
L
M
p_ulamek
Ulamek1
0x0011fec1
Ulamek2
0x0011fec9
CUlamek* p_ulamek = new CUlamek;
int *p_iPole = new int[40];
zwykle użycie w C
zwykłe użycie w C++
dowolna pozycja
znaku wskaźnika
Dynamiczna obsługa pamięci za
pomocą new i delete
zabezpieczenie przed użyciem
operatora delete
na rzecz wskaźnika do NULL
Wskaźnik
•
zmienna która zawiera adres obszaru pamięci
•
można wskazywać na: zmienne, obiekty, obszary kodu
•
możliwa wartość NULL oraz wkaźniki do niesprecyzowanego
typu
•
arytmetyka wskaźników (np. dostęp do zmiennych)
itm / MVLab (c) 2007-2011
Rozróżnienie wskaźników
i referencji
Referencje (nowy element składni C++)
l
Referencje dostarczają odnośników do obiektów
l
Analogicznie do wskaźników: odnośnik do adresu w pamięci
l
W C++
każdy obiekt automatycznie dostaje swoją referencję
CUlamek ulamek1(1,2);
//pobranie referencji
CUlamek& aliasulamka = ulamek1;
…operator+(const CUlamek& n_ulamek){
this->m_iz=m_iz+n_ulamek.m_iz;
};
0
1
L
M
l
Zastosowanie referencji jest, w porównaniu do wskaźników, pewniejsze,
ponieważ wskazują zawsze jeden, dokładnie zdefiniowany obiekt,
l
Brak arytmetyki –
referencje nie mogą być np. inkrementowane
l
Mechanizm przekazywania parametrów call-by-reference
l
Dostępne jak „normalne” zmienne
ALE: pomimo takiej samej pisowni
operator referencji jest łatwo odróżnialny
od operatora adresu
Op. referencji jest używany zwykle
w deklaracjach –zaraz za typem danych
Op. adresu używa się przy operacjach
na zmiennych i stoi zwykle przed ich nazwami
itm / MVLab (c) 2007-2011
Dokładniej o operatorze
przypisania I
l
Sumowanie atrybutów dwóch obiektów CUlamek;
l
Wynik typu CUlamek;
l
Niejawne wywołanie konstruktora kopiującego;
Dlaczego nie można zastosować zwracania wartości przez referencję?
l
Lokalne kopie obiektów zostaną na końcu działania funkcji usunięte
l
Wobec tego także referencja…
CUlamek ulamek1(4,6);
CUlamek ulamek2(5);
//pobranie referencji
CUlamek suma = ulamek1 + ulamek2;
suma.wyswietl();
CUlamek CUlamek::operator+ (const CUlamek &n_ulamek) {
CUlamek tmp(
(this->m_iLicznik+n_ulamek.m_iLicznik),
(this->m_iMianownik+n_ulamek.m_iMianownik));
return tmp; }
9
7
L
M
4
6
L
M
5
1
L
M
=
+
suma
ulamek1
ulamek2
W przypadku operatorów
wartość lewa i prawa
muszą
być identyczne
itm / MVLab (c) 2007-2011
Dokładniej o operatorze
przypisania I
l
Sumowanie atrybutów dwóch obiektów CUlamek;
l
Wynik typu CUlamek;
l
Niejawne wywołanie konstruktora kopiującego;
Dlaczego nie można zastosować zwracania wartości przez referencję?
l
Lokalne kopie obiektów zostaną na końcu działania funkcji usunięte
l
Wobec tego także referencja…
CUlamek ulamek1(4,6);
CUlamek ulamek2(5);
//pobranie referencji
CUlamek suma = ulamek1 + ulamek2;
suma.wyswietl();
CUlamek&
CUlamek::operator+ (const CUlamek &n_ulamek) {
CUlamek tmp(
(this->m_iLicznik+n_ulamek.m_iLicznik),
(this->m_iMianownik+n_ulamek.m_iMianownik));
return tmp; }
9
7
L
M
4
6
L
M
5
1
L
M
=
+
suma
ulamek1
ulamek2
W przypadku operatorów
wartość lewa i prawa
muszą być identyczne
itm / MVLab (c) 2007-2011
Dokładniej o operatorze
przypisania II
l
Obydwa obiekty istnieją – konieczne przypisanie
l
Musi być zgodność typów
l
Przeciążony operator "=" (głębokie kopiowanie)
3
możliwości implementacji
CUlamek ulamek1(4,6);
CUlamek ulamek2(17,4);
ulamek1 = ulamek2;
ulamek1.wyswietl();
CUlamek
CUlamek::operator= (const CUlamek &n_ulamek) {
if(this==&n_ulamek) //must avoid self destruction
//...
cout<<"assignment operator called";
this->m_iMianownik=n_ulamek.m_iMianownik;
this->m_iLicznik=n_ulamek.m_iLicznik;
CUlamek temp(this->m_iLicznik,this->m_iMianownik);
cout<<"assignment operator finished";
return temp;
}
itm / MVLab (c) 2007-2011
Dokładniej o operatorze
przypisania II
l
Obydwa obiekty istnieją –konieczne przypisanie
l
Musi być zgodność typów
l
Przeciążony operator "=" (głębokie kopiowanie)
3
możliwości implementacji
CUlamek ulamek1(4,6);
CUlamek ulamek2(17,4);
ulamek1 = ulamek2;
ulamek1.wyswietl();
CUlamek
CUlamek::operator= (const CUlamek &n_ulamek) {
if(this==&n_ulamek) //must avoid self destruction
//...
cout<<"assignment operator called";
this->m_iMianownik=n_ulamek.m_iMianownik;
this->m_iLicznik=n_ulamek.m_iLicznik;
cout<<"assignment operator finished";
return CUlamek (this->m_iLicznik, this->m_iMianownik);
}
itm / MVLab (c) 2007-2011
Dokładniej o operatorze
przypisania II
l
Obydwa obiekty istnieją –konieczne przypisanie
l
Musi być zgodność typów
l
Przeciążony operator "=" (głębokie kopiowanie)
3
możliwości implementacji
CUlamek ulamek1(4,6);
CUlamek ulamek2(17,4);
ulamek1 = ulamek2;
ulamek1.wyswietl();
CUlamek& CUlamek::operator =(const CUlamek& n_Ulamek)
{
if(this == &n_Ulamek) //must avoid self destruction
return *this;
cout << “assignment operator called“;
this->m_iLicznik = n_Ulamek.m_iLicznik;
this->m_iMianownik = n_Ulamek.m_iMianownik;
cout << "\nassignment operator finished\n";
return *this
; // wszystkie kopie zachowane
}
itm / MVLab (c) 2007-2011
Co już było (w rozdz. 3 i 4)?
l
To już potrafimy:
l
implementacja klas niezależnie od ich późniejszego użycia
l
rozdział na deklaracje (header) oraz definicje i użycie
(implementacja)
l
stosowanie zasady information-hiding
l
implementacja operacji w postaci
metod i operatorów
l
To musimy sobie przypomnieć:
l
krok 1.: deklaracja
i implementacja klas,
l
krok 2.: zastosowanie
w konkretnym przypadku
l
Teraz chodzi nam o:
l
implementacja zależności pomiędzy klasami.
Rodzic
Potomek 1
Potomek 2
itm / MVLab (c) 2007-2011
itm / MVLab (c) 2007-2008
4.4
Zależności między klasami
4.4.1
Relacje współpracy
Kompozycje
Agregacje
4.3.2 Relacje dziedziczenia
Asocjacje
Dziedziczenie proste
Dziedziczenie złożone
itm / MVLab (c) 2007-2011
Relacje współpracy (asocjacje)
Asocjacja
, to rodzaj zależności
pomiędzy klasami.
Opisuje wspólną semantykę i strukturę
zbioru powiązań między obiektami.
Instancja asocjacji
(połączenie między obiektami klasy)
określa się jako link.
Cechy rozróżniające:
l
wg typu
l
kompozycja
l
agregacja
l
(prosta) asocjacja
l
wg kierunkowości
l
wg wielokrotności
Część zależna
Całość
Część
Całość
1
*
0..1
*
Klasa 2
Klasa 1
-nazwisko
-nr_umowy
Pracownik
-nazwa
-adres
Firma
1
*
-pracodawca -pracobiorca
zatrudniony
-ulica
-kod_poczt
Adres
-
wartość
-adres
Rachunek
*
1
ma
X
itm / MVLab (c) 2007-2011
Kompozycja
Właściwości:
l
Kryterium: zachowanie danych
l
Kapsulacja części składowych
l
Zależność czasu życia
Część zależna
Całość
1
*
// Przykład kompozycji
//Calosc.h
class CCalosc {
private:
int m_wlasciwosc;
class CCzesc {
int m_a;
int m_b;
public:
CCzesc();
void test();
} m_drugaWlasciwosc;
};
// Przykład kompozycji
//Calosc.h
#include "Calosc.h"
CCalosc::CCzesc::CCzesc() {
m_a=0; m_b=0;
};
void CCalosc::CCzesc::test() {
m_a=1; m_b=1;
}
Zagnieżdżone deklaracje
klas i zmiennych wewnątrz
klasy zawierającej
itm / MVLab (c) 2007-2011
Agregacja
Właściwości:
l
Kryterim: zachowanie danych
l
Klasy posługują się klasami
l
Zależność składa-się-z…
Część
Całość 2
0..1
*
// Przykład agregacja
// Osoba.h
#include "MyString.h"
class COsoba {
private:
CMyString m_imie;
CMyString m_nazwisko;
public:
//...
};
// Przykład agregacji
// main.cpp
#include "Calosc.h"
void main() {
COsoba Osoba1;
//...
}
Klasa COsoba posługuje się
obiektami innej klasy
jako właściwościami.
Całość 1
0..1
itm / MVLab (c) 2007-2011
Osoba1
Agregacja:
Powoływanie obiektów
// Przykład agregacji
// main.cpp
#include "Calosc.h"
void main() {
COsoba Osoba1;
//...
}
0
NULL
imię
0
NULL
nazwisko
Osoba1
0
NULL
imię
0
NULL
nazwisko
Osoba1
0
NULL
imię
???
???
nazwisko
Osoba1
???
???
imię
???
???
nazwisko
Krok 1:
Alokacja pamięci
Krok 2: Konstruktor
domyślny dla imię
Krok 3: Konstruktor
domyślny dla nazwisko
Krok 4: Konstruktor
domyślny dla klasy
COsoba
Wywołanie konstruktorów
domyślnych komponentów
zagregowanych (w
kolejności deklaracji) –krok
2. i 3
. następuje przed
wywołaniem konstruktora
klasy.
itm / MVLab (c) 2007-2011
Lista inicjalizacyjna
l
Klasy bez konstruktorów domyślnych nie mogą być agregowane
przez inne klasy (z wyjątkiem tych gdzie są zdefiniowane inne
konstruktory, ale nie domyślny.
l
Wywołanie konstruktora domyślnego elementu agregowanego jest
bezsensowne, jeśli konstruktor obiektu agregującego nadpisze dane.
// Osoba.h
#include "MyString.h"
Class COsoba {
public:
COsoba(CMyString& imie,
CMyString& nazwisko);
//...
void main() {
CMyString vn = "Marcin";
CMyString nn = "Wróbel";
COsoba ja(vn,nn);
//...
//
Osoba.cpp (ZŁA WERSJA!)
#include "Osoba.h"
COsoba::COsoba(CMyString& imie,
CMyString& nazwisko) {
m_nazwisko = nazwisko;
m_imie = imie;
};
ja
6
imię
6
nazwisko
Marcin
Wróbel
Krok 4: Konstruktor
główny nadpisuje ewent.
wartości wpisane przez
konstruktor domyślny
.
itm / MVLab (c) 2007-2011
Lista inicjalizacyjna
Właściwości:
l
Pozwalają konstruktorom używanym do inicjalizacji komponentów
przekazywać argumenty przez wartość
l
Mogą zostać użyte tylko dla konstruktorów
Składnia:
l
Są wypisywane za listą argumentów funkcji, po dwukropku
l
Składają się z listy wyrażeń rozdzielanych przecinkiem, w postaci
komponent(argument);
// Osoba.cpp (wersja poprawiona)
#include "Osoba.h"
COsoba::COsoba(CMyString& imie, CMyString& nazwisko):
m_imie(imie), m_nazwisko(nazwisko)
{ // Nic więcej nie trzeba robić!
};
Dzięki takiej liście
nie będą uruchamiane domyślne
konstruktory klas zagregowanych,
lecz konstruktory kopiujące.
itm / MVLab (c) 2007-2011
(Proste) Asocjacje
Właściwości:
l
Kryterim: zachowanie danych
l
Obiekty różnych klas „znają się”
nawzajem
l
Umożliwienie wywołania metod
obiektów innych klas
l
Cechy: kierunkowość i wielokrotność
Pomocy!! -
Już nie mogę....
l
uwzględnienie wszystkich aspektów
wyszłoby daleko poza nasze ramy
l
ograniczamy się wyłącznie na
asocjacjach 1:1, jedno- i
dwukierunkowych.
-ulica
-kod_poczt
Adres
-
wartość
-adres
Rachunek
*
1
ma
X
Nie umiem
pływać, nie
umiem pływać!!!
A ja chodzić, a
mimo to nie drę
się jak głupi na
całą okolicę...
itm / MVLab (c) 2007-2011
Asocjacja -
przykład
Sytuacja:
l
Projektowana jest aplikacja do
obliczeń arytmetycznych, która
ma korzystać z obiektów klasy
ułamek.
l
„Zapoznanie” tych dwóch klas
ze sobą powinno się odbywać
w konstruktorze którejś z klas
programu obliczeniowego.
// Przykład asocjacji
// Arytm.h
#include "Ulamek.h"
class CArytm {
private:
CUlamek* p_ulamek;
public:
CArytm();
CArytm(CUlamek* p_ulamek);
//...
// Przykład asocjacji
// main.cpp
#include "Arytm.h"
void main() {
CUlamek ulamek1(2,3);
CArytm Ar1(&ulamek1);
//...
}
„Zapoznanie”
-licznik
-mianownik
Ulamek
Arytmetyka
0
1
zna
X
Asocjacja przez
wskaźnik na
obiekt zewnętrzny
itm / MVLab (c) 2007-2011
Zaprzyjaźnienia (friend)
Wszystkie dotychczas omówione zależności wymagają
dostępu jednej klasy do metod drugiej. Co jednak, gdy
nie można tego zrealizować
Przykłady:
l
Operator dodawania zmiennej int i obiektu klasy CUlamek,
gdy int jest pierwszym operandem wymaga przeciążenia operatora+ dla typu
int.
l
Wyjście obiektu klasy CUlamek na strumień cout za pomocą standardowego
operatora << wymaga jego przeciążenia w klasie ostream (z niej pochodzi
obiekt cout).
Rozwiązanie:
l
Globalne definicje przeładowywanych funkcji jako funkcji zaprzyjaźnionych,
oznaczonych słowem kluczowym friend, dzięki czemu
mogą
one mieć dostęp
do prywatnych składników klas z którymi są zaprzyjaźnione.
ALE:
Każdy kij ma dwa końce! -kolizja z zasadą information-hiding.
CUlamek ulamek1(5,7);
Culamek ulamek2;
int liczba = 3;
ulamek2=liczba+ulamek1;
cout<<"Wynik: "<<ulamek2;
itm / MVLab (c) 2007-2011
Zaprzyjaźnienia - przykład
Sytuacja:
l
chcemy umożliwić operację jak na przykładzie poniżej:
// main.cpp (Funkcje globalne)
ostream& operator<<(ostream& str, const CUlamek& ulamek) {
str << ulamek.m_iLicznik << "/" << ulamek.m_iMianownik;
return str;
}
//...
void main() //...
// CUlamek.h
class Culamek {
private:
int m_iLicznik, m_iMianownik;
friend ostream& operator<<(ostream&, const CUlamek&);
public:
//...
}
Dostęp do prywatnego
składnika klasy CUlamek.
Globalne przeciążenie operatora <<.
Zostanie przyjęte przez system
jeśli nie znajdzie się żaden
pasujący operator w klasie ostream.
cout << "Wartosc ulamka: "<< ulamek1 << endl;
W ten sposób funkcja
uzyska dostęp do prywatnych
składników klasy CUlamek.
itm / MVLab (c) 2007-2011
Dziedziczenie
Dziedziczenie
l
podklasy (także klasy częściowe, klasy wyprowadzone i podklasy)
dziedziczą wszystkie właściwości i metody swoich klas bazowych.
l
mogą posiadać własne -niezależne od klas bazowych oraz innych
klas potomnych (rodzeństwa) składniki oraz modyfikować metody
odziedziczone.
l
zasada ponownego wykorzystania kodu.
Problemy
l
dziedziczenie proste vs
. dziedziczenie złożone
l
polimorfizm vs. funkcje wirtualne.
Rodzic
Potomek
itm / MVLab (c) 2007-2011
Dziedziczenie proste
l
Specjalizacja klasy bazowej
l
Przykład: klasa CUlamekDzies specjalizujący
klasę CUlamek o możliwość definiowania
dokładności.
-Licznik: int
-Mianownik: int
Ulamek
-Dokladnosc: int
UlamekDzies
// Przykład dziedziczenia
// UlamekDzies.h
#include "Ulamek.h"
class CUlamekDzies: public CUlamek {
private:
int m_iDokladnosc;
public:
CUlamekDzies(int L, int M, int D);
void pokaz() const;
}
Wyrażenie
definiujące
dziedziczenie
Nowy konstruktor
(trójparametrowy)
Przedefiniowanie
(dopasowanie) metody
Nowy atrybut
itm / MVLab (c) 2007-2011
Dziedziczenie proste -
przykład
// Przykład dziedziczenia
// UlamekDzies.h
#include "Ulamek.h"
class CUlamekDzies:
public CUlamek
{
private:
int m_iDokladnosc;
public:
CUlamekDzies
(int L, int M, int D);
void pokaz() const;
}
Poniższe przykłady nie należą do dobrego stylu programistycznego
i są przytoczone w tej postaci tylko dla jasności kodu.
// Przykład dziedziczenia
// UlamekDzies.cpp
#include "Ulamek.h"
#include <math.h>
CUlamekDzies::CUlamekDzies
(int iL, int iM, int iD) {
this->Zmien(pow(10,iD)*iL/iM,
pow(10,iD));
m_iDokladnosc=iD;
}
void CUlamekDzies::pokaz() const {
CUlamek::pokaz();
cout<<"Dokladnosc: "<<m_iDokladnosc;
}
#include "UlamekDzies.h"
void main() {
CUlamekDzies mojUlamekDzies(1,3,3);
mojUlamekDzies.pokaz();
}
Odziedziczona metoda
Jawne wywołanie
metody klasy zewnętrznej
itm / MVLab (c) 2007-2011
Polimorfizm
l
dzięki polimorfizmowi możliwe jest wywoływanie metody obiektu bez
odgórnej wiedzy jakiej jest on klasy -
system sam ją określi i
spowoduje że na pewno zareaguje on na swój specyficzny sposób
l
dziedziczenie powoduje zachowania polimorficzne
#include "UlamekDzies.h"
void main() {
CUlamekDzies mojUlamekDzies(1,3,3);
CUlamek mojUlamek(2,5);
CUlamek *wynikUlamkowy;
//Przypadek 1
wynikUlamkowy = &mojUlamek;
wynikUlamkowy->pokaz();
//Przypadek 2
wynikUlamkowy = &mojUlamekDzies;
wynikUlamkowy->pokaz();
}
Niestety
nie pokaże tego
co chcemy
OK.
CUlamekDzies
jest przecież
również CUlamek
itm / MVLab (c) 2007-2011
Polimorfizm i wirtualność
// Ulamek.h
#include <iostream>
using namespace std;
class CUlamek {
private:
int m_iLicznik;
int m_iMianownik;
public:
virtual void pokaz() const;
virtual ~CUlamek();
//...
};
#include "UlamekDzies.h"
void main() {
CUlamekDzies
mojUlamekDzies(1,3,3);
CUlamek mojUlamek(2,5);
CUlamek *wynikUlamkowy;
//Przypadek 1
wynikUlamkowy = &mojUlamek;
wynikUlamkowy->pokaz();
//Przypadek 2
wynikUlamkowy =
&mojUlamekDzies;
wynikUlamkowy->pokaz();
}
Słowo kluczowe virtual w pliku nagłówkowym
mówi o tym, że metoda klasy potomnej
może zostać nadpisana.
Szczególnie ważne: wirtualny destruktor!!
Dopasowanie metod w czasie działania
przez tzw. wirtualną tablicę metod.
Zalety: zachowuje się tak jak chcemy
Wady: utrata efektywności.
itm / MVLab (c) 2007-2011
Przykład zastosowania:
Zdarzenia
+ ~DostZdarz()
DostawcaZdarzenia
+ DostZdarz(in Handl1: HandlZdarz&)
-Handler: HandlZdarz
DostawcaZdarzenia
+ HandlZdarz()
+ nowyAbonent(in Abnt: AbonentZdarz&)
+ usunAbonenta(in Abnt: AbonentZdarz&)
+ odklejOdZdarz(in Dost: DostZdarz&)
-Ilosc: int
-Abonamenty: wektor<AbonentZdarz*>
HandlerZdarzenia
DataChangedHandler
+ OnZdarzenie(in Dost: DostZdarz&): bool
AbonentZdarzenia
+ OnZdarzenie(in Dost: DostZdarz&): bool
Wizualizacja
itm / MVLab (c) 2007-2011
Powtórka i podsumowanie
rozdz. 4.4
•Kompozycje
•Agregacje
•Asocjacje
•
Klasy używają klas
•
Zależność „has-a”
•
Inne zależności
•
Zależność „is-a”
•Dopasowywanie i
rozszerzanie dziedziczonej
funkcjonalności
•Dziedziczenie proste
•(Dziedziczenie
wielokrotne)
Zależności między klasami
Zaprzyjaźnienia
(friends)
listy inicjalizacyjne
polimorfizm
wirtualność
itm / MVLab (c) 2007-2011
Czego zabrakło - rozdział 4.5
4.1
Zanim rozpoczniemy implementację...
4.2 Pierwsze kroki w kierunku
programowania w C++
4.3
Klasy „rosną”...
4.4
Zależności pomiędzy klasami
4.5
Implementacja rozwiązań
(w następnym semestrze)
Kiedy przećwiczymy podstawy
będziemy programować
pierwsze aplikacje obiektpwe w c++