Programowanie C++
1
Klasa
Klasa w C++ jest structurą zawierającą pewne dane składowe
oraz pewne funkcje składowe. Funkcje składowe działają na
danych składowych. Niektóre składniki są public, inne są private.
Klasa w C++ jest structurą zawierającą pewne dane składowe
oraz pewne funkcje składowe. Funkcje składowe działają na
danych składowych. Niektóre składniki są public, inne są private.
class Point {
private:
double x;
double y;
public:
Point();
Point(double x, double y);
~Point();
double X() const;
double Y() const;
void Set(double newX, double newY);
void Move(double deltaX, double deltaY);
double DistanceTo(const Point& other) const;
void Display() const;
};
class Point {
private:
double x;
double y;
public:
Point();
Point(double x, double y);
~Point();
double X() const;
double Y() const;
void Set(double newX, double newY);
void Move(double deltaX, double deltaY);
double DistanceTo(const Point& other) const;
void Display() const;
};
Przykład
:
dane składowe
funkcje składowe
Dane składowe powinny być
prywatne,
funkcje najczęściej są publiczne
Programowanie C++
2
Klasa jest typem danych
Zatem klasa Point jest używana do deklarowania obiektów:
Zatem klasa Point jest używana do deklarowania obiektów:
Point p1;
Point p2;
Point a[10];
Point p1;
Point p2;
Point a[10];
Z obiektami klasy Point możemy używać funkcji składowych tej
klasy . Zwróć uwagę na syntaks:
Z obiektami klasy Point możemy używać funkcji składowych tej
klasy . Zwróć uwagę na syntaks:
p1.Set(3.0, 4.0);
double x = p1.DistanceTo(p2);
a[2].Move(2.0, -2.5);
a[0].Display();
a[9].Set(a[1].X(), 0.0);
p1.Set(3.0, 4.0);
double x = p1.DistanceTo(p2);
a[2].Move(2.0, -2.5);
a[0].Display();
a[9].Set(a[1].X(), 0.0);
Jeden
punkt
Inny punkt
Tablica 10-ciu punktów
Programowanie C++
3
Funkcje posiadają jeden obiekt
Wszystkie funkcje klasy Point działają na obiekcie klasy
Point. Ten obiekt jest podmiotem funkcji
Wszystkie funkcje klasy Point działają na obiekcie klasy
Point. Ten obiekt jest podmiotem funkcji
p1.Set(3.0, 4.0);
double x = p1.DistanceTo(p2);
a[2].Move(2.0, -2.5);
a[0].Display();
a[9].Set(a[1].X(), 0.0);
p1.Set(3.0, 4.0);
double x = p1.DistanceTo(p2);
a[2].Move(2.0, -2.5);
a[0].Display();
a[9].Set(a[1].X(), 0.0);
p1 jest obiektem; 3.0 i 4.0 są
argumentami. Wartość p1 ulegnie
zmianie.
p1 jest obiektem-podmiotem; p2
jest obiektem- argumentem.
Wyliczana jest odległość pomiędzy
p1 i p2.
a[2] jest podmiotem. 2.0 i -2.5 są
argumentami. Wartość a[2] zmienia
się.
a[0] jest podmiotem; funkcja Display nie
ma argumentów. Wartość a[0] jest
wyświetlana.
a[9] podmiotem; a[1].X() i 0.0 są
argumentami. W wywołaniu funkcji
a[1].X(), a[1] jest podmiotem; funkcja X nie
posiada argumentów.
Obiektem(podmiotem) funkcji jest ten obiekt, na rzecz którego funkcja jest
wywoływana i który podlega jej działaniu.
Obiektem(podmiotem) funkcji jest ten obiekt, na rzecz którego funkcja jest
wywoływana i który podlega jej działaniu.
Programowanie C++
4
Konstruktory
Każda klasa ma jeden lub więcej konstruktorów. Konstruktory
mają taką samą nazwę jak klasa. Konstruktor jest wywoływany
zawsze wtedy, gdy tworzony jest obiekt danej klasy. Konstruktor
jest funkcją, która dba o to, by obiekt został utworzony
poprawnie.
Klasa Point posiada dwa konstruktory:
class Point {
...
Point();
Point(double x, double y);
...
};
class Point {
...
Point();
Point(double x, double y);
...
};
Konstruktor domyślny
W tym przypadku zakłada się, wydaje się rozsądne przypuszczenie, że
konstruktor domyślny ustawia obie dane składowe na 0.0, podczas gdy
drugi przypisuje im wartości przekazane przez parametry.
Programowanie C++
5
Destruktory
Każda klasa posiada dokładnie jeden destruktor. Destruktor jest
wywoływany automatycznie w chwili gdy obiekt przestaje istnieć.
Destruktor klasy X posiada nazwę ~X.
class Point {
...
~Point();
...
};
class Point {
...
~Point();
...
};
Zwykle destruktory są używane w sytuacji gdy chcemy odzyskać
pamięć, która była przydzielona dynamicznie a obiekt, który jej
używał nie będzie więcej potrzebny.
W prostych klasach takich jak Point, które nie rezerwują pamięci
dynamicznie destruktory nic nie robią.
Programowanie C++
6
Selektory
Selektory są funkcjami składowymi, które zwracają informację na
temat stanu obiektu, ale go nie zmieniają.
class Point {
...
double X() const;
double Y() const;
double DistanceTo(const Point& other) const;
void Display() const;
};
class Point {
...
double X() const;
double Y() const;
double DistanceTo(const Point& other) const;
void Display() const;
};
Nazwy tych funkcji sugerują ich znaczenie: funkcja X zwraca wartość
współrzędnej x; funkcja Y zwraca wartość współrzędnej y; funkcja
DistanceTo oblicza odległość od punktu do innego punktu; funkcja Display
wypisuje wartości współrzędnych na ekranie monitora.
Będziemy programowali selektory jako funkcje const.
Programowanie C++
7
Modyfikatory
Modyfikatory są funkcjami, które zmieniają stan obiektu i nic nie
zwracają
class Point {
...
void Set(double newX, double newY);
void Move(double deltaX, double deltaY);
...
};
class Point {
...
void Set(double newX, double newY);
void Move(double deltaX, double deltaY);
...
};
Funkcja Set przypisuje nowe wartości współrzędnym; funkcja Move
dodaje przesunięcie do współrzędnych.
Funkcje mogą być równocześnie selektorami i modyfikatorami, czyli
funkcjami zwracającymi stan obiektu i równocześnie zmieniającymi go.
Klasy do tego wykładu nie będą zawierały takich funkcji.
Programowanie C++
8
Przekazywanie parametrów
W C++, parametry mogą być przekazywane do funkcji na trzy
sposoby: przez wartość, przez referencję i przez stałą referencję.
Wybór sposobu przekazywania parametrów jest decyzją etapu
projektowania. Np. funkcja DistanceTo mogłaby być
zadeklarowana na różne sposoby:
class Point {
...
double DistanceTo(Point other) const;
...
};
class Point {
...
double DistanceTo(Point other) const;
...
};
class Point {
...
double DistanceTo(Point& other) const;
...
};
class Point {
...
double DistanceTo(Point& other) const;
...
};
class Point {
...
double DistanceTo(const Point& other) const;
...
};
class Point {
...
double DistanceTo(const Point& other) const;
...
};
Przez
wartość
Przez referencję
Preferujemy:
Przez stałą referencję
Programowanie C++
9
Przez wartość i stałą referencję
Parametry typów prostych (int, double, char lub wskaźnikowe)
przekazuje się przez wartość
Parametry typów obiektowych przekazujemy przez stałą
referencję
class Point {
...
Point(double x, double y);
...
void Set(double newX, double newY);
void Move(double deltaX, double deltaY);
...
};
class Point {
...
Point(double x, double y);
...
void Set(double newX, double newY);
void Move(double deltaX, double deltaY);
...
};
class Point {
...
double DistanceTo(const Point& other) const;
...
};
class Point {
...
double DistanceTo(const Point& other) const;
...
};
Programowanie C++
10
Definiowanie funkcji
(1)
Funkcje mogą być zdefiniowane w innym
pliku:
double Point::X() const
{
return x;
}
double Point::X() const
{
return x;
}
double Point::Y() const
{
return y;
}
double Point::Y() const
{
return y;
}
void Point::Set(double newX, double newY)
{
x = newX;
y = newY;
}
void Point::Set(double newX, double newY)
{
x = newX;
y = newY;
}
void Point::Move(double deltaX, double
deltaY)
{
x += deltaX;
y += deltaY;
}
void Point::Move(double deltaX, double
deltaY)
{
x += deltaX;
y += deltaY;
}
Selektory
zwracają
wartości danych
prywatnych
Ten modyfikator przypisuje
nowe wartości danym
prywatnym.
Ten modyfikator dodaje
wartości do danych
prywatnych
Definiując funkcje składowe klasy trzeba uwzględnić nazwę klasy
Programowanie C++
11
Przypisanie dla klas
Czy można użyć instrukcji przypisania dla obiektów klasy? Tak!
Domyślnie oznacza to przypisanie danych składowych. Zatem, jeśli
p1 i p2 są obiektami klasy Point, zapis
p1 = p2;
p1 = p2;
daje w efekcie:
p1.x = p2.x;
p1.y = p2.y;
p1.x = p2.x;
p1.y = p2.y;
Zauważ, że x i y są
prywatnymi danymi
składowymi, które nie
mogą być używane poza
klasą.
W przypadku wielu interesujących klas, przypisanie obiektu do
innego obiektu nie jest prostym przypisaniem składnika do
składnika. W takich przypadkach będziemy musieli zdefiniować
znaczenie operatora przypisania dla klasy jako funkcję składową.
(O tym później)
Programowanie C++
12
Definicje funkcji
(2)
Odległość obliczana jest zgodnie z twierdzeniem Pitagorasa:
double Point::DistanceTo(const Point& other) const
{
return sqrt(pow(this->x - other.x, 2) + pow(this->y - other.y, 2));
}
double Point::DistanceTo(const Point& other) const
{
return sqrt(pow(this->x - other.x, 2) + pow(this->y - other.y, 2));
}
double Point::DistanceTo(const Point& other) const
{
return sqrt(pow(
this->x
-
other.x
, 2) + pow(
this->y
-
other.y
,2));
}
double Point::DistanceTo(const Point& other) const
{
return sqrt(pow(
this->x
-
other.x
, 2) + pow(
this->y
-
other.y
,2));
}
To jest dana składowa x obiektu,
od którego odległość jest
obliczana...
… a to jest dana składowa x
parametru other.
Operator strzałki -> jest
używany ze wskaźnikami do
obiektów.
Operator kropki jest używany z
obiektami.
this jest wskaźnikiem w funkcji DistanceTo:
Programowanie C++
13
Odniesienie do obiektu
void Point::Display() const
{
cout << x << " " << y;
}
void Point::Display() const
{
cout << x << " " << y;
}
W wielu sytuacjach obiekt w definicji funkcji jest niejawny:
Jeśli chcemy wyrazić go jawnie używamy wskaźnika this:
Technicznie this jest wskaźnikiem do obiektu. Jego wartością jest adres
miejsca w pamięci, gdzie obiekt jest przechowywany. (Ten adres nie ulega
zmianie w czasie życia obiektu.)
Jeśli chcemy odnieść się do obiektu, a nie jego adresu, używamy
operatora gwiazdki aby dokonać dereferencji wskaźnika: *this.
double Point::DistanceTo(const Point& other) const
{
return sqrt(pow(this->x - other.x, 2) + pow(this->y - other.y, 2));
}
double Point::DistanceTo(const Point& other) const
{
return sqrt(pow(this->x - other.x, 2) + pow(this->y - other.y, 2));
}
x i y tutaj to x i y obiektu dla
którego funkcja Display jest
wywołana.
Użycie this tutaj jest kwestią stylu. Pominięcie wskaźnika
this nie zmieniłoby znaczenia funkcji.Taki zapis powoduje
jedynie wyraźne rozróżnienie pomiędzy x i y obiektu oraz x
i y parametru funkcji.
Programowanie C++
14
Definicje konstruktorów
Konstruktory są specjalnymi funkcjami o specjalnym syntaksie.
Dostarczają kodu do zainicjalizowania obiektów.
Point::Point():
x(0),
y(0)
{
}
Point::Point():
x(0),
y(0)
{
}
Point::Point(double x, double y):
x(x),
y(y)
{
}
Point::Point(double x, double y):
x(x),
y(y)
{
}
Konstruktor domyślny jest bardzo prosty:
To oznacza, że dane składowe x
i y otrzymają początkowo
wartość zero.
Drugi konstruktor jest również prosty:
Pierwsze x jest daną składową; x w nawiasach jest
parametrem. Tak samo dla y. Zatem składowa x otrzymuje
początkowo wartość parametru x, itd. (Moglibyśmy
oczywiście użyć innych nazw, ale po co?…)
W tych przypadkach bloki funkcji są puste, cała praca związana z
inicjalizacją danych wykonywana jest poprzez listę inicjalizacyjną.
Programowanie C++
15
Definiowanie destruktora
Destruktor klasy Point nic nie robi. Zatem jego blok jest pusty:
Point::~Point()
{
}
Point::~Point()
{
}
Oczywiście destruktory nie zawsze są tak proste.Zazwyczaj nie są
zbyt skomplikowane jednakże wymagają sporo uwagi. Wiele
błędów w programach pochodzi od źle zdefiniowanych
destruktorów.
Point::~Point()
{
cout << "Point <";
Display();
cout << "> about to be destroyed..." << endl;
}
Point::~Point()
{
cout << "Point <";
Display();
cout << "> about to be destroyed..." << endl;
}
Warto czasami aby destruktor wyświetlał komunikat „pożegnalny”:
Programowanie C++
16
Użycie klasy Point
Oto prosty program do testowania klasy Point:
int main()
{
double x;
double y;
Point p;
const Point origin;
cout << "First point (initial coordinates): ";
p.Display();
cout << endl;
cout << "New coordinates (x and y), please: ";
cin >> x >> y;
p.Set(x, y);
cout << "First point (new coordinates): ";
p.Display();
cout << endl;
double d;
d = p.DistanceTo(origin);
cout << "First point distance to origin: " << d << endl;
...
}
int main()
{
double x;
double y;
Point p;
const Point origin;
cout << "First point (initial coordinates): ";
p.Display();
cout << endl;
cout << "New coordinates (x and y), please: ";
cin >> x >> y;
p.Set(x, y);
cout << "First point (new coordinates): ";
p.Display();
cout << endl;
double d;
d = p.DistanceTo(origin);
cout << "First point distance to origin: " << d << endl;
...
}
Deklaracja dwóch liczb
rzeczywistych i dwóch
punktów. Oba punkty są
początkowo <0, 0>.
Zmiana wartości punktu
p i wyświetlenie tych
wartości.
Punkt origin jest
stały: można
używać tylko
selektorów.
Obliczanie odległości p od
początku układu
współrzędnych
Funkcja main.
Podobnie jak wszystkie
inne zmienne zmienne
obiektowe również mogą
być deklarowane w
dowolnym miejscu.
Programowanie C++
17
Użycie klasy Point (cd)
...
cout << "Now moving first point" << endl;
cout << "Displacements (x and y), please: ";
double dx;
double dy;
cin >> dx >> dy;
p.Move(dx, dy);
cout << "First point (after having moved): ";
p.Display();
cout << endl;
cout << "Now giving coordinates for second point" << endl;
cout << "Coordinates (x and y) for second point, please: ";
cin >> x >> y;
Point q(x, y);
cout << "Second point (initial coordinates): ";
q.Display();
cout << endl;
cout << "Distance from second point to first: "
<< q.DistanceTo(p) << endl;
cout << "Distance from first point to second: "
<< p.DistanceTo(q) << " (of course it's the same)" << endl;
}
...
cout << "Now moving first point" << endl;
cout << "Displacements (x and y), please: ";
double dx;
double dy;
cin >> dx >> dy;
p.Move(dx, dy);
cout << "First point (after having moved): ";
p.Display();
cout << endl;
cout << "Now giving coordinates for second point" << endl;
cout << "Coordinates (x and y) for second point, please: ";
cin >> x >> y;
Point q(x, y);
cout << "Second point (initial coordinates): ";
q.Display();
cout << endl;
cout << "Distance from second point to first: "
<< q.DistanceTo(p) << endl;
cout << "Distance from first point to second: "
<< p.DistanceTo(q) << " (of course it's the same)" << endl;
}
Przesunięcie punktu
p
Deklaracja punktu q z
wykorzystaniem drugiego
konstruktora
Wyliczenie oraz wyświetlenie
odległości od q do p i
odwrotnie.
Trzy destruktory klasy Point są
tutaj wywoływane niejawnie, dla
punktów q, origin i p.
Zauważ, że nie można “wysłać”
punktu bezpośrednio do
strumienia cout:
cout << q << endl;
BŁĄD!
Programowanie C++
18
Modularyzacja
Nasz program jest zapisany w trzech
plikach:
Point.h
Point.h
Point.cpp
Point.cpp
M_Point.cp
M_Point.cp
Plik nagłówkowy dla klasy Point.
Zawiera deklarację klasy Point.
Plik źródłowy klasy Point. Zawiera
definicje wszystkich funkcji
zadeklarowanych w pliku Point.h.
Główny plik programu. Zawiera
funkcję main.
Pliki nagłówkowe mają rozszerzenie “.h”. Pliki źródłowe w C++ mają
rozszerzenie “.cpp” (lub “.cp”, lub “.cxx”, w zależności od platformy).
Pliki C mają rozszerzenie “.c”.
Nazwy plików są dowolne, jednakże powinno się zachować pewne reguły.
Będziemy zawsze nazywali pliki zgodnie z nazwą klasy, której dotyczą i
tak plik nagłówkowy dla klasy X będzie miał nazwę X.h a plik źródłowy
X.cpp.
Programowanie C++
19
Włączanie plików nagłówkowych
Plik źródłowy musi mieć dołączony plik nagłówkowy zawierający
wymagane deklaracje. Plik Point.cpp wymaga pliku Point.h:
#include "Point.h"
Point::Point():
x(0),
y(0)
{
}
...
#include "Point.h"
Point::Point():
x(0),
y(0)
{
}
...
Oczywiście plik Point.cpp wymaga również bibliotecznych plików
nagłówkowych
#include <math.h>
#include <iostream.h>
#include "Point.h"
Point::Point():
x(0),
y(0)
{
}
...
#include <math.h>
#include <iostream.h>
#include "Point.h"
Point::Point():
x(0),
y(0)
{
}
...
Standardowe pliki nagłówkowe pojawiają
się w nawiasach < i > . Pliki nagłówkowe
użytkownika umieszczone są w apostrofach.
Programowanie C++
20
Unikanie wielokrotnych
dyrektyw include
Jeśli plik A włącza pliki B i C, a plik B dołącza plik C, wówczas plik
A dołącza plik C dwukrotnie. Może to spowodować problemy. Aby
ich uniknąć używamy stałych symbolicznych oraz dyrektywy
#define . Plik Point.h ma wówczas postać:
#ifndef _H_Point
#define _H_Point
class Point {
private:
...
public:
...
};
#endif
#ifndef _H_Point
#define _H_Point
class Point {
private:
...
public:
...
};
#endif
Oznacza to: jeśli stała symboliczna
_H_Point nie jest jeszcze
zdefiniowana, wówczas definiujemy ją
i dołączmy resztę pliku. Jeśli jest już
zdefiniowana pomijamy resztę pliku
(aż do dyrektywy #endif).
Wszystkie nasze deklaracje klas powinny być wewnątrz zakresu
#ifndef-#define-#endif. Nazwy stałych symbolicznych to nazwy
klasy poprzedzone znakami _H_.
Programowanie C++
21
Organizacja programu
W najprostszym przypadku plik
źródłowy M_ będzie zawierał tylko
jedną funkcję main:
Znacznie lepiej jest zdefiniować
większą liczbę funkcji testowych i
w funkcji main wywoływać tylko tą,
która nas w danym momencie
interesuje:
int main()
{
Point p;
...
return 0;
}
int main()
{
Point p;
...
return 0;
}
void TestPoints();
void TestDoubleOutput();
int main()
{
TestDoubleOutput();
return 0;
}
void TestPoints()
{
...
}
void TestDoubleOutput()
{
...
}
void TestPoints();
void TestDoubleOutput();
int main()
{
TestDoubleOutput();
return 0;
}
void TestPoints()
{
...
}
void TestDoubleOutput()
{
...
}
Programowanie C++
22
Najczęstsze błędy
• Wywoływanie funkcji bez obiektu (przykład: Display(p)
zamiast p.Display()).
• Brak nazwy klasy w definicji funkcji.
• Próba dostępu do danych prywatnych spoza klasy.
• Próba zmiany wartości obiektu w selektorze.
• Brak dołączenia koniecznych plików nagłówkowych.
• Użycie nawiasów w deklaracji obiektu w przypadku użycia
konstruktora domyślnego.(przykład Point p(); zamiast Point
p;).
• Brak nawiasów w wywołaniu funkcji bezargumentowej.
• Brak modyfikatorów const.
• Przesyłanie parametrów typu klasy przez wartość.
• Średnik po nagłówku funkcji w jej definicji.
• Brak średnika na końcu deklaracji klasy.
Programowanie C++
23
Jeszcze jedna klasa Rational
(1)
class Rational {
private:
int num;
int den;
public:
Rational();
Rational(int num);
Rational(int num, int den);
Rational(const Rational& other);
~Rational();
int Numerator() const;
int Denominator() const;
void Set(int newNum, int newDen);
...
}
Trzy konstruktory mogą
być zastąpione jednym z
domyślnymi parametrami:
Konstruktor
kopiujący.
Dwa naturalne
selektory
Zwyczajowa funkcja
Set.
Rational(int num1=0, int den=1);
Rational(int num1=0, int den=1);
Programowanie C++
24
Konstruktory i destruktor
Rational::Rational():
num(0), den(1)
{ }
Rational::Rational(int num):
num(num), den(1)
{ }
Rational::Rational(int num, int den):
num(num), den(den)
{ }
Rational::Rational(const Rational& other):
num(other.num),
den(other.den)
{}
Rational::~Rational()
{}
Rational::Rational():
num(0), den(1)
{ }
Rational::Rational(int num):
num(num), den(1)
{ }
Rational::Rational(int num, int den):
num(num), den(den)
{ }
Rational::Rational(const Rational& other):
num(other.num),
den(other.den)
{}
Rational::~Rational()
{}
Każda klasa ma przynajmniej dwa
konstruktory: domyślny i kopiujący
class T{
T();
T(const T&);
};
Jeśli klasa nie zawiera konstruktora
kopiującego to jest on tworzony
automatycznie.
Automatyczne wywołanie
konstruktora kopiującego:
-inicjalizacja obiektu w deklaracji,
-przekazanie obiektu do funkcji
przez wartość,
-zwracanie wartości obiektu z
funkcji.
Klasa Point powinna
mieć konstruktor
kopiujący tej postaci.
Nie ma nic do
zrobienia w
destruktorze.
Rational::Rational(int num, int den):
num(num), den(den)
{ }
Rational::Rational(int num, int den):
num(num), den(den)
{ }
Programowanie C++
25
Funkcje składowe
int Rational::Numerator() const
{ return num; }
int Rational::Denominator() const
{ return den; }
void Rational::Display() const
{ cout<<num<<'/'<<den; }
void Rational::Set(int newNum, int newDen)
{ num=newNum; den=newDen;}
int Rational::Gcd(int j, int k)
{
if (k==0) return j;
return Gcd(k,j%k);
}
void Rational::Reduce()
{
int g=Gcd(num,den);
num/=g;
den/=g;
}
int Rational::Numerator() const
{ return num; }
int Rational::Denominator() const
{ return den; }
void Rational::Display() const
{ cout<<num<<'/'<<den; }
void Rational::Set(int newNum, int newDen)
{ num=newNum; den=newDen;}
int Rational::Gcd(int j, int k)
{
if (k==0) return j;
return Gcd(k,j%k);
}
void Rational::Reduce()
{
int g=Gcd(num,den);
num/=g;
den/=g;
}
class Rational{
private:
...
public:
...
private:
int Gcd(int j, int k);
int Reduce();
};
class Rational{
private:
...
public:
...
private:
int Gcd(int j, int k);
int Reduce();
};
Funkcje składowe klasy zwykle są
deklarowane jako public;
Funkcje używane jedynie wewnątrz
klasy mogą być deklarowane jako
private, są to zwykle tzw. funkcje
pomocnicze.
Programowanie C++
26
class Rational{
private:
...
public:
...
Rational& operator = (const Rational &);// operator przypisania
Rational& operator *=(const Rational&);
Rational operator++(); //preinkrementacja
Rational operator++(int); //postinkrementacja
operator float() const; // operator konwersji
int& operator[](int); //operator indeksu
...
};
class Rational{
private:
...
public:
...
Rational& operator = (const Rational &);// operator przypisania
Rational& operator *=(const Rational&);
Rational operator++(); //preinkrementacja
Rational operator++(int); //postinkrementacja
operator float() const; // operator konwersji
int& operator[](int); //operator indeksu
...
};
Większość operatorów dostępnych w C++ może być przeładowana.
Wszystkie operatory arytmetyczne zarówno jedno jak i dwuargumentowe
mogą być przeładowane, jednakże z zachowaniem ich argumentowości.
Nie można poprzez przeładowanie zmienić priorytetu lub łączności
operatorów.
Nie można również definiować własnych operatorów.
Klasa Rational
(2)
Programowanie C++
27
Definicje operatorów
Rational& Rational::operator=(const Rational& other)
{
num=other.num;
den=other.den;
return *this;
}
Rational& Rational::operator=(const Rational& other)
{
num=other.num;
den=other.den;
return *this;
}
Operator przypisania zwraca referencję do
obiektu, aby umożliwić przypisanie
łańcuchowe.
Inicjalizacja i przypisanie to dwie różne operacje:
Rational x(22,3) // inicjalizacja
Rational y(x);
// inicjalizacja – konstruktor
kopiujący
Rational z = x; //inicjalizacja !!! – konstruktor
kopiujący
Rational w;
w = x;
//przypisanie
Rational& Rational::operator*=(const Rational& other)
{
num*=other.num;
den*=other.den;
Reduce();
return *this;
}
Rational& Rational::operator*=(const Rational& other)
{
num*=other.num;
den*=other.den;
Reduce();
return *this;
}
Wywołanie funkcji składowej, na rzecz
tego samego obiektu
Programowanie C++
28
Klasa Rational
(3)
class Rational{
friend Rational operator*(const Rational&, const Rational&)
friend int operator==(const Rational&, const Rational&)
private:
...
public:
...
};
class Rational{
friend Rational operator*(const Rational&, const Rational&)
friend int operator==(const Rational&, const Rational&)
private:
...
public:
...
};
Operatory w C++ nie muszą być funkcjami składowymi klasy. Zwykłe
operatory arytmetyczne oraz operatory relacji zwykle są definiowane
jako funkcje zaprzyjaźnione
Rational operator*(const Rational& x, const Rational& y)
{
Rational z(x.num*y.num, x.den*y.den);
z.Reduce();
return z;
}
int operator ==(const Rational& x, const Rational& y)
{
return (x.num*y.den==x.den*y.num);
}
Rational operator*(const Rational& x, const Rational& y)
{
Rational z(x.num*y.num, x.den*y.den);
z.Reduce();
return z;
}
int operator ==(const Rational& x, const Rational& y)
{
return (x.num*y.den==x.den*y.num);
}
Definicja funkcji zaprzyjaźnionej:
-brak słowa friend,
-brak nazwy klasy z operatorem
zakresu,
-brak wskaźnika this
Programowanie C++
29
Definicje operatorów c.d.
Rational Rational::operator++()
{
num+=den;
return *this;
}
Rational Rational::operator++(int)
{
Rational temp=*this;
num+=den;
return temp;
}
Rational::operator float() const
{
return float(num)/den;
}
int& Rational::operator[](int i)
{
if (i==1) return num;
if (i==0) return den;
cerr<<"ERROR:index out of range";
exit(0);
}
Rational Rational::operator++()
{
num+=den;
return *this;
}
Rational Rational::operator++(int)
{
Rational temp=*this;
num+=den;
return temp;
}
Rational::operator float() const
{
return float(num)/den;
}
int& Rational::operator[](int i)
{
if (i==1) return num;
if (i==0) return den;
cerr<<"ERROR:index out of range";
exit(0);
}
3
5
3
3
2
1
3
2
Deklaracje operatorów inkrementacji różnią
się tylko parametrem, który w
rzeczywistości nie jest przekazywany w
wywołaniu funkcji dlatego może być
nienazwany.
Pełni on rolę niemego parametru
rozróżniającego operator pre- i post-
Funkcja operatora konwersji (rzutowania)
nie określa zwracanego typu – jest nim typ
konwersji.
Konwersja typu int na typ wbudowany
dokonuje się poprzez konstruktor:
x=Rational(22)
Operator i konstruktor konwersji są
wywoływane automatycznie jeśli to
konieczne.
Programowanie C++
30
Operatory arytmetyczne jako
funkcje składowe vs funkcje
zaprzyjaźnione
class Rational{
friend Rational operator*(const Rational&, const Rational&)
public:
...
Rational operator/(const Rational&, const Rational&)
};
class Rational{
friend Rational operator*(const Rational&, const Rational&)
public:
...
Rational operator/(const Rational&, const Rational&)
};
Operatory ( ), [ ], -> oraz operatory przypisania muszą być
zadeklarowane jako funkcje składowe klasy.
Pozostałe operatory mogą być funkcjami nie należącymi do klasy.
Jeśli nie-składowa funkcja musi działać na danych prywatnych, powinna
być zadeklarowana jako friend.
Operatory niezależnie od tego czy są funkcjami zaprzyjaźnionymi czy
funkcjami składowymi są wywoływane w taki sam sposób.
Jeśli operator jest funkcją składową klasy najbardziej lewostronny
operand musi być obiektem bądź referencją do obiektu danej klasy.
Jeśli lewostronny operand musi być obiektem innej klasy bądź typem
wbudowanym wówczas funkcja operatorowa musi być funkcją nie
należącą do klasy.
Jeśli operator ma być funkcją przemienną wówczas nie powinien być
funkcją składową klasy.
Programowanie C++
31
Operatory << i >>
class Rational{
friend ostream& operator<<(ostream&, const Rational&)
friend istream& operator>>(istream&, Rational&)
...
};
class Rational{
friend ostream& operator<<(ostream&, const Rational&)
friend istream& operator>>(istream&, Rational&)
...
};
Operatory wstawiania do strumienia wyjściowego i pobierania ze
strumienia wejściowego wymagają lewostronnego operandu
odpowiednio ostream& i istream& dlatego nie mogą być funkcjami
składowymi klasy.
ostream& operator<<(ostream& output, const Rational& y)
{
output<<y.num<<'/'<<y.den;
return output;
}
istream& operator>>(istream& input, Rational& y)
{
input>>y.num;
input.ignore();
input>>y.den;
y.Reduce();
return input;
}
ostream& operator<<(ostream& output, const Rational& y)
{
output<<y.num<<'/'<<y.den;
return output;
}
istream& operator>>(istream& input, Rational& y)
{
input>>y.num;
input.ignore();
input>>y.den;
y.Reduce();
return input;
}
Programowanie C++
32
Testujemy operatory klasy
#include "rational.h"
main()
{
Rational u1, u2(2,2);
Rational u3(u2);
cout<<"u1 = "<<u1<<" u2 = "<<u2<<" u3 =
"<<u3<<endl;
cout<<"Podaj ulamek postaci a/b : ";
cin>>u1;
cout<<u1<<" * "<<u2<<" = "<<u1*u2<<endl;
cout<<"Wartość rzeczywista u1 = "<<float(u1)<<endl;
cout<<"Operator indeksu:"<<endl;
cout<<u1[1]<<" "<<u1[2]<<endl;
cout<<"u2++ "<<u2++<<endl;
cout<<"++u2 "<<++u2;
}
#include "rational.h"
main()
{
Rational u1, u2(2,2);
Rational u3(u2);
cout<<"u1 = "<<u1<<" u2 = "<<u2<<" u3 =
"<<u3<<endl;
cout<<"Podaj ulamek postaci a/b : ";
cin>>u1;
cout<<u1<<" * "<<u2<<" = "<<u1*u2<<endl;
cout<<"Wartość rzeczywista u1 = "<<float(u1)<<endl;
cout<<"Operator indeksu:"<<endl;
cout<<u1[1]<<" "<<u1[2]<<endl;
cout<<"u2++ "<<u2++<<endl;
cout<<"++u2 "<<++u2;
}
u1=0/1 u2=2/2 u3=2/2
Podaj ulamek postaci
a/b : ¾
3/4 * 2/2 = 3/4
Wartość rzeczywista u1 =
0.75
Operator indeksu:
3 4
u2++ 2/2
++u2 6/2
u1=0/1 u2=2/2 u3=2/2
Podaj ulamek postaci
a/b : ¾
3/4 * 2/2 = 3/4
Wartość rzeczywista u1 =
0.75
Operator indeksu:
3 4
u2++ 2/2
++u2 6/2
Programowanie C++
33
Kompozycja
#include "string.h"
#include "date.h"
class Person {
private:
String name,nationality;
int sex; //0 – female, 1 - male
Date birthday;
public:
Person(char* n="",char* nat="Polish",int s=1);
~Person();
String Name() const;
String Nationality() const;
int Sex() const;
Date Birthday() const;
void SetBirthday(int , int, int );
void Print()const;
};
Plik person.h
Dane składowe klasy
są obiektami innych
klas
void Person::SetBirthday(int d, int m, int y)
{
birthday.SetDate(d,m,y);
}
void Person::SetBirthday(int d, int m, int y)
{
birthday.SetDate(d,m,y);
}
Funkcja SetDate z klasy Date
wykorzystana w definicji
funkcji
SetBirthday klasy Person
Programowanie C++
34
Kompozycja (c.d)
#include "person.h"
main()
{
Person JK("Jan Kowalski");
JK.Print();
cout<<endl<<JK.Name()<<" is "<<JK.Nationality()<<endl;
JK.SetBirthday(1,1,2000);
cout<<"and he was born "<<JK.Birthday()<<endl;
}
#include "person.h"
main()
{
Person JK("Jan Kowalski");
JK.Print();
cout<<endl<<JK.Name()<<" is "<<JK.Nationality()<<endl;
JK.SetBirthday(1,1,2000);
cout<<"and he was born "<<JK.Birthday()<<endl;
}
Plik mperson.cpp
ostream& operator <<(ostream& ostr, const Date& d)
{
static char* monthName[13]={
"","January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
ostr<<d.day<<' '<<monthName[d.month]<<' '<<d.year;
return ostr;
}
ostream& operator <<(ostream& ostr, const Date& d)
{
static char* monthName[13]={
"","January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
ostr<<d.day<<' '<<monthName[d.month]<<' '<<d.year;
return ostr;
}
Plik date.cpp
Taki zapis jest możliwy
ponieważ w klasie String i
w klasie Date jest
przeładowany operator <<
My name is Jan Kowalski
Jan Kowalski is Polish
and he was born 1
January 2000
My name is Jan Kowalski
Jan Kowalski is Polish
and he was born 1
January 2000
Programowanie C++
35
Dziedziczenie
#include "person.h"
class Student: public Person {
private:
String id;
Date matriculation;
int credits;
public:
Student(char* n="", char* i="", int s=0);
~Student();
void SetDmat(int,int,int);
};
Plik student.h
Klasa Student jest pochodną od
klasy bazowej Person i
automatycznie dziedziczy wszystkie
składniki klasy bazowej
Nie dziedziczy się: konstruktorów, destruktora oraz operatora
przypisania.
Ponadto klasa pochodna może zawierać deklaracje nowych
składników lub zmieniać znaczenie operacji odziedziczonych.
Nowe operacje są niedostępne dla obiektów klasy bazowej.
Programowanie C++
36
Dziedziczenie (c.d)
#include "student.h"
main()
{
Student x("Ann Jones","23456789");
x.SetBirthday(13,5,1981);
x.SetDmat(30,9,2000);
x.Print();
cout<<"\n\t Born: "<<x.BirthDay();
cout<<"\n\t Matriculated: "<<x.Matriculation();
}
My name is Ann Jones
Born: 13 May 1981
Matriculated: 30
September 2000
My name is Ann Jones
Born: 13 May 1981
Matriculated: 30
September 2000
Funkcje składowe klasy Student
Funkcje składowe klasy bazowej
Person
Plik mstudent.cpp
Student::Student(char* n, char* i, int s):
Person(n,s), id(i), credits(0)
{}
Student::~Student()
{}
void Student::SetDmat(int d, int m, int y)
{
birthday.SetDate(d,m,y);
}
Student::Student(char* n, char* i, int s):
Person(n,s), id(i), credits(0)
{}
Student::~Student()
{}
void Student::SetDmat(int d, int m, int y)
{
birthday.SetDate(d,m,y);
}
Użycie konstruktora klasy bazowej
Person
do danych prywatnych nie ma
dostępu bezpośredniego; tylko
poprzez funkcje publiczne
Automatyczne wywołanie
destruktora
klasy bazowej Person
Programowanie C++
37
Składowe protected
Klasa pochodna nie ma bezpośredniego dostępu do danych
składowych zadeklarowanych jako
private
. Istnieje możliwość
udostępnienia danych składowych klasom pochodnym przy
jednoczesnym ograniczeniu dostępu z zewnątrz, poprzez
deklarację
protected
class Person {
protected:
String name,nationality;
int sex; //0 – female, 1 - male
Date birthday;
public:
...
};
W ten sposób dane będą dostępne dla wszystkich
potencjalnych klas pochodnych, natomiast spoza
klas będą
traktowane jak prywatne.
Klasa pochodna ma również możliwość
decydowania
O sposobie dostępu do składowych (tylko
protected
i public, bo private i tak nie są dla niej
dostępne)
class Student: public Person {
...
};
class Student: protected Person {
...
};
class Student: private Person {
...
};
protected protected
public public
protected protected
public protected
protected private
public private
Programowanie C++
38
Zagnieżdżanie zakresów
#include "person.h"
class Student: public Person {
...
public:
void Print()const;
};
#include "person.h"
class Student: public Person {
...
public:
void Print()const;
};
void Student::Print()const
{
Person::Print();
cout<<" and I am a student ";
};
void Student::Print()const
{
Person::Print();
cout<<" and I am a student ";
};
Jeśli w klasie pochodnej zadeklarowana jest funkcja bądź
składowa o nazwie takiej samej jak w klasie bazowej wówczas
następuje zasłanianie – w taki sam sposób jak w zagnieżdżaniu
zakresów. Dostęp do składnika zasłoniętego jest możliwy poprzez
operator zakresu.
#include "student.h"
main()
{
...
cout<<„Student speaking: "<<endl;
x.Print();
cout<<"\nPerson speaking: "<<endl;
x.Person::Print();
}
Student speaking:
My name is Ann Jones and I am a
student
Person speaking:
My name is Ann Jones
Student speaking:
My name is Ann Jones and I am a
student
Person speaking:
My name is Ann Jones
Programowanie C++
39
Konstruktory i destruktory
W hierarchii dziedziczenia każdy konstruktor zanim się „wykona”
wywołuje konstruktor z klasy bazowej, natomiast destruktor
wywołuje swojego przodka po wykonaniu własnej pracy.
class X {
public:
X(){cout<<"X::X() constructor \n";}
~X(){cout<<"X::X() destructor \n";}
};
class Y:public X {
public:
Y(){cout<<"Y::Y() constructor \n";}
~Y(){cout<<"Y::Y() destructor \n";}
};
class Z:public Y {
public:
Z(int n){cout<<"Z::Z(n) constructor \n";}
~Z(){cout<<"Z::Z() destructor \n";}
};
main()
{
Z z(44)
}
class X {
public:
X(){cout<<"X::X() constructor \n";}
~X(){cout<<"X::X() destructor \n";}
};
class Y:public X {
public:
Y(){cout<<"Y::Y() constructor \n";}
~Y(){cout<<"Y::Y() destructor \n";}
};
class Z:public Y {
public:
Z(int n){cout<<"Z::Z(n) constructor \n";}
~Z(){cout<<"Z::Z() destructor \n";}
};
main()
{
Z z(44)
}
X::X() constructor
Y::Y() constructor
Z::Z(n) constructor
Z::Z() destructor
Y::Y() destructor
X::X() destructor
X::X() constructor
Y::Y() constructor
Z::Z(n) constructor
Z::Z() destructor
Y::Y() destructor
X::X() destructor
Programowanie C++
40
Funkcje wirtualne (1)
main()
{
cout<<"Zmienne: \n";
Student x("Ann Jones");
x.Print();
cout<<endl;
Person y("John Smith");
y.Print();
cout<<„\nWskaźnik: \n";
Person* z=&y;
z->Print();
z=&x;
z->Print();
}
Funkcje wirtualne gwarantują tzw.
dynamiczne wiązanie czyli wywołanie
funkcji jest odłożone do czasu realizacji
programu i jest zależne od obiektu,
na rzecz którego dana funkcja jest
wywoływana.
Zmienne:
My name is Ann Jones and I am a student
My name is John Smith
Wskaźnik:
My name is John Smith
My name is Ann Jones
class Person {
...
virtual void Print ( ) const;
...
};
Wiązanie statyczne, zachodzące na etapie
kompilacji
Zmienne:
My name is Ann Jones and I am a student
My name is John Smith
Wskaźnik:
My name is John Smith
My name is Ann Jones and I am a student
Programowanie C++
41
Funkcje wirtualne(2)
class X{
public:
void f() {cout<<"X::f() executing \n";}
};
class Y: public X{
public:
void f(){cout<<"Y::f() executing \n";}
};
main()
{
X x;
Y y;
X* p=&x;
p->f();
p=&y;
p->f();
}
X::f() executing
X::f() executing
class X{
public:
virtual void f() {cout<<"X::f() executing \n";}
};
class Y: public X{
public:
void f(){cout<<"Y::f() executing \n";}
};
main()
{
X x;
Y y;
X* p=&x;
p->f();
p=&y;
p->f();
}
X::f() executing
Y::f() executing
Polimorfizm jest możliwy ponieważ wskaźnik klasy bazowej może wskazywać
na obiekt dowolnej klasy pochodnej.
Programowanie C++
42
Polimorfizm
Polimorfizm - pozwala by obiekty różnych typów powodowały
różne działanie w wywołaniu tej samej funkcji. Jest to możliwe
dzięki temu, że wskaźnik do obiektu klasy bazowej może
również pokazywać na obiekty klas pochodnych.
• Aby uzyskać wiązanie dynamiczne parametry muszą być przekazywane
przez wskaźnik bądź referencję
• Słowo virtual pojawia się tylko w deklaracji funkcji w klasie
podstawowej, nie musi pojawić w klasie pochodnej
• Jeśli klasa bazowa deklaruje funkcję wirtualną to musi zawierać jej
definicję nawet jeśli ciało funkcji jest puste
• Klasa pochodna nie musi definiować ponownie funkcji wirtualnej,
wówczas domyślnie wywoływana jest funkcja z klasy bazowej
• Klasa pochodna we własnej definicji nie może zmieniać typu
zwracanego przez funkcję wirtualną
Programowanie C++
43
Wirtualne destruktory
Destruktory definiujemy jako funkcje wirtualne!
class X {
private:
int* p;
public:
X() {p = new int[2]; cout<<” X(). ”;}
~X() { delete [] p; cout<<”~X (). \n”;}
};
class Y: public X{
private:
int* q;
public:
Y() {q = new int[100]; cout<<”Y() : Y::q = ”<<q<<”. ”;}
~Y() { delete [] q; cout<<”~Y() ”;}
};
main ( )
{ for (int i = 0; i<3; i++) {
X* r = new Y;
delete r;
}
X (). Y() : Y::q = 0x0d18. ~X().
X (). Y() : Y::q = 0x0de4. ~X().
X (). Y() : Y::q = 0x0eb0. ~X().
virtual ~X() { delete [] p; cout<<”~X (). \n”;}
X (). Y() : Y::q = 0x0d24. ~Y() ~X().
X (). Y() : Y::q = 0x0d24. ~Y() ~X().
X (). Y() : Y::q = 0x0d24. ~Y() ~X().
Programowanie C++
44
Czyste funkcje wirtualne i klasy
abstrakcyjne
Med
ia
Audio
Book
Periodical
CD
Magazine
Newspaper Journal
Tape
Record
Czysta funkcja wirtualna - funkcja nie mająca w swojej
klasie implementacji.
virtual void f( ) = 0;
Abstrakcyjna klasa bazowa - klasa posiadająca jedną lub więcej
czystych funkcji wirtualnych.
class Media{
protected:
String title;
public:
virtual void print( ) = 0;
virtual char* id( ) = 0;
};
class Book: public Media{
private:
String author, publisher,isbn;
public:
void print ( )
{ cout<<title<<" by "<<author;}
void id ( ) { cout<<isbn; }
};
Programowanie C++
45
Dziedziczenie - abstrakcyjna
klasa bazowa
//Definition of abstract base class SHAPE
// plik shape.h
#ifndef _H_Shape
#define _H_Shape
#include <iostream.h>
class Shape{
public:
virtual double Area() const {return 0.0;}
virtual double Volume() const {return 0.0;}
// pure virtual function overridden in derived classes
virtual void PrintShapeName() const = 0;
virtual void Print() const = 0;
};
#endif
Funkcje wirtualne wraz z domyślną implementacją;
Klasy pochodne nie definiujące własnych wersji
tych funkcji odziedziczą implementację.
Czyste funkcje wirtualne;
Jeśli w klasie pochodnejnie znajdą się implementacje
tych funkcji, to klasy pochodne będą również klasami
abstrakcyjnymi.
Programowanie C++
46
Pochodna klasy bazowej Shape
// pochodna klasy bazowej Shape
//plik point1.h
#ifndef _H_Point1
#define _H_Point1
#include "shape.h"
class Point:public Shape{
private:
int x,y;
public:
Point( int =0,int=0); //default constructor
void SetPoint(int a,int b);
int X() const {return x;}
int Y() const {return y;}
virtual void PrintShapeName() const;
virtual void Print() const;
};
#endif
Dziedziczenie publiczne
Dane prywatne
i dodatkowe funkcje
“wzbogacające” klasę Point w stosunku do klasy
Shape
//plik point1.cpp
#include <iostream.h>
#include "point1.h"
Point::Point(int x,int y):
x(x),y(y)
{}
void Point::SetPoint(int a, int b)
{
x=a;
y=b;
}
void Point::PrintShapeName() const
{
cout<<"Point: ";
}
void Point::Print() const
{
cout<<"[ "<<x<<','<<y<<" ]";
}
Programowanie C++
47
Klasa pochodna od klasy Point
//plik circle.h
#ifndef _H_Circle
#define _H_Circle
#include "point1.h"
class Circle: public Point{
private:
double radius;
public:
Circle(double r =0.0, int x=0, int y=0);
void SetRadius(double );
double Radius() const;
virtual double Area() const;
virtual void PrintShapeName() const;
virtual void Print() const;
};
#endif
Jawne odwołanie do funkcji
z klasy bazowej
//plik circle.cpp
#include "circle.h"
Circle::Circle(double r, int a, int b):
Point(a,b), radius(r)
{}
void Circle::SetRadius(double r)
{ radius = r > 0 ? r : 0; }
double Circle::Radius() const
{ return radius;}
double Circle::Area() const
{
return 3.14159*radius*radius;
}
void Circle::PrintShapeName() const
{
cout<<"Circle : ";
}
void Circle::Print() const
{
Point::Print();
cout<<"; Radius = "<<radius;
}
Programowanie C++
48
Jeszcze jeden poziom
dziedziczenia
// plik cylinder.h
#ifndef _H_Cylinder
#define _H_Cylinder
#include "circle.h"
class Cylinder:public Circle{
private:
double height;
public:
Cylinder(double h=0.0, double r=0.0, int x=0, int y=0);
void SetHeight(double);
double Height() const;
virtual double Area() const;
virtual double Volume() const;
virtual void PrintShapeName() const;
virtual void Print() const;
};
#endif
//plik cylinder.cpp
#include "cylinder.h"
Cylinder::Cylinder(double h, double r, int x,
int y):
Circle(r,x,y), height(h)
{}
void Cylinder::SetHeight(double h)
{ height=h>0 ? h : 0; }
double Cylinder::Height() const
{ return height; }
double Cylinder::Area() const
{ return 2*Circle::Area()
+2*3.14159*Radius()*height; }
double Cylinder::Volume() const
{ return Circle::Area()*height;}
void Cylinder::PrintShapeName() const
{
cout<<"Cylinder : ";
}
void Cylinder::Print() const
{
Circle::Print();
cout<<"; Height = "<<height;
}
Programowanie C++
49
Jak to wszystko działa?
// plik mshape.cpp
#include <iostream.h>
#include <iomanip.h>
#include <conio.h>
#include "shape.h"
#include "point1.h"
#include "circle.h"
#include "cylinder.h"
void VirtualViaPointer(const Shape*);
void VirtualViaReference(const Shape&);
main()
{
clrscr();
cout<<setiosflags(ios::fixed|
ios::showpoint)<<setprecision(2);
Point p(7,11);
p.PrintShapeName();
p.Print();
cout<<endl;
...
...
Circle c(3.5,22,8);
c.PrintShapeName();
c.Print();
cout<<endl;
Cylinder cyl(10,3.3,10,10);
cyl.PrintShapeName();
cyl.Print();
cout<<endl<<endl;
...
Point: [ 7,11 ]
Circle : [ 22,8 ]; Radius = 3.50
Cylinder : [ 10,10 ]; Radius = 3.30; Height = 10.00
Programowanie C++
50
Funkcje wirtualne w akcji
...
Shape* arrayOfShapes[3];
arrayOfShapes[0]=&p;
arrayOfShapes[1]=&c;
arrayOfShapes[2]=&cyl;
cout<<" Virtual calls via base-class pointers\n";
for(int i=0; i<3; i++)
VirtualViaPointer(arrayOfShapes[i]);
cout<<endl;
...
void VirtualViaPointer(const Shape* baseClassPtr)
{
baseClassPtr->PrintShapeName();
baseClassPtr->Print();
cout<<"\nArea = "<<baseClassPtr->Area()
<<"\nVolume = "<<baseClassPtr->Volume()<<endl;
}
Virtual calls via base-class pointers
Point: [ 7,11 ]
Area = 0.00
Volume = 0.0
Circle : [ 22,8 ]; Radius = 3.50
Area = 38.48
Volume = 0.0
Cylinder : [ 10,10 ]; Radius = 3.30; Height = 10.00
Area = 275.77
Volume = 342.12
Programowanie C++
51
Funkcje wirtualne w akcji c.d.
...
cout<<" Virtual calls via base-class references\n";
for( i=0; i<3; i++)
VirtualViaReference(*arrayOfShapes[i]);
cout<<endl;
return 0;
}
void VirtualViaReference(const Shape& baseClassRef)
{
baseClassRef.PrintShapeName();
baseClassRef.Print();
cout<<"\nArea = "<<baseClassRef.Area()
<<"\nVolume = "<<baseClassRef.Volume()<<endl;
}
Virtual calls via base-class pointers
Point: [ 7,11 ]
Area = 0.00
Volume = 0.0
Circle : [ 22,8 ]; Radius = 3.50
Area = 38.48
Volume = 0.0
Cylinder : [ 10,10 ]; Radius = 3.30; Height = 10.00
Area = 275.77
Volume = 342.12