background image

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

background image

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

background image

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.

background image

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.

background image

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ą.  

background image

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.

background image

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.

background image

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ę

background image

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;
  ...
};

background image

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

 

background image

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)  

background image

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:

 

background image

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.

background image

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ą. 

background image

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”:

background image

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 

i wyświetlenie tych 

wartości.

Punkt origin jest 

stały: można 

używać tylko 

selektorów.

Obliczanie odległości 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. 

background image

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 

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!

background image

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.

background image

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.

background image

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_.

background image

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()

{

  ...

}

background image

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.

background image

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);

background image

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)
{ }

background image

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.

background image

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)

background image

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

background image

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

background image

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.

background image

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.

background image

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& 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;
}

background image

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

background image

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

background image

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

background image

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.

background image

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

background image

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 
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

background image

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

background image

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 

background image

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

background image

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.

background image

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ą

background image

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().

background image

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; }
};
     

background image

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.

background image

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<<" ]";
}

background image

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;
}

background image

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;
}

background image

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

background image

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

background image

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


Document Outline