C++-Grundlagen (Zeiger & Arrays)
-
- MUSS man den Wert "\0" selbst setzen, um das Ende eines Char(-Arrays) zu markieren? Selbst-Antwort: Ich glaube schon (wgn. evtl. Absturz-Vermeidung), nur daß dies nicht in ALLEN C++-Büchern/Tutorials audrückl. erwähnt wird...
- Zitat aus einem Buch: In C++ können Zeiger und Arrays beliebig ausgetauscht werden. Jeder Zeiger kann wie ein Array angesprochen werden, jedes Array wie ein Zeiger. In einem C++-Tutorial wird aber darauf hingewiesen, daß dies kein guter Programmierstil sei, und man einen Pointer wie einen Pionter und einen Zeiger wie einen Zeiger behandeln sollte!?!
- Allgemein: Bei welchen C++-"Subjekten" muß/sollte man manuell Speicher anfordern (z. B. mit "new") bzw. wieder freigeben ("delete")? Objektinstanzen? Arrays?
- "Wo" kommen Zeiger in Frage?
Was ich kapiere:int var_a; int *TestZeiger; //Deklaration des Zeigers TestZeiger = &var_a; //Zuweisung des Zeigers zu einer ("normalen") Variablen
Aber:
Wie ists bei der Parameterübergabe bei Strukturen (und auch Funktionen)? Hier ein Beispiel, das ich nicht versteh:Typedef struct { int width; int heigth; char *data; //was passiert hier??? "data" besteht doch noch gar nicht!!! } Map; //was ist das denn noch?? Wieso steht das nicht hinter "Typedef struct"? Map* createMap(int width, int height) //warum ist der "*" jetzt links bzw. was passiert hier? { }
-Frage zu Klassen: Was sind, verflucht nochmal, Konstruktoren??? Das wird immer so dahingestellt, nie erklärt (*argh*)!!! So hab ichs mir zusammengereimt: Man benennt die Klasse "ausführlich", d. h. mit einem langen Bezeichner. Zusätzlich gibt man noch den "Konstruktor" an, also ein Name, mit dem man später Instanzen der Klasse erstellt, der aber nicht so lange ist wie der Klassenname???
Das sind alles Sachen, die in den meisten (allen?) C++-Büchern einzeln (Zeiger, Arrays, Strukturen, Funktionen) erklärt werden, aber nie im Zusammenhang!!!
Danke für die Aufmerksamkeit.
-
tandorf schrieb:
- MUSS man den Wert "\0" selbst setzen, um das Ende eines Char(-Arrays) zu markieren? Selbst-Antwort:
müssen tut man gar nichts. nur setzen viele funktionen wie zb strlen und konsorten das voraus. aber auf die solltest du in C++ sowieso zu gunsten von std::string verzichten.
- Zitat aus einem Buch: In C++ können Zeiger und Arrays beliebig ausgetauscht werden. Jeder Zeiger kann wie ein Array angesprochen werden, jedes Array wie ein Zeiger. In einem C++-Tutorial wird aber darauf hingewiesen, daß dies kein guter Programmierstil sei, und man einen Pointer wie einen Pionter und einen Zeiger wie einen Zeiger behandeln sollte!?!
pointer ist das englische pendant für zeiger.
ein array kann in vielen fällen als zeiger auf das erste element gesehen werden:int array[10]; *array = 10; //entspricht array[0] = 10 *(array+1) = 20; //entspricht array[1] = 20
und umgekehrt kannst du auf zeiger auch den [] operator anwenden:
void foo (int *zeiger) { zeiger[10] = 20; }
- Allgemein: Bei welchen C++-"Subjekten" muß/sollte man manuell Speicher anfordern (z. B. mit "new") bzw. wieder freigeben ("delete")? Objektinstanzen? Arrays?
muß? sollte? das hängt von der situation ab. meistens verwendet man new und delete, wenn man die lebensdauer eines objektes selbst bestimmen will.
- "Wo" kommen Zeiger in Frage?
wann immer du sie brauchst. allerdings ersetzen referenzen in C++ oft zeiger.
zb. wenn du das call by value (also übergabe per wert = als kopie) umgehen willst (nennt sich dann auch call by reference)Was ich kapiere:
int var_a; int *TestZeiger; //Deklaration des Zeigers //Und dann: TestZeiger = &var_a; //Zuweisung des Zeigers zu einer ("normalen") Variablen
wenn du das kapierst, dann sag mir: worauf zeigt TestZeiger, wenn du ihn "deklarierst"?
Aber:
Wie ists bei der Parameterübergabe bei Strukturen (und auch Funktionen)? Hier ein Beispiel, das ich nicht versteh:Typedef struct { int width; int heigth; char *data; //was passiert hier??? "data" besteht doch noch gar nicht!!! } Map; //was ist das denn noch?? Wieso steht das nicht hinter "Typedef struct"? [/quote] data ist ein Zeiger, der im moment aber noch kein objekt hat, wo er drauf zeigt. das heißt ein dereferenzieren erzeugt undefiniertes verhalten. genauso wie oben: der zeiger TestZeiger zeigt irgendwo hin, erst mit der zeile [cpp] TestZeiger = &var_a; [/cpp] sagst du ihm, welches objekt er referenzieren soll. [quote] Map* createMap(int width, int height) //warum ist der "*" jetzt links bzw. was passiert hier? { }
hier hast du eine funktion, die einen Zeiger auf ein Objekt vom Typ Map zurückgibt. Wahrscheinlich erzeugt die Funktion das Objekt intern dynamisch und verlässt sich darauf, dass irgendjemand den Speicher wieder freigibt.
-Frage zu Klassen: Was sind, verflucht nochmal, Konstruktoren??? Das wird immer so dahingestellt, nie erklärt (*argh*)!!! So hab ichs mir zusammengereimt: Man benennt die Klasse "ausführlich", d. h. mit einem langen Bezeichner. Zusätzlich gibt man noch den "Konstruktor" an, also ein Name, mit dem man später Instanzen der Klasse erstellt, der aber nicht so lange ist wie der Klassenname???
nein. Der name eines konstruktors muss immer auch der name der klasse sein.
der konstruktor ist die funktion, die beim instanziieren einer klasse aufgerufen wird (also die 1. funktion) und dafür verantwortlich ist, das objekt in einen gültigen zustand zu bringen.class test { public: test () { cout << "tests Konstruktor\n"; } }; int main () { test t; //hier erzeugst du ein objekt vom typ test, d.h. der konstruktor wird aufgerufen test *zeiger = new test; //new t erzeugt ein objekt vom typ test, d.h. der konstruktor wird aufgerufen delete zeiger; //speicher wieder freigeben }
analog zum konstruktor gibt es einen destruktor, der aufgerufen wird, sobald ein objekt zerstört wird. der name des destruktors ist immer der klassenname inklusive vorangestellter tilde (~)
class test2 { public: test2 () { cout << "test2 konstruktor\n"; } ~test2 () { cout << "test2 destruktor\n"; } }; void foo () { test2 *x = new test2; //Konstruktor wird aufgerufen delete x; //destruktor wird aufgerufen { test t; //konstruktor wird aufgerufen } //da der aktuelle gültigkeitsbereich verlassen wird, werden alle lokalen objekte zerstört, d.h. der destruktor von test wird für t aufgerufen test t; //konstruktor wird aufgerufen } //funktion wird verlassen, alle lokalen objekte zerstören, d.h. der destruktor wird für t aufgerufen
Jede Klasse hat einen Konstruktor und einen Destruktor, auch wenn du sie nicht selbst schreibst, dann macht es der Compiler für dich:
class leer {};
auch leer hat einen konstruktor und einen destruktor (sowie ein paar andere elementfunktionen) - d.h. du kannst objekte vom typ leer erstellen und zerstören.
ich hoffe ich konnte ein wenig klarheit bringen, aber ich denke, dass du mit der zeit mehr erfahrung sammeln wirst und das dann auch besser verstehst
-
Uff, ne ganze Menge ...
tandorf schrieb:
- MUSS man den Wert "\0" selbst setzen, um das Ende eines Char(-Arrays) zu markieren? Selbst-Antwort: Ich glaube schon (wgn. evtl. Absturz-Vermeidung), nur daß dies nicht in ALLEN C++-Büchern/Tutorials audrückl. erwähnt wird...
Das kann man nicht pauschalisieren.
- Zitat aus einem Buch: In C++ können Zeiger und Arrays beliebig ausgetauscht werden. Jeder Zeiger kann wie ein Array angesprochen werden, jedes Array wie ein Zeiger.
Buch wegschmeißen, das ist Unfug. Wahr ist: Wenn ein Array in einem Ausdruck auftaucht, wird es in einen Zeiger auf sein erstes Element umgewandelt (Ausnahme: Als Argument des sizeof-Operators.) Und um die Verwirrung komplett zu machen: Der "Arrayindex"-Operator hat gar nichts mit Arrays zu tun. a[ b ] ist einfach eine Abkürzung für *(a+b).
- Allgemein: Bei welchen C++-"Subjekten" muß/sollte man manuell Speicher anfordern (z. B. mit "new") bzw. wieder freigeben ("delete")? Objektinstanzen? Arrays?
Das hat nichts mit den "Subjekten" zu tun, sondern mit der gewünschten Objektlebensdauer. Objekte auf dem Stack werden am Blockende automatisch zerstört, globale und statische Objekte am Programmende ... wenn du mehr Kontrolle darüber wilst, brauchst du new/delete. Und natürlich bei Arrays, deren Größe erst zur Laufzeit feststeht.
Wie ists bei der Parameterübergabe bei Strukturen (und auch Funktionen)? Hier ein Beispiel, das ich nicht versteh ...
typedef struct { int width; int heigth; char *data; //was passiert hier??? "data" besteht doch noch gar nicht!!! (1) } Map; //was ist das denn noch?? Wieso steht das nicht hinter "Typedef struct"? (2) Map* createMap(int width, int height) //warum ist der "*" jetzt links bzw. was passiert hier? (3) { }
1.: Da wird ein Zeiger auf char deklariert. Was meinst du damit, dass data nicht besteht? Natürlich nicht, es wird ja grad erst deklariert.
2.: Map ist der Typname, der hier mit typedef definiert wird. Im Prinzip wird da gesagt, dass Map ab sofort ein Synonym für den Typ struct { int width; int height; char *data; } sein soll. In C++ macht man sowas aber nicht, schreib einfach:struct Map { int width; int height; char *data; };
3.: Das ist eine Definition einer Funktion, die einen Zeiger auf Map -- also Map* -- zurückgibt.
-Frage zu Klassen: Was sind, verflucht nochmal, Konstruktoren??? Das wird immer so dahingestellt, nie erklärt (*argh*)!!!
Kann das daran liegen, dass du einigermaßen unsystematisch lernst? Das wird überall erklärt.
So hab ichs mir zusammengereimt: Man benennt die Klasse "ausführlich", d. h. mit einem langen Bezeichner. Zusätzlich gibt man noch den "Konstruktor" an, also ein Name, mit dem man später Instanzen der Klasse erstellt, der aber nicht so lange ist wie der Klassenname???
Quark, schon allein weil der Konstruktorname identisch mit dem Klassennamen ist. Ein Konstruktor ist eine Pseudo-Funktion, die aufgerufen wird, wenn man eine Instanz einer Klasse erstellt (egal ob statisch, automatisch oder dynamisch.)
Das sind alles Sachen, die in den meisten (allen?) C++-Büchern einzeln (Zeiger, Arrays, Strukturen, Funktionen) erklärt werden, aber nie im Zusammenhang!!!
Wahrscheinlich kannst du den Zusammenhang nicht herstellen, weil du wie es scheint immer kreuz und quer zwischen zig Büchern und Tutorials hin- und herhuschst. Beschränk dich erstmal auf eins (ein gutes wenn's geht,) dann wird das meiste klarer.
-
zb. dieses (Tutorial)
-
Ich versuch mal paar Fragen zu beantworten.
1.) Manche Funktionen fügen das abschliessende '\0' selbst ein, z.B. strcpy, andere tun dies nicht. Üblicherweise solltest du den Speicher für deinen char-array mit '\0' initialisieren, z.B. memset(..., 0x00, sizeof(...)).
2.) Speicher musst du für alle Objekte anfordern die du DYNAMISCH zur Laufzeit anlegen willst oder musst. Wenn eine Funktion zum Beispiel als Ergebnis ein Objekt zurückliefern, wird üblicherweise das Objekt in der Funktion mit new() angelegt und als Returncode ein Pointer auf das Objekt zurückgeliefert der dann nach Gebrauch vom Aufrufer auch wieder durch delete() freigegeben werden muss.
3.) Ist ein Beispiel für 2. die Funktion createMap() liefert als Ergebnis einen Zeiger auf Map zurück. In der Funktion wird wahrscheinlich mit new() eine Struktur angelegt "gefüllt" und als Ergebnis zurückgeliefert.
4.) Konstruktoren sind Funktionen die zur Erstellung eines Objekts erforderlich sind.
class A { A(); }
Konstrukturen dienen zur Initialisierung deiner Klasse. Du kannst mehrere Konstrukturen mit verschiedenen Parameterlisten haben, die vom Ablauf der Initialisierung unterschiedlich ablaufen. Wenn du selbst keinen Konstruktur erstellst dann tut der Compiler das AUTOMATISCH für dich, d.h. du siehst ihn nicht er ist aber trotzdem da.
Und zum Aufräumen bei "Zerstörung" deines Objekts werden dann Destruktoren verwendet.class B { ~B(); }
Beide Funktionen düfen keine Returncode liefern, nicht einmal void !!
Scheinst keine besonders guten Bücher zu haben. Wenn dich Englisch nicht stört kann ich persönlich "C++ Primer" von Stan Lippman empfehlen. Da werden zumindest alle obigen Fragen beantwortet.
mfg JJ
-
@davie: Danke, so einiges fiel mir jetzt von der Seele! Auf die Sache mit *"char data; //was passiert hier??? "data" besteht doch noch gar nicht!!!" hätt ich auch selber kommen können...
Jetzt weiß ich zwar, was ein Konstrukor ist, verstehe aber die Idee dahinter nicht. Pseudocode (so würd ichs als "normal" empfinden)://Klassendeklaration. class TestKlasse{} //Instanz erzeugen: Test = new TestKlasse(); //wozu noch einen Konstruktor? Das könnt die IDE doch //im Hintergrund regeln, was ja wohl auch gemacht wird? delete Test; //easy, wenn nicht mehr benötigt.
Kann ich so vorgehen (schmerzfrei)?
@bashar: Danke für die Auflösung bez.:
struct Map { int width; int height; char *data; };
Denn so ists mir wesentlich wohler!
Das ist eine Definition einer Funktion, die einen Zeiger auf Map -- also Map -- zurückgibt.*
Das muß ich erstmal verinnerlichen!!!Zu meiner Vorgehensweise beim Lernen der Sprache: Hab das Buch "C++ - die prof. Referenz" (o. ä.) systematisch durchgearbeitet, und mir selber eine Zusammenfassung geschrieben (inkl. selbst erst. Beispielen...). Und zwar so weit, bis ich auf ein Thema gestoßen bin, das ich nicht verstand. Dann hab ich mir das nächste Buch gekauft, das Thema aufgearbeitet, und dabei noch (viel) bessere Erläuterungen zu anderen, schon verstanden geglaubten, Themen gefunden, und wieder von vorne angefangen...Bis ich auf das erste Buch von "Stefan Zerbst" gestoßen bin - genial und kompakt wird C++ erklärt - bis zum Thema "verkettete Liste"...Da war dann wieder "Ende"...
Jedenfalls hast Du recht, und ich hab jetzt in meiner 40-Seitigen Word-Datei ein riesen Chaos (z. B. 3x das Thema "der Präprozessor" angefangen...).
Ok, ich mach das nur als Hobby, und zwar schon seit 5 Jahren. Aber: WENN ich fertig bin, veröffentliche ich das Ding als das ultimative "C++ für dumme Dummies"-Werk!!!@John Doe:
Ah, deswegen die Konstruktoren (versch. Parameterlisten...). Mir stellt sich nur die Frage, ob das am Ende nicht unübersichtlicher wird bzw. bei großen Projekten schwerer zu überblicken...Wenn man sich überlegt, was los ist, wenn man dann noch mit "Vererbung" anfängt...Rein hypothetisch, natürlich, da ich bisher noch kein riesen Projekt (in C++) entwickelt hab, aber ich würd versuchen, für "jeden Mist" eine eigene Klasse zu bauen...@all: Danke, die Informationen brachten mich richtig weiter und werden in mein "Buch" übernommen!
-
//Klassendeklaration. class TestKlasse{} //Instanz erzeugen: Test = new TestKlasse(); //wozu noch einen Konstruktor? Das könnt die IDE doch //im Hintergrund regeln, was ja wohl auch gemacht wird? delete Test; //easy, wenn nicht mehr benötigt.
der Konstruktor ist eben dafür da, wenn deine klasse einmal komplexer ist, und die membervariablen irgendwie initialisiert werden müssen (zb datenbankverbindung aufbauen), das auch zu tun (oder zb dynamischen Speicher anzufordern). Der Destruktor gibt den Speicher dann wieder frei oder disconnected sich von der Datenbank.
Außerdem kannst du mehrere Konstruktoren definieren, die verschiedene Argumente übernehmen (so wie du eine Funktion überladen kannst)
ein Beispiel:class Datenbank { public: Datenbank (); //erzeugt eine standardverbindung zu einem mysql server an localhost Datenbank (IP ip_des_servers, DatabaseType, User, Password); //... ~Datenbank (); //trennt die verbindung wieder void query (...); //etc. }; //Ohne Konstruktoren/Destruktoren: class Datenbank2 { public: connect (); //übernimmt die aufgaben eines konstruktors disconnect (); //übernimmt die aufgaben eines destruktors };
Warum ist die Lösung mit Konstruktoren/Destruktoren besser?
1.) Weniger Schreibarbeit
void foo () { Datenbank my_database ("10.0.0.0", "MySQL", "david", "42"); my_database.query(...); }
jetzt der äquivalente code mit der anderen klasse ohne ctors und dtors
void foo () { Datenbank2 my_database; my_database.connect("10.0.0.0", "MySQL", "david", "42"); my_database.query(...); my_database.disconnect(); }
2.) Mehr Schreibarbeit = Mehr Fehler können sich einschleichen
void foo () { Datenbank2 my_database; my_database.query(...); } //ups, habe vergessen, verbindung zu schließen //Mit der anderen Klasse wäre das nicht passiert, denn die hätte durch den Konstruktor alle Verbindungen freigegeben.
3.) Was kann denn noch alles passieren?
Stellen wir uns vor, die Memberfunktion query darf eine Ausnahme werfen. Wie gehen wir damit um? Wie sichern wir, dass die Datenbankverbindung auf alle Fälle geschlossen wird?void foo () { Datenbank2 my_database; my_database.connect(...); try { my_database.query(...); } catch (...) { my_database.disconnect(); throw; //Ausnahme weiterwerfen: Um den Fehler soll sich wer anderer kümmern, wir sorgen nur dafür, dass die Verbindungen alle geregelt terminiert werden. } my_database.disconnect(); my_database.close(); }
Uh-oh, sieht ganz schön umständlich aus, nicht?
Wie ginge es denn mit der Klasse mit Konstruktoren und Destruktoren?
So:void foo () { Datenbank my_database(...); my_database.query(...); }
Na, überzeugt?
Außerdem wird für jede Klasse ein Konstruktor und ein Destruktor angelegt. Wenn du jetzt zB das Erzeugen von Klassen verhindern willst, dann mach einmal:
class Unerzeugbar { private: //zum hervorheben Unerzeugbar(); ~Unerzeugbar(); };
Private Funktionen können aber von außen nicht aufgerufen werden, d.h.
Unerzeugbar foo; //Funktioniert nicht Unerzeugbar *bar = new Unerzeugbar; //Funktioniert genausowenig
Das sieht vielleicht jetzt etwas seltsam aus, findet aber trotzdem oft praktische Anwendung. Das zu erläutern geht jetzt aber über diesen Beitrag hinaus *g*
Der Konstruktor initialisiert außerdem konstante Elementvariablen und Referenzen
class Foo { const int nummer_; const string &name_; public: Foo (const string &name) : nummer_(0), name_(name) {} };
Dieses syntaktische Konstrukt nennt man Elementinitialisierungsliste.
Der Konstruktor ist also dafür zuständig, die sogenannte Invariante einer Klasse zu erzeugen: ohne Konstruktoraufruf sprechen wir noch nicht von einer richtigen Instanz. Ich kann beispielsweise Speicher anfordern, der Groß genug ist, um ein Objekt aufzunehmen:
void fubar () { char *c = new char[sizeof(Datenbamk)]; Datenbank *db = reinterpret_cast<Datenbank*>(c); }
Das wird aber nichts daran ändern, dass db nicht auf ein richtiges Objekt vom Typ Datenbank zeigt. db ist nicht benutzbar. Erst der Konstruktor macht es zu einer Instanz vom Typ Datenbank. Den Destruktor auf so eine Speicherlacke kannst du übrigens mittels sogenanntem "placement new" aufrufen:
db = new (c) Datenbank;
Hier wird sozusagen ein neues Datenbankobjekt in den Speicher, auf den c zeigt, hineinkonstruiert.
Uff. das ist jetzt eigentlich mehr geworden, als ich sagen wollte *g* aber hoffentlich erscheinen dir so bestimmte Anwendungsbereiche von Konstruktoren und Konstruktoren überhaupt verständlicher
-
@davie: Zu den Beispielen "mit- und ohne Konstruktor/Destruktor": Hab ich nicht richtig kapiert. Will man die Klasse nur "auf eine Art" einsetzen, so kann man doch den Konstruktor im Hintergrund von der IDE erstellen lassen und trotzdem eine (standard-)Instanz erzeugen, und diese auch wieder "destroyen"?!?
Auch, wenn es mehr Schreibarbeit bedeuten würde...
-
tandorf schrieb:
Will man die Klasse nur "auf eine Art" einsetzen, so kann man doch den Konstruktor im Hintergrund von der IDE erstellen lassen und trotzdem eine (standard-)Instanz erzeugen, und diese auch wieder "destroyen"?!?
Klar kann man, wenn auch die IDE damit nichts zu tun hat (wär ja blöd, was machen dann die, die keine IDE verwenden?) Wenn kein Konstruktor deklariert ist, erstellt der Compiler selbst einen Standardkonstruktor (d.h. einen ohne Argumente). Der tut eigentlich nichts weltbewegendes, er ruft nur die Standardkonstruktoren der Basisklassen auf und dann initialisiert er gegebenenfalls die Membervariablen, indem er ihren Standardkonstruktor aufruft. Membervariablen von primitiven Typen bleiben uninitialisiert.
Genau dann, wenn man das nicht will, schreibt man einen eigenen Konstruktor.
-
Hier 3 Möglichkeiten, per "Referenz" eine Strukturvariable (Instanz?) zu erzeugen:
typedef struct player_type { int Lebensenergie; int Position_x; int Position_y; } PLAYER, *PLAYER_PTR; PLAYER_PTR p_Mein_Spieler1; p_Mein_Spieler1 = (PLAYER_PTR)malloc(sizeof(PLAYER)); //___________________________________________________________________________ struct Map { int width; int heigth; char *data; }; Map* createMap(int width, int height); Map *map = (Map*) malloc(sizeof(Map) ); //___________________________________________________________________________ //So wärs mir am liebsten: struct Map //Hier also nicht "typedef struct"... { int width; int heigth; char *data; }MAP, *StandardMapPointer; StandardtMapPointer MeineErsteMap; MeineErsteMap = (StandardtMapPointer)malloc(sizeof(MAP));
Würde diese (letzte) Variante funktionieren bzw. als "sauber" gelten?
-
als sauber gelten würde
struct Map //Hier also nicht "typedef struct"... { int width; int heigth; char *data; }; Map* meineMap = new Map;
- Wozu ein typedef auf einen Pointer? Hast du Angst vor Sternen?
- Warum malloc, wenn es new gibt?
- Deine "saubere" Variante ist die einzige, die nicht compiliert
-
Hui, diese Variante ist auch "annehmbar". Und ja, scheinbar hab ich eine gewisses Stern-Hemmung
!!!