Hallo Zusammen,
ich habe zu den Themen Call by Value/Stack und "char-Arrays" unter C++
jeweils eine Frage, für die es zwar andere Lösungen gibt, jedoch geht es
mir um das Verständnis.
1. Wie genau funktioniert Call by Value, da in Büchern oft nicht exakt
beschrieben wird, wie es funktioniert. Wir bei Aufruf einer Funktion der
Wert des zu übergebenden Parameters an einer bestimmten Adresse im Stack
gespeichert und von der Funktion selbst an einer anderen Adresse, wo
eine Instanz erzeugt wird, der Wert sozusagen vom Stack kopiert?
2. Ich kann char-Arrays deklarieren und definieren:
char Name[30] = "Test";
Getrennt funktioniert das aber nicht:
char Name[30];
Name[] = "Test";
Woran liegt das? Mir ist klar, dass eine Zuweisung nur wie folgt möglich
ist:
Name[0] = 'T';
Name[1] = 'e';
Name[2] = 's';
Name[3] = 't';
Aber wieso ist eine Zuweisung des Strings "Test" direkt bei der
Deklaration möglich, später jedoch nur, wenn die einzelnen Elemente []
angesprochen werden? Speicher wird mit oder ohne String bei der
Deklaration reserviert, also spielt das zumindest keine Rolle bei der
Zuweisung.
Danke vorab für die Mühe.
Michi schrieb:> 1. Wie genau funktioniert Call by Value, da in Büchern oft nicht exakt> beschrieben wird, wie es funktioniert.
Das hängt von der Plattform und Calling Convention ab und kann daher
nicht allgemein beantwortet werden, deswegen steht das da nicht. Es wird
eine Kopie des Parameters an die Funktion übergeben, welche dann diese
Kopie bearbeiten kann. Wenn der Parameter ein Pointer oder eine Referenz
ist, wird eben der Pointer/Referenz kopiert, aber nicht das Ziel.
Michi schrieb:> Wir bei Aufruf einer Funktion der> Wert des zu übergebenden Parameters an einer bestimmten Adresse im Stack> gespeichert
Bei vielen Plattformen werden die ersten Parameter in Prozessor-Register
kopiert und der Rest auf den Stack.
Michi schrieb:> und von der Funktion selbst an einer anderen Adresse, wo> eine Instanz erzeugt wird, der Wert sozusagen vom Stack kopiert?
Was für eine Instanz? Die Funktion nutzt die Werte vom Stack/Register
direkt, ohne Kopie.
Michi schrieb:> Woran liegt das? Mir ist klar, dass eine Zuweisung nur wie folgt möglich
Man kann Arrays in C halt nicht direkt kopieren. Das wurde vermutlich so
entschieden weil das eine Schleife beinhalten würde, und C Entwickler
mögen keine versteckten aufwändigen Operationen. In C++ wurde das
Verhalten auf den Built-In-Typen größtenteils übernommen, weswegen da
auch so ist.
Wenn du so etwas machen möchtest, nutze std::array oder std::vector oder
std::string.
Michi schrieb:> struct S_Spieler{> int Energie;> int Punkte;> int xPosition;> int yPosition;> char Name[30]="Test"; //Nicht möglich, obwohl Speicherplatz> reserviert wird> };
dann hast du nen ziemlich angestaubten C++ Compiler.
1
structTest{
2
charname[20]="Default";
3
inti=5;
4
floatf=4.f;
5
6
};
meiner übersetzt sowas ohne zu zucken
Davon abgesehen, sieht das, was du da anstellst relativ wenig nach C++
aus.
Da würde man
1
std::stringName;
benutzen.
Da gibts auch gleich die passenden Konstruktoren für string-Literale;
char name[20] = "Default";
Eigentlich ist es nicht richtig, dass der Compiler es übersetzt, da eine
Struktur nur Deklarationen enthalten sollte. Die Initalisierung sollte
immer mit den jeweiligen Instanzen erfolgen.
Und was Dr. Sommer schrieb: "Was für eine Instanz? Die Funktion nutzt
die Werte vom Stack/Register direkt, ohne Kopie."
Die Funktion nutzt zwar Werte, jedoch wird die Variable beim Aufruf
nicht direkt übergeben sondern vielmehr eine Kopie, die nach dem Beenden
der Funktion ihre Gültigkeit verliert.
Michi schrieb:> Andersherum kann ich meine char Name[30] innerhalb einer Struktur nicht> initialisieren:struct S_Spieler{> int Energie;> int Punkte;> int xPosition;> int yPosition;> char Name[30]="Test"; //Nicht möglich, obwohl Speicherplatz> reserviert wird> };
hier wird kein Speicher reserviert. Das legt nur eine Struct an, es gibt
noch keine passende Variable.
Michi schrieb:> Eigentlich ist es nicht richtig, dass der Compiler es übersetzt, da eine> Struktur nur Deklarationen enthalten sollte. Die Initalisierung sollte> immer mit den jeweiligen Instanzen erfolgen.
Eine struct ist eine public class. In-class init ist ab C++11 möglich.
> Und was Dr. Sommer schrieb: "Was für eine Instanz? Die Funktion nutzt> die Werte vom Stack/Register direkt, ohne Kopie."
Das ist die Kopie.
Michi schrieb:> Woran liegt das?
C-style Array werden (überwiegend) wie Adressen behandelt.
Dementsprechend ist call-by-value nicht möglich. Du kannst allerdings
ein Array in eine struct einbetten und die struct by-value übergeben
(siehe std::array in C++).
Michi schrieb:> char name[20] = "Default";>> Eigentlich ist es nicht richtig, dass der Compiler es übersetzt, da eine> Struktur nur Deklarationen enthalten sollte. Die Initalisierung sollte> immer mit den jeweiligen Instanzen erfolgen.
Natürlich erfolgt die Initialisierung bei der Instanzierung. Vorher ist
ja kein Speicher da.
Nur ist das die Moderne Variante in C++ Member zu initialisieren. Damit
vermeidet man in jedem Konstruktor die selben Werte schreiben zu müssen,
die Gefahr laufen, bei Änderung vergessen zu werden.
Außerdem sind so die Defaultwerte schön sichtbar neben der Definition.
Außerdem spart man sich so oft, überhaupt einen Default-Konstruktor
schreiben zu müssen.
Für primitive Typen gibts das seit c++11. Mit c++14 sind nochmal die
Regeln, wann das geht erleichtert worden.
Michi schrieb:> Doch, wenn ich per sizeof(S_Spieler) Aufrufe, ist Speicher reserviert,> obwohl noch keine Instanz erzeugt.
nein, siezeof liefert die größe, sagt aber überhaupt nicht ob Speicher
reserviert ist.
Vlad T. schrieb:> Für primitive Typen gibts das seit c++11. Mit c++14 sind nochmal die> Regeln, wann das geht erleichtert worden.
Ich nutze den Visual Studio Express 2012 Compiler. Daher wird das wohl
noch nicht unterstützt und ich kann die Initialisierung erst nach der
Deklaration einer Instanz durchführen.
Ich habe dazu noch eine andere Frage. Wenn ich eine Variable einer
Struktur über eine Funktion ändern möchte, ist es dann sinnvoll die
komplette Struktur per Zeiger zu übergeben und dann in der FUnktion die
entsprechende Variable per "STRUKTUR->Punkte=293 .... " zu ändern oder
kann ich auch einfach nur diese Variable übergeben? Funktionieren tut
beides.
Aufruf:
BerechnePunkte(&Spieler1, Bonus);
Funktion:
void BerechnePunkte(S_Spieler Spielertemp, int Bonus)
------------------
Das hier geht auch:
Aufruf:
BerechnePunkte(&Spieler1.Punkte, Bonus);
Funktion:
void BerechnePunkte(int *p_Punkte, int Bonus)
-------------------
Denn wenn ich mir in der Mainfunktion eine Instanz erzeuge, besitzt die
Strukturvariable "Punkte" bereits eine Adresse. Also kann ich die
Parameterübergabe per Zeiger auch nur für diese eine Variable machen.
Dennoch wird meistens die komplette Struktur per Zeiger übergeben,
warum?
Michi schrieb:> Ich nutze den Visual Studio Express 2012 Compiler.
dann nutze doch einen neueren.
Wenn du dich sowieso in C++ einarbeitest, dann nutze doch wenigstens
eine aktuelle Version davon.
Michi schrieb:> Ich habe dazu noch eine andere Frage. Wenn ich eine Variable einer> Struktur über eine Funktion ändern möchte, ist es dann sinnvoll die> komplette Struktur per Zeiger zu übergeben und dann in der FUnktion die> entsprechende Variable per "STRUKTUR->Punkte=293 .... " zu ändern oder> kann ich auch einfach nur diese Variable übergeben? Funktionieren tut> beides.
Das ist immer eine Frage des Kontextes.
Wenn die Informationen, die du in der Funktion brauchst, alle auch ohne
das Objekt da sind, könnte man auch über einen einfachen Rückkagebewert
nachdenken.
Zeiger verwendet man in c++ nicht ganz so oft (eigentlich nur, wenn man
optionale by-referenze Argumente braucht) Ansonsten sollte man
Referenzen benutzen.
> void BerechnePunkte(S_Spieler Spielertemp, int Bonus)
wird nicht funktionieren, oder ist S_Spieler ein Pointer?
Falls ja: sowas ist schlechter Stil. Seperate Pointer-Typ-Definitionen
vermüllen meist nur den Namensraum und machen den Code unübersichtlich,
weil man beim Lesen das S_Spieler erst in Spieler* übersetzen muss.
Man sollte vielleicht auch überlegen, zu wem diese Funktion gehört.
Ist es vielleicht eine Memberfunktion von Spieler? dann sollte sie so
aussehen:
1
classSpieler{
2
voidBerechnePunkte(intBonus)
3
// ...
4
}
oder gehört sie eher in die "Game"-Klasse.
Die kennt ja in der Regel enthaltenen Spieler. hier könnte der Parameter
also ein Index, oder eine Name sein, über den die Game-Klasse die
Spieler referenziert.
Ich hab das Gefühl bei deinem derzeitigem Wissensstand wäre es
sinnvoller, du suchst dir ein modernes C++ Tutorial und arbeitest das
erst mal durch (zusammen mit einem aktuellerem Compiler, wie VS2015 oder
VS2017).
Vielleicht findest du auch ein nettes Tutorial über Objektorientiertes
Design
Ob der VSE2012 Compiler C++11 schon unterstützt, weiß ich nicht, aber
selbst wenn, müsste man ihn erst per Commandline-Parameter darum bitten.
Eine typische Aktion bei mir nach dem ersten Compile eines C++-Projekts
mit Eclipse/GCC: -std=C++14 in die Projektsettings eintragen.