C++: Grundlagen der objektorientierten Programmierung (OOP)

Die zentralen Begriffe zum Verst├Ąndnis der objekt-orientierten Programmierung sind Klassse und Objekt: Die Klasse ist der allgemeine Bauplan f├╝r die konkreten Objekte. Gezeigt wird die Syntax in C++, mit der Klassen angelegt werden, ihre Datenelemente und Methoden definiert werden, wie konkrete Objekte erzeugt werden und wie diese in anderen Programmen genutzt werden.
Noch keine Stimmen abgegeben
Noch keine Kommentare

Einordnung des Artikels

In den bisherigen Kapiteln ├╝ber C++: Fortgeschrittene Syntax wurde ein kleines Statistik-Projekt entwickelt ÔÇö bisher im Sinne der strukturierten Programmierung. Dieses Projekt wird jetzt im Sinne der objekt-orientierten Programmierung neu entworfen.

Einf├╝hrung

Welche Vorteile die objektorientierte Programmierung liefert, wurde bereits auf einer abstrakten, von einer speziellen Programmiersprache unabh├Ąngigen Ebene beschrieben (siehe ├ťbersicht ├╝ber Programmiersprachen). In diesem und den folgenden Abschnitten wird gezeigt, wie die Konzepte der objektorientierten Programmierung in C++ realisiert werden. Dazu werden zun├Ąchst die Begriffe

  • Klasse (class)
  • Objekt (object)
  • Datenelement (data member)
  • Methode (member function)

eingef├╝hrt.

Dies geschieht mit Hilfe des kleinen Statistik-Projektes, das im Sinne der strukturierten Programmierung in den letzten Abschnitten aufgebaut wurde; es soll jetzt im Sinne der objektorientierten Programmierung noch einmal neu aufgesetzt werden. Dabei wird an der Funktionalit├Ąt zuerst nichts ge├Ąndert. Erst sp├Ąter werden neue Funktionalit├Ąten hinzugef├╝gt.

Das erste OOP-Projekt: Statistische Auswertung von Zufallsfolgen

Beschreibung des Projektes

Ausgangspunkt ist das Statistik-Projekt, das im Sinne der strukturierten Programmierung in drei Dateien aufgeteilt wurde:

Statistik.h             // Deklarationen
Statistik.cpp           // Implementierungen
main.cpp                // Test der Funktionen

Hier die Quelltexte der drei Dateien:

// Statistik.h
// Deklarationen der statistischen Funktionen

#ifndef STATISTIK_H_INCLUDED
#define STATISTIK_H_INCLUDED

#include <vector>

double mean(double x1, double x2);

// Berechnet den Mittelwert eines Vektors (Zufallsfolge randomSequence = rs)
double mean(std::vector<int> rs);

// Erzeugt Zufallsfolge mit int-Zahlen von min bis max der L├Ąnge number
std::vector<int> createRandomSequence(int min, int max, int number);

#endif // STATISTIK_H_INCLUDED
// Statistik.cpp
// Implementierungen der statistischen Funktionen

#include "Statistik.h"

#include <iostream>
#include <vector>

using namespace std;

double mean(double x1, double x2)
{
    return (x1 + x2) / 2;
}

// Berechnet den Mittelwert eines Vektors (Zufallsfolge randomSequence)
double mean(vector<int> randomSequence)
{
    int number = randomSequence.size();
    int summe {0};
    for (int value : randomSequence)
    {
        summe += value;
    }
    return static_cast<double> (summe) / number;
}

// Erzeugt Zufallsfolge mit int-Zahlen von min bis max der L├Ąnge number
vector<int> createRandomSequence(int min, int max, int number)
{
    int diff = max - min + 1;

    vector<int> randomSequence(number);

    for (int i = 0; i < number; i++)
    {
        randomSequence[i] = (i % diff) + min;       // This is a FAKE! TODO: echte Zufallsfolge erzeugen!
        cout << randomSequence[i] << endl;
    }

    return randomSequence;
}
// main.cpp

#include "Statistik.h"

#include <iostream>
using namespace std;

int main() 
{
    vector<int> rs = createRandomSequence(1, 6, 20);        // 20x W├╝rfeln
    for (int randomElement : rs)
    {
        cout << randomElement;              //  12345612345612345612
    }
    cout << "\nMittelwert: " << mean(rs) << endl;
    return 0;
}

Bevor das Projekt Statistische Auswertung von Zufallsfolgen neu aufgebaut wird, muss man sich vergegenw├Ąrtigen, welche Aufgaben es erledigen soll und in welche Richtung es sp├Ąter weiterentwickelt werden soll:

  1. Ein Client soll die M├Âglichkeit haben, Zufallsfolgen eines Zufallsexperimentes beliebiger L├Ąnge zu erzeugen.
  2. Die Zufallsfolgen werden gebildet aus aufeinanderfolgenden ganzen Zahlen min, min + 1, ... , max - 1, max, wobei der Client min und max vorgeben kann. So kann man etwa mit min = 1 und max = 6 einen W├╝rfel simulieren oder mit min = 0 und max = 1 einen M├╝nzwurf (mit 0 und 1 statt Kopf und Zahl).
  3. Zun├Ąchst soll die Laplace-Annahme gelten, das hei├čt dass jede Zahl mit gleicher Wahrscheinlichkeit erscheint.
  4. Nachdem eine entsprechende Zufallsfolge erzeugt wurde, kann der Client mehrere statistische Funktionen aufrufen, um die Folge auszuwerten:
    • der Mittelwert der Zahlenfolge kann berechnet werden,
    • die empirische Standardabweichung vom Mittelwert kann berechnet und
    • f├╝r alle Zahlen kann die relative H├Ąufigkeit angegeben werden, mit der sie in der Zufallsfolge vorkommen.
    • Man kann nach beliebigen Mustern in der Zufallsfolge suchen und sich deren Anzahl ausgeben lassen (etwa: wie h├Ąufig kommt die Zahlenfolge 123 beim 20-maligen W├╝rfeln vor?).

Liest man die Anforderungen an das Projekt, wird man vermutlich sofort anspruchsvoller und w├╝nscht sich folgende Erweiterungen:

  1. Warum muss die Laplace-Annahme gelten? Kann man nicht etwa einen gezinkten W├╝rfel simulieren?
  2. Die statistischen Auswertungen sind doch eher d├╝rftig: Kann man nicht Hypothesen-Tests anbieten? (Etwa in der Form: Wie gro├č ist die Wahrscheinlichkeit daf├╝r, dass die Zufallsfolge nicht von einem Laplace-W├╝rfel sondern von einem gezinkten W├╝rfel erzeugt wurde, bei dem die 6 mit einer Wahrscheinlichkeit von mindestens 0.2 erscheint?)
  3. Ebenso ist die Ausgabe der Auswertung auf der Konsole unbefriedigend: Warum kann man die Ergebnisse nicht geeignet graphisch aufbereiten?

Die Liste l├Ąsst sich beliebig fortsetzen.

Sie werden sehen: Wenn ein guter objektorientierter Entwurf des Projektes gelingt, k├Ânnen diese zus├Ątzlichen Anforderungen leicht integriert werden. Umgekehrt: Als Programmierer sollten Sie immer daran denken, dass Ihre Software irgendwann verbessert oder erweitert wird und daf├╝r m├╝ssen Sie Spielraum lassen.

Ein schlechtes Vorbild

Um besser zu verstehen, wie man bei einem objektorientierten Entwurf eines Programmes vorgeht, sei nochmal daran erinnert, wie im Sinne der prozeduralen Programmierung das Erzeugen und Auswerten einer Zufallsfolge (f├╝r einen W├╝rfel) aussehen w├╝rde. Dazu diene folgende im Pseudocode formulierte main()-Methode:

int main(){
        // Zufallsfolge der L├Ąnge n f├╝r einen W├╝rfel erzeugen
        // Mittelwert der Zufallsfolge berechnen
        // Standardabweichung der Zufallsfolge berechnen
        // relative H├Ąufigkeiten der einzelnen Augenzahlen berechnen
        // statistische Ergebnisse ausgeben
        // Zufallsfolge nach gegebenen Mustern durchsuchen und auswerten
        return 0;
    }

Man sieht an diesem Negativ-Beispiel:

  • es gibt mehrere Aufgaben, die sich eigentlich gruppieren lassen ÔÇö man spricht dann auch von Verantwortlichkeiten -, die hier aber vermischt werden.
  • Wiederverwendbarkeit: Erzeugt man im Sinne der strukturierten Programmierung geeignete Funktionen, lassen diese sich f├╝r andere Programme ebenfalls nutzen; gr├Â├čere Module, die als Ganzes in anderen Programmen nutzbar sind, sind aber nicht erkennbar.
  • Erweiterbarkeit: Soll das Programm sp├Ąter ver├Ąndert werden (etwa Aufgeben der Laplace-Annahme), muss man das ganze Programm absuchen, um herauszufinden, welche Quelltexte abzu├Ąndern beziehungsweise neu zu implementiere sind

Ein objektorientierter Entwurf

Liest man den Pseudocode obiger main()-Methode, so fallen sofort drei Verantwortlichkeiten auf:

  1. Der Client nutzt die Funktionalit├Ąten, die von den anderen Programmteilen angeboten werden.
  2. Es gibt einen Zufallsfolgen-Generator:
    • Er muss ÔÇö als Klasse ÔÇö die gew├╝nschten Eigenschaften bereitstellen (wie L├Ąnge, Zahlenbereich, Laplace-Annahme oder beliebige Verteilung).
    • Der Zufallsfolgen-Generator kann verschiedene Auspr├Ągungen (Objekt) annehmen, etwa als W├╝rfel oder als M├╝nze ÔÇö je nachdem wie min und max gesetzt sind.
  3. Eine weitere Instanz sorgt f├╝r die statistische Auswertung der Zufallsfolgen.

Entsprechend dieser Aufteilung lautet ein Vorschlag f├╝r einen objektorientierten Entwurf:

1. Der Client wird nicht objektorientiert modelliert. Denn er m├╝sste erst dann realisiert werden, wenn man etwa f├╝r die Ausf├╝hrung seiner Aktivit├Ąten eine graphische Oberfl├Ąche anbietet. Vorerst bleibt die Datei main.cpp so wie sie im strukturierten Entwurf war und innerhalb der main()-Methode werden die anderen Objekte erzeugt und angesprochen.

2. Der Zufallsfolgen-Generator wird als Klasse modelliert:

  • Als Datenelemente (data member) erh├Ąlt er die beiden Zahlen min und max.
  • Als Methoden (member function) erh├Ąlt er vorerst lediglich die bekannte Funktion createRandomSequence() sowie eine weitere Funktion zur Ausgabe einer Zufallsfolge:
// Erzeugt Zufallsfolge mit int-Zahlen von min bis max der L├Ąnge number
vector<int> createRandomSequence(int number);

// Ausgabe einer Zufallsfolge
void printRandomSequence(std::vector<int> rs);

Man beachte, dass min und max jetzt keine Eingabewerte von createRandomSequence() sein m├╝ssen, da diese als Datenelemente vorliegen: die Methode kann jederzeit darauf zugreifen und daher m├╝ssen sie nicht ├╝bergeben werden. Wird von der Klasse des Zufallsfolgen-Generators ein Objekt erzeugt, so wird f├╝r dieses Objekt festgelegt, wie gro├č min und max sind. Somit entspricht dann ein Objekt einem W├╝rfel, einer M├╝nze, einem Roulette-Spiel und so weiter.

3. Eine weitere Klasse entspricht der statistischen Auswertung der Zufallsfolgen. Die statistischen Funktionen sollen wie die mathematischen Funktionen aus der cmath-Bibliothek, also wie globale Funktionen aufrufbar sein. Sie werden daher als statische Methoden modelliert.

Tip:

Beginnen Sie erst dann Klassen zu erzeugen und Quelltexte zu schreiben, wenn Ihnen klar ist:

  • welche Klassen Sie f├╝r Ihr gesamtes Projekt ben├Âtigen und
  • welche Datenelemente und Methoden diese Klassen besitzen werden.

Die Umsetzung des objektorientierten Entwurfs

Die main()-Methode ist in der Datei

Client.cpp		// enth├Ąlt main()

enthalten. Der Name soll andeuten, dass sich dahinter der Client verbirgt, der die Zufallsfolgen erzeugt und auswertet und dass dieses Programm eigentlich noch zu ├╝berarbeiten ist (als graphische Oberfl├Ąche oder wie auch immer). Zum Testen der anderen Programme reicht die main()-Methode v├Âllig aus.

F├╝r die weiteren Klasssen werden insgesamt vier Dateien angelegt:

RandomGenerator.h           // Deklaration der Klasse RandomGenerator
RandomGenerator.cpp         // Implementierungen
Statistics.h                // Deklaration der Klasse Statistics
Statistics.cpp              // Implementierungen

Die Klasse RandomGenerator

Wie in der strukturierten Programmierung werden wieder die Deklarationen und die Implementierungen in zwei verschiedenen Dateien abgelegt:

RandomGenerator.h           // Deklarationen
RandomGenerator.cpp         // Implementierungen

Neu ist, dass in RandomGenerator.h die Deklarationen zu einer Klasse zusammengefasst werden; in der Klasse sind dann alle Datenelemente und Methoden enthalten. Und in den Implementierungen (in RandomGenerator.cpp) wird dann mit dem Namen der Klasse und dem Operator :: die Verbindung zur Deklaration hergestellt. Dieser Operator, der im Englischen als scope resolution operator bezeichnet wird, wird unten n├Ąher erkl├Ąrt.

Die entsprechenden Dateien werden sofort gezeigt und anschlie├čend erkl├Ąrt.

// RandomGenerator.h
// Deklaration der Klasse RandomGenerator, die Zufallsfolgen gew├╝nschter L├Ąnge erzeugen kann
// Ein Objekt spezifiziert den Wertebereich der Zahlenfolge (siehe Konstruktor)

#ifndef RANDOMGENERATOR_H_INCLUDED
#define RANDOMGENERATOR_H_INCLUDED

#include <vector>

class RandomGenerator
{
public:

    // Konstruktor zum Setzen von min und max
    RandomGenerator(int min, int max);

    // Erzeugt Zufallsfolge mit int-Zahlen von min bis max der L├Ąnge number
    std::vector<int> createRandomSequence(int number);

    // Ausgabe einer Zufallsfolge
    void printRandomSequence(std::vector<int> rs);

    void setMin(int min);
    int getMin();

    void setMax(int min);
    int getMax();

private:

    int min;
    int max;
};

#endif // RANDOMGENERATOR_H_INCLUDED
// RandomGenerator.cpp
// Implementierung der Klasse RandomGenerator

#include "RandomGenerator.h"
#include <iostream>

using namespace std;

// Konstruktor (initialisiert die Datenelemente min, max
RandomGenerator::RandomGenerator(int minimum, int maximum) : min {minimum}, max {maximum}
{
}

// Erzeugt Zufallsfolge mit int-Zahlen von min bis max der L├Ąnge number
vector<int> RandomGenerator::createRandomSequence(int number)
{
    int diff = max - min + 1;

    vector<int> randomSequence(number);

    for (int i = 0; i < number; i++)
    {
        randomSequence[i] = (i % diff) + min;       // This is a FAKE! TODO: echte Zufallsfolge erzeugen!
        // cout << randomSequence[i] << endl;       // nur zum Testen
    }

    return randomSequence;
}

// Ausgabe einer Zufallsfolge (ohne Trennungszechen)
// TODO: Trennungszeichen einf├╝hren, da mehrdeutig bei mehrstelligen Zahlen
void RandomGenerator::printRandomSequence(vector<int> rs)
{
     for (int randomElement : rs)
    {
        cout << randomElement;
    }
    cout  << endl;
}

void RandomGenerator::setMin(int minimum)
{
    min = minimum;
}

int RandomGenerator::getMin()
{
    return min;
}

void RandomGenerator::setMax(int maximum)
{
    max = maximum;
}

int RandomGenerator::getMax()
{
    return max;
}
// Client.cpp

#include "RandomGenerator.h"

#include <iostream>
using namespace std;

int main()
{
    RandomGenerator rg(1, 6);
    vector<int> rs = rg.createRandomSequence(20);        // 20x W├╝rfeln
    rg.printRandomSequence(rs);
    return 0;
}

Zur Erkl├Ąrung:

1. RandomGenerator.h:

a) Wie in der strukturierten Programmierung m├╝ssen wieder die include-guards eingesetzt werden (Zeile 5, 6 und 35), die hier nicht nochmal erkl├Ąrt werden (siehe Globale Funktionen in Strukturierte Programmierung mit Funktionen).

b) Alle Deklarationen f├╝r die Klasse RandomGenerator sind in

class RandomGenerator
{
...
};

eingeschlossen (Zeile 10, 11 und 33). Man beachte:

  • Das Schl├╝sselwort class definiert die Klasse (Zeile 10).
  • Nach dem Schl├╝sselwort class folgt der Name der Klasse, hier RandomGenerator. Klassen-Namen sollen mit Gro├čbuchstaben beginnen und die CamelCase-Schreibweise verwenden.
  • Alle Methoden und Datenelemente sind in geschweiften Klammern eingeschlossen.
  • Am Ende wird die Klasse durch einen Strichpunkt abgeschlossen (Zeile 33).

c) Die beiden Schl├╝sselworte public und private werden als access spicifier bezeichnet (Zeile 12 und 29). Sie definieren die Zugriffsberechtigung:

  • public: auf ├Âffentliche Methoden und Datenelemente kann von anderen Klassen beziehungsweise deren Methoden zugegriffen werden
  • private: auf private Methoden und Datenelemente kann nur die Klasse selbst zugreifen, also die Methoden der Klasse.

Diese Aufteilung ist insbesondere f├╝r den Client relevant: er kann nur auf Datenelemente und Methoden zugreifen, die als public gekennzeichnet sind, nicht auf private Datenelemente und Methoden.

d) V├Âllig neu ist der Konstruktor (Zeile 15). Er sieht fast wie eine herk├Âmmliche Funktion aus, ist aber daran als Konstruktor zu erkennen, dass er den Namen der Klasse tr├Ągt. Der Konstruktor wird verwendet, wenn ein Objekt der Klasse erzeugt wird. Er besitzt dann wie eine Funktion einen K├Ârper und in diesem K├Ârper kann auf die zwei Eingabewerte zugegriffen werden; sie werden hier verwendet, um die Datenelemente min und max zu setzen. Ein Konstruktor besitzt keinen R├╝ckgabetyp, also auch nicht void ÔÇö er wird ja benutzt, um ein Objekt vom Datentyp der betreffenden Klasse zu erzeugen.

e) Die Methode createRandomSequence() ist bereits bekannt (Zeile 18); die Methode printRandomSequence() sollte sich selbst erkl├Ąren (Zeile 21).

f) Neu sind auch die sogenannten Setter und Getter (Zeile 23 bis 27): damit werden die Methoden bezeichnet, mit denen man die Datenelemente setzen oder ihren Wert abfragen kann. Es hat sich als g├╝nstig herausgestellt, Datenelemente nicht direkt zu setzen und abzufragen, sondern dies ├╝ber wohldefinierte Schnittstellen, n├Ąmlich ├Âffentliche Methoden zu erledigen. Damit lassen sich die Zugriffe besser kontrollieren und es ist besser m├Âglich zu verifizieren, ob ein g├╝ltiger Wert gesetzt wurde.

g) Entsprechend sind die Datenelemente min und max als private gekennzeichnet (Zeile 31, 32).

2. RandomGenerator.cpp:

a) Die Datei mit den Implementierungen muss nat├╝rlich die Datei mit den Deklarationen inkludieren (Zeile 4).

b) S├Ąmtliche Methoden, auch der Konstruktor, werden hier implementiert. Man erkennt auch, dass vor jedem Methoden-Namen der Klassen-Name und der Operator :: steht, wie etwa beim Konstruktor (Zeile 10):

RandomGenerator::RandomGenerator(int minimum, int maximum) : min {minimum}, max {maximum}
{
}

Die Verbindung von RandomGenerator.cpp zu RandomGenerator.h wird ├╝ber die include-Direktive hergestellt. Die umgekehrte Verbindung wird durch den Operator :: (scope resolution operator) hergestellt. Ohne den Vorsatz RandomGenerator:: vor den Methoden (beziehungsweise dem Konstruktor) k├Ânnte der Compiler die Implementierung der Methoden (beziehungsweise des Konstruktors) nicht der Klasse RandomGenerator zuordnen; der Aufruf der Methode w├╝rde zu einem Fehler f├╝hren. Man kann die Bedeutung des Operators :: auch so erkl├Ąren: Er qualifiziert eine Variable, eine Funktion oder eine Klasse mit dem ├╝bergeordneten Element; diese ├╝bergeordnete Element kann eine Klasse oder ein Namensraum (namespace) sein. F├╝r den Fall, dass eine Variable, Funktion oder Klasse mehrfach definiert ist, wird sie so eindeutig qualifiziert. Sie kennen diesen Mechanismus aus der Verwendung von std::cout : F├╝r den Fall, dass sie selber eine Variable mit dem Namen cout definiert haben, spricht std::cout eindeutig cout aus der Standard-Bibliothek an.

c) Der Konstruktor sieht zun├Ąchst wie eine gew├Âhnliche Methode aus, aber dann folgt ein Doppelpunkt und eine Initialisierungs-Liste. Dagegen bleibt der K├Ârper des Konstruktors leer. Man h├Ątte den Konstruktor auch folgenderma├čen schreiben k├Ânnen:

RandomGenerator::RandomGenerator(int minimum, int maximum)
{
    min = minimum;
    max = maximum;
}

Die beiden Konstruktoren sind gleichwertig, die Initialisierungs-Liste sorgt daf├╝r, dass die Datenelemente min und max gesetzt werden.

d) Die Setter- und Getter-Methoden werde hier nur dazu verwendet, die Datenelemente zu setzen oder zur├╝ckzugeben.

3. Client.cpp:

a) Die Datei ist keine Klasse, sondern lediglich die Datei, die die main()-Methode enth├Ąlt.

b) Einzig neu ist, dass in Zeile 10 der Konstruktor-Aufruf geschieht. Man sollte Zeile 10 folgenderma├čen lesen: Es wird ein Objekt namens rg vom Datentyp RandomGenerator erzeugt (ganz wie bei fundamentalen Datentypen, wie bei int n; ). Allerdings folgt auf den Namen rg runde Klammern, die den Konstruktor-Aufruf anzeigen. Das hei├čt es wird der Konstruktor aus Zeile 10 von RandomGenerator.cpp aufgerufen. Und der Konstruktor besitzt die Implementierung, nach der die beiden Datenelemente min und max gesetzt werden (hier mit den Zahlenwerten 1 und 6).

c) Wie alle Funktionen kann auch ein Konstruktor ├╝berladen werden; da hier kein weiterer Konstruktor angeboten wurde, kann der Konstruktor-Aufruf nur mit zwei int-Argumenten erfolgen.

d) Das Objekt rg wird verwendet, um die Methoden der Klasse RandomGenerator aufzurufen (Zeile 11 und 12). Dazu wird der Zugriffs-Operator . verwendet: Das Objekt (hier rg) greift auf ein Datenelement oder eine Methode der eigenen Klasse zu (hier finden zwei Methoden-Aufrufe statt).

Aufgaben:

1. Erzeugen Sie ausgehend von einem Empty Project aus den angef├╝hrten Quelltexten ein neues Projekt.

2. Die Methode createRandomSequence() erzeugt einen Vektor, der einer Zufallsfolge simuliert. In der Version oben wird dieser Vektor in der main()-Methode deklariert und dort gesetzt.

Schreiben Sie das Programm so um, dass diese Zufallsfolge ein privates Datenelement der Klasse RandomGenerator ist. Die Anzahl der Folgenglieder soll weiterhin aus der main()-Methode heraus gesetzt werden.

Machen Sie sich zuerst klar, wo Sie überall Änderungen vornehmen müssen.

3. ├ťberladen Sie den Konstruktor: Schreiben Sie einen weiteren Konstruktor, der zus├Ątzlich eine Boolsche Variable als Eingabewert besitzt.

  • Falls sie true gesetzt ist, soll gepr├╝ft werden, ob tats├Ąchlich min < max.
  • Falls nicht, sollen min und max vertauscht werden.

Die Klasse Statistics

Als n├Ąchste Aufgabe soll die Klasse implementiert werden, die die statistischen Funktionen bereitstellt; diese sollen wie globale Funktionen aufgerufen werden k├Ânnen. Da man aber globale Funktionen (wie globale Variablen) vermeiden soll, werden sie als sogenannte statische Funktionen realisiert.

Vorerst wird nur eine statistische Funktion angeboten:

// Berechnet den Mittelwert eines Vektors (Zufallsfolge randomSequence = rs)
static double mean(std::vector<int> rs);

Erweiterungen k├Ânnen Sie anschlie├čend selbst vornehmen.

// Statistics.h
// enth├Ąlt die Deklarationen der Klasse Statistics

#ifndef STATISTICS_H_INCLUDED
#define STATISTICS_H_INCLUDED

#include <vector>

class Statistics
{
public:

// Berechnet den Mittelwert eines Vektors (Zufallsfolge randomSequence = rs)
static double mean(std::vector<int> rs);

};

#endif // STATISTICS_H_INCLUDED
// Statistics.cpp
// Implementierung der Klasse Statistics

#include "Statistics.h"
#include <iostream>

using namespace std;

// Berechnet den Mittelwert eines Vektors (Zufallsfolge randomSequence = rs)
double Statistics::mean(std::vector<int> rs)
{
    int number = rs.size();
    int summe {0};
    for (int value : rs)
    {
        summe += value;
    }
    return static_cast<double> (summe) / number;
}
// Client.cpp

#include "RandomGenerator.h"
#include "Statistics.h"

#include <iostream>
using namespace std;

int main()
{
    RandomGenerator rg(1, 6);
    vector<int> rs = rg.createRandomSequence(20);        // 20x W├╝rfeln
    rg.printRandomSequence(rs);
    cout << "\nMittelwert: " << Statistics::mean(rs) << endl;
    return 0;
}

Zur Erkl├Ąrung:

1. Statistics.h:

a) Neu ist hier einzig das Schl├╝sselwort static in der Deklaration der Methode mean() (Zeile 14). Es bedeutet, dass die Methode der Klasse Statistics zugeh├Ârig ist; anders formuliert: es muss nicht erst ein Objekt dieser Klasse erzeugt werden, um die Methode aufzurufen. Der Zugriff erfolgt dann nicht von einem Objekt aus (siehe Client.cpp).

b) Bei n├Ąherem Hinsehen f├Ąllt zudem auf, dass die Klasse Statistics keinen Konstruktor besitzt. Da vorerst keine Objekte dieser Klasse angelegt werden, muss die Klasse keinen Konstruktor haben. Wenn kein Konstruktor vorliegt, erzeugt der Compiler immer den sogenannten Default-Konstruktor:

Statistics::Statistics()
{
}

Dieser besitzt keine Parameter und es werden keine Aktionen ausgef├╝hrt. Den Default-Konstruktor m├╝ssen Sie also nicht selber implementieren. Sobald aber irgendein Konstruktor vorliegt, wird der Default-Konstruktor nicht erzeugt; wenn es jetzt einen geben soll, muss man ihn implementieren.

2. Statistics.cpp:

a) Wie schon bekannt, muss die Datei Statistics.h inkludiert werden (Zeile 4) und es wird wieder der scope resolution operator eingesetzt, um dem Compiler mitzuteilen, dass die Funktion mean() aus der Datei Statistics.h implementiert wird (Zeile 10).

b) Das Schl├╝sselwort static wird bei der Implementierung nicht nochmals angegeben.

3. Client.cpp:

Zum Testen des Aufrufes der statischen (static) Funktion mean() wird sie in Zeile 14 ├╝ber den Klassen-Namen und dem scope resolution operator :: (und nicht mit dem Zugriffs-Operator) angesprochen.

Tips:

1. Die Verwendung von Setter- und Getter-Methoden klingt nach viel Schreibarbeit. Jede bessere Entwicklungsumgebung nimmt Ihnen aber diese Arbeit ab. Dazu sollte Ihnen aber schon beim Anlegen einer neuen Klasse klar sein, welche Datenelemente sie besitzt und f├╝r welche davon Sie Setter- oder Getter-Methoden anbieten wollen.

2. Wenn Sie diesen Abschnitt durchgearbeitet haben und verstanden haben, wie man eine Klasse aufbaut, sollten Sie sich damit vertraut machen, wie man mit Code::Blocks eine neue Klasse anlegt; Sie finden dies unter:

File -> New -> Class...

Aufgaben:

Im Kapitel zur Einf├╝hrung in C++, Anwendungen, wurden zahlreiche Aufgaben formuliert.

├ťberarbeiten Sie Ihre L├Âsungen im Sinne der objekt-orientierten Programmierung. Falls Sie die Aufgaben noch nicht gel├Âst haben, entwickeln Sie die entsprechenden Programme, aber achten Sie von Anfang an darauf, wie Sie Klassen und Methoden geschickt einsetzen k├Ânnen.