Struktur als Rückgabewert und notwendige Initialisierung

Schnelle objektorientierte, kompilierende Programmiersprache.
Antworten
forumnewbie
Beiträge: 80
Registriert: Di Jan 15, 2013 9:02 pm

Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von forumnewbie » So Mär 24, 2013 5:03 pm

Hi,

ich habe ein Beispiel mit Strukturen gebastelt, was auch richtig funktioniert:

Code: Alles auswählen

#include <iostream>

using namespace std;

struct S_Human
{
	int alter;
	char name[30];
};

S_Human createHuman(S_Human human);

int main()
{
	S_Human human1, human2;
	
	//Human initialisieren
	human1.alter = 0;
	human1.name[0] = '\0';
	human2.alter = 0;
	human2.name[0] = '\0';

	human1 = createHuman(human1);
	human2 = createHuman(human2);

	cout << "Alter:" << human1.alter << "\tName:" << human1.name << endl;
	cout << "Alter:" << human2.alter << "\tName:" << human2.name << endl;
	return 0;
}

S_Human createHuman(S_Human human)
{
	cout << "Alter:";
	cin >> human.alter;
	cout << "Name:";
	cin >> human.name;

	return human;
}
Könnt ihr bitte überprüfen, ob ich diesen Vorgang (Übergabe von Instanzen an eine Funktion und ihr Rückgabewert vom Typ dieser Instanzen) richtig verstanden habe:

Code: Alles auswählen

human1 = createHuman(human1);
human2 = createHuman(human2);
Ich übergebe zwei Instanzen/Kopien human1 und human2 vom Typ meiner Struktur S_Human an die Funktion createHuman(). Dabei werden alle Werte dieser Instanzen, die bestimmten Datentyp haben, vorher auf den Stack kopiert. Aus diesem Grund ist es unbedingt erforderlich, dass ich meine Instanz vor der Übergabe an eine Funktion mit Werten initialisiere, da diese Werte (Cal by Value) auf den Stack kopiert werden müssen, aber ohne vorherige Initialisierung sind meine Instanzen human1 und human2 leer und haben keine Werte.
In der Funktion createHuman() wird bei jedem Aufruf eine andere lokale Instanz der selben Struktur erzeugt, die nur innerhalb dieser Funktion gültig ist und nach dem Funktionsaufruf (wenn der Funktionskörper verlassen wird) sofort wieder zerstört wird. Beim Aufruf holt sich diese Funktion die Werte vom Stack und weist diese Werte der lokalen Instanz human zu. Dann werden die Werte innerhalb der Funktion mittels cin verändert und diese Instanz wird mit neuen Werten als Rückgabewert zurückgegeben. Das heißt, dass diese neue Werte wieder auf den Stack gelegt werden.
Diese neue Werte holen sich meine Instanzen human1 und human2 wieder vom Stack und überschreiben damit ihre alte Werte.

Habe ich diesen Vorgang korrekt verstanden? Etwas wichtiges vergessen?

Vielen Dank!

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von Xin » So Mär 24, 2013 5:35 pm

forumnewbie hat geschrieben:Habe ich diesen Vorgang korrekt verstanden? Etwas wichtiges vergessen?
Den Vorgang hast Du sogar erstaunlich gut verstanden - insbesondere wenn man sich Deine andere Frage ansieht. ^^

Bist Du mit Zeigern soweit vertraut? Wenn ja, übergib einen Zeiger auf die Struktur an die Funktion und ändere die Struktur, die bei main auf dem Stack liegt. Das erspart Dir die ganze Kopiererei.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

forumnewbie
Beiträge: 80
Registriert: Di Jan 15, 2013 9:02 pm

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von forumnewbie » So Mär 24, 2013 6:41 pm

Den Vorgang hast Du sogar erstaunlich gut verstanden - insbesondere wenn man sich Deine andere Frage ansieht. ^^
Danke! Endlich!!! :) Das habe ich meinem neuen Buch (C++ für Spieleprogrammierer von Heiko Kalista) zu verdanken. Anscheinend gibt es nichts besseres, als ein gutes Buch. In diesem Buch lernt man zwar keine 2D/3D-Spiele zu programmieren, aber der Autor erklärt sehr verständlich und anschaulich die C++ Grundlagen - in der Konsole. Zum Schluss wird ein kleines 2D-Spiel gezeigt. Bin bis jetzt mit dem Buch sehr zufrieden.

Dieses Beispiel und die Erklärung ist von mir. Er hat das Thema an mehreren Stellen versucht zu erklären und ich habe versucht das zusammenzufassen und dazu mir ein Beispiel ausgedacht.(Er erklärt einige Sachen Stück für Stück, weil für bestimmte Erklärungen noch die Grundlagen fehlen, die in späteren Kapiteln erklärt werden).
Bist Du mit Zeigern soweit vertraut? Wenn ja, übergib einen Zeiger auf die Struktur an die Funktion und ändere die Struktur, die bei main auf dem Stack liegt. Das erspart Dir die ganze Kopiererei.
Genau bei diesem Thema bin ich gerade angekommen :) : Übergabe als Wert, als Zeiger und als Referenz an eine Funktion. Er hat geschrieben, dass wenn man etwas als Zeiger oder Referenz übergibt (Referenz immer mit const - wegen Verwechslungsgefahr mit Call-By-Value), dass dann keine Daten auf den Stack gelegt werden müssen und dadurch viel Rechenzeit gespart wird (bei großen Instanzen). Beziehungsweise hat er das nur bei den Referenzen so gesagt. Aber bei bei Übergabe per Zeiger wird bestimmt auch kein Stack verwenden?

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von Xin » Mo Mär 25, 2013 12:08 pm

forumnewbie hat geschrieben:Anscheinend gibt es nichts besseres, als ein gutes Buch.
Man könnte einen guten Lehrer nehmen, aber die sind noch schwerer zu finden als ein gutes Buch. :-D
forumnewbie hat geschrieben:
Bist Du mit Zeigern soweit vertraut? Wenn ja, übergib einen Zeiger auf die Struktur an die Funktion und ändere die Struktur, die bei main auf dem Stack liegt. Das erspart Dir die ganze Kopiererei.
Genau bei diesem Thema bin ich gerade angekommen :) : Übergabe als Wert, als Zeiger und als Referenz an eine Funktion. Er hat geschrieben, dass wenn man etwas als Zeiger oder Referenz übergibt (Referenz immer mit const - wegen Verwechslungsgefahr mit Call-By-Value), dass dann keine Daten auf den Stack gelegt werden müssen und dadurch viel Rechenzeit gespart wird (bei großen Instanzen). Beziehungsweise hat er das nur bei den Referenzen so gesagt. Aber bei bei Übergabe per Zeiger wird bestimmt auch kein Stack verwenden?
Naja, C kennt kein Call-By-Reference. Aber wenn man den Zeiger auf den Stack legt (und der Zeiger wird dann per Call-By-Value auf den Stack gelegt), dann ist technisch das selbe, semantisch kopierst Du aber den Zeiger auf eine Instanz, was nur deswegen was mit der Instanz zu tun hat, weil der kopierte Zeiger nunmal "zufälligerweise" auch drauf zeigt. Ein Zeiger ist dabei natürlich viel schneller kopiert als große Instanzen.

Benutze nach Möglichkeit Referenzen (int & statt int *). Zeiger solltest Du nur nutzen, wenn Du damit ausdrücken möchtest, dass der Zeiger NULL oder veränderlich sein darf.

Die Sache mit dem const, kommentiere ich mal nicht. Ich bin sicher, dass da in dem Buch noch was kommt und Kommentare Dich da höchstens verwirren würden.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

forumnewbie
Beiträge: 80
Registriert: Di Jan 15, 2013 9:02 pm

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von forumnewbie » Mo Mär 25, 2013 6:44 pm

Also wenn man mit Zeigern und Referenzen arbeitet, dann wird zwar auch der Stack benutzt, aber nur um eine Adresse darauf zu legen, mit der dann eine Funktion weiterarbeitet. Somit wird nur ein einziger Wert auf den Stack kopiert - anstatt von z.B. 10 oder mehr Werten bei großen Objekten/Instanzen. Richtig?

PS: Der Autor hat geschrieben, dass ich Zeiger und Referenzen bei Strukturen/Objekten benutzen soll, um das Kopieren auf den Stack zu meiden. Er hat aber geschrieben, dass man Referenzen nehmen soll, wenn man keine Werte ändern will, sondern nur diese auslesen. Deshalb benutzt er Referenzen immer mit const. Und sobald man Werte eines Objektes verändern möchte, dann nutzt man den Zeiger. Somit kann man sofort an der Parameterliste erkennen, ob eine Funktion einen Wert eines Objektes verändert oder nicht.

PSS:
z.B.
aenderePunkte(Spieler *pSpieler);
und
zeigePunkte(const Spieler &Spieler);

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von Xin » Mo Mär 25, 2013 9:22 pm

forumnewbie hat geschrieben:Also wenn man mit Zeigern und Referenzen arbeitet, dann wird zwar auch der Stack benutzt, aber nur um eine Adresse darauf zu legen, mit der dann eine Funktion weiterarbeitet. Somit wird nur ein einziger Wert auf den Stack kopiert - anstatt von z.B. 10 oder mehr Werten bei großen Objekten/Instanzen. Richtig?
Richtig.
forumnewbie hat geschrieben:PS: Der Autor hat geschrieben, dass ich Zeiger und Referenzen bei Strukturen/Objekten benutzen soll, um das Kopieren auf den Stack zu meiden.
Das Kopieren solltest Du vermeiden, außer Du möchtest nicht mit dem Original arbeiten, weil Du sowieso grundsätzlich eine Kopie benötigst. Wenn Du keine Kopie benötigst, solltest Du auch keine erstellen, außer die Kopie zu erstellen ist einfacher, als den Wert zu kopieren. Eine Referenz auf int zum Beispiel kostet mehr Zeit als dass sie einspart.
forumnewbie hat geschrieben:Er hat aber geschrieben, dass man Referenzen nehmen soll, wenn man keine Werte ändern will, sondern nur diese auslesen. Deshalb benutzt er Referenzen immer mit const. Und sobald man Werte eines Objektes verändern möchte, dann nutzt man den Zeiger. Somit kann man sofort an der Parameterliste erkennen, ob eine Funktion einen Wert eines Objektes verändert oder nicht.
Das würde ich sagen, hast Du falsch verstanden. Alternativ hat der Autor schlecht erklärt.

Wenn Du nichts änderst, dann übergibst Du einen Const-Parameter - egal ob als Zeiger oder per Referenz, Hauptsache const. Eine Referenz übergibst Du dann, wenn Du eine existierende Instanz erwartest, einen Zeiger, wenn Du auch NULL übergeben darfst.
Da Du einen Zeiger erst fragen musst, ob er NULL ist, verwendest Du bevorzugt Referenzen, denn die musst Du nicht fragen.
forumnewbie hat geschrieben: PSS:
z.B.
aenderePunkte(Spieler *pSpieler);
und
zeigePunkte(const Spieler &Spieler);
Tu Dir für die Zukunft selbst den Gefallen und füge das const hinter den Datentyp an:

Code: Alles auswählen

void aenderePunkte( Spieler & spieler ); // kein const => Änderungen möglich!
void zeigePunkte( Spieler const & spieler ); // const => keine Änderungen möglich!
Beide Funktionen benötigen einen real existiere Spielerinstanz. Beide Funktionen sollten mit Referenzen arbeiten.

Warum const hinten?
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

forumnewbie
Beiträge: 80
Registriert: Di Jan 15, 2013 9:02 pm

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von forumnewbie » Mo Mär 25, 2013 10:23 pm

Hi und danke für deine Antwort.

Er hat drei Regeln aufgestellt, die darüber entscheiden, ob ein Parameter als Wert, per Zeiger oder per Referenz übergeben werden soll.

Zu Referenzen hat er folgendes geschrieben geschrieben:
"Referenten sollten man dann verwenden, wenn man Instanzen und große Objekte übergibt, die von der aufzurufenden Funktion nicht geändert werden müssen. [...] Indem man eine konstante Referenz übergibt, verbindet man den Vorteil der Übergabe per Adresse und der Sicherheit, dass das übergebene Objekt nicht geändert werden kann.[...]
Zu den Zeigern:
"Wenn eine Variable oder eine Instanz von der Funktion geändert werden soll, die man diese übergibt, dann ist ein Zeiger angebracht. Man sieht schon beim Funktionsaufruf, dass es sich um einen Zeiger handelt und der Wert seines Ziels geändert werden kann, da man diesen explizit mit dem Adressoperator übergeben muss.[...]"

Ich habe noch eine andere wichtige Frage. Könntet ihr bitte überprüfen, ob ich folgendes richtig verstanden habe bzw. ob das überhaupt stimmt? :
Wenn eine Funktion mit lokalen Variablen arbeitet, werden dann ALLE diese Variablen (ihre Werte) automatisch auf dem Stack abgelegt. Und alle Variablen auf dem Stack sind nur innerhalb der Funktionen gültig und werden sofort vom Stack gelöscht, wenn Funktionen verlassen werden.
Wenn man jedoch das verhindern will - wenn man zum Beispiel mit Objekten arbeitet (Klassen), die aus vielen solchen Variablen bestehen, dann kann man stattdessen den Heap-Speicher nutzen, in dem man mittels new den Speicher selbst auf dem Heap reserviert und dann mit delete später wieder vom Heap löscht. Dort ablegte Variablen bzw. ihre Werte verlieren nicht ihre Gültigkeit, wenn eine Funktion verlassen wird.

Danke!
Zuletzt geändert von forumnewbie am Mo Mär 25, 2013 11:01 pm, insgesamt 2-mal geändert.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8862
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von Xin » Mo Mär 25, 2013 10:59 pm

forumnewbie hat geschrieben:Hi und danke für deine Antwort.

Er hat drei Regeln aufgestellt, die darüber entscheiden, ob ein Parameter als Wert, per Zeiger oder per Referenz übergeben werden soll.

Zu den Zeigern:
da man diesen explizit mit dem Adressoperator übergeben muss.[...]"
Das würde ich so definitiv nicht unterschreiben.

Die Idee, eine Änderung im Quelltext zu markieren ist sinnvoll, aber der Address-Of-Operator nimmt, bestimmt die Adresse einer Variablen und markiert nichts anderes. Hier(für) Zeiger zu verwenden halte ich für semantisch absolut falsch.
forumnewbie hat geschrieben:Ich habe noch eine andere wichtige Frage. Könntet ihr bitte überprüfen, ob ich folgendes richtig verstanden habe bzw. ob das überhaupt stimmt? :
Wenn eine Funktion mit lokalen Variablen arbeitet, werden dann ALLE diese Variablen (ihre Werte) automatisch auf dem Stack abgelegt. Und alle Variablen auf dem Stack sind nur innerhalb der Funktionen gültig und werden sofort vom Stack gelöscht, wenn Funktionen verlassen werden.
Ja, wenn sie nicht als statisch markiert sind und ja, wenn sie nicht als statisch markiert sind.
forumnewbie hat geschrieben:Wenn man jedoch das verhindern will - wenn man zum Beispiel mit Objekten arbeitet (Klassen), die aus vielen solchen Variablen bestehen, dann kann man stattdessen den Heap-Speicher nutzen, in dem man mittels new den Speicher selbst auf dem Heap reserviert und dann später selbst wieder vom Heap löscht. Dort ablegte Variablen bzw. ihre Werte verlieren nicht ihre Gültigkeit, wenn eine Funktion verlassen wird.
Richtig, es können aber sinnvollerweise aber Member der Klasse sein.

Es gibt selten eindeutige, uneingeschränkt zu empfehlende Regeln. Du lernst eine neue Sprache. Wenn Du sprechen lernen möchtest, lerne die Regeln der Grammatik und halte Dich stur daran. Ein Dichter wirst Du so aber nicht.

Überlege, welches Problem Du lösen willst und welche Lösung Dir dafür sinnvoll erscheint. Diskutiere mit anderen Entwicklern. Die Sache mit dem Address-Of-Operator ist nicht dumm, aber es ist auch ein Missbrauch von Sprachmitteln, um Probleme zu lösen, für der Address-Of-Operator nicht gedacht ist. Und ich hoffe, der Autor hat darauf hingewiesen, dass die Funktion auch NULL-Pointer akzeptiert, abfragen muss und dafür sinnfrei Rechenleistung verbrät, damit dieser Marker an der Aufrufstelle steht. Und dieser Marker kann auch in die Irre führen, wenn man einen Parameter vom Typ (int const *&) hat.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

forumnewbie
Beiträge: 80
Registriert: Di Jan 15, 2013 9:02 pm

Re: Struktur als Rückgabewert und notwendige Initialisierung

Beitrag von forumnewbie » Mo Mär 25, 2013 11:10 pm

Danke! Ich bin froh, dass ich diese Vorgänge endlich ungefähr verstehe - das was im Hintergrund passiert, wenn ich meine Funktionen und Variablen benutze.

Er erklärt Themen Stück für Stück, um den Leser nicht zu überfordern. Es kann sein, dass später im Buch noch weitere und detaillierte Erklärungen folgen werden.
Bin gerade beim Thema Vererbung angekommen (7. Kapitel von 12). Statische Membervariablen kommen direkt danach (nach Vererbung).

Antworten