Zum Inhalt springen

Methoden in C++


alligator

Empfohlene Beiträge

Servus.

Isch hab da mal ne Frage zu Methoden ...

Beispiel:

float Konto::Zins_Lesen()

{

float Konto::Zins_Berechnung (float Zinssatz)

{

float zins;

zins = (Kontostand * Zinssatz) / 100;

Kontostand += zins;

return (zins);

}

return (Zins_Berechnung(1.5));

}

Kann ich eine Methode in einer Methode deklarieren ? Bei mir bringt er mir da einen Fehler:

Fehler: nr44test.CPP(53,3): Identifier 'Zins_Berechnung' cannot have a type qualifier

Falls es nicht geht(was ich jetzt mal stark vermute), warum nicht ?

Wäre echt super, wenn mir das jmd. verständlich erklären kann :D

cu

alligator

Link zu diesem Kommentar
Auf anderen Seiten teilen

Methoden müssen immer einer Klasse klar zugeordnet sein. Eine "versteckte" Methode darf einfach nicht absolut unsichtbar sein - es wäre nämlich schwer rekonstruierbar, wo sie sich befindet und wie die Implementation davon sein soll. Soll etwas versteckt sein hat man ja noch immer die private: und protected:-Sections um die Methode nach außen hin zu verstecken.

Es gibt aber noch einen Zweiten Grund, warum in einer Methode eine weitere Methode nicht deklariert werden darf. Eine Methode ist ein Unterprogrammaufruf. Würdest Du eine zweite Methode innerhalb einer definieren würde das bedeuten Du erschaffst eine Alias-Methode. Das bedeutet eine Methode willst Du unter zwei Namen erzeugen!!! (das hat aber nichts mit der unterschiedlichen Namensvergabe der zwei Methoden zu tun, sondern mit der Deklaration).

Eine Methode ist nichts weiter als ein Unterprogramm, welches aufgerufen werden kann. Und ein Unterprogramm kann zwar weitere Unterprogramme aufrufen oder sich selbst, aber nur einmal unter einem Namen existieren (lediglich ein Überschreiben der Methode ist erlaubt, d.h. die Aufrufparameter dürfen variieren).

Korrekt wäre es also Zins_Berechnung() noch einmal extra als eigene Methode zu definieren und danach darfst Du dann ohne weiteres von Zins_Lesen() aus Zins_Berechnung() aufrufen!

Eine Methode muß einer Klasse ganz klar zugeordnet werden können. Deshalb ist ein "verstecken" vor der Klasse verboten.

Ich weiß ja jetzt nicht wie weit Du Dich mit Klassen schon beschäftigt hast, aber wenn man eine Virtuelle Basis-Klasse (sagen wir mal Tier erzeugst mit der Methode Geraeuschmachen()) hat, davon zwei unterschiedliche Klassen (Hund und Katze) ableitet (die Methode Geräuschemachen() beim Hund mit einer Ausgabe "Kläffen" und bei der Katze mit "Miauen" überschreibst) und davon wieder per Mehrfachvererbung eine zusammengesetzte Klasse Katzenhund() (diamantförmige Vererbung), dann tritt (fast) dasselbe Problem auf. Funktionen der virtuellen Tier-Klasse existieren im Katzenhund zweimal - nämlich vom Hund und von der Katze. Wenn die Funktion vom Katzenhund aufgerufen wird muß ich ihm ganz klar sagen ob sie vom Hund oder von der Katze aufgerufen werden soll. Ein Hund kann nämlich nur Kläffen und eine Katze nur Miauen! Dieselbe Methode kann auch hier nur durch erzeugen unterschiedlicher Objekte und der ganz klaren Aufrufunterscheidung durch den Qualifier Katze:: oder Hund:: existieren.

Es gibt aber bei der Klassendefinition eine Möglichkeit eine Funktion nach außen hin absolut versteckt zu halten: Durch Klassendeklarationen innerhalb einer Klasse!!!

Ich habe mal mit Deinem Beispiel sowas gemacht um es zu zeigen:

class Konto

{

class inneresKonto

{

public:

inneresKonto(){};

~inneresKonto(){};

float ZinsBerechnung(float Zinssatz,float Kontostand)

{

float zins;

zins = (Kontostand * Zinssatz) / 100;

Kontostand += zins;

return zins;

}

};

public:

Konto(float x=0):Kontostand(x){}

~Konto(){};

float ZinsLesen(float zins=1.5){return ki.ZinsBerechnung(zins,Kontostand);}

private:

inneresKonto ki; // trotz allem muß ein Objekt der inneren Klasse als Member-Objekt erzeugt werden!

float Kontostand;

};

main

{

Konto k(200); // Mit Kontostand 200 initialisieren

float zins;

zins=k.ZinsLesen(); // Zins 1,5% auslesen

zins=k.ZinsLesen(5); // eigens definierten Zinssatz auslesen

}

Funktionsdeklarationen in Funktionen sind halt einfach unzulässig!!! Willst Du Aliases von Funktionen erreichen, so kann man das allenfalls noch mit Namespaces erreichen - aber halt nicht innerhalb von Funktionsdeklarationen!!!

War das einigermaßen verständlich?

pom.jpg

Link zu diesem Kommentar
Auf anderen Seiten teilen

Weil ich jetzt das mit der Diamantvererbung so breit getreten habe und Du bestimmt nur 1000 Fragen dazu hast, habe ich mal ein BingDingDong-Beispiel gemacht:

class bing

{

public:

bing(int x=0):zahl(0){}

virtual ~bing(){};

virtual void set(const int x){zahl=x;}

virtual int get() const {return zahl;}

private:

int zahl;

};

class ding : virtual bing

{

public:

ding(){bing::bing();};

virtual ~ding(){bing::~bing();};

void set(const int x){bing::set(x);}

void setown(const int x) {zahl=x;}

int get() const {return bing::get();}

int getown() const {return zahl;}

private:

int zahl;

};

class dong : virtual bing

{

public:

dong(){bing::bing();};

virtual ~dong(){bing::~bing();};

void set(const int x){dong::zahl=x;}

int get() const {return dong::zahl;}

private:

int zahl;

};

class dingdong : public ding, public dong

{

public:

dingdong(){ding();}

virtual ~dingdong(){ding();}

void set(const int x) {ding::set(x);}

void set2(const int x){dong::set(x);}

int get() const {return ding::get();}

int get2() const {return dong::get();}

};

main()

{

dingdong x1;

int v1;

x1.set2(3);

x1.setown(5);

v1=x1.get2();

v1=x1.get();

x1.set(2);

v1=x1.get2();

v1=x1.get();

v1=x1.getown();

}

Durch diese Konstruktion mit der virtuellen Basisklasse kann man übrigens wunderbar Schnittstellen definieren - Methoden der Basisklasse erzwingen, verstecken (Hiding) - und erweitern!

Hoffentlich bekommst Du keinen Wadenkrampf von dem Code... =8-D

Link zu diesem Kommentar
Auf anderen Seiten teilen

Servus Crush.

Erstmal thx für deine ausführliche Erklärungen.

Ich hab jetzt erst grad angefangen mit Klassen, Methoden und so.

Also hab noch kein Plam vom Vererbung und so. Und ich hab auch nix gemacht mit Klassen in Klassen, was ja anscheinen geht :rolleyes:

Ich hab zwar jetzt verstanden, dass es nicht geht, aber blick immer noch nicht ganz warum.

Denn diese Methode " float Konto::Zins_Berechnung (float Zinssatz)" wird in meinem Programm nicht direkt aufgerufen sondern nur "float Konto::Zins_Lesen()" und die weiss ja dann das es die andere Methode gibt ähh oder so.

Bsp:

case 3:

cout << "\n Ihr Zinsertrag: " << Testkonto.Zins_Lesen();

und es wird nie

Testkonto.Zins_Berechnung ();

aufgerufen!

cu

alligator

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn man das ganze aus Assembler-Sicht betrachtet wird einem klar, warum das nicht geht. Ein Funktionsaufruf ist nichts weiter als ein Unterprogram. Programmtechnisch wird in ein Unterprogramm reingesprungen und nach dessen Abarbeitung wieder zurückgekehrt. Innerhalb eines Unterprogramms kann halt leider bestenfalls in ein neues Unterprogramm reingesprungen werden, aber auch da muß die Adresse eindeutig sein. Variablen und Funktionen müssen immer eindeutig sein, daß der Compiler weiß wo wann in welche Methode reingesprungen werden muß. Deshalb ist es nicht erlaubt eine Funktion oder eine Variable im selben Adress-, Namensraum oder Klasse zu definieren! Die einzigste Methode die Dir bleiben würde um sowas ähnliches zu schaffen wäre es Funktionszeiger unterschiedlicher Bezeichnung zu erzeugen und auf dieselbe Methode zeigen zu lassen. Mal abgesehen, daß es (logisch) ziemlich sinnlos ist an einer Stelle einer Methode zwei unterschiedliche Namen zu geben - das würde höchstens der allgemeinen Verwirrung beitragen bei wenn es möglich wäre.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 2 Wochen später...

Ein Verkünder und Huldiger der objektorientierten Softwareentwicklung, die da heißt: Keine Sprünge, nur unter Verschachtelungen sind erlaubt oder Aufrufe per Exceptions oder Messages. Das war die Story mit dem "Spaghetti-Code", als man früher wild im Programm durch die Gegend gejumped ist, wodurch halt viele unerfahrene Programmierer nicht mehr mitgekommen sind, was ein anderer da überhaupt getan hat. Trotzdem konnte man früher schon alles auch mit GOTO programmieren und wenn man keine Pfeife war lief´s auch irgendwie. Bei C++ gibts das ja noch im Sprachschatz, weil die Leute drauf bestanden haben, aber bei Java ist damit dann entgültig Schluß. Das GOTO ist mit ein Grund, warum viele C++ nicht als echte objektorientierte Sprache betrachten (mal abgesehen von den internen Datentypen, die nicht wie bei Java in Klassen vorliegen). Dabei hat glaub auch niemand behauptet, daß C++ eine rein objektorientierte Sprache sein soll, sondern lediglich ein C mit objektorientierter Erweiterung.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Also das mit den konstruktoren musst du dir wohl nochmal ansehen, Crush.

class bing

{

public:

bing(int x=0):zahl(0){}

virtual ~bing(){};

virtual void set(const int x){zahl=x;}

virtual int get() const {return zahl;}

private:

int zahl;

};

class ding : virtual bing

{

public:

ding(){bing::bing();}; // basis konstruktor wird zweimal aufgerufen

virtual ~ding(){bing::~bing();}; // subobject vom typ bing wird zweimal zerstört

void set(const int x){bing::set(x);}

void setown(const int x) {zahl=x;}

int get() const {return bing::get();}

int getown() const {return zahl;}

private:

int zahl;

};

ich hab mal die zwei klassen als beispiel genommen. wozu wird im ctor von ding der basis-ctor expilzit aufgerufen? wenn das programm die öffnende geschweifte Klammer des ctors von ding erreicht ist das komplette ding - objekt schon konstruiert und der basis ctor wurde bereits aufgerufen. der compiler generiert standardmäßig einen aufruf des default ctors der Basisklasse. wenn man das nicht möchte oder es nicht geht, muss man den aufruf selbst explizit in der initialisierungsliste VOR der geschweiften klammer angeben

ding() : bing("ding ruft bing") {}

// unter der annahme bing hat einen ctor bing(char* Msg);

im vorliegenden fall

ding(){bing::bing();};

wird innerhalb das anweisungsblocks ein namenloses objekt vom typ bing erzeugt, das beim verlassen des blocks sofort wieder zerstört wird (im debugger kann man den destruktor aufruf von bing verfolgen).

ähnlich ist es beim destruktor. NACHDEM ~ding() fertig ist wird automatisch ~bing() aufgerufen. der explizite aufruf innerhalb von ~ding() ist etwa so, als wollte man den keller vor dem dach abreißen.

der folgende code würde zum beispiel abstürzen

class bing

{

public:

bing(int x=0):zahl(0){pFoo = new int(10);}

virtual ~bing(){delete pFoo;}

private:

int zahl;

int *pFoo;

};

class ding : virtual bing

{

public:

ding(){bing::bing();};

virtual ~ding(){bing::~bing();}; // zweimal delete pFoo

private:

int zahl;

};

man könnte das problem umgehen

virtual ~bing(){if (pFoo) {delete pFoo; pFoo = NULL;};}

aber die eigentliche logik ist nicht korrekt. richtiger wäre ding so zu definieren

class ding : virtual bing

{

public:

ding(){}

virtual ~ding(){}

private:

int zahl;

};

und noch was zum goto. ich selbst hab mein letztes goto vor vielen jahren (damals noch in basic) verwendet. aber es gibt durchaus sinnvolle verwendungen, wie ich erst jetzt wieder in Tom Archers 'Inside C#' gelesen habe (ja c# hat ein goto). er beschreibt, dass der goto befehl in ungnade fiel mit der veröffentlichung des artikels 'Go To Statement Considered Harmful' (Edsger W. Dijkstra, 1968) http://www.acm.org/classics/oct95/

in einem switch block zum beispiel könnte es sinnvoll sein ein goto zu verwenden.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Stimmt! Ich habe da wohl leichtsinnig einen Denkfehler begangen. Geht man mit F11 die Konstruktion durch sieht man, daß der Bing-Konstruktor tatsächlich zweimal hintereinander aufgerufen wird. Ich habe vorher auch noch nie diese komische Vererbungsart verwendet, allerdings wollte ich das eh mal testweise machen um zu sehen wie´s läuft. Da ich keine zusätzlichen Speicherreservierungen gemacht habe ist auch nix abgestürzt und alles scheinbar ordentlich gelaufen - also war kein Grund gegeben, da weiter nachzuschauen. Du bist ein aufmerksamer Leser!!! Man sollte tatsächlich innerhalb der Initialisierungsliste alles ablaufen lassen, damit es keinen Ärger gibt.

Ich persönlich halte GOTO überhaupt nicht für falsch. Und das beste Argument für ein Goto ist eine mehrfach verschachtelte Switch-Anweisung, bei der im inneresten Block ein rekursives Abbruchkriterium benötigt wird - ohne GOTO absolut unübersichtlich mit dramatisch anwachsendem unnötigen Code, der wesentlich unübersichtlicher wäre als der Sprungbefehl selbst. In einem einzelnen Switch-Block wäre das nicht unbedingt so sinnvoll, aber bei iterativen Funktionsblöcken hat ein Goto durchaus seine Existenzberechtigung.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...