Zeiger in C: Wann wird das kaufmännische Und und das Sternchen verwendet?

298

Ich fange gerade mit Zeigern an und bin etwas verwirrt. Ich weiß, &bedeutet die Adresse einer Variablen, *die vor einer Zeigervariablen verwendet werden kann, um den Wert des Objekts zu erhalten, auf das der Zeiger zeigt. Aber die Dinge funktionieren anders, wenn Sie mit Arrays, Strings arbeiten oder wenn Sie Funktionen mit einer Zeigerkopie einer Variablen aufrufen. Es ist schwierig, in all dem ein logisches Muster zu erkennen.

Wann soll ich &und verwenden *?

Pieter
quelle
5
Bitte veranschaulichen Sie, wie Sie sehen, dass Dinge manchmal anders funktionieren. Ansonsten müssen wir raten, was Sie verwirrt.
Drew Dormann
1
Stimmen Sie Neil Butterworth zu. Wahrscheinlich werden wir viel mehr Informationen aus erster Hand aus einem Buch erhalten, und die Erklärung von K & R ist ziemlich klar.
Tom
145
Ich bin nicht einverstanden mit allen, die sagen, dass es keine gute Idee ist, diese Art von Fragen zu SO zu stellen. SO ist die Ressource Nummer 1 bei der Suche bei Google geworden. Sie geben diesen Antworten nicht genug Anerkennung. Lesen Sie die Antwort von Dan Olson. Diese Antwort ist wirklich aufschlussreich und für Neulinge unglaublich hilfreich. RTFMist nicht hilfreich und ehrlich gesagt sehr unhöflich. Wenn Sie keine Zeit haben zu antworten, respektieren Sie diejenigen, die sich Zeit nehmen, um diese Fragen zu beantworten. Ich wünschte, ich könnte dies an "anon" senden, aber offensichtlich hat er / sie nicht die Zeit, auf sinnvolle Weise einen Beitrag zu leisten.
SSH Dies
18
Was SSH Dies gesagt ist absolut wahr. Einige Leute rufen "Just go it it", aber heutzutage ist es umgekehrt: "Schauen Sie sich einfach StackOverflow an." Diese Frage ist für viele Menschen nützlich. (Daher die Upvotes und keine Downvotes.)
MC Emperor

Antworten:

610

Sie haben Zeiger und Werte:

int* p; // variable p is pointer to integer type
int i; // integer value

Sie verwandeln einen Zeiger in einen Wert mit *:

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

Sie verwandeln einen Wert in einen Zeiger mit &:

int* p2 = &i; // pointer p2 will point to the address of integer i

Bearbeiten: Bei Arrays werden sie sehr ähnlich wie Zeiger behandelt. Wenn Sie sie als Zeiger betrachten, werden Sie sie verwenden *, um zu den Werten in ihnen zu gelangen, wie oben erläutert. Es gibt jedoch auch eine andere, häufigere Methode, den []Operator zu verwenden:

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

So erhalten Sie das zweite Element:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

Der []Indexierungsoperator ist also eine spezielle Form des *Operators und funktioniert folgendermaßen:

a[i] == *(a + i);  // these two statements are the same thing
Dan Olson
quelle
2
Wie kommt es, dass das nicht funktioniert? int aX[] = {3, 4}; int *bX = &aX;
Pieter
5
Arrays sind speziell und können transparent in Zeiger konvertiert werden. Dies zeigt einen anderen Weg, um von einem Zeiger auf einen Wert zu gelangen. Ich werde ihn der obigen Erklärung hinzufügen.
Dan Olson
4
Wenn ich das richtig verstehe ... int *bX = &aX;funktioniert das Beispiel nicht, weil das aXbereits die Adresse von aX[0](dh &aX[0]) zurückgibt , also &aXdie Adresse einer Adresse erhalten würde, die keinen Sinn ergibt . Ist das richtig?
Pieter
6
Sie haben Recht, obwohl es Fälle gibt, in denen Sie möglicherweise tatsächlich die Adresse der Adresse wünschen. In diesem Fall würden Sie es als int ** bX = & aX deklarieren, aber dies ist ein fortgeschritteneres Thema.
Dan Olson
3
@ Dan, gegeben int aX[] = {3,4};, int **bX = &aX;ist ein Fehler. &aXist vom Typ "Zeiger auf Array [2] von int", nicht "Zeiger auf Zeiger auf int". Insbesondere wird der Name eines Arrays nicht als Zeiger auf sein erstes Element für unär behandelt &. Sie können tun:int (*bX)[2] = &aX;
Alok Singhal
28

Beim Umgang mit Arrays und Funktionen gibt es ein Muster. es ist zunächst nur ein wenig schwer zu sehen.

Beim Umgang mit Arrays ist Folgendes zu beachten: Wenn in den meisten Kontexten ein Array-Ausdruck angezeigt wird, wird der Typ des Ausdrucks implizit von "N-Element-Array von T" in "Zeiger auf T" konvertiert und sein Wert festgelegt um auf das erste Element im Array zu zeigen. Die Ausnahmen von dieser Regel sind , wenn der Array Ausdruck erscheint als Operanden entweder die &oder sizeofBetreiber, oder wenn es ein Stringliteral als Initialisierer in einer Erklärung verwendet wird.

Wenn Sie also eine Funktion mit einem Array-Ausdruck als Argument aufrufen, erhält die Funktion einen Zeiger und kein Array:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

Dies ist , warum Sie nicht die Verwendung &auf „% s“ entsprechenden Operator für Argumente scanf():

char str[STRING_LENGTH];
...
scanf("%s", str);

scanf()Empfängt aufgrund der impliziten Konvertierung einen char *Wert, der auf den Anfang des strArrays zeigt. Dies gilt für jede Funktion mit einem Array Ausdruck als Argument (nur über eine der genannten str*Funktionen *scanfund *printfFunktionen, etc.).

In der Praxis werden Sie wahrscheinlich niemals eine Funktion mit einem Array-Ausdruck mit dem &Operator aufrufen , wie in:

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

Ein solcher Code ist nicht sehr verbreitet; Sie müssen die Größe des Arrays in der Funktionsdeklaration kennen, und die Funktion funktioniert nur mit Zeigern auf Arrays bestimmter Größe (ein Zeiger auf ein Array mit 10 Elementen von T ist ein anderer Typ als ein Zeiger auf ein Array mit 11 Elementen von T).

Wenn ein Array-Ausdruck als Operand für den &Operator angezeigt wird , ist der Typ des resultierenden Ausdrucks "Zeiger auf N-Element-Array von T" oder T (*)[N]unterscheidet sich von einem Array von Zeigern ( T *[N]) und einem Zeiger auf den Basistyp (). T *).

Beim Umgang mit Funktionen und Zeigern gilt folgende Regel: Wenn Sie den Wert eines Arguments ändern und im aufrufenden Code wiedergeben möchten, müssen Sie einen Zeiger auf das Element übergeben, das Sie ändern möchten. Wieder werfen Arrays einen kleinen Schraubenschlüssel in die Werke, aber wir werden uns zuerst mit den normalen Fällen befassen.

Denken Sie daran, dass C alle Funktionsargumente als Wert übergibt . Der formale Parameter erhält eine Kopie des Werts im tatsächlichen Parameter, und Änderungen am formalen Parameter werden nicht im tatsächlichen Parameter berücksichtigt. Das häufigste Beispiel ist eine Swap-Funktion:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

Sie erhalten folgende Ausgabe:

vor dem Tausch: a = 1, b = 2
nach dem Tausch: a = 1, b = 2

Die formalen Parameter xund ysind verschiedene Objekte von aundb , daher ändern sich Änderungen an xund ywerden nicht in aund wiedergegeben b. Da wir die Werte von aund ändern möchten b, müssen wir Zeiger darauf an die Swap-Funktion übergeben:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

Jetzt wird Ihre Ausgabe sein

vor dem Tausch: a = 1, b = 2
nach dem Tausch: a = 2, b = 1

Beachten Sie, dass wir in der Swap-Funktion nicht die Werte von xund ändern y, sondern die Werte von whatx und y zeigen auf . Schreiben an *xunterscheidet sich vom Schreiben an x; Wir aktualisieren den Wert nicht an xsich, wir erhalten einen Speicherort von xund aktualisieren den Wert an diesem Speicherort.

Dies gilt auch, wenn wir einen Zeigerwert ändern möchten. wenn wir schreiben

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

dann ändern wir den Wert des Eingabeparameters stream, nicht wasstream ändern zeigt. Eine Änderung streamhat also keine Auswirkung auf den Wert von in. Damit dies funktioniert, müssen wir einen Zeiger auf den Zeiger übergeben:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

Auch hier werfen Arrays einen kleinen Schraubenschlüssel in die Werke. Wenn Sie einen Array-Ausdruck an eine Funktion übergeben, empfängt die Funktion einen Zeiger. Aufgrund der Definition der Array-Subskription können Sie einen Indexoperator für einen Zeiger genauso verwenden wie für ein Array:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

Beachten Sie, dass Array-Objekte möglicherweise nicht zugewiesen werden. dh du kannst so etwas nicht machen

int a[10], b[10];
...
a = b;

Sie sollten also vorsichtig sein, wenn Sie mit Zeigern auf Arrays arbeiten. etwas wie

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

wird nicht funktionieren.

John Bode
quelle
16

Einfach ausgedrückt

  • &bedeutet die Adresse von , Sie werden sehen, dass in Platzhaltern für Funktionen zum Ändern der Parametervariablen wie in C Parametervariablen als Wert übergeben werden, wobei das kaufmännische Und als Referenz verwendet wird.
  • *bedeutet die Dereferenzierung einer Zeigervariable, was bedeutet, den Wert dieser Zeigervariable zu erhalten.
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

Das obige Beispiel zeigt, wie eine Funktion foomithilfe der Referenzübergabe aufgerufen wird. Vergleichen Sie dies

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

Hier ist ein Beispiel für die Verwendung einer Dereferenzierung

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

Das Obige zeigt, wie wir die Adresse von erhalten y und sie der Zeigervariablen zugewiesen haben p. Dann dereferenzieren wir, pindem wir das *an der Vorderseite anbringen , um den Wert von zu erhalten p, dh *p.

t0mm13b
quelle
10

Ja, das kann ziemlich kompliziert sein, da das *in C / C ++ für viele verschiedene Zwecke verwendet wird.

Wenn *vor einer bereits deklarierten Variablen / Funktion angezeigt wird, bedeutet dies entweder:

  • a) *gibt Zugriff auf den Wert dieser Variablen (wenn der Typ dieser Variablen ein Zeigertyp ist oder der *Operator überladen ist ).
  • b) *hat die Bedeutung des Multiplikationsoperators, in diesem Fall muss links von der eine andere Variable stehen*

Wenn es *in einer Variablen- oder Funktionsdeklaration erscheint, bedeutet dies, dass diese Variable ein Zeiger ist:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

Wenn es &in einer Variablen- oder Funktionsdeklaration erscheint, bedeutet dies im Allgemeinen, dass diese Variable eine Referenz auf eine Variable dieses Typs ist.

Wenn &vor einer bereits deklarierten Variablen angezeigt wird, wird die Adresse dieser Variablen zurückgegeben

Außerdem sollten Sie wissen, dass Sie beim Übergeben eines Arrays an eine Funktion immer auch die Arraygröße dieses Arrays übergeben müssen, es sei denn, das Array ist so etwas wie eine 0-terminierte Zeichenfolge (char-Array).

Smerlin
quelle
1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (ungültiger Speicherplatz)
PixnBits
4

Wenn Sie eine Zeigervariable oder einen Funktionsparameter deklarieren, verwenden Sie das *:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

NB: Jede deklarierte Variable benötigt ihre eigene *.

Wenn Sie die Adresse eines Werts übernehmen möchten, verwenden Sie &. Wenn Sie den Wert in einem Zeiger lesen oder schreiben möchten, verwenden Sie *.

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

Arrays werden normalerweise nur wie Zeiger behandelt. Wenn Sie einen Array-Parameter in einer Funktion deklarieren, können Sie genauso einfach deklarieren, dass es sich um einen Zeiger handelt (dies bedeutet dasselbe). Wenn Sie ein Array an eine Funktion übergeben, übergeben Sie tatsächlich einen Zeiger auf das erste Element.

Funktionszeiger sind die einzigen Dinge, die den Regeln nicht ganz folgen. Sie können die Adresse einer Funktion ohne Verwendung von & übernehmen und einen Funktionszeiger ohne Verwendung von * aufrufen.

Jay Conrod
quelle
4

Ich war alle durch die wortreichen Erklärungen suchen , so stattdessen auf ein Video von der University of New South Wales für rescue.Here gedreht ist die einfache Erklärung: Wenn wir eine Zelle , die Adresse hat xund Wert 7, die indirekte Art und Weise für die Adresse des Wertes zu stellen 7ist &7und die indirekte Art und Weise für Wert bei der Adresse zu fragen xist *x.So (cell: x , value: 7) == (cell: &7 , value: *x).another Art und Weise, diese zu prüfen: Johnsitzt auf 7th seat.Der *7th seatwird Punkt Johnund &Johngeben address/ Position der 7th seat. Diese einfache Erklärung hat mir geholfen und hoffe, dass sie auch anderen hilft. Hier ist der Link für das hervorragende Video: Klicken Sie hier.

Hier ist ein weiteres Beispiel:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

Add-on: Initialisieren Sie den Zeiger immer, bevor Sie ihn verwenden. Wenn nicht, zeigt der Zeiger auf etwas, was zum Absturz des Programms führen kann, da das Betriebssystem Sie daran hindert, auf den Speicher zuzugreifen, von dem Sie wissen, dass Sie ihn nicht besitzen p = &x;Wir weisen dem Zeiger eine bestimmte Position zu.


quelle
3

Eigentlich hast du es geschafft, es gibt nichts mehr, was du wissen musst :-)

Ich würde nur die folgenden Bits hinzufügen:

  • Die beiden Operationen sind entgegengesetzte Enden des Spektrums. &nimmt eine Variable und gibt Ihnen die Adresse, *nimmt eine Adresse und gibt Ihnen die Variable (oder den Inhalt).
  • Arrays "verschlechtern" sich zu Zeigern, wenn Sie sie an Funktionen übergeben.
  • Sie können tatsächlich mehrere Ebenen für die Indirektion haben ( char **pbedeutet, dass dies pein Zeiger auf einen Zeiger auf a ist char.

Was die Dinge betrifft, die anders funktionieren, nicht wirklich:

  • Arrays werden, wie bereits erwähnt, zu Zeigern (auf das erste Element im Array) degradiert, wenn sie an Funktionen übergeben werden. Sie behalten keine Größeninformationen bei.
  • In C gibt es keine Zeichenfolgen, sondern nur Zeichenarrays, die gemäß der Konvention eine Zeichenfolge darstellen, die mit einem Nullzeichen ( \0) abgeschlossen ist.
  • Wenn Sie die Adresse einer Variablen an eine Funktion übergeben, können Sie den Zeiger de-referenzieren, um die Variable selbst zu ändern (normalerweise werden Variablen als Wert übergeben (außer bei Arrays)).
paxdiablo
quelle
3

Ich denke du bist ein bisschen verwirrt. Sie sollten ein gutes Tutorial / Buch über Zeiger lesen.

Dieses Tutorial ist sehr gut für den Anfang (erklärt klar, was &und *sind). Und ja, vergessen Sie nicht, das Buch Pointers in C von Kenneth Reek zu lesen .

Der Unterschied zwischen &und *ist sehr klar.

Beispiel:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}
Prasoon Saurav
quelle
1

Ok, es sieht so aus, als ob dein Beitrag bearbeitet wurde ...

double foo[4];
double *bar_1 = &foo[0];

Sehen Sie, wie Sie die verwenden können &, um die Adresse des Anfangs der Array-Struktur zu erhalten? Folgende

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

werde das gleiche tun.

Wheaties
quelle
Die Frage wurde mit C nicht C ++ markiert.
Prasoon Saurav
1
Und ich habe den beleidigenden Cout <<
Wheaties