SimpleStat mit Methoden Als Ausgangspunkt nehmen wir folgende Definition (Version 1) von SimpleStat: double sum2; -Quadrate Dieser Objekttyp verfügt über keine Methoden und alle Datenattribute sind public. Warum ist das ein Problem? In einem Anwendungsprogramm steht dann z.b. : SimpleStat stat; if (stat.n = 10) // Fehler! Es sollte heißen if (stat.n == 10) Hier wird der Stichprobenzähler versehentlich umgesetzt und die Statistik liefert völlig falsche Werte. Verhindern lässt sich das nur dadurch, dass man die Datenattribute macht (Version 2): double sum2; -Quadrate Genau denselben Effekt hätte man erzielt, wenn man das Schlüsselwort struct durch class ersetzt. Allerdings kann jetzt niemand mehr mit der Version 2 arbeiten, weil NICHTS darauf zugreifen darf. Man muss jetzt Objektmethoden oder befreundete Funktionen definieren, die auf die Attribute zugreifen dürfen. Hier genügt es, eine Stichprobe zu verarbeiten und die statistische Endauswertung auszugeben. Wir haben dazu bisher 2 Funktionen geschrieben (eine davon ist der Streamausgabe-Operator): void push_back(simplestat& s, double x) { ++(s.n);... ostream& operator<<(ostream& out, const SimpleStat& s)
{ cout << "Statistische Auswertung: " << s.title << '\n'; return out; Am einfachsten macht man diese 2 Funktionen zu Freunden (Version 3): double sum2; -Quadrate friend void push_back(simplestat&, double); C++ beschwert sich jetzt noch über die fehlerhafte Instanzierung: SimpleStat daten{"double Zahlenreihe", 0, 0., 0. Wenn ein Objekt private Attribute besitzt, dann kann man diese nicht mehr ohne Konstruktoren initialisiieren. Eine Liste in geschwungenen Klammern mit allen Attributwerten reicht dann nicht mehr. Wir schreiben also einen Konstruktor, der den Titel setzt. Alle übrigen Werte sind ohnehin 0 oder 0. Der Konstruktor muss, damit man ihn überhaupt verwenden darf, public sein. Hier eine einfache Version mit Initialisierungsliste innerhalb der Objektdefinition (Version 4): SimpleStat(string text) : title{text, n{, sum{, sum2{ { double sum2; -Quadrate friend void push_back(simplestat&, double);
n{ oben bedeutet, dass n als Pod mit dem Defaultwert initialisiert wird (dieser ist 0 bzw. 0. für Gleitkommatypen). Es ist üblich (es gibt aber keinen zwingenden Grund dafür!), größere Objekte eher als class statt als struct zu definieren. SimpleStat(string text) : title{text, n{, sum{, sum2{ { double sum2; -Quadrate friend void push_back(simplestat&, double); Statt Freund-Funktionen kann man auch Methoden einsetzen. Es ändert sich die Übergabe des Referent-Elements: Aus der Funktion void push_back(simplestat& stat, double x); wird die Methode (diese muss jetzt public sein!) void push_back(double x); Die Streamausgabe kann zu keiner Methode umgeschrieben werden, da das 1. Argument der Stream ist und nicht die SimpleStat-Instanz. Es ist auch üblich (guter Stil), alle Methoden des Objekts (auch die Konstruktoren) außerhalb der Objektdefinition zu programmieren (Version 5): SimpleStat(string); void push_back(double); double sum2; -Quadrate SimpleStat::SimpleStat(string text) : title{text, n{, sum{, sum2{ { void SimpleStat::push_back(double x) { ++n;
sum += x; sum2 += x*x; Der Aufruf muss noch angepasst werden. Aus push_back(daten, x); wird daten.push_back(x); Damit wäre man eigentlich schon fertig. Viele Objekte haben eine befreundete Streamausgabe. Ich habe aber mit der Statistik-Klasse noch etwas anderes vor und deshalb möchte ich die Ausgabe ändern. Ich erledige diese durch eine neue public Objektmethode void print(ostream& out)const Die Streamausgabe ruft dann lediglich diese Objektmethode auf und braucht daher selbst keinen Zugriff mehr auf die privaten Objekt-Attribute. Damit kann man auch diese friend- Deklaration löschen (Version 6): SimpleStat(string); void push_back(double); void print(ostream&) const; double sum2; -Quadrate ostream& operator<<(ostream& out, const SimpleStat& stat) { stat.print(out); return out; Man schreibt noch die alte Streamausgabe zur print-methode um: void SimpleStat::print(ostream& out) const { out << "Statistische Auswertung: " << title << '\n'; SimpleStat ist bereits jetzt ein nützliches Tool zur statistischen Messwert-Analyse. Um die Wiederverwertung zu erleichtern, extrahiert man den Objektcode in 2 separate Dateien: Eine Headerdatei SimpleStat.h (nur die Objektdefinition und die Deklarationen der Utility-Funktionen), eine Codedatei SimpleStat.cpp. Das Anwendungsprogramm
inkludiert den Header und beim Kompilieren gibt man die Codedatei mit an. Die Anwendung anwendung.cpp: #include "SimpleStat.h" SimpleStat daten{"double Zahlenreihe" for (double x; cin >> x;) daten.push_back(x); cout << daten; Die Headerdatei SimpleStat.h: #ifndef SIMPLESTAT_H #define SIMPLESTAT_H // protect against multiple Inclusions #include <string> #include <iostream> SimpleStat(std::string); void push_back(double); void print(std::ostream&) const; std:: double sum2; std::ostream& operator<<(std::ostream&, const SimpleStat&); #endif // #ifndef SIMPLESTAT_H Die Headerdatei muss alles inkludieren, was sie selbst benötigt (iostream, string). Auch ist es schlechter Stil, in Headerdateien die using namespace std; Anweisung zu verwenden, weshalb man hier die benötigten std:: schreiben muss. Erklärung: Ein Anwender von SimpleStat möchte gerne selbst entscheiden, ob er diese using- Anweisung verwenden will oder nicht. Es kommt nicht gut an, wenn eine Headerdatei, die man inkludieren muss, diese Entscheidung trifft. Auch sollte man einen Schutz gegen mehrmaliges Inkludieren des Headers vorsehen (s.o.). Den C++-Code der Methoden und der Streamausgabe packt man in SimpleStat.cpp.
Diese inkludiert SimpleStat.h und implementiert nur mehr den fehlenden Code der Methoden und der Streamausgabe. Eine kleine Unterlassung in der Ausgabe habe ich auch noch behoben. Selbstverständlich darf man hier die using-anweisung wieder verwenden, wenn man möchte, da diese Datei nirgends inkludiert wird: #include <cmath> #include "SimpleStat.h" using namespace std; SimpleStat::SimpleStat(string text) : title{text, n{, sum{, sum2{ { void SimpleStat::push_back(double x) { ++n; sum += x; sum2 += x*x; void SimpleStat::print(ostream& out) const { out << "Statistische Auswertung: " << title << '\n'; out << "Anzahl der Stichproben: " << n << '\n'; if (n == 0) return; auto mw = sum/n; out << "Mittelwert der Daten: " << mw << '\n'; if (n == 1) return; auto sa = (sum2 - n*mw*mw)/(n-1); out << "Standardabweichung: " << sa << '\n'; out << "Streuung: " << sqrt(sa) << '\n'; ostream& operator<<(ostream& out, const SimpleStat& stat) { stat.print(out); return out;