Zwei spezielle technische Fragen (zur Auswertungsreihenfolge & Funktionstypen)
-
Also ich arbeite weiterhin "Einführung in die Programmierung mit C++" durch und habe mich vorgekämpft in die letzten Etappen der Grundlagen.
Jetzt verstehe ich ein paar Dinge nicht.
Die erste: Es heißt, man soll keine Ausdrücke der Art
v[i] = ++i; v[i++] = i; cout << ++i << ' ' << i << '\n'verwenden, mit dem Hinweis "Greifen Sie in einem Ausdruck nicht ein zweites Mal auf die Variable zu."
Spontan sehe ich schon dass diese Codebeispiele nicht gerade ansprechend aussehen, aber was genau ist das Problem? Es geht wohl um die Auswertungsreihenfolge beim compilieren.
Die zweite,
const Date& default_date() { static const Date dd(1970, 1, 1); return dd; }Also, bisher weiß ich wohl, dass const Date& ne const-Referenz ist und wie das bei Argumenten von Funktionen funktioniert. Hier wird aber der Rückgabewert als const-Referenz betrachtet, sehe ich das richtig?
Wenn ich also in meiner main sowas stehen habe wiedate Standartdatum = default_date();Zeigt dann mein "Standartdatum" auf "dd"? Oder wie soll ich mir das vorstellen?
Und wenn, ist mir das etwas sehr speziell. Kann mir jemand vllt. nen anderes Beispiel geben, wo man const "typ"& als Typ für eine Funktion wählt? Einfach damit ich mir besser was darunter vorstellen kann, wann und warum man das machen sollte.Vielen Dank im voraus,
mit freundlichen Grüßen,
Thez
-
Frage 1:
Du siehst das Problem nicht? Dann kannst du gewiss folgende Frage beantworten: Was wird hier ausgegeben?int i = 1; cout << ++i << ' ' << i << '\n';
Selbst wenn du diese Frage nicht beantworten kannst (oder gerade deswegen), bemerkst du nun hoffentlich, wo das Problem liegt.Frage 2:
Zuerst einmal: Weißt du, wieso man manchmal Referenzen (ohne const) als Rückgabewert gebrauchen möchte? Falls nicht, kurzes Beispiel:class Foo { int i; public: int& bar() { return i; } }; int main() { Foo foo; foo.bar() = 3; // ändert foo.i }Das sieht hier ein bisschen umständlich aus, aber im Zusammenhang mit Operatorüberladung kann man damit feine Sachen machen. Beispielsweise gibt der operator[] eines std::vector eine Referenz zurück. Insgesamt ist es also wie bei Funktionsparametern, bloß eben als Rückgabewert: Man benutzt die Referenz, wenn das übergebene Objekt irgendwie geändert werden soll.
Warum sollte man nun eine const-Referenz zurück geben wollen? Wieder aus dem gleichen Grund, warum man const-Referenzen auch als Funktionsparameter benutzen will: Man möchte eine Kopie vermeiden, aber gleichzeitig keine Veränderung zulassen:
class Foo { std::vector<int> grosser_dicker_vector; public: const std::vector<int>& bar() const { return grosser_dicker_vector; } }; int main() { Foo foo; std::cout << foo.bar().size(); // Der vector wird nicht kopiert, kann aber auch nicht geändert werden }Zu deinem Beispiel:
date Standartdatum = default_date();Zeigt dann mein "Standartdatum" auf "dd"?
Nein, denn
Standarddatumist ein eigenständiges Objekt, keine Referenz. Es wird aber mit der von default_date zurückgelieferten Referenz initialisiert, wird also zu einer Kopie des von default_date referenzierten Objekts. Insgesamt macht es hier also keinen Unterschied, wenn default_date gleich eine Kopie zurück gegeben hätte.P.S.: Wenn du die Codetags mit
[code="cpp"]einleitest (oder einfacher: den "C++"-Button neben dem "Code"-Button benutzt), dann wird dein Code auch farblich hervor gehoben. Vergleiche meine Codebeispiele mit deinen.
-
Thez schrieb:
verwenden, mit dem Hinweis "Greifen Sie in einem Ausdruck nicht ein zweites Mal auf die Variable zu."
Spontan sehe ich schon dass diese Codebeispiele nicht gerade ansprechend aussehen, aber was genau ist das Problem? Es geht wohl um die Auswertungsreihenfolge beim compilieren.
Jup. Wegen dem Problem mit der Auswertungsreihenfolge darfst Du das gar nicht machen.
Genaueres unter google sequence pointHoffentlich willst Du es auch nicht machen. Also ich finde Code unübersichtlich, wo so viel auf einmal passiert.
v[i] = ++i; v[i++] = i; cout << ++i << ' ' << i << '\n'//v[i] = ++i; v[i] = i+1;//da sehe ich doch deutlicher, daß in v[15] die 16 landen wird. ++i; //v[i++] = i; v[i] = i+1;//dito ++i; //cout << ++i << ' ' << i << '\n' ++i; cout << i << ' ' << i << '\n' //cout << i++ << ' ' << i << '\n' cout << i << ' ' ; ++i; cout << i << '\n' //Hmm, weiß nicht. cout << i << ' ' << i+1 << '\n'; ++i;Ich gehe davon aus, daß der optimierende Compiler aus allen Versionen gleich schnellen Code bastelt. Bei so einfachen Umstellungen achte ich nur auf die Lesbarkeit des Codes und sonst nix.
-
Gut, erstmal vielen Dank für die Antworten.
Ehm. Also, ich sehe du kannst offenbar mit Referenz auf den Private bereich zugreifen, und das sogar recht einfach. Also ich nehme das mal mit eigenen Worten so auf:
Eine Funktion der Form:typ& Funktions_Name(){/*...*/ return a;}Ist selbst eine Referenz an den Rückgabewert. Kann man das so sagen?
Also in deinem Fall ist bar() eine Referenz auf i, weswegen foo.bar()=3, i einen neuen Wert gibt.In meinem Codebeispiel:
const Date& default_date() { static const Date dd(1970, 1, 1); return dd; }Welcher übrigens eine Lösung sein sollte eine globale Variable zu umgehen,
zeigt default_date() an sich auf dd, welches einmal deklariert und initialisiert wird.Dann frag ich mich aber, warum nochmal "const Date dd"? Wollte man etwas wie:
default_date() = 5;schreiben, mault der Compiler doch eh da wir ja einen const Date& Typ haben. Oder ist das nur, damit niemand auf die Idee kommt dd direkt zu ändern?
Und in dem Fall, da man ja eh nicht direkt auf dd oder default_date zugreifen kann, steht int& auch nur da, damit nichts kopiert wird, sprich wir sparen Speicher.
Hab ich das alles so richtig verstanden? (Hoffentlich
)Zu
Gut ich weiss, dass mein Compiler daraus 2 2 machen würde. Aber ich nehme mal an, dasses nicht ganz allgemein definiert ist?
Zu volkard:
Hoffentlich willst Du es auch nicht machen. Also ich finde Code unübersichtlich, wo so viel auf einmal passiert.
Nein, natürlich nicht, sieht fürchterlich aus. Aber mir ist nicht ganz klar warum das nicht funktioniert. Ich denke ich google mal wie vorgeschlagen sequence point und lese etwas darüber.
Aber die von dir (volkard) angeführten Umstellungen sind soweit legitim, oder?
-
Thez schrieb:
Ehm. Also, ich sehe du kannst offenbar mit Referenz auf den Private bereich zugreifen, und das sogar recht einfach.
Problem?
Also ich nehme das mal mit eigenen Worten so auf:
Eine Funktion der Form:typ& Funktions_Name(){/*...*/ return a;}Ist selbst ein Zeiger auf den Rückgabewert. Kann man das so sagen?
Nein, auf keinen Fall. Erstens ist hier nirgends ein Zeiger in Sicht, und zweitens ist eine Funktion kein Zeiger, sondern eine Funktion.
Alles, was man hier sagen kann, ist, dass die Funktion eine Referenz auf ein Objekt vom Typ
typzurückgibt. Oder äquivalent umformuliert: Der FunktionsaufrufFunktions_Name()ist eine Referenz auf ein Objekt vom Typtyp.Also in deinem Fall ist bar() ein Zeiger auf i, weswegen foo.bar()=3, i einen neuen Wert gibt.
Nicht Zeiger, sondern Referenz. Wenn es ein Zeiger wäre, müsstest du
*foo.bar() = 3schreiben.In meinem Codebeispiel:
const Date& default_date() { static const Date dd(1970, 1, 1); return dd; }Welcher übrigens eine Lösung sein sollte eine globale Variable zu umgehen,
zeigt default_date() an sich auf dd, welches einmal deklariert und initialisiert wird.Dann frag ich mich aber, warum nochmal "const Date dd"? Wollte man etwas wie:
default_date() = 5;schreiben, mault der Compiler doch eh da wir ja einen const Date& Typ haben. Oder ist das nur, damit niemand auf die Idee kommt dd direkt zu ändern?
Was genau fragst du, warum das const ist? Das macht hier technisch keinen Unterschied, es gibt keine Möglichkeit, das dd zu ändern. Das ist wohl eine Stil-Sache, manche schreiben erstmal überall const hin und lassen es bei änderbaren Variablen weg.
Wichtiger finde ich die Frage, warum
Date standard_datumkeine Referenz ist.Und in dem Fall, da man ja eh nicht direkt auf dd oder default_date zugreifen kann, steht int& auch nur da, damit nichts kopiert wird, sprich wir sparen Speicher.
Wo steht int&? Meinst du Date&? Dann ja, allerdings spart man auch Zeit. Das dürfte in der Regel wichtiger sein als die Speicherersparnis.
Zu
Gut ich weiss, dass mein Compiler daraus 2 2 machen würde. Aber ich nehme mal an, dasses nicht ganz allgemein definiert ist?
Ja, das ist nicht festgelegt. Es gibt drei Stufen: implementation-defined: Der Compiler entscheidet sich für eine der möglichen Varianten und du weißt (theoretisch) welche das ist, weil es im Handbuch oder sonstwo steht. unspecified: Der Compiler entscheidet sich für eine sinnvolle Variante, sagt dir aber nicht welche (hängt z.B. vom Optimierungslevel oder von der Compilerversion ab). undefined behavior: Es passiert irgendwas. Das "richtige", etwas sinnvolles, etwas nicht sinnvolles, ein Absturz, dein Rechner explodiert, halt irgendwas.
Ich denke, hier liegt unspecified behavior vor, kann aber auch undefinied sein (zu faul nachzulesen.) Das ist im Endeffekt beides schlecht, also sollte man solchen Code nicht schreiben.
-
Ha okay, ich dachte Zeiger bietet nen gutes "Bild" für Referenz, weil die Referenz ja quasi verweist. Ja Zeiger ist noch eine eigene Geschichte, dann editiere ich das mal.
Der Funktionsaufruf Funktions_Name() ist eine Referenz auf ein Objekt vom Typ typ.
Gut. Und welches Objekt das speziell ist wird über return bestimmt, oder?
Wichtiger finde ich die Frage, warum Date standard_datum keine Referenz ist.
Worauf willst du hinaus?
Wo steht int&? Meinst du Date&? Dann ja, allerdings spart man auch Zeit. Das dürfte in der Regel wichtiger sein als die Speicherersparnis.
Ja, genau Date&. Stimmt. Okay.
Sorry, wenn ich mich etwas dumm anstelle.
Edit: Gut also ich denke nochmal darüber nach mit dem Stoff den ihr mir da gegeben habt und versuche mir das am besten zu illustrieren. Ist mir irgendwie wichtig dass diese Basics richtig und sicher in meinem Kopf sitzen und nicht so "irgendwie".
-
Und welches Objekt das speziell ist wird über return bestimmt, oder?
Ja.
Worauf willst du hinaus?
Die aufgerufene Funktion gibt eine Referenz zurück, du kopierst das Datum aber; warum? Du kannst doch denselben Typ verwenden, also eine Const-Referenz.
-
date Standartdatum = default_date();Ach ging es hierrum? Ja, sehe ich ein, im Endeffekt quatsch. Mir war da nicht genau klar, was ich mit "die Funktion gibt eine Referenz zurück" anfangen soll.
Der Funktionsaufruf Funktions_Name() ist eine Referenz auf ein Objekt vom Typ typ.
Das macht es denke ich klar.