Warum Zeiger verwenden? [geschlossen]


356

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

Grundsätzlich habe ich drei Fragen:

  1. Warum Zeiger über normalen Variablen verwenden?
  2. Wann und wo soll ich Zeiger verwenden?
  3. Wie verwendet man Zeiger mit Arrays?

5
Vorher bei dieser Frage besprochen Hoffe das hilft!
Doug T.

4
Eine Liste der Bücher finden Sie unter stackoverflow.com/questions/388242/… . Nach Java fand ich Accelerated C ++ sehr nützlich.
Jabba

163
Wir verwenden Zeiger, weil es einfacher ist, jemandem eine Adresse für Ihr Zuhause zu geben, als jedem eine Kopie Ihres Hauses zu geben.
Rishi Dua

14
@ RishiDua Das ist die beste Erklärung für einen Zeiger, den ich je gesehen habe. Vielen Dank dafür, es hat mein Verständnis verbessert :)
Josh

2
Zeiger können auch verwendet werden, wenn Sie mehr als einen Wert / ein Objekt zurückgeben möchten.
Gaurav

Antworten:


172
  • Warum Zeiger über normalen Variablen verwenden?

Die kurze Antwort lautet: Nicht. ;-) Zeiger sind zu verwenden, wenn Sie nichts anderes verwenden können. Dies liegt entweder am Mangel an geeigneter Funktionalität, an fehlenden Datentypen oder an der reinen Leistung. Mehr unten ...

  • Wann und wo soll ich Zeiger verwenden?

Die kurze Antwort lautet hier: Wo Sie nichts anderes verwenden können. In C werden komplexe Datentypen wie eine Zeichenfolge nicht unterstützt. Es gibt auch keine Möglichkeit, eine Variable "als Referenz" an eine Funktion zu übergeben. Hier müssen Sie Zeiger verwenden. Sie können sie auch auf praktisch alles verweisen lassen, auf verknüpfte Listen, Mitglieder von Strukturen usw. Aber darauf wollen wir hier nicht eingehen.

  • Wie verwendet man Zeiger mit Arrays?

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

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

Sie können jedes Element im Array wie folgt erreichen

printf("Second char is: %c", a[1]);

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

Oder Sie könnten dies auch tun

printf("Second char is: %c", *(a+1));

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

printf("Second char is: %s", (a+1)); /* WRONG */

Dies hätte aber nicht nur das zweite Zeichen gedruckt, sondern alle Zeichen in den nächsten Speicheradressen, bis ein Nullzeichen (\ 0) gefunden wurde. Und hier fangen die Dinge an, gefährlich zu werden. Was ist, wenn Sie versehentlich versuchen, eine Variable vom Typ Integer anstelle eines Zeichenzeigers mit dem Formatierer% s zu drucken?

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

Dies würde alles drucken, was auf der Speicheradresse 120 gefunden wurde, und weiter drucken, bis ein Nullzeichen gefunden wurde. Es ist falsch und illegal, diese printf-Anweisung auszuführen, aber es würde wahrscheinlich trotzdem funktionieren, da ein Zeiger in vielen Umgebungen tatsächlich vom Typ int ist. Stellen Sie sich die Probleme vor, die Sie verursachen könnten, wenn Sie stattdessen sprintf () verwenden und auf diese Weise zu lange "char array" einer anderen Variablen zuweisen, der nur ein bestimmter begrenzter Speicherplatz zugewiesen wurde. Sie würden höchstwahrscheinlich über etwas anderes im Speicher schreiben und Ihr Programm zum Absturz bringen (wenn Sie Glück haben).

Oh, und wenn Sie dem char-Array / Zeiger beim Deklarieren keinen Zeichenfolgenwert zuweisen, MÜSSEN Sie ihm genügend Speicher zuweisen, bevor Sie ihm einen Wert geben. Mit malloc, calloc oder ähnlichem. Dies liegt daran, dass Sie nur ein Element in Ihrem Array / eine einzelne Speicheradresse deklariert haben, auf die Sie zeigen möchten. Hier einige Beispiele:

char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
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\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
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\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);

Beachten Sie, dass Sie die Variable x weiterhin verwenden können, nachdem Sie ein freies () des zugewiesenen Speichers ausgeführt haben, aber nicht wissen, was sich darin befindet. Beachten Sie auch, dass die beiden printf () möglicherweise unterschiedliche Adressen angeben, da nicht garantiert werden kann, dass die zweite Speicherzuweisung im selben Bereich wie die erste erfolgt.


8
Ihr Beispiel mit 120 ist falsch. Es wird% c und nicht% s verwendet, daher gibt es keinen Fehler. es wird lediglich der Kleinbuchstabe x gedruckt. Darüber hinaus ist Ihre nachfolgende Behauptung, dass ein Zeiger vom Typ int ist, falsch und ein sehr schlechter Rat an einen C-Programmierer, der mit Zeigern unerfahren ist.
R .. GitHub STOP HELPING ICE

13
-1 Sie haben gut angefangen, aber das erste Beispiel ist falsch. Nein, es ist nicht dasselbe. Im ersten Fall ahandelt es sich um einen Zeiger und im zweiten Fall aum ein Array. Habe ich es schon erwähnt? Es ist nicht das gleiche! Überzeugen Sie sich selbst: Vergleichen Sie sizeof (a) und versuchen Sie, einem Array eine neue Adresse zuzuweisen. Es wird nicht funktionieren.
Sellibitze

42
char* a = "Hello";und char a[] = "Hello";sind nicht gleich, sie sind ganz anders. Einer deklariert einen Zeiger, der andere ein Array. Versuchen Sie es mit a sizeofund Sie sehen den Unterschied.
Patrick Schlüter

12
"Es ist nicht falsch oder illegal, diese printf-Anweisung auszuführen". Das ist einfach falsch. Diese printf-Anweisung hat ein undefiniertes Verhalten. Bei vielen Implementierungen führt dies zu einer Zugriffsverletzung. Zeiger sind nicht vom Typ int, sondern Zeiger (und sonst nichts).
Mankarse

19
-1, Sie haben hier so viele Fakten falsch verstanden und ich werde nur sagen, dass Sie die Anzahl der Upvotes oder den Status der akzeptierten Antwort nicht verdienen.
Asveikau

50

Ein Grund für die Verwendung von Zeigern besteht darin, dass eine Variable oder ein Objekt in einer aufgerufenen Funktion geändert werden kann.

In C ++ ist es besser, Referenzen als Zeiger zu verwenden. Obwohl Referenzen im Wesentlichen Zeiger sind, verbirgt C ++ die Tatsache bis zu einem gewissen Grad und lässt es so erscheinen, als würden Sie einen Wert übergeben. Dies macht es einfach, die Art und Weise zu ändern, in der die aufrufende Funktion den Wert empfängt, ohne die Semantik der Übergabe ändern zu müssen.

Betrachten Sie die folgenden Beispiele:

Referenzen verwenden:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                         // by reference, but the syntax makes it appear as if i is passed
                         // by value
}

public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

Verwenden von Zeigern:

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

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

16
Wahrscheinlich ist es eine gute Idee zu erwähnen, dass Referenzen sicherer sind, da Sie keine Nullreferenz übergeben können.
SpoonMeiser

26
Ja, das ist wahrscheinlich der größte Vorteil der Verwendung von Referenzen. Vielen Dank für den Hinweis. Kein Wortspiel beabsichtigt :)
trshiv

2
Sie können sicher eine Nullreferenz übergeben. Es ist einfach nicht so einfach wie das Übergeben eines Nullzeigers.
n0rd

1
stimme mit @ n0rd überein. Wenn Sie glauben, dass Sie keine Nullreferenz übergeben können, liegen Sie falsch. Es ist einfacher, eine baumelnde Referenz als eine Nullreferenz zu übergeben, aber letztendlich können Sie beides einfach genug tun. Referenzen sind keine Silberkugel, die einen Ingenieur davor schützt, sich in den Fuß zu schießen. Sehen Sie es live .
WhozCraig

2
@ n0rd: Dies zu tun ist explizit undefiniertes Verhalten.
Daniel Kamil Kozar

42
  1. Mit Zeigern können Sie von mehreren Stellen aus auf denselben Speicherplatz verweisen. Dies bedeutet, dass Sie den Speicher an einem Ort aktualisieren können und die Änderung von einem anderen Ort in Ihrem Programm aus gesehen werden kann. Sie sparen außerdem Platz, indem Sie Komponenten in Ihren Datenstrukturen gemeinsam nutzen können.
  2. Sie sollten Zeiger an jeder Stelle verwenden, an der Sie die Adresse abrufen und an eine bestimmte Stelle im Speicher weitergeben müssen. Sie können auch Zeiger verwenden, um in Arrays zu navigieren:
  3. Ein Array ist ein Block zusammenhängenden Speichers, der einem bestimmten Typ zugewiesen wurde. Der Name des Arrays enthält den Wert des Startpunkts des Arrays. Wenn Sie 1 hinzufügen, gelangen Sie zum zweiten Punkt. Auf diese Weise können Sie Schleifen schreiben, die einen Zeiger inkrementieren, der über das Array gleitet, ohne einen expliziten Zähler für den Zugriff auf das Array zu haben.

Hier ist ein Beispiel in C:

char hello[] = "hello";

char *p = hello;

while (*p)
{
    *p += 1; // increase the character by one

    p += 1; // move to the next spot
}

printf(hello);

druckt

ifmmp

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


because it takes the value for each character and increments it by one. Ist in ASCII-Darstellung oder wie?
Eduardo S.

28

Zeiger sind eine Möglichkeit, einen indirekten Verweis auf eine andere Variable zu erhalten. Anstatt den Wert einer Variablen zu halten, teilen sie Ihnen deren Adresse mit . Dies ist besonders nützlich, wenn Sie sich mit Arrays befassen, da Sie mit einem Zeiger auf das erste Element in einem Array (dessen Adresse) das nächste Element schnell finden können, indem Sie den Zeiger (auf die nächste Adressposition) erhöhen.

Die beste Erklärung für Zeiger und Zeigerarithmetik, die ich gelesen habe, ist in K & Rs The C Programming Language . Ein gutes Buch für den Einstieg in C ++ ist C ++ Primer .


1
Vielen Dank! Zum Schluss noch eine praktische Erklärung der 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?

Dies ist wahrscheinlich die beste Erklärung, die ich gelesen habe. Leute haben eine Möglichkeit, Dinge zu "komplizieren" :)
Detilium

23

Lassen Sie mich auch versuchen, dies zu beantworten.

Zeiger ähneln Referenzen. Mit anderen Worten, es handelt sich nicht um Kopien, sondern um eine Möglichkeit, auf den ursprünglichen Wert zu verweisen.

Ein Ort, an dem Sie normalerweise häufig Zeiger verwenden müssen, ist der Umgang mit eingebetteter Hardware . Möglicherweise müssen Sie den Status eines digitalen E / A-Pins umschalten. Möglicherweise verarbeiten Sie einen Interrupt und müssen einen Wert an einem bestimmten Ort speichern. Du bekommst das Bild. Wenn Sie sich jedoch nicht direkt mit Hardware befassen und sich nur fragen, welche Typen Sie verwenden sollen, lesen Sie weiter.

Warum Zeiger im Gegensatz zu normalen Variablen verwenden? Die Antwort wird klarer, wenn Sie mit komplexen Typen wie Klassen, Strukturen und Arrays arbeiten. Wenn Sie eine normale Variable verwenden, erstellen Sie möglicherweise eine Kopie (Compiler sind intelligent genug, um dies in einigen Situationen zu verhindern, und C ++ 11 hilft auch, aber wir werden uns vorerst von dieser Diskussion fernhalten).

Was passiert nun, wenn Sie den ursprünglichen Wert ändern möchten? Sie könnten so etwas verwenden:

MyType a; //let's ignore what MyType actually is right now.
a = modify(a); 

Das wird gut funktionieren und wenn Sie nicht genau wissen, warum Sie Zeiger verwenden, sollten Sie sie nicht verwenden. Hüten Sie sich vor dem Grund "sie sind wahrscheinlich schneller". Führen Sie Ihre eigenen Tests durch und verwenden Sie sie, wenn sie tatsächlich schneller sind.

Angenommen, Sie lösen ein Problem, bei dem Sie Speicher zuweisen müssen. Wenn Sie Speicher zuweisen, müssen Sie die Zuordnung aufheben. Die Speicherzuweisung kann erfolgreich sein oder nicht. Hier kommen Zeiger zum Einsatz - Sie können die Existenz des von Ihnen zugewiesenen Objekts testen und auf das Objekt zugreifen, für das der Speicher zugewiesen wurde, indem Sie die Referenzierung des Zeigers aufheben.

MyType *p = NULL; //empty pointer
if(p)
{
    //we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
    //we can do something with our allocated array
    for(size_t i=0; i!=50000; i++)
    {
        MyType &v = *(p+i); //get a reference to the ith object
        //do something with it
        //...
    }
    delete[] p; //we're done. de-allocate the memory
}

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

Der andere Grund, warum Sie Zeiger verwenden würden (oder zumindest damit umgehen müssen), ist, dass es sich um einen Datentyp handelt, der vor Referenzen vorhanden war. Wenn Sie also Bibliotheken verwenden, um die Dinge zu tun, von denen Sie wissen, dass sie besser sind, werden Sie feststellen, dass viele dieser Bibliotheken überall Zeiger verwenden, einfach weil sie schon so lange da sind (viel) von ihnen wurden vor C ++ geschrieben).

Wenn Sie keine Bibliotheken verwendet haben, können Sie Ihren Code so gestalten, dass Sie sich von Zeigern fernhalten. Da Zeiger jedoch einer der Grundtypen der Sprache sind, gilt: Je schneller Sie sich mit ihnen vertraut machen, desto mehr tragbar wären Ihre C ++ - Kenntnisse.

Unter dem Gesichtspunkt der Wartbarkeit sollte ich auch erwähnen, dass Sie bei Verwendung von Zeigern entweder deren Gültigkeit prüfen und den Fall behandeln müssen, wenn sie nicht gültig sind, oder einfach davon ausgehen, dass sie gültig sind, und die Tatsache akzeptieren, dass Ihre Programm wird abstürzen oder schlimmer, wenn diese Annahme gebrochen ist. Anders ausgedrückt, Ihre Wahl mit Zeigern besteht darin, entweder Code-Komplexität oder mehr Wartungsaufwand einzuführen, wenn etwas kaputt geht und Sie versuchen, einen Fehler aufzuspüren, der zu einer ganzen Klasse von Fehlern gehört, die Zeiger verursachen, wie z. B. Speicherbeschädigung.

Wenn Sie also Ihren gesamten Code kontrollieren, halten Sie sich von Zeigern fern und verwenden Sie stattdessen Verweise. Halten Sie diese konstant, wenn Sie können. Dies zwingt Sie dazu, über die Lebenszeiten Ihrer Objekte nachzudenken, und führt dazu, dass Ihr Code leichter verständlich bleibt.

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

Also sage ich, dass es unmöglich ist, eine ungültige Referenz zu erstellen? Nein, es ist absolut möglich, weil Sie mit C ++ fast alles machen können. Es ist nur schwieriger, dies unbeabsichtigt zu tun, und Sie werden erstaunt sein, wie viele Fehler unbeabsichtigt sind :)


Sie können nette Wrapper-Klassen für speicherabgebildete E / A schreiben, mit denen Sie die Verwendung von Zeigern im Wesentlichen vermeiden können.
Einpoklum

13

Hier ist eine etwas andere, aber aufschlussreiche Darstellung, warum viele Funktionen von C sinnvoll sind: http://steve.yegge.googlepages.com/tour-de-babel#C

Grundsätzlich handelt es sich bei der Standard-CPU-Architektur um eine Von-Neumann-Architektur, und es ist äußerst nützlich, auf einem solchen Computer auf die Position eines Datenelements im Speicher verweisen und damit rechnen zu können. Wenn Sie eine Variante der Assemblersprache kennen, werden Sie schnell erkennen, wie wichtig dies auf niedriger Ebene ist.

C ++ macht Zeiger etwas verwirrend, da es sie manchmal für Sie verwaltet und ihre Wirkung in Form von "Referenzen" verbirgt. Wenn Sie gerade C verwenden, ist die Notwendigkeit von Zeigern viel offensichtlicher: Es gibt keine andere Möglichkeit, Call-by-Reference durchzuführen, es ist die beste Möglichkeit, eine Zeichenfolge zu speichern, es ist die beste Möglichkeit, ein Array zu durchlaufen usw.


12

Eine Verwendung von Zeigern (ich werde nicht erwähnen, was bereits in den Posts anderer Leute behandelt wurde) ist der Zugriff auf Speicher, den Sie nicht zugewiesen haben. Dies ist für die PC-Programmierung nicht sehr nützlich, wird jedoch in der eingebetteten Programmierung verwendet, um auf speicherabgebildete Hardwaregeräte zuzugreifen.

Früher war es unter DOS möglich, direkt auf den Videospeicher der Grafikkarte zuzugreifen, indem Sie einen Zeiger auf Folgendes deklarierten:

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

Viele eingebettete Geräte verwenden diese Technik immer noch.


Kein Grund, einen Zeiger zu verwenden - eine spanartige Struktur - die auch die Länge enthält - ist viel geeigneter. In diesen Tagen ist es gsl::spanund bald wird es sein std::span.
Einpoklum

10

Zeiger sind größtenteils Arrays (in C / C ++) - sie sind Adressen im Speicher und können auf Wunsch wie ein Array aufgerufen werden (in "normalen" Fällen).

Da sie die Adresse eines Artikels sind, sind sie klein: Sie nehmen nur den Platz einer Adresse ein. Da sie klein sind, ist es billig, sie an eine Funktion zu senden. Und dann erlauben sie dieser Funktion, auf das eigentliche Objekt anstatt auf eine Kopie zu arbeiten.

Wenn Sie eine dynamische Speicherzuweisung durchführen möchten (z. B. für eine verknüpfte Liste), müssen Sie Zeiger verwenden, da diese die einzige Möglichkeit sind, Speicher vom Heap abzurufen.


8
Ich finde es irreführend zu sagen, Zeiger seien Arrays. In Wirklichkeit sind Arraynamen konstante Zeiger auf das erste Element des Arrays. Nur weil Sie auf einen beliebigen Punkt zugreifen können, als wäre es ein Array, heißt das nicht, dass es ... Sie könnten eine Zugriffsverletzung bekommen :)
rmeador

2
Zeiger sind keine Arrays. Abschnitt 6 internationale des comp.lang.c FAQ .
Keith Thompson

Zeiger sind wirklich keine Arrays. Auch in Raw-Arrays gibt es nicht viel Verwendung - schon gar nicht nach C ++ 11 und std::array.
Einpoklum

@einpoklum - Zeiger sind nahe genug an Arrays, um in Referenzoperationen äquivalent zu sein und durch Arrays zu iterieren :) .... auch - C ++ 11 war 3 Jahre von der Veröffentlichung entfernt, als diese Antwort 2008 geschrieben wurde
warren

@warren: Zeiger sind weder Arrays noch nahe an Arrays, sie zerfallen lediglich in Arrays. Es ist pädagogisch unangemessen, den Menschen zu sagen, dass sie ähnlich sind. Sie können auch Verweise auf Arrays haben, die nicht vom Typ Zeiger sind und die Größeninformationen beibehalten. siehe hier
einpoklum

9

Zeiger sind in vielen Datenstrukturen wichtig, deren Design die Fähigkeit erfordert, einen "Knoten" effizient mit einem anderen zu verknüpfen oder zu verketten. Sie würden keinen Zeiger gegenüber einem normalen Datentyp wie float "auswählen", sie haben einfach unterschiedliche Zwecke.

Zeiger sind nützlich, wenn Sie eine hohe Leistung und / oder einen kompakten Speicherbedarf benötigen.

Die Adresse des ersten Elements in Ihrem Array kann einem Zeiger zugewiesen werden. Auf diese Weise können Sie direkt auf die zugrunde liegenden zugewiesenen Bytes zugreifen. Der springende Punkt eines Arrays ist jedoch, dass Sie dies nicht tun müssen.


9

Eine Möglichkeit, Zeiger auf Variablen zu verwenden, besteht darin, den erforderlichen doppelten Speicher zu eliminieren. Wenn Sie beispielsweise ein großes komplexes Objekt haben, können Sie einen Zeiger verwenden, um für jede Referenz, die Sie erstellen, auf diese Variable zu zeigen. Bei einer Variablen müssen Sie den Speicher für jede Kopie duplizieren.


Dies ist ein Grund, Referenzen (oder höchstens Referenz-Wrapper) und keine Zeiger zu verwenden.
Einpoklum

6

In C ++, wenn Sie verwenden möchten Subtyp Polymorphismus , Sie haben auf Zeiger zu verwenden. Siehe diesen Beitrag: C ++ Polymorphismus ohne Zeiger .

Wirklich, wenn Sie darüber nachdenken, macht dies Sinn. Wenn Sie den Subtyp-Polymorphismus verwenden, wissen Sie letztendlich nicht im Voraus, welche Klassen- oder Unterklassenimplementierung der Methode aufgerufen wird, da Sie nicht wissen, um welche Klasse es sich handelt.

Diese Idee, eine Variable zu haben, die ein Objekt einer unbekannten Klasse enthält, ist nicht mit dem Standardmodus von C ++ (ohne Zeiger) zum Speichern von Objekten auf dem Stapel kompatibel, bei dem der zugewiesene Speicherplatz direkt der Klasse entspricht. Hinweis: Wenn eine Klasse 5 Instanzfelder gegenüber 3 hat, muss mehr Speicherplatz zugewiesen werden.


Beachten Sie, dass, wenn Sie '&' verwenden, um Argumente als Referenz zu übergeben, die Indirektion (dh Zeiger) hinter den Kulissen immer noch beteiligt ist. Das '&' ist nur syntaktischer Zucker, der (1) Ihnen die Mühe erspart, die Zeigersyntax zu verwenden, und (2) es dem Compiler ermöglicht, strenger zu sein (z. B. das Verbot von Nullzeigern).


Nein, Sie nicht haben , um Zeiger zu verwenden - Sie Verweis verwenden können. Und wenn Sie schreiben "Zeiger sind hinter den Kulissen beteiligt" - das ist bedeutungslos. gotoAnweisungen werden auch hinter den Kulissen verwendet - in den Anweisungen des Zielcomputers. Wir behaupten immer noch nicht, sie zu benutzen.
Einpoklum

@ einpoklum-reinstateMonica Wenn Sie eine Reihe von Objekten haben, auf die Sie einwirken möchten, jedes Element der Reihe nach einer temporären Variablen zuweisen und eine polymorphe Methode für diese Variable aufrufen, benötigen Sie einen Zeiger, da Sie eine Referenz nicht erneut binden können.
Sildoreth

Wenn Sie eine Basisklassenreferenz auf eine abgeleitete Klasse haben und eine virtuelle Methode für diese Referenz aufrufen, wird die Überschreibung der abgeleiteten Klasse aufgerufen. Liege ich falsch?
Einpoklum

@ einpoklum-reinstateMonica Das ist richtig. Sie können jedoch nicht ändern, auf welches Objekt verwiesen wird. Wenn Sie also eine Liste / Menge / ein Array dieser Objekte durchlaufen, funktioniert eine Referenzvariable nicht.
Sildoreth

5

Weil das Kopieren großer Objekte überall Zeit und Speicher verschwendet.


4
Und wie helfen Zeiger dabei? Ich denke, eine Person, die aus Java oder .Net kommt, kennt den Unterschied zwischen dem Stapel und dem Heap nicht, daher ist diese Antwort ziemlich nutzlos.
Mats Fredriksson

Sie können als Referenz übergeben. Das verhindert das Kopieren.
Martin York

1
@MatsFredriksson - Anstatt eine große Datenstruktur zu übergeben (zu kopieren) und das Ergebnis erneut zu kopieren, zeigen Sie einfach auf die Position im RAM und ändern es dann direkt.
John U

Kopieren Sie also keine großen Objekte. Niemand hat gesagt, dass Sie dafür Hinweise brauchen.
Einpoklum

5

Hier ist meine Antwort, und ich werde nicht versprechen, ein Experte zu sein, aber ich habe in einer meiner Bibliotheken, die ich zu schreiben versuche, großartige Hinweise gefunden. In dieser Bibliothek (es ist eine Grafik-API mit OpenGL :-)) können Sie ein Dreieck mit darin übergebenen Scheitelpunktobjekten erstellen. Die Zeichenmethode verwendet diese Dreiecksobjekte und zeichnet sie basierend auf den von mir erstellten Scheitelpunktobjekten. Na ja, es geht.

Aber was ist, wenn ich eine Scheitelpunktkoordinate ändere? Verschieben oder etwas mit moveX () in der Vertex-Klasse? Nun, ok, jetzt muss ich das Dreieck aktualisieren, mehr Methoden hinzufügen und die Leistung wird verschwendet, weil ich das Dreieck jedes Mal aktualisieren muss, wenn sich ein Scheitelpunkt bewegt. Immer noch keine große Sache, aber es ist nicht so toll.

Was ist, wenn ich ein Netz mit Tonnen von Eckpunkten und Tonnen von Dreiecken habe und das Netz sich dreht und bewegt und so weiter. Ich muss jedes Dreieck aktualisieren, das diese Eckpunkte verwendet, und wahrscheinlich jedes Dreieck in der Szene, weil ich nicht wissen würde, welche welche Eckpunkte verwenden. Das ist sehr computerintensiv, und wenn ich mehrere Maschen auf einer Landschaft habe, oh Gott! Ich bin in Schwierigkeiten, weil ich jedes Dreieck fast jeden Frame aktualisiere, weil sich diese Eckpunkte ständig ändern!

Mit Zeigern müssen Sie die Dreiecke nicht aktualisieren.

Wenn ich drei * Scheitelpunktobjekte pro Dreiecksklasse hätte, spare ich nicht nur Platz, weil zig Dreiecke nicht drei Scheitelpunktobjekte haben, die selbst groß sind, sondern auch diese Zeiger immer auf die Scheitelpunkte zeigen, für die sie bestimmt sind, egal wie oft sich die Eckpunkte ändern. Da die Zeiger immer noch auf denselben Scheitelpunkt zeigen, ändern sich die Dreiecke nicht und der Aktualisierungsprozess ist einfacher zu handhaben. Wenn ich Sie verwirren würde, würde ich es nicht bezweifeln, ich gebe nicht vor, ein Experte zu sein, sondern wirf nur meine zwei Cent in die Diskussion.


4

Die Notwendigkeit von Zeigern in der Sprache C wird hier beschrieben

Die Grundidee ist, dass viele Einschränkungen in der Sprache (wie die Verwendung von Arrays, Strings und das Ändern mehrerer Variablen in Funktionen) durch Manipulieren mit den Speicherorten der Daten beseitigt werden könnten. Um diese Einschränkungen zu überwinden, wurden in C Zeiger eingeführt.

Darüber hinaus können Sie mithilfe von Zeigern Ihren Code schneller ausführen und Speicher sparen, wenn Sie große Datentypen (wie eine Struktur mit vielen Feldern) an eine Funktion übergeben. Das Erstellen einer Kopie solcher Datentypen vor dem Übergeben würde Zeit in Anspruch nehmen und Speicher verbrauchen. Dies ist ein weiterer Grund, warum Programmierer Zeiger für große Datentypen bevorzugen.

PS: Eine ausführliche Erklärung mit Beispielcode finden Sie unter dem angegebenen Link .


Die Frage ist über C ++, diese Antwort ist über C.
einpoklum

Nein, die Frage ist mit c AND c ++ markiert. Vielleicht ist das c-Tag irrelevant, aber es ist von Anfang an hier.
Jean-François Fabre

3

In Java und C # sind alle Objektreferenzen Zeiger. In C ++ haben Sie mehr Kontrolle darüber, wo Sie auf Zeiger zeigen. Denken Sie daran, mit großer Kraft geht große Verantwortung einher.


1

In Bezug auf Ihre zweite Frage müssen Sie beim Programmieren im Allgemeinen keine Zeiger verwenden. Es gibt jedoch eine Ausnahme, wenn Sie eine öffentliche API erstellen.

Das Problem mit C ++ - Konstrukten, das normalerweise zum Ersetzen von Zeigern verwendet wird, hängt stark von dem verwendeten Toolset ab. Dies ist in Ordnung, wenn Sie über die gesamte Kontrolle über den Quellcode verfügen. Wenn Sie jedoch beispielsweise eine statische Bibliothek mit Visual Studio 2008 kompilieren Wenn Sie versuchen, es in einem Visual Studio 2010 zu verwenden, werden Sie eine Menge Linker-Fehler erhalten, da das neue Projekt mit einer neueren Version von STL verknüpft ist, die nicht abwärtskompatibel ist. Noch schlimmer wird es, wenn Sie eine DLL kompilieren und eine Importbibliothek angeben, die in einem anderen Toolset verwendet wird, da in diesem Fall Ihr Programm früher oder später ohne ersichtlichen Grund abstürzt.

Um große Datenmengen von einer Bibliothek in eine andere zu verschieben, können Sie einen Zeiger auf ein Array für die Funktion geben, die die Daten kopieren soll, wenn Sie andere nicht zwingen möchten, dieselben Tools zu verwenden, die Sie verwenden . Das Gute daran ist, dass es nicht einmal ein Array im C-Stil sein muss. Sie können einen std :: vector verwenden und den Zeiger angeben, indem Sie beispielsweise die Adresse des ersten Elements & vector [0] angeben und verwenden der std :: vector, um das Array intern zu verwalten.

Ein weiterer guter Grund, Zeiger in C ++ erneut zu verwenden, betrifft Bibliotheken. Erwägen Sie, eine DLL zu haben, die nicht geladen werden kann, wenn Ihr Programm ausgeführt wird. Wenn Sie also eine Importbibliothek verwenden, ist die Abhängigkeit nicht erfüllt und das Programm stürzt ab. Dies ist beispielsweise der Fall, wenn Sie neben Ihrer Anwendung eine öffentliche API in einer DLL angeben und von anderen Anwendungen aus darauf zugreifen möchten. In diesem Fall müssen Sie zur Verwendung der API die DLL von ihrem Speicherort laden (normalerweise in einem Registrierungsschlüssel) und anschließend einen Funktionszeiger verwenden, um Funktionen innerhalb der DLL aufrufen zu können. Manchmal sind die Leute, die die API erstellen, nett genug, um Ihnen eine .h-Datei zu geben, die Hilfsfunktionen enthält, um diesen Prozess zu automatisieren und Ihnen alle Funktionszeiger zu geben, die Sie benötigen.


1
  • In einigen Fällen sind Funktionszeiger erforderlich, um Funktionen zu verwenden, die sich in einer gemeinsam genutzten Bibliothek befinden (.DLL oder .so). Dies umfasst das sprachübergreifende Durchführen von Aufgaben, bei denen häufig eine DLL-Schnittstelle bereitgestellt wird.
  • Compiler erstellen
  • Erstellen Sie wissenschaftliche Taschenrechner, bei denen Sie eine Array-, Vektor- oder Zeichenfolgenkarte mit Funktionszeigern haben?
  • Versuchen Sie, den Videospeicher direkt zu ändern - erstellen Sie Ihr eigenes Grafikpaket
  • Eine API erstellen!
  • Datenstrukturen - Knotenverknüpfungszeiger für spezielle Bäume, die Sie erstellen

Es gibt viele Gründe für Hinweise. Das Verwechseln von C-Namen ist besonders in DLLs wichtig, wenn Sie die sprachübergreifende Kompatibilität beibehalten möchten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.