Klassen definieren
-
Rofl natürlich tut der Code nicht guck dir mal die Kommentare an da steht sogar wo der Fehler liegt

Ich wollte dir doch nur zeigen was man mit privaten oder public variablen machen kann und was nicht.
:schland: :schland:
-
tobZel schrieb:
Rofl natürlich tut der Code nicht guck dir mal die Kommentare an da steht sogar wo der Fehler liegt

Ich wollte dir doch nur zeigen was man mit privaten oder public variablen machen kann und was nicht.
:schland: :schland:Aber könntest du mir bitte nur noch erklären,wie man das mit dem "in Public schreibt" und wie das mit " private"funktioniert?
Für Public:
-) Was man da wirklich reinschreiben muss

Für private:
-) Wie der Code dafür geht bzw. was man darein schreiben muss,damit der auch im int main() funktioniert
Roland
-
Hallo
Stronghold_$niper schrieb:
tobZel schrieb:
Rofl natürlich tut der Code nicht guck dir mal die Kommentare an da steht sogar wo der Fehler liegt

Ich wollte dir doch nur zeigen was man mit privaten oder public variablen machen kann und was nicht.
:schland: :schland:Aber könntest du mir bitte nur noch erklären,wie man das mit dem "in Public schreibt" und wie das mit " private"funktioniert?
Für Public:
-) Was man da wirklich reinschreiben muss

Für private:
-) Wie der Code dafür geht bzw. was man darein schreiben muss,damit der auch im int main() funktioniert
Roland
Sag mal bist du ein Troll? Benutz doch einfach mal google oder wiki.
Und trotzdem helfe ich mal noch ein bisschen: du kannst in private und in public (es gibt übrigens auch noch protected) Variabeln und Funktionen
"reinschreiben". Wie man das macht, sieht man sehr gut an dem Beispiel, was hier schon gepostet wurde, allerdings dort eben nur mit Varibalen und nicht mit Funktionen.chrische
-
So,
hier mal eine kleine Geschichte:
Stellen wir uns einmal vor, wir müssen ein Programm schreiben, das Kontodaten verwaltet. Unser erster Ansatz sieht wie folgt aus:
double Konto;Vielleicht haben wir auch noch eine Funktion, die mit dem Konto arbeitet:
// Da alle Kontoinhaber monatlich eine Kontogebuehr in immer derselben // Hoehe bezahlen muessen, gibt es hierfuer eine Funktion, die das erledigt. // Als Argument wird die Adresse des zu bearbeitenden Kontos uebernommen. void gebuehrenAbziehen(double* dasKonto) { *dasKonto -= 42.3; return; }So einfach geht das normalerweise, einfach Typ und Namen angeben, schon haben wir eine Variable angelegt, die Funktion wird wie gewohnt definiert.
Der Code sieht dann so aus:#include <iostream> void gebuehrenAbziehen(double* dasKonto); int main() { std::cout << "_DAS_ Kontoprogramm!\n"; double Konto = 500.0; std::cout << "Kontostand: " << Konto << "\n"; // Zahltag! gebuehrenAbziehen(&Konto); std::cout << "Kontostand: " << Konto << "\n"; return 0; } void gebuehrenAbziehen(double* dasKonto) { *dasKonto -= 42.3; return; }Kaum sind wir also fertig mit dieser schwierigen Aufgabe, kommt auch schon der Chef herein um uns von unserem Erfolgstrip runterzuholen. "Freundlich" bittet er uns nämlich darum, den Code auch dem anderen Entwicklerteam der Firma zur Verfügung zu stellen, da diese ebenfalls an einem Kontosystem arbeitet. Natürlich machen wir das auch sofort, und prompt bekommen wir auch eine Mail vom empörten Projektleiter des anderen Teams, der uns mindestens genauso "freundlich" mitteilt, dass einer seiner Programmierer folgenden Code geschrieben hat:
double dispoKonto = 111.19; double giroKonto = 1000.0; gebuehrenAbziehen(&giroKonto);Eigentlich nicht weiter schlimm, so kann doch der Programmierer die Funktion einfach nicht aufrufen, und schon wären alle zufrieden. Denkste!
Das Kontosystem wird für einen externen Kunden entwickelt, der selber vorhat, noch diverse Erweiterungen von seiner IT-Abteilung einfügen zu lassen. Deshalb - so meint der Chefprogrammierer - geht es "unter gar keinen Umständen", dass von einem Girokonto Gebühren abgezogen werden. Obwohl wir noch etwas missmutig dreinschauen, sind unsere grauen Zellen inzwischen darauf gekommen, dass es einfach untragbar wäre, wenn ein Programmierer der Bank einen Fehler machen würde und die Bankkunden somit fälschlicherweise Geld abgezogen bekommen. So nimmt die Bank das niemals ab, sondern nur "bombensicher und ohne Fehlermöglichkeiten". Also entscheiden wir uns für einen objektorientierten Ansatz, wo alle Konten nur genau so eingesetzt werden dürfen, wie sie auch sollen. Dazu verwenden wir das C++-Klassenkonzept, mit dem man sich seine eigenen Datentypen bauen und nach belieben konfigurieren kann, damit sie genau an die Aufgabe angepasst sind. Somit ist eine Klasse in etwa der "Bauplan" für alle "Variablen" (Instanzen/Objekte) unseres Typs (vgl.structs). Alles, was in der Klasse definiert wird, hat jedes Objekt später selbst (sagen wir mal, für sich, bei den Methoden stimmt das nicht so ganz, aber das bleibt für den Anfang mal ausser Acht). Unser zweiter Ansatz sieht dann so aus:class dispoKonto { // Leer. }; class giroKonto { // Leer. };Damit wollen wir praktisch "unser eigenes
doublemachen", das genau an die Kontoaufgabe angepasst ist. Dank C++ geht dies mit dem Klassenkonzept, und wir haben auch schon zwei leere definiert. Der Code sieht dann so aus:#include <iostream> class dispoKonto { // Leer. }; class giroKonto { // Leer. }; int main() { // Ersetzt "double dispoKonto = 111.19;". dispoKonto KontoNR1; // Ersetzt "double giroKonto = 1000.0;". giroKonto KontoNR2; }Sieht doch schonmal ganz gut aus, oder? Jetzt haben wir immerhin zwei unterschiedliche Kontotypen. Sprich: in
main()haben wir zwei "Variablen" (bei Klassen heisst das dann Objekte oder Instanzen) vom TypdispoKontoundgiroKontoerzeugt, wie wir vorher zwei Variablen vom Typdoubleerzeugt haben. Soweit, so gut. Jetzt wollen wir aber, dass die Konten- einen Kontostand beinhalten können
- einen sinnvollen Startwert annehmen können
- auch leer angelegt werden können
Jetzt kommen
privateundpublicsowie die Konstruktoren ins Spiel. Mitprivate/publicwird die Zugriffserlaubnis von aussen (sprich: dem normalen Programmablauf, etwamain()oder einer anderen Funktion) geregelt.class dispoKonto { public: private: double Kontostand; }; class giroKonto { public: private: double Kontostand; };Jetzt haben beide Kontentypen erstmal einen Kontostand im
private-Bereich, d.h. von aussen darf niemand zugreifen. Es darf z.B. nicht erlaubt sein, dass eine fremde Funktion den Kontostand ändert. Warum das wichtig ist? Wenn die Funktion versucht, von einem Girokonto 400 Euro abzuziehen, aber nur 200 Euro drauf sind, darf die Transaktion nicht ausgeführt werden, denn negative Kontostände sind beim Girokonto nicht erlaubt. Folgendes funktioniert folglich nicht:giroKonto KontoNR2; KontoNR2.Kontostand = 5000;Mit dem
.-Operator wird auf die Elementvariablen (so werden die Variablen einer Klasse u.a. genannt) zugegriffen. Da dieses aberprivateist, ist der Zugriff ungültig. Aus gutem Grund, wie wir später sehen werden. Nun wenden wir uns den Konstruktoren zu. Ein Konstruktor hat die Aufgabe, beim erzeugen eines Objekts für den korrekten Aufbau und die sinnvolle Initialisierung ALLER Elementvariablen zu sorgen. Also definieren wir uns jeweils zwei Konstruktoren (die den exakt gleichen Namen haben wie die Klasse selbst und keinen Rückgabetypen besitzen, nicht einmalvoid):class dispoKonto { public: dispoKonto(); // Standardkonstruktor. dispoKonto(double startKontostand); // Ueberladener Konstruktor mit einem // Uebergabeparameter. private: double Kontostand; }; // Implementierung/Definition der Konstruktoren, wie bei Funktionen auch. dispoKonto::dispoKonto() // Bedeutet so viel wie: von der Klasse dispoKonto // die Methode dispoKonto() (Konstruktor) definieren. { Kontostand = 0; // Standardwert für private Kontostand, ist hier // zulaessig, da der Konstruktor ja selbst Teil der // Klasse ist und nicht von "aussen" kommt. } dispoKonto::dispoKonto(double startKontostand) { Kontostand = startKontostand; // S.o. // Alternativ koennte man genauso schreiben: /* dispoKonto::Kontostand = startKontostand; */ // -> wuerde so viel bedeuten wie "setze die Elementvariable Kontostand // des dispoKonto-Objekts auf startKontostand. } class giroKonto { public: giroKonto() { giroKonto::Kontostand = 0; } // Inline-Konstruktordefinition. giroKonto(double startKontostand); private: double Kontostand; }; giroKonto::giroKonto(double startKontostand) { if (startKontostand >= 0.0) { // Hier koennen wir erstmals was fuer die Sicherheit tun! // Ab jetzt ist es unmoeglich, ein Girokonto mit negativem // Startguthaben anzulegen... giroKonto::Kontostand = startKontostand; } else { // Natuerlich muessen wir auch sicherstellen, dass selbst // bei fehlerhaften Eingaben alle Elementvarialben sinnvolle // Startwerte erhalten. giroKonto::Kontostand = 0.0; } }Und schon haben wir unsere neuen Datentypen dahingehend erweitert, dass sie sich wie normale integrale Datentypen (die Datentypen, die zum Sprachumfang dazugehören) verwenden lassen, was für externe User unserer Klassen (und uns selber natürlich auch ^^) unheimlich angenehm ist. Hier ein Beispiel der neuen Möglichkeiten:
dispoKonto kontoNR1; // Aufruf von dispoKonto::dispoKonto(). giroKonto kontoNR2; // Aufruf von giroKonto::giroKonto(). dispoKonto kontoNR3(42.3); // Aufruf von dispoKonto::dispoKonto(double). giroKonto kontoNR4(111.1); // Autruf von giroKonto::giroKonto(double). giroKonto kontoNR5(-12.0); // Aufruf von giroKonto::giroKonto(double).Die Werte, die dann in den Kontostand-Elementvariablen der einzelnen Objekte stehen, sind folgende:
kontoNR1: 0.0
kontoNR2: 0.0
kontoNR3: 42.3
kontoNR4: 111.1
kontoNR5: 0.0 // Hier greift die if-Abfrage, die negative Startguthaben
// fuer Girokonten unterbindet.OK, das alles funktioniert bereits, aber wie können wir den Kontostand nach Erzeugung des Objekts noch verändern, wenn der Kontostand doch
privateist? Genau hierfür gibt es die get-/set-Methoden. Wir werden dies mal nur für das Girokonto implementieren:class giroKonto { public: giroKonto() { giroKonto::Kontostand = 0; } giroKonto(double startKontostand); double getKontostand(); // get-Methode: Ueber sie kann Kontostand gelesen // werden. bool setKontostand(double neuerKontostand); // set-Methode: Ueber sie kann // Kontostand neu gesetzt // werden. private: double Kontostand; }; giroKonto::giroKonto(double startKontostand) { if (startKontostand >= 0.0) { giroKonto::Kontostand = startKontostand; } else { giroKonto::Kontostand = 0.0; } } double giroKonto::getKontostand() // Hier ist der Rueckgabetyp double, also // wird nur eine Kopie des Kontostands // zurueckgegeben, er bleibt weiterhin // unzugreifbar. { return giroKonto::Kontostand; // Das ist alles für get ;-) } bool giroKonto::setKontostand(double neuerKontostand) { if (neuerKontostand < 0.0) { return true; // Neuer Wert unzulaessig, der Rueckgabewert kann nun // in einer aufrufenden Funktion verwendet werden, um // ein if zwecks Fehlerbehandlung auszuloesen. } else { giroKonto::Kontostand = neuerKontostand; return false; // Alles in Ordnung, neues Setzen war erfolgreich. } }Damit lässt sich nun auch was vernünftiges anstellen:
// Oben hinzufuegen: #include <iostream> // Irgendwo im Code: double monatsgehalt = 1000.00; double neuerKontostand = kontoNR4.getKontostand() + monatsgehalt; // An einer anderen Stelle vielleicht: std::cout << kontoNR4.getKontostand() << std::endl;Soviel dazu. Und was fehlt jetzt noch? Richtig! Die
gebuehrenAbziehen()-Methode.
Hier ist sie:class giroKonto { public: giroKonto() { giroKonto::Kontostand = 0; } giroKonto(double startKontostand); double getKontostand(); bool setKontostand(double neuerKontostand); bool gebuehrenAbziehen(double hoehe); // Sie unterscheidet sich von der // anfaenglichen Funktion etwas... private: double Kontostand; }; giroKonto::giroKonto(double startKontostand) { if (startKontostand >= 0.0) { giroKonto::Kontostand = startKontostand; } else { giroKonto::Kontostand = 0.0; } } double giroKonto::getKontostand() { return giroKonto::Kontostand; } bool giroKonto::setKontostand(double neuerKontostand) { if (neuerKontostand < 0.0) { return true; } else { giroKonto::Kontostand = neuerKontostand; return false; } } bool giroKonto::gebuehrenAbziehen(double hoehe) // Das konkrete Konto muss // nicht mehr als Argument // uebergeben werden, da diese // Methode selbst Teil eines // konkreten Kontos ist - // dass stattdessen die Hoehe // der Gebuehr uebergeben wird, // dient lediglich Demonstrat- // ionszwecken... { // Hier auch nochmal die if-Abfragen: Endlich ist es nicht mehr // moeglich, einen negativen Kontostand bei Girokonten zu verursachen, // damit sollte der Projektleiter des anderen Programmiererteams // zufriedengestellt sein. if ((giroKonto::Kontostand - hoehe) < 0.0) { return true; } else { giroKonto::Kontostand -= hoehe; return false; } }In der Anwendung sieht das so aus:
giroKonto kontoNR4(10.00); giroKonto kontoNR5(10.00); if (kontoNR4.gebuehrenAbziehen(10.00)) // OK. { std::cout << "Fehler! Transaktion gescheitert...\n"; } if (kontoNR5.gebuehrenAbziehen(10.01)) // Transaktion fehlgeschlagen... { std::cout << "Fehler! Transaktion gescheitert...\n"; }Die Element-Methode
gebuehrenAbziehen()wurde jetzt nur fürgiroKontoimplementiert, beidispoKontolassen wir dieif-Abfrage gegen0einfach Weg, und schon kann der Dispokonto-Inhaber einen negativen Kontostand haben. Das ist genau das, was wir wollen - und wir können unseren Datentyp nun guten Gewissens weiterverteilen, da es praktisch ausgeschlossen ist, dass ihn jemand falsch verwendet (wenn wir sauber programmiert haben). Dieses Prinzip der Kapselung bringt sehr viele Vorteile, z.B. kann man nur bestimmte Zahlen als Variablenwerte zulassen etc., eine versehentliche Änderung des Kontostands durch unbefugte Codeteile kann größtenteils vermieden werden - um mal zwei zu nennen.Die Klasse kann nun beliebig erweitert und an die Umstände angepasst werden, mit einem überladenen
+-Operator und einem entsprechenden Kopierkonstruktor geht dann z.B. sowas:giroKonto kontoNR6; giroKonto kontoNR7(42.00); giroKonto kontoNR8(23.00); kontoNR6 = kontoNR7 + kontoNR8;Dabei könnten wir selbst genau definieren, was addiert werden soll - die Kontostände? Oder die Kreditlimits? Oder sollen vielleicht gar die Namen der Kontoinhaber verknüpft werden? Oder alle drei Vorschläge auf einmal? All das - und noch viel mehr - liegt nun in unserer Hand. Mit...
double Konto;...wäre das absolut undenkbar geblieben.
Aber wozu das Ganze jetzt eigentlich? Nun ja - auf diese Weise lassen sich viele Fehler im Vorfeld eliminieren, man betrachtet die Daten strukturiert als Objekte und nicht über das ganze Programm verstreut und hat überdies die Garantie, dass sie gar nicht falsch verwendet werden können (wenn man alles richtig gemacht hat), und obendrauf ist die Verwendung des eigenen Datentyps genau so, wie bei integralen Datentypen (mindestens aber genau so, wie man es erwarten würde). So können auch große Software-Projekte realisiert werden, was in C beispielsweise mit vielen einzelnen Funktionen relativ schwierig wäre. Und das hier ist lediglich die erste Stufe der OOP-Leiter: Vererbung, Polymorphismus, Templates, überladene Operatoren, ADTs, ...
Wie auch immer, ich hoffe, aufgrund der Codes oben ist halbwegs nachvollziehbar, wieso man Klassen bastelt und sich nicht mit ein paar Funktionen +
structs + Variablen zufrieden gibt.
-
@proggaholic:
Du schreibst ja ganze (gute!) Romane.

Wenn jetzt nicht alle Klarheitn beseitigt sind, dann weis ich auch nicht...
Greetz, Swordfish@proggaholic:
-
@Swordfish:
Dankeschön. Hoffen wir mal, dass nicht allzu viele Fehler drinne sind und es Stronghold_$niper halbwegs weiterhilft.
-
Hallo
@proggaholic: Das ist ja der Wahnsinn. Hast du das selber geschrieben, oder kopiert. Du hast doch auch in einem andern Post dir soviel Arbeit gemacht. Respekt.
chrische
-
du machst deinem namen alle ehre!
besonders nach den anderen antworten sich noch SO EINE ARBEIT zu machen....die FAQ-TAUGLICH ist, respekt!!!!!Auf jedenfall verweise ich in zukunft bei fragen zu klassen (anfängerprobs oop) auf diesen thread!
:schland:
-
@chrische5:
Ja, das stammt aus meiner Feder.
-
Wirklick cool gemacht.Wenn du das dann noch etwas erweitern würdest mit Vererbung, Abstrakten Basisklassen und Virtuellen Methoden, was alles sehr schön mit deinem Beispiel gezeigt werden kann, sollte man so etwas auf jeden Fall in die FAQ stecken oder vielleicht zu den Artikeln

-
Klar, kann ich bei Gelegenheit mal machen, allerdings würde das ein paar Tage dauern...erinnert mich bitte hin und wieder daran.