459 Stimmen

Warum Pointer verwenden?

Ich weiß, dass dies eine wirklich grundlegende Frage ist, aber ich habe gerade erst mit einigen grundlegenden C++-Programmierungen begonnen, nachdem ich einige Projekte mit Hochsprachen programmiert habe.

Grundsätzlich habe ich drei Fragen:

  1. Warum Pointer anstelle von normalen Variablen verwenden?
  2. Wann und wo sollte ich Pointer verwenden?
  3. Wie verwendet man Pointer mit Arrays?

8 Stimmen

Bereits diskutiert unter dieser Frage Hoffe, das hilft!

0 Stimmen

Eine weitere Diskussion über Pointer hier.

4 Stimmen

Für eine Liste von Büchern, siehe stackoverflow.com/questions/388242/…. Nach Java fand ich Accelerated C++ sehr nützlich.

227voto

Tooony Punkte 3531
  • Warum Pointer anstelle von normalen Variablen verwenden?

Kurze Antwort: Tu es nicht. ;-) Pointer sollten dort verwendet werden, wo nichts anderes möglich ist. Entweder wegen des Mangels an geeigneter Funktionalität, fehlenden Datentypen oder rein zur Leistungssteigerung. Mehr unten...

  • Wann und wo sollte ich Pointer verwenden?

Kurze Antwort hier: Dort, wo du nichts anderes verwenden kannst. In C hast du keine Unterstützung für komplexe Datentypen wie einen String. Es gibt auch keine Möglichkeit, eine Variable "per Referenz" an eine Funktion zu übergeben. Dort muss man Pointer verwenden. Außerdem kannst du sie auf praktisch alles zeigen lassen, verkettete Listen, Elemente von Strukturen usw. Aber lassen wir das hier außen vor.

  • Wie benutzt man Pointer mit Arrays?

Mit wenig Aufwand und viel Verwirrung. ;-) Wenn wir über einfache Datentypen wie int und char sprechen, gibt es wenig Unterschied zwischen einem Array und einem Pointer. Diese Deklarationen sind sehr ähnlich (aber nicht gleich - z.B. sizeof gibt unterschiedliche Werte zurück):

char* a = "Hallo";
char a[] = "Hallo";

Du kannst auf jedes Element im Array so zugreifen

printf("Das zweite Zeichen ist: %c", a[1]);

Index 1, da das Array mit Element 0 beginnt. :-)

Oder du könntest genauso gut dies tun

printf("Das zweite Zeichen ist: %c", *(a+1));

Der Zeigeroperator (das *) wird benötigt, da wir printf mitteilen, dass wir ein Zeichen drucken wollen. Ohne das *, würde die Zeichenrepräsentation der Speicheradresse selbst gedruckt werden. Jetzt verwenden wir stattdessen das Zeichen selbst. Wenn wir anstelle von %c %s verwendet hätten, hätten wir printf gebeten, den Inhalt der Speicheradresse, auf die 'a' zeigt plus eins (in diesem obigen Beispiel) zu drucken, und wir hätten das * nicht voranstellen müssen:

printf("Das zweite Zeichen ist: %s", (a+1)); /* FALSCH */

Aber das hätte nicht nur das zweite Zeichen gedruckt, sondern stattdessen alle Zeichen in den darauf folgenden Speicheradressen, bis ein Nullzeichen (\0) gefunden wurde. Und hier beginnen die Dinge gefährlich zu werden. Was ist, wenn du versehentlich versuchst, eine Variable des Typs integer anstelle eines char-Zeigers mit dem %s-Formatierer zu drucken?

char* a = "Hallo";
int b = 120;
printf("Das zweite Zeichen ist: %s", b);

Dies würde das drucken, was auf der Speicheradresse 120 gefunden wird und würde weiter drucken, bis ein Nullzeichen gefunden wird. Es ist falsch und illegal, diese printf-Anweisung auszuführen, aber sie würde wahrscheinlich trotzdem funktionieren, da ein Zeiger tatsächlich in vielen Umgebungen vom Typ int ist. Stelle dir die Probleme vor, die du verursachen könntest, wenn du sprintf() verwenden würdest und auf diese Weise zu viel "Zeichenarray" einer anderen Variablen zuweisen würdest, die nur einen bestimmten begrenzten Speicherplatz hat. Du würdest wahrscheinlich etwas anderes im Speicher überschreiben und dein Programm zum Absturz bringen (wenn du Glück hast).

Ach ja, und wenn du bei der Deklaration dem char-Array / Zeiger keinen Zeichenwert zuweist, musst du ihm UNBEDINGT eine ausreichende Menge Speicher zuweisen, bevor du ihm einen Wert gibst. Verwende malloc, calloc oder Ähnliches. Hier sind ein paar Beispiele:

char* x;
/* Reserviere 6 Bytes Speicher für mich und zeige mit x auf das erste davon. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" an Adresse: %d\n", x, x);
/* Lösche die Speicherreservierung. */
/* Der char-Zeiger x zeigt allerdings immer noch auf diese Speicheradresse! */
free(x);
/* Das Gleiche wie malloc, aber hier ist der zugeordnete Speicher mit Nullzeichen gefüllt! */
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" an Adresse: %d\n", x, x);
/* Und lösche die Speicherreservierung erneut... */
free(x);
/* Wir können die Größe auch bei der Deklarationszeit festlegen */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" an Adresse: %d\n", xx, xx);

Beachte, dass du den Zeiger x weiter verwenden kannst, nachdem du den reservierten Speicher freigegeben hast, aber du weißt nicht, was darin ist. Beachte auch, dass die beiden printf() dir möglicherweise unterschiedliche Adressen geben, da nicht garantiert ist, dass die zweite Speicherzuweisung im gleichen Bereich wie die erste erfolgt.

10 Stimmen

Dein Beispiel mit 120 darin ist falsch. Es verwendet %c anstelle von %s, daher gibt es keinen Fehler; es druckt lediglich den Kleinbuchstaben x aus. Außerdem ist deine anschließende Behauptung, dass ein Zeiger vom Typ int ist, falsch und ein sehr schlechter Rat für einen C-Programmierer, der unerfahren mit Zeigern ist.

0 Stimmen

@R.. der %c wurde zu %s geändert

14 Stimmen

-1 Du hast gut angefangen, aber das erste Beispiel ist falsch. Nein, es ist nicht dasselbe. Im ersten Fall ist a ein Pointer und im zweiten Fall a ein Array. Habe ich das schon erwähnt? Es ist nicht dasselbe! Überprüfe selbst: Vergleiche sizeof(a), versuche, einer neuen Adresse ein Array zuzuweisen. Es wird nicht funktionieren.

62voto

trshiv Punkte 2365

Einer der Gründe für die Verwendung von Pointern besteht darin, dass eine Variable oder ein Objekt in einer aufgerufenen Funktion geändert werden kann.

In C++ ist es eine bessere Praxis, Referenzen anstelle von Pointern zu verwenden. Obwohl Referenzen im Wesentlichen Pointer sind, verbirgt C++ teilweise die Tatsache und lässt es so erscheinen, als ob man nach Wert übergibt. Dadurch ist es einfach, die Art und Weise zu ändern, wie die aufrufende Funktion den Wert empfängt, ohne die Semantik der Übergabe ändern zu müssen.

Betrachten Sie die folgenden Beispiele:

Verwendung von Referenzen:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // übergibt i per Referenz, da doSomethingElse() es per Referenz empfängt
                         // aber die Syntax macht es so aussehen, als ob i per Wert übergeben wird
}

public void doSomethingElse(int& i)  // empfängt i als Referenz
{
    cout << i << endl;
}

Verwendung von Pointern:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}

public void doSomethingElse(int* i)
{
    cout << *i << endl;
}

27 Stimmen

Es ist wahrscheinlich eine gute Idee zu erwähnen, dass Referenzen sicherer sind, da man keinen Null-Verweis übergeben kann.

37 Stimmen

Ja, das ist wahrscheinlich der größte Vorteil von Verweisen. Danke, dass du darauf hingewiesen hast. Kein Wortspiel beabsichtigt :)

2 Stimmen

Du kannst sicherlich null Referenzen übergeben. Es ist nur nicht so einfach wie das Übergeben eines Null-Zeigers.

51voto

Kyle Cronin Punkte 74993
  1. Zeiger ermöglichen es Ihnen, auf denselben Speicherplatz von mehreren Standorten aus zu verweisen. Dies bedeutet, dass Sie den Speicher an einem Ort aktualisieren können und die Änderung von einem anderen Ort in Ihrem Programm aus sichtbar ist. Sie sparen auch Platz, da Sie Komponenten in Ihren Datenstrukturen gemeinsam nutzen können.
  2. Sie sollten Zeiger überall dort verwenden, wo Sie die Adresse eines bestimmten Speicherorts abrufen und übergeben müssen. Sie können auch Zeiger zum Navigieren in Arrays verwenden:
  3. Ein Array ist ein Block zusammenhängenden Speichers, der mit einem bestimmten Typ allokiert wurde. Der Name des Arrays enthält den Wert des Startpunkts des Arrays. Wenn Sie 1 hinzufügen, gelangen Sie zum zweiten Punkt. Dies ermöglicht es Ihnen, Schleifen zu schreiben, die einen Zeiger inkrementieren, der durch das Array gleitet, ohne einen expliziten Zähler für den Zugriff auf das Array zu benötigen.

Hier ist ein Beispiel in C:

char hello[] = "hello";

char *p = hello;

while (*p)
{
    *p += 1; // erhöhen Sie den Zeichen um eins

    p += 1; // gehe zum nächsten Punkt
}

printf(hello);

gibt aus

ifmmp

weil es den Wert für jedes Zeichen nimmt und um eins erhöht.

1 Stimmen

weil es den Wert für jeden Buchstaben nimmt und um eins erhöht. Ist das in ascii darstellung oder wie?

0 Stimmen

Würde p=0; den Zeiger zurücksetzen?

37voto

Bill the Lizard Punkte 384619

Zeiger sind eine Möglichkeit, einen indirekten Verweis auf eine andere Variable zu erhalten. Anstatt den Wert einer Variable zu halten, geben sie dir seine Adresse an. Dies ist besonders nützlich beim Umgang mit Arrays, da du mit einem Zeiger auf das erste Element in einem Array (seine Adresse) schnell das nächste Element finden kannst, indem du den Zeiger inkrementierst (zur nächsten Adressposition).

Die beste Erklärung von Zeigern und Zeigerarithmetik, die ich gelesen habe, steht in K & R's The C Programming Language. Ein gutes Buch für den Einstieg in das Lernen von C++ ist C++ Primer.

1 Stimmen

Vielen Dank! Endlich eine praktische Erklärung für die Vorteile der Verwendung des Stacks! Verbessert ein Zeiger auf die Position in einem Array auch die Leistung beim Zugriff auf die Werte @ und relativ zum Zeiger?

28voto

Carl Punkte 41134

Lassen Sie mich versuchen, auch darauf zu antworten.

Zeiger sind ähnlich wie Referenzen. Mit anderen Worten, es handelt sich nicht um Kopien, sondern vielmehr um eine Möglichkeit, auf den Originalwert zu verweisen.

Vor allem an einem Ort, wo Sie in der Regel häufig Zeiger verwenden müssen, ist der Umgang mit eingebetteter Hardware. Vielleicht müssen Sie den Zustand eines digitalen IO-Pins umschalten. Vielleicht verarbeiten Sie einen Interrupt und müssen einen Wert an einem bestimmten Speicherort speichern. Sie verstehen sicher, worum es geht. Wenn Sie jedoch nicht direkt mit Hardware arbeiten und sich nur fragen, welche Typen Sie verwenden sollten, lesen Sie weiter.

Warum Zeiger anstelle von normalen Variablen verwenden? Die Antwort wird klarer, wenn Sie es mit komplexen Typen wie Klassen, Strukturen und Arrays zu tun haben. Wenn Sie eine normale Variable verwenden würden, könnten Sie am Ende eine Kopie erstellen (Compiler sind in einigen Situationen schlau genug, um dies zu verhindern, und auch C++11 hilft, aber wir werden das Thema vorerst meiden).

Was passiert jedoch, wenn Sie den Originalwert ändern möchten? Sie könnten etwas wie dies verwenden:

MyType a; //lassen Sie uns vorerst ignorieren, was MyType tatsächlich ist.
a = modify(a); 

Das funktioniert einwandfrei und wenn Sie nicht genau wissen, warum Sie Zeiger verwenden, sollten Sie sie nicht verwenden. Vorsicht vor dem Argument "sie sind wahrscheinlich schneller". Führen Sie Ihre eigenen Tests durch und wenn sie tatsächlich schneller sind, verwenden Sie sie.

Angenommen, Sie lösen ein Problem, bei dem Sie Speicher zuweisen müssen. Wenn Sie Speicher zuweisen, müssen Sie diesen auch freigeben. Die Speicherzuweisung kann erfolgreich sein oder auch nicht. Hier kommen Zeiger nützlich ins Spiel - sie ermöglichen es Ihnen, auf das Vorhandensein des Objekts zu überprüfen, für das Sie Speicher zugewiesen haben, und sie ermöglichen es Ihnen, auf das Objekt zuzugreifen, für das der Speicher zugewiesen wurde, indem Sie den Zeiger dereferenzieren.

MyType *p = NULL; //leerer Zeiger
if(p)
{
    //wir kommen nie hierhin, weil der Zeiger auf nichts zeigt
}
//lassen Sie uns jetzt etwas Speicher zuweisen
p = new MyType[50000];
if(p) //wenn der Speicher zugewiesen wurde, wird dieser Test bestehen
{
    //wir können etwas mit unserem zugewiesenen Array machen
    for(size_t i=0; i!=50000; i++)
    {
        MyType &v = *(p+i); //eine Referenz auf das i-te Objekt erhalten
        //etwas damit tun
        //...
    }
    delete[] p; //wir sind fertig. Speicher freigeben
}

Dies ist der Schlüssel, warum Sie Zeiger verwenden würden - Referenzen setzen voraus, dass das von Ihnen referenzierte Element bereits vorhanden ist. Ein Zeiger nicht.

Ein weiterer Grund, warum Sie Zeiger verwenden würden (oder zumindest enden würden, damit umzugehen), ist, dass sie ein Datentyp sind, der vor Referenzen existierte. Daher werden Sie feststellen, dass viele dieser Bibliotheken Zeiger überall verwenden, einfach wegen ihrer langen Existenz (viele wurden vor C++ geschrieben).

Wenn Sie keine Bibliotheken verwenden, könnten Sie Ihren Code so entwerfen, dass Sie sich von Zeigern fernhalten, aber da Zeiger einer der grundlegenden Typen der Sprache sind, desto schneller Sie sich daran gewöhnen, sie zu verwenden, desto portabler wären Ihre C++-Fähigkeiten.

Aus Sicht der Wartbarkeit sollte ich auch erwähnen, dass wenn Sie Zeiger verwenden, Sie entweder auf ihre Gültigkeit testen und den Fall behandeln müssen, wenn sie ungültig sind, oder einfach davon ausgehen, dass sie gültig sind und akzeptieren müssen, dass Ihr Programm abstürzt oder schlimmer wird, WENN diese Annahme gebrochen wird. Anders ausgedrückt, Ihre Wahl bei Zeigern besteht darin, entweder Codekomplexität einzuführen oder mehr Wartungsaufwand zu betreiben, wenn etwas bricht und Sie versuchen, einen Fehler nachzuvollziehen, der zu einer ganzen Klasse von Fehlern gehört, die Zeiger einführen, wie z.B. Speicherkorruption.

Wenn Sie den gesamten Code kontrollieren, bleiben Sie von Zeigern fern und verwenden stattdessen Referenzen, halten Sie diese const, wenn Sie können. Dadurch werden Sie gezwungen, über die Lebensdauer Ihrer Objekte nachzudenken, und Ihr Code wird einfacher zu verstehen sein.

Denken Sie nur an diesen Unterschied: Eine Referenz ist im Grunde ein gültiger Zeiger. Ein Zeiger ist nicht immer gültig.

Sage ich also, dass es unmöglich ist, eine ungültige Referenz zu erstellen? Nein. Es ist vollkommen möglich, weil C++ Ihnen fast alles erlaubt. Es ist jedoch schwieriger, dies unbeabsichtigt zu tun, und Sie werden erstaunt sein, wie viele Fehler unbeabsichtigt sind :)

0 Stimmen

Sie können schöne Wrapper-Klassen für speichergekartetes IO schreiben, mit denen Sie die Verwendung von Zeigern im Wesentlichen vermeiden können.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X