Hi,
ich habe mal eine Frage zu diesem C++, was ich grade zu lernen versuche.
Ich habe folgende Klasse
class Punkt
{
private: int Koordinate [2];
public: void set_x (int);
void set_y (int);
int get_x ();
int get_y ();
// Konstruktor
Punkt (int K_x = 0, int K_y = 0);
// Destructor
~Punkt ();
}
Also, jetzt hab ich meine Klasse Punkt definiert, mit einer privaten
Variable Koordinaten für X/Y als Array und jeweils eine Funktion get und
set, um die Variablen zu setzen und auszulesen.
Mit dem Konstruktor kann ich vorher Werte definieren, mit denen die
Variablen der Klasse vorher initialisiert werden können. Als nächstes
kann ich die einzelnen Funktionen nun beschreiben, was sie machen sollen
void Punkt :: set_x (int x_Koord)
{
Koordinate [0] = x_Koord;
}
int Punkt :: get_x ()
{
return Koordinate [0];
}
// Konstruktorfunktion
Punkt :: Punkt (int K_x, int K_y)
{
Koordinate [0] = K_x;
Koordinate [1] = K_y;
}
// Destructor
Punkt :: ~Punkt ()
{
delete Koordinate;
}
Meine Frage dazu ist folgende ---> wann genau wird der Destructor
aufgerufen? Der Konstruktor muss doch an der Stelle aufgerufen werden,
wenn ich die Klasse Punkt in einer Funktion definiere also
Punkt Mittelpunkt;
und wenn ich dann schreibe
cout << Mittelpunkt.get_x;
dann müsste doch der Wert Null erscheinen, weil ich ihn beim Beschreiben
der Klasse oben unter class Punkt ... mit Null gesetzt habe.
Aber wann wird der Destruktor aufgerufen? Wird er automatisch dann
aufgerufen, wenn z.B. die Funktion, in der ich Mittelpunkt definiert
hab, beendet wird?
Da musst du dich in C++ selbst drum kümmern. Wenn du eben deinen
Mittelpunkt nicht mehr brauchst.
"Modernere" Sprachen wie Java haben einen Garbage Kollektor und erkennen
automatisch ob eine Instanz noch gebraucht wird.
ms schrieb:> Da musst du dich in C++ selbst drum kümmern.
nein muss man nicht, nur wenn man objekte mit new anlegt muss man sie
wieder mit delete löschen.
Ich finde das verhalten von C++ sehr gut, im vergleich zu den Garbage
Kollektor sprachen.
Wasserfallkarzinom schrieb:> Der Konstruktor muss doch an der Stelle aufgerufen werden,> wenn ich die Klasse Punkt in einer Funktion definiere also
Wenn der Programmfluß an der Stelle vorbeikommt, an der
eine Variable von dem Typ definiert ist.
Also im Prinzip ja, mit der Einchränkung daß deine
Formulierung nicht stimmt, weil du nicht die Klasse nochmal
definierst, sondern ein Objekt davon, auch Instanz genannt.
> Aber wann wird der Destruktor aufgerufen? Wird er automatisch dann
Genau dann, wenn der Gültigkeitsbereich einer Variable verlassen
wird, also wenn die schließende geschweifte Klammer des
innersten Blocks erreicht wird, der die Defintion der Variable
umgibt.
(Soweit gilt das für automatische Variablen.
Bei allokierten Objekten wird der Konstruktor aufgerufen
beim new, der Destruktor bei delete bzw. bei regulärem
Progerammende. Bei statischen lokalen Variablen der ctor
beim ersten Durchlauf der Definition, der dtor bei
Programmende. Bei globalen Variablen vor Eintritt in main()
bzw. bei Programmende.)
Steht in jedem besseren C++-Buch...
warum delete Koordinate?
Du hast den Member ja auch nicht mit new allokiert!
Solange du nichts mit new innerhalb der Klasse allokierst, brauchst du
auch nichts deleten. Ganz im Gegenteil: So ist das ein schwerer Fehler!
Klaus Wachtler schrieb:> der Destruktor bei delete bzw. bei regulärem Progerammende
das glaube ich nicht! Es gibt ja keine Runtime die objekte kennt. In C
ist es nur ein zeiger und dort macht niemand beim programmende eine
delete.
Zu jedem new muss man auch ein delete aufrufen(oder eine smartptr
verwenden) sonst wird auch keine Destruktor aufgerufen.
Peter schrieb:> Klaus Wachtler schrieb:>> der Destruktor bei delete bzw. bei regulärem Progerammende> das glaube ich nicht! Es gibt ja keine Runtime die objekte kennt. In C> ist es nur ein zeiger und dort macht niemand beim programmende eine> delete.> Zu jedem new muss man auch ein delete aufrufen(oder eine smartptr> verwenden) sonst wird auch keine Destruktor aufgerufen.
ups, sorry!
Du hast natürlich recht, bei new-Objekten wird auch bei Programmende
kein dtor aufgerufen.
Da war ich in Gedanken wohl schon bei den statischen :-(
Zur Laufzeit belegten Speicher auf dem Stack löscht man natürlich nicht
von Hand. Zur Laufzeit allokierten Speicher auf dem Heap sollte man
tunlichst löschen, sofern man nicht ein nettes kleines Speicherleck
haben will.
Ich danke Euch für Eure Hilfe. Ich hab das Programm jetzt mal so
eingetippt, hab auch im Konstruktor das new ergänzt, aber es kommen
immer noch viele Fehlermeldungen :-(, die hab ich mal mit sternchen
unten dranmakiert. Was mache ich denn noch falsch ?
// Versuchsprogramm
#include <iostream>
class Punkt
{
private: int *Koordinate;
public: void set_x (int x);
void set_y (int y);
int get_x ();
int get_y ();
Punkt (int x = 0, int y = 0);
~ Punkt ();
}
void Punkt::set_x (int x)
{ ***
Koordinate [0] = x ;
}
void Punkt :: set_y (int y)
{
Koordinate [1] = y;
}
int Punkt :: get_x ()
{
return Koordinate [0];
}
int Punkt :: get_y ()
{
return Koordinate [1];
}
Punkt :: Punkt (int x, int y)
{
Koordinate = new int [2];
Koordinate [0] = x;
Koordinate [1] = y;
}
Punkt :: ~Punkt ()
{
delete Koordinate;
}
int main ()
{
Punkt Mittelpunkt;
return 0;
}
***
error: new types may not be defined in a return-typ
note: (perhaps a semicolon is missing after the definition of Punkt)
error two or more data types in declaratiopn of set_x
error ...
mach es lieber ohne das new im Konstruktor!.
Jezt hast du ein Problem wenn du das Objekt kopierst.
1
intmain()
2
{
3
PunktMittelpunkt;
4
5
Punkt2Mittelpunkt=Punkt;
6
7
return0;
8
}
und schon stützt dein Programm ab, weil du den Zeiger 2mal freigeben
willst. Wenn man zeiger in Objekten verwendet sollte man genau wissen
mas man macht.
Auch wenn ich verstehe, dass es sich hier um ein Übungsbeispiel handelt,
sollte man es trotzdem ansprechen:
+-----------------------------------------------------------+
| |
| Allokiere nichts mit new, wenn du nicht musst! |
| |
+-----------------------------------------------------------+
In deinem Beispiel musst du nicht. Mit
1
classPunkt
2
{
3
public:
4
Punkt(intx=0,inty=0);
5
6
voidset_x(intx);
7
voidset_y(inty);
8
intget_x();
9
intget_y();
10
11
private:
12
intKoordinate[2];
13
};
ist deine Klasse perfekt und du brauchst auch keinen Destruktor explizit
schreiben.
Wenn du nämlich innerhalb der Klasse mittels new etwas allokierst, dann
musst du dich auch um die 'Rule of 3' kümmern:
Wenn eine Klasse mindestens einen der 3
* Destruktor
* Copy Constructor
* Zuweisungsoperator
hat, dann ist es sehr wahrscheinlich, dass alle 3 notwendig wären.
Deine Klasse hat einen Destruktor (aus gutem Grund) und sie müsste
eigentlich auch einen von dir geschriebenen Copy Constructor und einen
Zuweisungsoperator haben, um mit ihr fehlerfrei arbeiten zu können. Hat
sie aber nicht. Wenn es daher bei der Verwendung deiner Klasse dazu
kommt, dass eines der beiden benutzt wird, und sei es nur auf diese Art
1
intmain()
2
{
3
Punkta;
4
Punktb;
5
6
a=b;
7
}
dann hat deine Klasse mit der dynamischen Allokierung jetzt einen
schweren Fehler gebaut, weil die vom Compiler generierte
Zuweisungsoperation das Falsche macht.
PS: Das hier
Punkt :: ~Punkt ()
{
delete Koordinate;
}
ist sowieso falsch.
Du allokierst mit einem Array-new
Koordinate = new int[2];
also musst du auch ein Array-delete verwenden
delete [] Koordinate;
Karl heinz Buchegger schrieb:> PS: Das hier> Punkt :: ~Punkt ()> {> delete Koordinate;> }
Ich hatte gedacht, da ich doch in dem Bereich class Punkt Koordinate als
Zeiger definiert hab, kann ich das so schreiben ohne die Arrayklammer
[], da doch das Dingen ein Pointer sein soll, oder hab ich das falsch
gedacht?
Peter schrieb:> int main ()> {> Punkt Mittelpunkt;>> Punkt2 Mittelpunkt = Punkt;>> return 0;> }
Ich verstehe dass jetzt leider auch noch nicht, warum wird erst
geschrieben Punkt Mittelpunkt, und dann Punkt2 Mittelpunkt = Punkt, was
passiert denn dann? Dass heisst doch irgendwie Mittelpunkt sei eine
Variable vom Typ Punkt2 mit dem Inhalt Punkt, aber es gibt doch in dem
Programm bisher kein Punkt2?
Wasserfallkarzinom schrieb:> Karl heinz Buchegger schrieb:>> PS: Das hier>> Punkt :: ~Punkt ()>> {>> delete Koordinate;>> }>> Ich hatte gedacht, da ich doch in dem Bereich class Punkt Koordinate als> Zeiger definiert hab, kann ich das so schreiben ohne die Arrayklammer> [], da doch das Dingen ein Pointer sein soll, oder hab ich das falsch> gedacht?
Die Sache ist eigentlich ganz einfach und eindeutig
Benutzt du
new
dann lautet das Gegenstück dazu
delete
benutzt du
new []
dann lautet das Gegenstück dazu
delete []
Zusammengefasst:
new - delete
new [] - delete []
Ich wünschte, alle Regeln in C++ wären so dermassen eindeutig und klar
und völlig ohne Ausnahmen :-)
(Das einzige was ich mir noch mehr wünschen würde wäre, dass es bei
delete keine 2 Formen geben müsste. Aber aus Gründen, die hier nicht
weiter diskutiert werden sollen, ist das nun mal so)
Wie allokierst du?
Versuch es doch mal bitte mit einem C++-Buch.
Es ist relativ ineffizient, jedem alles extra erklären zu müssen,
was schon in 531 Büchern steht und dort nur gelesen werden muss.
Hier müsstest du es doch auch lesen...
Wasserfallkarzinom schrieb:> Peter schrieb:>> int main ()>> {>> Punkt Mittelpunkt;>>>> Punkt2 Mittelpunkt = Punkt;>>>> return 0;>> }>> Ich verstehe dass jetzt leider auch noch nicht
Peter hat gepatzt :-)
er wollte eigentlich das hier schreiben
ach so, du meinst das jetzt bezogen auf die klasse, die du mir so
geschickt hast, weil du geschrieben hast
private:
int Koordinate [2];, darum folglich delete [] Koordinate.
Und wenn ich geschrieben hab
int *Koordinate;
dann hätte ich nur schreiben müssen
delete Koordinate, oder? weil ich das bei mir ja noch mit int
*Koordinate hätte stehen lassen müssen.
Ich hab mal noch eine andere Frage. Wenn ich jetzt folgendes mache: Im
Class-Bereich:
private: int *Koordinate;
und dann im Konstruktor:
Koordinate = new int [2];
dann müsste das doch auch gleich sein, oder? und dann kann ich doch
schreiben im Destruktor:
delete Koordinate?
Ich bin da immer noch ein bißchen durcheinander, sorry. Ich versuche das
erstmal, es zu verstehen. Ich verstehe z.B. auch noch nicht, warum man
im Class-Bereich bei der Definition schreibt:
public: void set_x (int);
also, man sagt also damit jetzt dem Compiler, set_x kann eine Variable
vom Typ int annehmen, oder?
wenn ich jetzt folgendes machen würde:
public: void set_x (int);
void set_x (float);
kann ich dann später bei den Funktionen, wo ich schreibe, was denn die
Funktion set_x machen soll, dann auch zwei FUnktionen schreiben
Punkt :: set_x (int x) {...};
Punkt :: set_x (float x) {...};
und er nimmt dann einfach bei einem Float die zweite Funktion und bei
int die erste Funktion, ist dass dann auch so?
Sind denn eigentlich diese beiden Dinger identisch?
*(Koordinate+1) = Koordinate [1], also dass ich bei Zuweisungen sowohl
schreiben kann
*(Koordinate+1) = y und Koordinate[1] = y; wäre dass identisch? Wenn ja,
wann nehme ich die Zuweisung mit dem Pointer und wann mit dem Arrayfeld?
Wasserfallkarzinom schrieb:> ach so, du meinst das jetzt bezogen auf die klasse, die du mir so> geschickt hast,
nein
> weil du geschrieben hast>>> private:> int Koordinate [2];, darum folglich delete [] Koordinate.
in dem Fall braucht es überhaupt kein delete
>> Und wenn ich geschrieben hab> int *Koordinate;> dann hätte ich nur schreiben müssen> delete Koordinate, oder?
Ist das wirklich so schwer?
Du schreibst.
1
Punkt::Punkt(intx,inty)
2
{
3
Koordinate=newint[2];
4
Koordinate[0]=x;
5
Koordinate[1]=y;
6
}
d.h. du allokierst ein Array mittles new[]
also musst du beim Freigeben auch die Schreibweise für Arrays benutzen
1
Punkt::~Punkt()
2
{
3
delete[]Koordinate;
4
}
Das ist doch wirklich nicht schwer!
> weil ich das bei mir ja noch mit int> *Koordinate hätte stehen lassen müssen.
Der Pointer hat damit nichts zu tun.
Es geht darum worauf der Pointer zeigt:
ein einzelnes Objekt oder ein Array von Objekten
Ich möchte Euch sehr herzlich für Eure Erklärungen bedanken. Natürlich
habe ich auch ein Buch und auch ein PDF-Script, anhand dessen versuche
ich, dass ja auch so ein bißchen zu kapieren, aber manchmal verstehe ich
das noch nicht, was da auch in dem Buch bzw. Script drinnen steht, dann
ist es sehr hilfreich, wenn man eine Bestätigung bekommt, ob man etwas
richtig verstanden hat, wie z.B. meine Frage eben, ob diese Sache mit
den zwei Sachen einmal gleich ist und so und das mit dem Semikolon
hinter der Klasse, die ich bei der einen Fehlermeldung einfach nicht
erkannt hab, dass hab ich halt einfach so nicht gesehen, weil ich da
noch recht frisch bin, für Euch ist das wahrscheinlich standardmaessig
so, dass ihr hinter einem Ende einer Klasse automatisch ein Semikolon
setzt und so einen Fehler sofort seht, ich war eben als halt bei der
Funktion darunter, weil dort der Compiler den Fehler angezeigt hat, es
aber dann auf die vorige Klasse gemeint hatte und ich hätte
wahrscheinlich noch 2 Stunden davor gehangen und wäre nicht
weitergekommen mit dem Compilieren, wenn Ihr mir nicht geholfen hättet .
Wasserfallkarzinom schrieb:> public: void set_x (int);>> also, man sagt also damit jetzt dem Compiler, set_x kann eine Variable> vom Typ int annehmen, oder?
Ja.
set_x ist eine Funktion, die einen int als Argument annimmt und nichts
zurückliefert.
Aber diese Schreibweise solltest du dir gleich gar nicht angewöhnen. Gib
dem Argument gleich einen Namen, damit derjenige, der die
Klassenbeschreibung liest, auch weiß was Sache ist.
Oder kennst du dich aus, bei folgendem Funktionskopf
double CalculateNettoPreis( double, double );
Was ist das erste Argument, was ist das zweite Argument?
Im Vergleich dazu
double CalculateNettoPres( double BruttoPreis, double Steuersatz );
Welche der beiden Formen ist klarer in der Benutzung?
> wenn ich jetzt folgendes machen würde:>> public: void set_x (int);> void set_x (float);>> kann ich dann später bei den Funktionen, wo ich schreibe, was denn die> Funktion set_x machen soll, dann auch zwei FUnktionen schreiben>> Punkt :: set_x (int x) {...};> Punkt :: set_x (float x) {...};
du kannst nicht nur, du musst sogar. Das erste ist innerhalb der
Klassendeklaration. In der ist alles zusammengefasst, was ein Benutzer
der Klasse wissen muss. Da drinnen ist quasi das Versprechen einer
Klasse an die Aussenwelt "Das alles kann ich". Die Aussenwelt muss nur
wissen, dass es 2 Funktionen set_x gibt. Aber nur das Versprechen "Es
gibt da 2 Funktionen" ist natürlich zu wenig. Die beiden Funktionen
müssen auch existieren!
> und er nimmt dann einfach bei einem Float die zweite Funktion und bei> int die erste Funktion, ist dass dann auch so?
Ja, das ist so
> Sind denn eigentlich diese beiden Dinger identisch?>> *(Koordinate+1) = Koordinate [1], also dass ich bei Zuweisungen sowohl> schreiben kann
Ja. sind identisch.
Aber tu dir selbst einen Gefallen, Benutze nicht die erste Schreibweise,
solange du nicht einen guten Grund dafür hast (und nein, dass Koordinate
ein Pointer ist, ist kein guter Grund)
Wasserfallkarzinom schrieb:> *(Koordinate+1) = y und Koordinate[1] = y; wäre dass identisch? Wenn ja,> wann nehme ich die Zuweisung mit dem Pointer und wann mit dem Arrayfeld?
Die Variante mit Feld nimmt man, wenn es sich um ein Feld handelt,
die andere Variante eher, wenn es kein Feld ist, sondern nur ein
Zeiger mit irgendeiner Rechnerei.
Dem Compiler ist es egal, aber mit der Schreibweise kann man
ausdrücken, was man machen will - für einen menschlichen Leser,
Die dritte Variante wäre 1[Koordinate] = y und ebenfalls
gleichwertig.
Die nimmt man aber nie, außer um andere zu ärgern oder um zu
zeigen, was man alles für tolle Sachen beherrscht.
Karl heinz Buchegger schrieb:> Aber diese Schreibweise solltest du dir gleich gar nicht angewöhnen. Gib> dem Argument gleich einen Namen, damit derjenige, der die> Klassenbeschreibung liest, auch weiß was Sache ist.>> Oder kennst du dich aus, bei folgendem Funktionskopf>> double CalculateNettoPreis( double, double );>> Was ist das erste Argument, was ist das zweite Argument?>> Im Vergleich dazu>> double CalculateNettoPres( double BruttoPreis, double Steuersatz );>> Welche der beiden Formen ist klarer in der Benutzung?
Naja, stimmt, die zweite Variante ist die bessere, weil man dann sieht,
dass ein Double-Wert zurückgeliefert werden kann und welche beiden
Argumente übergeben werden. Kann ich denn bei der Deklaration die
Anmerkungen BruttoPreis und Steuersatz als Kommentare betrachten, oder
muss ich dann auch bei dem Programmteil dann, wo ich drinnen stehen hab
XYZ :: CalculateNettoPreis (double BruttoPreis, ...)
oder kann ich dann auch sowas reinschreiben
XYZ :: CalculateNettoPreis (double PreisBrutto, ...)
Ich habe jetzt an dem Programm von mir noch ein bißchen rumprobiert und
ich hab einfach mal ausprobiert, wenn ich dann verschiedene Namen hab im
Class-Bereich und in dem Methodendefinitionsbereich, wo ich sage, was
die Funktionen eigentlich machen sollen:
// Versuchsprogramm
#include <iostream>
class Punkt
{
private: int *Koordinate;
public: void set_x (int x);
void set_y (int y);
void ausgeben ();
int get_x ();
int get_y ();
Punkt (int x = 10, int y = 0);
~Punkt ();
};
void Punkt :: ausgeben ()
{
std::cout << "\n X_Koord: " << get_x () << " Y_Koord: " << get_y ();
}
void Punkt :: set_x (int x)
{
Koordinate [0] = x ;
}
void Punkt :: set_y (int y)
{
Koordinate [1] = y;
}
int Punkt :: get_x ()
{
return *(Koordinate);
}
int Punkt :: get_y ()
{
return *(Koordinate+1);
}
Punkt :: Punkt (int xd, int y)
{
Koordinate = new int [2];
*(Koordinate) = xd;
*(Koordinate+1) = y;
}
Punkt :: ~Punkt ()
{
delete Koordinate;
std::cout << "Im Destruktor ";
}
int main ()
{
Punkt Mittelpunkt;
std::cout << " \n Wert: " << Mittelpunkt.get_x ();
std::cout << "\n";
Mittelpunkt.ausgeben ();
Mittelpunkt.set_x (200);
Mittelpunkt.set_y (300);
Mittelpunkt.ausgeben ();
return 0;
}
Karl heinz Buchegger schrieb:>> Sind denn eigentlich diese beiden Dinger identisch?>>>> *(Koordinate+1) = Koordinate [1], also dass ich bei Zuweisungen sowohl>> schreiben kann>> Ja. sind identisch.> Aber tu dir selbst einen Gefallen, Benutze nicht die erste Schreibweise,> solange du nicht einen guten Grund dafür hast
Du schreibst mir jetzt, ich soll die erste Variante
*(Koordinate+1) nur bei einem guten Grund verwenden, was ist denn ein
guter Grund bzw. was ist jetzt daran schlechter als an Koordinate [1]?
Oder ist das auch eher nur der Überschtlichkeit halber, weil man im
zweiten Fall gleich sieht, dass Koordinate ein Array ist?
Wasserfallkarzinom schrieb:> Punkt :: Punkt (int xd, int y)> {> Koordinate = new int [2];> *(Koordinate) = xd;> *(Koordinate+1) = y;> }
Das ist so total falsch! Du musst Koordinate = new... ersetzen durch
malloc (Koordinate,sizeof(int[2]));
Wasserfallkarzinom schrieb:> Naja, stimmt, die zweite Variante ist die bessere, weil man dann sieht,> dass ein Double-Wert zurückgeliefert werden kann und welche beiden> Argumente übergeben werden. Kann ich denn bei der Deklaration die> Anmerkungen BruttoPreis und Steuersatz als Kommentare betrachten,
LOL.
Eigenartige Ausdrucksweise, aber ich versteh was du meinst.
Ja, die kannst du im Grunde (in diesem Fall) als Kommentare betrachten.
An der Stelle der Klassendekleration interessiert sich der Compiler nur
für den Datentyp.
> XYZ :: CalculateNettoPreis (double BruttoPreis, ...)> oder kann ich dann auch sowas reinschreiben> XYZ :: CalculateNettoPreis (double PreisBrutto, ...)
Kannst du. Die Argumentnamen müssen nicht übereinstimmen.
Allerdings: Es ist keine so gute Idee, da schlampig zu sein :-)
> *(Koordinate+1) nur bei einem guten Grund verwenden, was ist denn ein> guter Grund
Bei dir nicht.
Bei dir ist ganz klar, dass Koordinate ein Pointer auf ein Array ist.
Also benutz auch die Array-Syntax. C (und mit ihm C++) hat ein paar
Sonderregeln eingeführt, damit genau das möglich ist.
Deine Sichtweise der Dinge soll sein: Hinter Koordinaten steht ein
Array, also nehm ich auch Array Syntax, weil sie sich dafür natürlich
anbietet.
Die andere, die Pointer-Variante, ist gleichwertig und ist in C++ weit
weniger gebräuchlich als in C. Eigentlich so gut wie gar nicht, weil die
Dinge in C++ meistens sehr viel strukturierter gemacht werden. Auch wird
in C++ sehr viel weniger mit Pointern gearbeitet als in C. C++ hat hier
einfach bessere Möglichkeiten.
> bzw. was ist jetzt daran schlechter als an Koordinate [1]?
Es verschleiert.
Oberste Maxime soll immer sein: Code soll für Menschen lesbar sein.
Wenn du ein Array hast, dann ist es das natürlichste von der Welt, dass
Koordinate[0] das erste Array Element ergibt und Koordinate[1] das
nächste.