Eine Klassenhierarchie geometrischer Figuren

1. Aufgabe

Implementieren Sie eine Hierarchie mit Klassen, die zwei- und dreidimensionale geometrische Figuren repräsentieren. Bei den zweidimensionalen Figuren sollten ein Dreieck (Klasse Triangle), ein Viereck (Klasse Rectangle) und ein Kreis (Klasse Circle) nicht fehlen. Zu den dreidimensionalen Figuren gehört ein Würfel (Klasse Cube), eine Kugel (Klasse Sphere), ein Kegel (Klasse Cone) sowie ein Quader (Klasse Cuboid).

Überlegen Sie, welche Instanzvariablen zur Beschreibung der jeweiligen geometrischen Figur notwendig sind. Neben offensichtlichen Instanzvariablen wie der Radius eines Kreises oder die Seitenlänge eines Würfels soll jede Figur eine Positionsangabe besitzen, also einen x- und y-Wert für eine zweidimensionale Figur bzw. ein Tripel (x,y,z) bei einer dreidimensionalen Figur. Welcher Klasse in der Klassenhierarchie sind diese Positionsvariablen am besten zuzuordnen?

Konzipieren Sie für jede Klasse einen geeigneten Konstruktor. Ziehen Sie, soweit möglich, auch den Basisklassenkonstruktor bei der Erzeugung eines Objekts mit ein.

Klassenhierarchie geometrischer Figuren.

Abbildung 1. Klassenhierarchie geometrischer Figuren.


Überlegen Sie, welchen Klassen Sie die Methoden Circumference (Umfang), Volume (Volumen), Area (Fläche) und Surface (Oberfläche) zuordnen können. Natürlich sollten alle Klassen den Ausgabeoperator << unterstützen und eine Methode MoveTo besitzen. Die Programmausgabe studieren Sie am folgenden Codefragment:

Beispiel:

#include "Shape.h"

void main ()
{
    Triangle tria (1, 1, 2, 3, 4);
    cout << tria << endl;

    Rectangle rect (10, 10, 20, 40);
    cout << rect << endl;

    Circle circle (20, 20, 10);
    cout << circle << endl;

    Cube cube (30, 30, 20, 10);
    cout << cube << endl;

    Cuboid cuboid (50, 50, 40, 10, 20, 30);
    cout << cuboid << endl;

    Sphere sphere (40, 40, 30, 15);
    cout << sphere << endl;

    Cone cone (60, 60, 50, 20, 30);
    cout << cone << endl;
}

Ausgabe:

Shape: Triangle
  TwoDimensional
  Position: 1.00, 1.00
    A: 2.00
    B: 3.00
    C: 4.00
    Area: 2.90
    Circumference: 9.00

Shape: Rectangle
  TwoDimensional
  Position: 10.00, 10.00
    Width: 20.00
    Height: 40.00
    Area: 800.00
    Circumference: 120.00

Shape: Circle
  TwoDimensional
  Position: 20.00, 20.00
    Radius: 10.00
    Area: 314.16
    Circumference: 62.83

Shape: Cube
  ThreeDimensional
  Position: 30.00, 30.00, 20.00
    Size: 10.00
    Volume: 1000.00
    Surface: 600.00

Shape: Cuboid
  ThreeDimensional
  Position: 50.00, 50.00, 40.00
    Width: 10.00
    Height: 20.00
    Depth: 30.00
    Volume: 6000.00
    Surface: 2200.00

Shape: Sphere
  ThreeDimensional
  Position: 40.00, 40.00, 30.00
    Radius: 15.00
    Volume: 14137.17
    Surface: 2827.43

Shape: Cone
  ThreeDimensional
  Position: 60.00, 60.00, 50.00
    Radius:  20.00
    Height:  30.00
    Volume:  12566.37
    Surface: 3522.07

2. Lösung

Quellcode: Siehe auch github.com/peterloos/Cpp_Shapes.git.

In den nachfolgenden Listings finden Sie der Reihe nach

  • den Entwurf der abstrakten Basisklasse Shape,

  • zwei Spezialisierungen für zwei- und dreidimensionale Figuren in Gestalt der Klassen TwoDimensional bzw. ThreeDimensional,

  • die Realisierungen der konkreten Klassen Triangle, Rectangle und Circle für zweidimensionale Figuren und schließlich

  • alle Realisierungen der konkreten Klassen Cube, Cuboid, Sphere und Cone für dreidimensionale Figuren

vor.

01: #include <iostream>
02: using namespace std;
03: 
04: class Shape
05: {
06: private:
07:     char* m_name;
08: 
09: protected:
10:     // c'tor / d'tor
11:     Shape (char* name);
12: 
13: public:
14:     virtual ~Shape() = 0;
15: 
16:     // output
17:     friend ostream& operator<< (ostream&, const Shape&);
18: };
19: 
20: Shape::Shape (char* name)
21: {
22:     int len = strlen (name);
23:     m_name = new char [len + 1];
24:     strncpy_s (m_name, len + 1, name, len + 1);
25: }
26: 
27: Shape::~Shape ()
28: {
29:     delete[] m_name;
30: }
31: 
32: ostream& operator<< (ostream& os, const Shape& s)
33: {
34:     os << "Shape: " << s.m_name;
35:     return os;
36: }

Beispiel 1. Klasse Shape


In Listing 1 finden Sie eine kleine Kuriosität vor: Der virtuelle Basisklassendestruktor in Zeile 14 ist pure virtuell definiert, um damit zu erreichen, dass die Klasse Shape abstrakt ist. In Zeile 27 wiederum finden Sie eine Implementierung des Destruktors vor. Ist das ein Widerspruch zu Zeile 14? Normalerweise ja, denn pure virtuelle Methodendefinitionen dienen ja gerade dem Zweck, die Schnittstelle einer Methode ohne Implementierung vorzugeben. Im Falle des Basisklassendestruktors gibt es eine Ausnahme von dieser Regel. Seine Implementierung muss vorhanden sein, so dass alle Spezialisierungen von abgeleiteten Objekten bei ihrer Freigabe den Destruktor der Basisklasse erreichen – und eben dort auch eine Implementierung vorfinden.

01: class TwoDimensional : public Shape
02: {
03: protected:
04:     double m_x;
05:     double m_y;
06: 
07: public:
08:     // c'tor
09:     TwoDimensional (char* name, double x, double y);
10: 
11:     // public interface
12:     void MoveTo (double x, double y);
13: 
14:     // contract for derived classes
15:     virtual double Area () const = 0;
16:     virtual double Circumference () const = 0; 
17: 
18:     // output
19:     friend ostream& operator<< (ostream&, const TwoDimensional&);
20: };
21: 
22: class ThreeDimensional : public Shape
23: {
24: protected:
25:     double m_x;
26:     double m_y;
27:     double m_z;
28: 
29: public:
30:     // c'tor
31:     ThreeDimensional (char* name, double x, double y, double z);
32: 
33:     // public interface
34:     void MoveTo (double x, double y, double z);
35: 
36:     // contract for derived classes
37:     virtual double Volume () const = 0;
38:     virtual double Surface () const = 0;
39: 
40:     // output
41:     friend ostream& operator<< (ostream&, const ThreeDimensional&);
42: };
43: 
44: TwoDimensional::TwoDimensional (char* name, double x, double y)
45:     : Shape (name)
46: {
47:     m_x = x;
48:     m_y = y;
49: }
50: 
51: void TwoDimensional::MoveTo (double x, double y)
52: {
53:     m_x = x;
54:     m_y = y;
55: }
56: 
57: ostream& operator<< (ostream& os, const TwoDimensional& td)
58: {
59:     os << (Shape&) td << endl;
60:     os << "  TwoDimensional" << endl;
61:     os << "  Position: " << fixed << setprecision(2) << td.m_x << ", " << td.m_y;
62:     return os;
63: }
64: 
65: ThreeDimensional::ThreeDimensional (char* name, double x, double y, double z)
66:     : Shape (name)
67: {
68:     m_x = x;
69:     m_y = y;
70:     m_z = z;
71: }
72: 
73: void ThreeDimensional::MoveTo (double x, double y, double z)
74: {
75:     m_x = x;
76:     m_y = y;
77:     m_z = z;
78: }
79: 
80: ostream& operator<< (ostream& os, const ThreeDimensional& td)
81: {
82:     os << (Shape&) td << endl;
83:     os << "  ThreeDimensional" << endl;
84:     os << "  Position: " << fixed << setprecision(2)
85:        << td.m_x << ", " << td.m_y << ", " << td.m_z;
86:     return os;
87: }

Beispiel 2. Spezialisierungen TwoDimensional und ThreeDimensional


Auch zu Listing 2 geben wir einen Hinweis: Der klassische virtuelle Methodenaufrufmechanismus ist für friend-Elemente einer Klasse nicht definiert. Befreundete Methoden zählen ja überhaupt nicht zur Klasse. Sie stellen globale Funktionen ganz im Sinne von C dar, nur mit der Ausnahme, dass sie auf die privaten Elemente einer befreundeten Klasse zugreifen dürfen. Diese Beobachtungen schließen jedoch nicht aus, dass man beispielsweise den operator<<-Operator in allen Klassen einer Klassenhierarchie definieren kann und mit Hilfe des Cast-Operators auf eine Implementierung in einer Basisklasse verzweigen kann, so wie es in Zeile 82 von Listing 2 praktiziert wird.

001: #include <iomanip>
002: #define _USE_MATH_DEFINES
003: #include <math.h>
004: 
005: class Triangle : public TwoDimensional
006: {
007: protected:
008:     double m_a;
009:     double m_b;
010:     double m_c;
011: 
012: public:
013:     Triangle (double x, double y, double a, double b, double c);
014: 
015:     double Area () const;
016:     double Circumference () const;
017:     
018:     // output
019:     friend ostream& operator<< (ostream&, const Triangle&);
020: };
021: 
022: class Rectangle : public TwoDimensional
023: {
024: protected:
025:     double m_width;
026:     double m_height;
027: 
028: public:
029:     Rectangle (double x, double y, double width, double height);
030: 
031:     double Area () const;
032:     double Circumference () const;
033:     
034:     // output
035:     friend ostream& operator<< (ostream&, const Rectangle&);
036: };
037: 
038: class Circle : public TwoDimensional
039: {
040: protected:
041:     double  m_radius;
042: 
043: public:
044:     Circle (double x, double y, double r);
045: 
046:     double Area () const;
047:     double Circumference () const;
048:     
049:     // output
050:     friend ostream& operator<< (ostream&, const Circle&);
051: };
052: 
053: Triangle::Triangle (double x, double y, double a, double b, double c)
054:     : TwoDimensional ("Triangle", x, y)
055: {
056:     m_a = a;
057:     m_b = b;
058:     m_c = c;
059: }
060: 
061: double Triangle::Area () const
062: {
063:     return sqrt(((4 * m_a * m_a * m_c * m_c) -
064:         (m_a * m_a + m_c * m_c - m_b * m_b) *
065:         (m_a * m_a + m_c * m_c - m_b * m_b)) / 16.0);
066: }
067: 
068: double Triangle::Circumference () const
069: {
070:     return m_a + m_b + m_c;
071: }
072: 
073: ostream& operator<< (ostream &os, const Triangle& t)
074: {
075:     os << (TwoDimensional&) t << endl;
076:     os << fixed;
077:     os << "    A: " << setprecision(2) << t.m_a << endl;
078:     os << "    B: " << setprecision(2) << t.m_b << endl;
079:     os << "    C: " << setprecision(2) << t.m_c << endl;
080:     os << "    Area: " << setprecision(2) << t.Area () << endl;
081:     os << "    Circumference: " << setprecision(2) << t.Circumference () << endl;
082:     return os;
083: }
084: 
085: Rectangle::Rectangle (double x, double y, double width, double height)
086:     : TwoDimensional ("Rectangle", x, y)
087: {
088:     m_width = width;
089:     m_height = height;
090: }
091: 
092: double Rectangle::Area () const
093: {
094:     return m_width * m_height;
095: }
096: 
097: double Rectangle::Circumference () const
098: {
099:     return 2 * (m_width + m_height);
100: }
101: 
102: ostream& operator<< (ostream& os, const Rectangle& r)
103: {
104:     os << (TwoDimensional&) r << endl;
105:     os << fixed;
106:     os << "    Width: " << setprecision(2) << r.m_width << endl;
107:     os << "    Height: " << setprecision(2) << r.m_height << endl;
108:     os << "    Area: " << setprecision(2) << r.Area () << endl;
109:     os << "    Circumference: " << setprecision(2) << r.Circumference () << endl;
110:     return os;
111: }
112: 
113: Circle::Circle (double x, double y, double r)
114:     : TwoDimensional ("Circle", x, y)
115: {
116:     m_radius = r;
117: }
118: 
119: double Circle::Area () const
120: {
121:     return m_radius * m_radius * M_PI;
122: }
123: 
124: double Circle::Circumference () const
125: {
126:     return 2 * M_PI * m_radius;
127: }
128: 
129: ostream& operator<< (ostream &os, const Circle& c)
130: {
131:     os << (TwoDimensional&) c << endl;
132:     os << fixed;
133:     os << "    Radius: " << setprecision(2) << c.m_radius << endl;
134:     os << "    Area: " << setprecision(2) << c.Area () << endl;
135:     os << "    Circumference: " << setprecision(2) << c.Circumference () << endl;
136:     return os;
137: }

Beispiel 3. Die zweidimensionalen Klassen Triangle, Rectangle und Circle


001: #include <iomanip>
002: #define _USE_MATH_DEFINES
003: #include <math.h>
004: 
005: class Cube : public ThreeDimensional
006: {
007: protected:
008:     double m_size;
009: 
010: public:
011:     Cube (double x, double y, double z, double size);
012: 
013:     double Volume () const;
014:     double Surface () const;
015: 
016:     // output
017:     friend ostream& operator<< (ostream&, const Cube&);
018: };
019: 
020: class Cuboid : public ThreeDimensional
021: {
022: protected:
023:     double m_width;
024:     double m_height;
025:     double m_depth;
026: 
027: public:
028:     Cuboid (double x, double y, double z,
029:         double width, double height, double depth);
030: 
031:     double Volume () const;
032:     double Surface () const;
033: 
034:     // output
035:     friend ostream& operator<< (ostream&, const Cuboid&);
036: };
037: 
038: class Sphere : public ThreeDimensional
039: {
040: protected:
041:     double m_radius;
042: 
043: public:
044:     Sphere (double x, double y, double z, double radius);
045: 
046:     double Volume () const;
047:     double Surface () const;
048: 
049:     // output
050:     friend ostream& operator<< (ostream&, const Sphere&);
051: };
052: 
053: class Cone : public ThreeDimensional
054: {
055: protected:
056:     double m_radius;
057:     double m_height;
058: 
059: public:
060:     Cone (double x, double y, double z, double radius, double height);
061: 
062:     double Volume () const;
063:     double Surface () const;
064: 
065:     // output
066:     friend ostream& operator<< (ostream&, const Cone&);
067: };
068: 
069: Cube::Cube (double x, double y, double z, double size)
070:     : ThreeDimensional ("Cube", x, y, z)
071: {
072:     m_size = size;
073: }
074: 
075: double Cube::Volume () const
076: {
077:     return m_size * m_size * m_size;
078: }
079: 
080: double Cube::Surface () const
081: {
082:     return 6 * m_size * m_size;
083: }
084: 
085: ostream& operator<< (ostream &os, const Cube& c)
086: {
087:     os << (ThreeDimensional&) c << endl;
088:     os << fixed;
089:     os << "    Size: " << setprecision(2) << c.m_size << endl;
090:     os << "    Volume: " << setprecision(2) << c.Volume () << endl;
091:     os << "    Surface: " << setprecision(2) << c.Surface () << endl;
092:     return os;
093: }
094: 
095: Cuboid::Cuboid (double x, double y, double z, double width, double height, double depth)
096:     : ThreeDimensional ("Cuboid", x, y, z)
097: {
098:     m_width = width;
099:     m_height = height;
100:     m_depth = depth;    
101: }
102: 
103: double Cuboid::Volume () const
104: {
105:     return m_width * m_height * m_depth;
106: }
107: 
108: double Cuboid::Surface () const
109: {
110:     return 2 * (m_width * m_height + m_height * m_depth + m_width * m_depth);
111: }
112: 
113: ostream& operator<< (ostream &os, const Cuboid& c)
114: {
115:     os << (ThreeDimensional&) c << endl;
116:     os << fixed;
117:     os << "    Width: " << setprecision(2) << c.m_width << endl;
118:     os << "    Height: " << setprecision(2) << c.m_height << endl;
119:     os << "    Depth: " << setprecision(2) << c.m_depth << endl;
120:     os << "    Volume: " << setprecision(2) << c.Volume () << endl;
121:     os << "    Surface: " << setprecision(2) << c.Surface () << endl;
122:     return os;
123: }
124: 
125: Sphere::Sphere (double x, double y, double z, double r)
126:     : ThreeDimensional ("Sphere", x, y, z)
127: {
128:     m_radius = r;
129: }
130: 
131: double Sphere::Volume () const
132: {
133:     return (4.0 / 3.0) * M_PI * m_radius * m_radius * m_radius;
134: }
135: 
136: double Sphere::Surface () const
137: {
138:     return 4 * M_PI * m_radius * m_radius;    
139: }
140: 
141: ostream& operator<< (ostream &os, const Sphere& s)
142: {
143:     os << (ThreeDimensional&) s << endl;
144:     os << fixed;
145:     os << "    Radius: " << setprecision(2) << s.m_radius << endl;
146:     os << "    Volume: " << setprecision(2) << s.Volume () << endl;
147:     os << "    Surface: " << setprecision(2) << s.Surface () << endl;
148:     return os;
149: }
150: 
151: Cone::Cone (double x, double y, double z, double radius, double height)
152:     : ThreeDimensional ("Cone", x, y, z)
153: {
154:     m_radius = radius;
155:     m_height = height;
156: }
157: 
158: double Cone::Volume () const
159: {
160:     return (M_PI * m_radius * m_radius * m_height) / 3;
161: }
162: 
163: double Cone::Surface () const
164: {
165:     return M_PI * m_radius *
166:         (m_radius + sqrt (m_height * m_height + m_radius * m_radius));
167: }
168: 
169: ostream& operator<< (ostream &os, const Cone& c)
170: {
171:     os << (ThreeDimensional&) c << endl;
172:     os << fixed;
173:     os << "    Radius:  " << setprecision(2) << c.m_radius << endl;
174:     os << "    Height:  " << setprecision(2) << c.m_height << endl;
175:     os << "    Volume:  " << setprecision(2) << c.Volume () << endl;
176:     os << "    Surface: " << setprecision(2) << c.Surface () << endl;
177:     return os;
178: }

Beispiel 4. Die dreidimensionalen Klassen Cube, Cuboid, Sphere und Cone