Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

28.04.2011, 16:02

Verständnisproblem Zeiger und Referenz

Hallo Leute,

ich versuche mich gerade mit Zeigern und Referenzen auseinanderzusetzen, weil man da bei C++ nicht drumherum kommt.
Bisher habe ich mich immer schwer getan, Zeiger zu verwenden.

Was ich mitbekommen habe ist, dass man Zeiger bei komplexen Datentypen als Funktionsparameter verwenden sollte.
Da ja bekanntlich ein Zeiger nur eine Speicheradresse (bei 32-Bit Systemen also 4 byte) beinhaltet und komplexe Datentypen wesentlich mehr Speicher verbraten können, sollte der Zeiger als Parameter benutzt werden.

Ich habe zu Testzwecken mal folgendes, kleines Programm geschrieben. Als komplexer Datentyp muss die "struct Starship" herhalten. Die überladene Funktion "ausgabeDaten" kann mit einer "Starship-Instanz" oder mit einem Zeiger auf ein "Starship" als Parameter aufgerufen werden.

Hier ist der Code:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <iostream>

using namespace std;

struct Starship
{
    unsigned int iWidth;
    unsigned int iHeight;
    unsigned int iShieldEnergy;
    unsigned int iVMax;
    unsigned int iWeaponRange;
    const char *pShipName;
};

//Funktion mit kompletter Struktur als Parameter
void ausgabeDaten(Starship s)
{
    cout << "-----------------------------------------------------" << endl;
    cout << "Funktionsaufruf mit kompletter Struktur als Parameter" << endl;
    cout << "-----------------------------------------------------" << endl;
    cout << endl << s.pShipName << endl;
    cout << "----------------------------" << endl;
    cout << "Breite:            \t"   << s.iWidth << endl;
    cout << "Hoehe:             \t"   << s.iHeight << endl;
    cout << "Schildenergie:     \t"   << s.iShieldEnergy << endl;
    cout << "Max.Geschwindigkeit:\t"   << s.iVMax << endl;
    cout << "Waffenreichweite:   \t"   << s.iWeaponRange << endl << endl <<endl;
}

//Funktion mit Zeiger auf Struktur als Parameter
void ausgabeDaten(Starship *s)
{
    cout << "-----------------------------------------------------" << endl;
    cout << "Funktionsaufruf mit Zeiger auf Struktur als Parameter" << endl;
    cout << "-----------------------------------------------------" << endl;
    cout << endl << s->pShipName << endl;
    cout << "----------------------------" << endl;
    cout << "Breite:            \t"   << s->iWidth << endl;
    cout << "Hoehe:             \t"   << s->iHeight << endl;
    cout << "Schildenergie:     \t"   << s->iShieldEnergy << endl;
    cout << "Max.Geschwindigkeit:\t"   << s->iVMax << endl;
    cout << "Waffenreichweite:   \t"   << s->iWeaponRange << endl << endl << endl;
}

//Funktion mit kompletter Struktur als Parameter
void manipuliereBreite(Starship s)
{
    s.iWidth = 0;
}

//Funktion mit Zeiger auf Struktur als Parameter
void manipuliereBreite(Starship *s)
{
    s->iWidth = 0;
}



int main()
{
    Starship ISS;

    ISS.iHeight     = 20;
    ISS.iWidth      = 50;
    ISS.iShieldEnergy = 1000;
    ISS.iVMax       = 2000;
    ISS.iWeaponRange  = 3000;
    ISS.pShipName   = "International Space Station";

    //Funktionsaufruf mit Referenz auf Struktur im Parameter
    ausgabeDaten(&ISS);
    //Funktionsaufruf mit kompletter Struktur im Parameter
    ausgabeDaten(ISS);

    //Funktionsaufruf mit Referenz auf Struktur im Parameter
    manipuliereBreite(&ISS);
    //Funktionsaufruf mit kompletter Struktur im Parameter
    manipuliereBreite(ISS);


    cout << endl << "Ausgabe der manipulierten Breite: " << endl;
    cout <<         "==================================" << endl;

    //Funktionsaufruf mit Referenz auf Struktur im Parameter
    ausgabeDaten(&ISS);
    //Funktionsaufruf mit kompletter Struktur im Parameter
    ausgabeDaten(ISS);


    Starship *pStarship;

    cout << "Speicherbedarf vom Datentyp Starship:  \t" << sizeof(Starship)  << " byte" << endl;
    cout << "Speicherbedarf eines Zeigers auf Starship:\t" << sizeof(pStarship) << " byte" << endl << endl;

    return 0;
}


Die Funktion "manipuliereDaten()" zeigt, dass beide Parametertypen eine Manipulation der "Starship-Instanz" zulassen.

Ich habe noch ein paar Fragen:

1. Bringt der geringere Speicherbedarf beim Zeiger wirklich Performancegewinn, oder ist das eher vernachlässigbar?
Ich vermute mal man sollte immer Zeiger verwenden, wenn der Parameter größer als ein Integer ist, oder?
Die größte Wirkung zeigt sich vermutlich bei der Übergabe von Klasseninstanzen, also von Objekten einer Klasse.

2. Ist mein Code korrekt, oder seht ihr grobe Schnitzer, die ich unbedingt anders lösen sollte?

3. Was für Vorteile hat die Parameterübergabe als Zeiger noch?

Bin für jeden Tipp sehr dankbar...

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

2

28.04.2011, 16:14

Änder mal das hier:

C-/C++-Quelltext

1
2
3
4
void manipuliereBreite(Starship s)
{
    s.iWidth = 0;
}


in das hier:

C-/C++-Quelltext

1
2
3
4
void manipuliereBreite(Starship s)
{
    s.iWidth = 5;
}


und dann schau dir die Ausgaben des Programms nochmal an. Aber nur diese Zeilen, die zweite Funktion lässt du wie sie ist.

Und bitte... schreib entweder alles Englisch oder alles Deutsch. Der Mix ist grauenvoll.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

3

28.04.2011, 16:20

Ja, ist klar durch die zweite manip-Funktion wird der Wert wieder auf 0 gesetzt. Weil der Zeiger auf das gleiche Objekt zeigt.
Der doppelte Funktionsaufruf ist also völliger Quatsch, weil es letztendlich ein und das selbe ist.

Welche Funktion ist aber programmiertechnisch effektiver? Ich denke die mit Zeiger, oder?

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

4

28.04.2011, 16:21

Ja doch ist schon richtig so. Klar gibts da viel was man noch dazu sagen kann, jedoch ist das dann nichts für den Anfang. Du solltest aber nochmal auf deine manipulations-Funktion schauen. Erstell mal 2 Objekte vom Typ Schiff und mit dem einen regelst du alles über Zeiger und mit dem anderen ohne. Dann fällt dir die wichtigste Eigenschaft der Zeiger auf;)
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

5

28.04.2011, 16:34

Nein das ist falsch. Bei der einen Version wird nichts geändert. Nimm mal für beide Varianten verschiedene Objekte dann siehst du es. Einfach 2 verschiedene Instanzen benutzen.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

6

28.04.2011, 16:44

Ja, ist klar durch die zweite manip-Funktion wird der Wert wieder auf 0 gesetzt. Weil der Zeiger auf das gleiche Objekt zeigt.
Der doppelte Funktionsaufruf ist also völliger Quatsch, weil es letztendlich ein und das selbe ist.

Nein, falsch. Mach es einfach mal. Der Wert wird nämlich nicht bei 5 landen. Die zweite Funktion soll ihn ja auf 5 ändern. Macht sie aber nicht. Eben weil keine Referenz und kein Pointer übergeben wird, sondern eine Kopie. Und in einer Kopie kannst du so viel ändern, wie du willst, es hat keinen Effekt auf das ursprüngliche Objekt.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Fred

Supermoderator

Beiträge: 2 121

Beruf: Softwareentwickler

  • Private Nachricht senden

7

28.04.2011, 16:48

Der doppelte Funktionsaufruf ist also völliger Quatsch, weil es letztendlich ein und das selbe ist.


C-/C++-Quelltext

1
void manipuliereBreite(Starship s)

und

C-/C++-Quelltext

1
void manipuliereBreite(Starship* s)

sind nicht ein und dasselbe. Dein Programm ist nur ein bisschen ungünstig, weil es nur am Ende etwas ausgibt und du so nicht siehst, was da egtl. passiert. Außerdem sollen beide Funktionen die Breite auf den gleichen Wert setzen. Was aber wenn es nur eine Funktion tut? Dann fällt dir das in deinem Fall nämlich nicht auf. Und du wirst staunen. Die Breite wird tatsächlich nur von einer Funktion manipuliert. Nämlich von der Funktion, die einen Zeiger als Parameter erwartet. Die andere Funktion manipuliert zwar auch eine Breite, aber nicht die, von deinem Objekt. Sie arbeitet nämlich mit einer Kopie deiner Instanz und verändert die Werte der Kopie. Das Original bleibt aber erhalten und ändert sich nicht. Und damit wären wir auch schon beim großen Geheimnis von Call-by-reference and und call-by-value ;).

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

8

28.04.2011, 16:58

Und damit wären wir auch schon beim großen Geheimnis von Call-by-reference and und call-by-value .
Sorry aber ich find das and in dem deutschen Satz zu geil;)
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

9

28.04.2011, 21:43

Sorry, dass ich mich erst jetzt wieder melde.

Ich denke, ich habe den Unterschied der beiden Funktionen jetzt kapiert und das habe ich euch zu verdanken Leute.

Wenn ich die manip-Funktionen in Rückgabetyp int ändere, werden aber beide Breiten geändert:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Funktion mit kompletter Struktur als Parameter
int manipuliereBreite(Starship s)
{
s.iWidth = 0;
return s.iWidth;
}

//Funktion mit Zeiger auf Struktur als Parameter
int manipuliereBreite(Starship *s)
{
s->iWidth = 0;
return s->iWidth;
}

//Weiter unten im Code dann:
cout << "Breite:            \t"   << manipuliereBreite(s) << endl;


Wahrscheinlich liegt es daran, dass ich dann mit die Breite der Kopie von der Struktur ausgebe, oder?

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

10

28.04.2011, 21:51

Nein, die Breite wird noch immer NICHT dauerhaft geändert. Die Funktion gibt nur einen passenden Wert zurück (nämlich den der Kopie). Das Original bleibt weiterhin unverändert. Verstanden hast Du es leider somit doch noch nicht.

"Viel zu lernen Du noch hast"[/quote]
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige