Zeigerprobleme



  • Hallo zusammen,
    ich versuche gerade mich etwas in c++ rein zu arbeiten aber , wie scheinbar ziemlich viele andere, hänge ich bei den Zeigern irgendwie fest. Ich verstehe einfach nicht den Sinn der Zeiger. Ich programmiere seit langer Zeit beruflich in PHP und bin auch in VB relativ fit. Was das Programmieren angeht bin ich also nicht gerade unerfahren. Ich benutze natürlich auch beispielsweise in PHP Referenzen und komme damit gut klar aber die Zeiger in c++ geben mir den Rest. Es liegt sicherlich zum Teil daran, dass ich einige Sachen dabei einfach nur unlogisch finde (wahrscheinlich weil ich nicht ganz dahintersteige).

    Beispiel:

    int a = 10;
    int *p;
    
    p = &a;
    
    cout << *p; // gibt 10 aus
    

    So weit, so gut.
    Jetzt kommt etwas, dass sich meiner Logik verschliesst:

    int a = 10;
    int *p;
    
    *p = &a; // geht nicht. Compiler meckert.
    
    int a = 10;
    int *p = &a; // geht!
    

    Wo ist bei diesen beiden Beispielen der Unterschied?

    Gibt es ein einfaches Beispiel, an dem man mal nachvollziehen kann, was man mit Zeigern realisieren kann, nicht aber mit "normalen" Referenzen?

    Viele Grüße
    Matthias Bo



  • Der zweite Code funktioniert nicht, weil du den Zeiger (unbeabsichtigt) dereferenziert hast -selbst wenn der Compiler das durchgehen lassen würde, wäre die Folge ein SegFault. Das dritte Beispielt stellt eine Definition (int*p) mit Initialisierung (=&a) dar.


  • Mod

    Matthias Bo schrieb:

    Jetzt kommt etwas, dass sich meiner Logik verschliesst:

    int a = 10;
    int *p;
    
    *p = &a; // geht nicht. Compiler meckert.
    
    int a = 10;
    int *p = &a; // geht!
    

    Wo ist bei diesen beiden Beispielen der Unterschied?

    Das eine ist ein Ausdruck (* ist hier ein Operator, der einen Zeiger als Operand hat), das andere eine Deklaration und Definition (* ist hier ein Deklarator, der den Grundtyp int dekoriert und den von int abgeleiteten Typ Zeiger-auf-int generiert).

    int a = 10;
    int *p; // Deklariert, dass p in allen folgenden Ausdrücken auf ein Zeigerobjekt auf int verweist
    
    *p = &a; // geht nicht. Compiler meckert.
             // Der Subausdruck p verweist auf ein Zeigerobjekt
             // Der unäre Operator * benutzt den Wert des Arguments (d.h. den Wert des Zeigerobjekts, auf das p verweist bzw. das p heißt) und liefert als Ergebnis einen Verweis (technisch: lvalue) auf das int-Objekt, auf das p zeigt)
             // der Subausdruck *p verweist also auf ein int (und hat damit den Typ int)
             // Der Subausdruck &a ist dagegen ein Zeiger auf ein int
             // damit sind die Typen auf beiden Seiten der Zuweisung inkompatibel, der Ausdruck ill-formed
    


  • int a = 10; // Deklaration und Instanzierung einer Integervariable und Zuweisung eines Wertes an die Variable
    int *p; // Deklaration eines Zeigers auf eine Integervariable
    p = &a; // Zuweisung der Adresse der Variable a an den Zeiger p
    
    int a = 10; // s.o.
    int *p; // s.o.
    *p = &a; // geht nicht, weil Du derefenzierst *)
    

    ) Durch das voranstellen eines * an eine Variable greifst Du nicht mehr auf den Zeiger zu, sondern auf den Wert, der an dieser Adresse gespeichert ist. Tückisch ist hierbei, dass es sich bei int *p um eine Deklaration und bei *p um eine Derefenzierung handelt. Ich vermute genau deswegen verwenden viele die Schreibweise int p.

    int a = 10;
    int *p = &a; // das ist das gleiche wie Zeile 2 und 3 aus Bespiel 1, nur in einer Zeile zusammengefaßt
    


  • Ok, vielen Dank soweit. Das scheint mir jetzt einleuchtend zu sein. Aber bis ich hinter die ganzen "Zeigergeschichten" gestiegen bin, wird wohl noch einiges an Zeit verstreichen. Der Sinn der Grundgedankens "Zeiger" ist mir dennoch etwas rätselhaft.

    Wahrscheinlich muss man selbst irgendwann erst an einen Punkt im Code kommen, an dem es ohne Zeiger nicht geht, um wirklich zu verstehen, wozu die Dinger gut sind :).



  • Ich kann dir direkt ein Beispiel nennen bei denen du ohne Zeiger (oder auch Referenzen) nicht auskommen würdest.

    Nehmen wir an du wolltest die Werte zweier Variablen in einer Tausch-Funktion tauschen lassen:

    //Übergabe als Zeiger
    #include <iostream>
    #include <conio.h> // Für den lieben Borland
    using namespace std;
    
    //--
    
    void Tauschen(int *x,int *y); //Prototyp der Funktion, nimmt die Werte der Variable entgegen, auf die die Zeiger gerichtet sind
    
    int main()
    {
      int X = 5,Y = 10;
      cout << "Vor dem Tauschen: X = " << X << " , Y = " << Y << endl;  
      Tauschen(&X,&Y);//Übergabe der Adresse an die Funktion
      cout << "Nach dem Tauschen: X = " << X << " , Y = " << Y << endl;
      getch();//Damit das Fenster offen bleibt ( Borland ;> )
    }
    
    void Tauschen(int *x,int *y)
    {
      int TEMP;
      TEMP = *y;
      *y = *x;
      *x = TEMP;
    }
    

    Da nur die Werte der Variablen an die Funktionen übergeben werden, würde das ohne Zeiger nicht Funktionieren, da sonst nur innerhalb der Funktion Tauschen() die Werte der lokalen Variablen vertauscht würden.

    Du kannst ja mal schauen was hierbei rauskommen 😉 Gruß

    //Übergabe als Zeiger
    #include <iostream>
    #include <conio.h> // Für den lieben Borland
    using namespace std;
    
    //--
    
    void Tauschen(int X,int Y); //Prototyp der Funktion, nimmt die Werte der Variablen entgegen, auf die die Zeiger im Speicher zeigen
    
    int main()
    {
      int X = 5,Y = 10;
      cout << "Vor dem Tauschen: X = " << X << " , Y = " << Y << endl;  
      Tauschen(X,Y);//Übergabe der Adresse an die Funktion
      cout << "Nach dem Tauschen: X = " << X << " , Y = " << Y << endl;
      getch();//Damit das Fenster offen bleibt ( Borland ;> )
    }
    
    void Tauschen(int X,int Y)
    {
      int TEMP;
      TEMP = Y;
      Y = X;
      X = TEMP;
    }
    


  • Ok, das ist einleuchtend. Aber in PHP würde ich das nach diesem Prinzip machen:

    #include <iostream>
    using namespace std;
    
    void myswap(int &a, int &b){
    
    	int tmp;
    	tmp = a;
    	a = b;
    	b = tmp;
    }
    
    int main (){
    
    	int a = 10;
    	int b = 20;
    
    	myswap(a, b);
    
    	cout << a << " " << b << endl;
    
    	return 0;
    }
    

    Ich würde also lediglich Referenzen benutzen und auf Zeiger verzichten. Genau aus diesem Grund sind mir die Zeiger ja so suspekt. Ich sehe einfach kein einleuchtendes Beispiel, bei dem ich unbedingt Zeiger brauche.



  • Zeiger brauchst du generell für verkettete Datenstrukturen (Bäume, Listen usw.) sowie zur Verwaltung dynamisch angeforderten Speichers (new/delete).



  • Außerdem ist es praktisch, wenn wahlweise ein Objekt übergeben werden kann, etwa so:

    int foo( int* a, int* b )
    {
        int i = 5;
        if ( a != 0 ) // FALLS ein gültiger Zeiger auf einen int übergeben wurde
            i *= *a;  //   dann i mit dem Wert dahinter malnehmen
        if ( b != 0 )
            i *= b;
        return i;
    }
    
    ..
    
    //entweder
    int a=7, b=9;
    int c = foo( &a, &b ); // Ergebnis wäre 5*7*9
    
    //oder
    int a=8;
    int c = foo( &a, 0 ); // Ergebnis wäre 5*8
    
    // oder
    int a = 100;
    int c = foo( 0, &a ); // Ergebnis wäre 5*100
    

Anmelden zum Antworten