142 Stimmen

Platzierung des Sternchens in Zeigerdeklarationen

Ich habe kürzlich beschlossen, dass ich endlich C/C++ lernen muss, und es gibt eine Sache, die ich nicht wirklich über Zeiger verstehe, genauer gesagt, ihre Definition.

Wie wäre es mit diesen Beispielen?

  1. int* test;
  2. int *test;
  3. int * test;
  4. int* test,test2;
  5. int *test,test2;
  6. int * test,test2;

Nun, nach meinem Verständnis, die ersten drei Fälle sind alle das gleiche tun: Test ist nicht ein int, aber ein Zeiger auf ein.

Die zweite Gruppe von Beispielen ist etwas komplizierter. In Fall 4 sind sowohl test als auch test2 Zeiger auf einen int, während in Fall 5 nur test ein Zeiger ist, während test2 ein "echter" int ist. Was ist mit Fall 6? Dasselbe wie in Fall 5?

19 Stimmen

In C/C++ ändern Leerzeichen die Bedeutung nicht.

38 Stimmen

7. int*test; ?

4 Stimmen

+1, denn ich hatte nur daran gedacht, nach 1 - 3 zu fragen. Beim Lesen dieser Frage habe ich etwas über 4 - 6 gelernt, woran ich nie gedacht hatte.

160voto

Milan Babuškov Punkte 57324

4, 5 und 6 sind das Gleiche, nur Test ist ein Zeiger. Wenn Sie zwei Zeiger wollen, sollten Sie verwenden:

int *test, *test2;

Oder, noch besser (um alles klar zu machen):

int* test;
int* test2;

7 Stimmen

Dann ist Fall 4 also tatsächlich eine Todesfalle? Gibt es eine Spezifikation oder weiterführende Literatur, die erklärt, warum int* test,test2 nur die erste Variable zu einem Zeiger macht?

14 Stimmen

@ Michael Stum Es ist C++, glauben Sie wirklich, dass es eine logische Erklärung gibt?

8 Stimmen

Lesen Sie K&R (Die Programmiersprache C). Darin wird dies alles sehr deutlich erklärt.

57voto

Ates Goral Punkte 132294

Leerzeichen um Sternchen haben keine Bedeutung. Alle drei bedeuten das Gleiche:

int* test;
int *test;
int * test;

Die " int *var1, var2 " ist eine böse Syntax, die nur dazu dient, die Leute zu verwirren, und sollte vermieden werden. Sie erweitert sich zu:

int *var1;
int var2;

3 Stimmen

Das Leerzeichen vor oder nach dem Sternchen ist nur eine Frage der Ästhetik. Der Google Coding Standard geht jedoch mit int *test ( google-styleguide.googlecode.com/svn/trunk/ ). Seien Sie einfach konsequent

0 Stimmen

@SebastianRaschka Die Google C++ Style Guide erlaubt ausdrücklich die Platzierung eines Sternchens. Vielleicht hat sich der Text geändert, seit Sie ihn gelesen haben.

1 Stimmen

51voto

John Bode Punkte 112486

Es gibt drei Teile dieses Puzzles.

Der erste Punkt ist, dass Leerzeichen in C und C++ normalerweise keine Bedeutung haben, außer dass sie benachbarte Token trennen, die ansonsten nicht voneinander zu unterscheiden sind.

In der Vorverarbeitungsphase wird der Ausgangstext in eine Folge von Token - Bezeichner, Interpunktionszeichen, numerische Literale, String-Literale, usw. Diese Folge von Token wird später auf Syntax und Bedeutung analysiert. Der Tokenizer ist "gierig" und wird das längste gültige Token bilden, das möglich ist. Wenn Sie etwas schreiben wie

inttest;

sieht der Tokenizer nur zwei Token - den Bezeichner inttest gefolgt von dem Interpunktionszeichen ; . Es erkennt nicht int in diesem Stadium nicht als separates Schlüsselwort verwenden (dies geschieht später im Prozess). Damit die Zeile als Deklaration einer Ganzzahl mit dem Namen test müssen wir Leerzeichen verwenden, um die Bezeichner-Token zu trennen:

int test;

Le site * Zeichen ist nicht Teil eines Bezeichners; es ist ein separates Token (Interpunktionszeichen). Wenn Sie also schreiben

int*test;

sieht der Compiler 4 separate Token - int , * , test y ; . Daher sind Leerzeichen in Zeigerdeklarationen nicht von Bedeutung, und alle

int *test;
int* test;
int*test;
int     *     test;

werden auf die gleiche Weise interpretiert.


Das zweite Teil des Puzzles ist, wie Deklarationen in C und C++ tatsächlich funktionieren 1 . Erklärungen sind in zwei Hauptteile unterteilt - eine Folge von Deklarationsspezifizierer (Speicherklassenspezifizierer, Typenspezifizierer, Typqualifizierer usw.), gefolgt von einer durch Komma getrennten Liste von (möglicherweise initialisierten) Deklaratoren . In der Erklärung

unsigned long int a[10]={0}, *p=NULL, f(void);

Die Deklarationsspezifikationen sind unsigned long int und die Deklaratoren sind a[10]={0} , *p=NULL y f(void) . Der Deklarator leitet den Namen des zu deklarierenden Gegenstandes ein ( a , p y f ) zusammen mit Informationen über die Array-Eigenschaft, Zeiger-Eigenschaft und Funktions-Eigenschaft dieses Dings. Ein Deklarator kann auch einen zugehörigen Initialisierer haben.

Die Art der a ist "10-Elemente-Array von unsigned long int ". Dieser Typ wird vollständig durch den Kombination der Deklarationsspezifizierer und des Deklarators, und der Anfangswert wird mit dem Initialisierer ={0} . In ähnlicher Weise wird die Art der p ist "Zeiger auf unsigned long int ", und auch dieser Typ wird durch die Kombination der Deklarationsangaben und des Deklarators spezifiziert und wird mit NULL . Und die Art der f ist "Funktion, die zurückkehrt unsigned long int " mit der gleichen Argumentation.

Das ist der Schlüssel - es gibt keinen "Zeiger auf". Typ-Bezeichner Es gibt keinen "Array-of"-Typ, genauso wenig wie es einen "function-returning"-Typ gibt. Wir können ein Array nicht deklarieren als

int[10] a;

weil der Operand der Methode [] Betreiber ist a , nicht int . Ähnlich verhält es sich mit der Erklärung

int* p;

der Operand von * es p , nicht int . Da der Indirektionsoperator jedoch unär ist und Leerzeichen keine Rolle spielen, wird der Compiler sich nicht beschweren, wenn wir ihn so schreiben. Allerdings ist es immer interpretiert als int (*p); .

Wenn Sie also schreiben

int* p, q;

der Operand von * es p und wird daher wie folgt interpretiert

int (*p), q;

Somit sind alle

int *test1, test2;
int* test1, test2;
int * test1, test2;

das Gleiche tun - in allen drei Fällen, test1 ist der Operand von * und hat somit den Typ "Zeiger auf int ", während test2 hat Typ int .

Deklaratoren können beliebig komplex werden. Sie können Arrays von Zeigern haben:

T *a[N];

können Sie Zeiger auf Arrays haben:

T (*a)[N];

können Sie Funktionen haben, die Zeiger zurückgeben:

T *f(void);

können Sie Zeiger auf Funktionen haben:

T (*f)(void);

können Sie Arrays von Zeigern auf Funktionen haben:

T (*a[N])(void);

können Sie Funktionen haben, die Zeiger auf Arrays zurückgeben:

T (*f(void))[N];

können Sie Funktionen haben, die Zeiger auf Arrays von Zeigern auf Funktionen zurückgeben, die Zeiger auf T :

T *(*(*f(void))[N])(void); // yes, it's eye-stabby.  Welcome to C and C++.

und dann haben Sie signal :

void (*signal(int, void (*)(int)))(int);

die wie folgt lautet

       signal                             -- signal
       signal(                 )          -- is a function taking
       signal(                 )          --   unnamed parameter
       signal(int              )          --   is an int
       signal(int,             )          --   unnamed parameter
       signal(int,      (*)    )          --   is a pointer to
       signal(int,      (*)(  ))          --     a function taking
       signal(int,      (*)(  ))          --       unnamed parameter
       signal(int,      (*)(int))         --       is an int
       signal(int, void (*)(int))         --     returning void
     (*signal(int, void (*)(int)))        -- returning a pointer to
     (*signal(int, void (*)(int)))(   )   --   a function taking
     (*signal(int, void (*)(int)))(   )   --     unnamed parameter
     (*signal(int, void (*)(int)))(int)   --     is an int
void (*signal(int, void (*)(int)))(int);  --   returning void

und das kratzt gerade mal an der Oberfläche dessen, was möglich ist. Beachten Sie jedoch, dass Array-, Zeiger- und Funktionseigenschaften immer Teil des Deklarators sind, nicht des Typbezeichners.

Eine Sache, auf die Sie achten sollten - const kann sowohl den Zeigertyp als auch den Typ, auf den gezeigt wird, ändern:

const int *p;  
int const *p;

Die beiden oben genannten erklären p als Zeiger auf eine const int Objekt. Sie können einen neuen Wert in p auf ein anderes Objekt verweisen:

const int x = 1;
const int y = 2;

const int *p = &x;
p = &y;

aber Sie können nicht in das Objekt, auf das gezeigt wird, schreiben:

*p = 3; // constraint violation, the pointed-to object is const

Allerdings,

int * const p;

erklärt p als const Zeiger auf eine Nicht-Konst int ; Sie können die Sache anschreiben p zeigt auf

int x = 1;
int y = 2;
int * const p = &x;

*p = 3;

aber Sie können nicht die p um auf ein anderes Objekt zu verweisen:

p = &y; // constraint violation, p is const

Das bringt uns zum dritten Teil des Puzzles - warum Erklärungen auf diese Weise strukturiert sind.

Die Absicht ist, dass die Struktur einer Deklaration die Struktur eines Ausdrucks im Code genau widerspiegeln soll ("declaration mimics use"). Nehmen wir zum Beispiel an, wir haben ein Array mit Zeigern auf int namens ap und wir wollen auf die int Wert, auf den der i Element. Wir würden auf diesen Wert wie folgt zugreifen:

printf( "%d", *ap[i] );

Le site Ausdruck *ap[i] hat Typ int Die Erklärung von ap wird geschrieben als

int *ap[N]; // ap is an array of pointer to int, fully specified by the combination
            // of the type specifier and declarator

Der Deklarator *ap[N] hat die gleiche Struktur wie der Ausdruck *ap[i] . Die Betreiber * y [] verhalten sich in einer Deklaration genauso wie in einem Ausdruck - [] hat einen höheren Vorrang als unäre * , so dass der Operand von * es ap[N] (es wird geparst als *(ap[N]) ).

Ein weiteres Beispiel: Nehmen wir an, wir haben einen Zeiger auf ein Array von int namens pa und wir wollen auf den Wert des Feldes i Element. Wir würden das schreiben als

printf( "%d", (*pa)[i] );

Der Typ des Ausdrucks (*pa)[i] es int so dass die Erklärung wie folgt geschrieben wird

int (*pa)[N];

Auch hier gelten die gleichen Regeln für Vorrang und Assoziativität. In diesem Fall wollen wir die Dereferenzierung der i Element von pa wollen wir auf die i Das Element von was pa zeigt auf gruppieren, also müssen wir explizit die * Betreiber mit pa .

Le site * , [] y () Operatoren sind alle Teil des Ausdruck im Code, sie sind also alle Teil der Deklarator in der Erklärung. Der Deklarator sagt Ihnen, wie Sie das Objekt in einem Ausdruck verwenden können. Wenn Sie eine Deklaration haben wie int *p; die Ihnen sagt, dass der Ausdruck *p in Ihrem Code wird eine int Wert. Im weiteren Sinne sagt es Ihnen, dass der Ausdruck p ergibt einen Wert vom Typ "Zeiger auf int ", oder int * .


Und was ist mit Dingen wie Guss und sizeof Ausdrücke, in denen wir Dinge verwenden wie (int *) o sizeof (int [10]) oder solche Dinge? Wie lese ich etwas wie

void foo( int *, int (*)[10] );

Es gibt keinen Deklarator, sind nicht die * y [] Operatoren, die den Typ direkt ändern?

Nun, nein - es gibt immer noch einen Deklarator, nur mit einem leeren Bezeichner (bekannt als abstrakter Deklarator ). Wenn wir einen leeren Bezeichner mit dem Symbol darstellen, dann können wir diese Dinge lesen als (int *) , sizeof (int [10]) und

void foo( int *λ, int (*λ)[10] );

und sie verhalten sich genau wie jede andere Erklärung. int *[10] ein Array von 10 Zeigern darstellt, während int (*)[10] stellt einen Zeiger auf ein Array dar.


Und nun der rechthaberische Teil dieser Antwort. Ich bin kein Freund der C++-Konvention, einfache Zeiger zu deklarieren als

T* p;

und betrachten sie schlechte Praxis aus den folgenden Gründen:

  1. Das ist nicht mit der Syntax vereinbar;
  2. Sie stiftet Verwirrung (wie diese Frage, alle Duplikate zu dieser Frage, Fragen zur Bedeutung von T* p, q; alle Duplikate nach die Fragen, usw.);
  3. Es ist intern nicht konsistent - die Deklaration eines Arrays von Zeigern als T* a[N] ist bei der Verwendung asymmetrisch (es sei denn, Sie haben die Angewohnheit, die * a[i] );
  4. Sie kann nicht auf Zeiger-auf-Array- oder Zeiger-auf-Funktions-Typen angewandt werden (es sei denn, Sie erstellen ein Typedef, nur damit Sie die T* p saubere Konvention, die... keine );
  5. Der Grund dafür - "es betont die Zeigerhaftigkeit des Objekts" - ist fadenscheinig. Sie kann nicht auf Array- oder Funktionstypen angewandt werden, und ich würde denken, dass diese Eigenschaften genauso wichtig zu betonen sind.

Letzten Endes deutet dies nur auf ein verwirrtes Denken darüber hin, wie die Typensysteme der beiden Sprachen funktionieren.

Es gibt gute Gründe, Posten getrennt zu deklarieren; die Umgehung einer schlechten Praxis ( T* p, q; ) gehört nicht dazu. Wenn Sie Ihre Deklaratoren schreiben richtig ( T *p, q; ), ist die Wahrscheinlichkeit geringer, dass Sie Verwirrung stiften.

Ich betrachte es so, als würden Sie absichtlich alle Ihre einfachen for Schleifen als

i = 0;
for( ; i < N; ) 
{ 
  ... 
  i++; 
}

Syntaktisch gültig, aber verwirrend, und es besteht die Gefahr, dass die Absicht falsch interpretiert wird. Allerdings ist die T* p; Die Konvention ist in der C++-Gemeinschaft fest verankert, und ich verwende sie in meinem eigenen C++-Code, weil Konsistenz in der gesamten Codebasis eine gute Sache ist, aber es juckt mich jedes Mal, wenn ich es tue.


  1. Ich werde die C-Terminologie verwenden - die C++-Terminologie ist ein wenig anders, aber die Konzepte sind weitgehend dieselben.

6 Stimmen

Dies ist die beste Antwort auf diese Frage. Sie sollte höher bewertet werden.

1 Stimmen

Das bedeutet, dass dies auch für eine Referenzdeklaration gilt: int &ref = x;

47voto

Scott Langham Punkte 55597

In vielen Kodierungsrichtlinien wird empfohlen, dass Sie nur folgende Angaben machen eine Variable pro Zeile . Damit wird eine Verwirrung vermieden, wie Sie sie hatten, bevor Sie diese Frage stellten. Die meisten C++-Programmierer, mit denen ich gearbeitet habe, scheinen sich daran zu halten.


Ich weiß, das ist nur eine Randbemerkung, aber ich habe es als nützlich empfunden, Erklärungen rückwärts zu lesen.

int* test;   // test is a pointer to an int

Dies fängt an, sehr gut zu funktionieren, vor allem, wenn Sie beginnen deklarieren const Zeiger und es wird schwierig zu wissen, ob es der Zeiger, der const ist, oder ob seine die Sache, die der Zeiger zeigt auf, dass const ist.

int* const test; // test is a const pointer to an int

int const * test; // test is a pointer to a const int ... but many people write this as  
const int * test; // test is a pointer to an int that's const

1 Stimmen

Die "eine Variable pro Zeile" scheint zwar nützlich zu sein, aber wir haben die Situation, dass das Sternchen mehr links oder mehr rechts steht, immer noch nicht vollständig gelöst. Ich bin mir ziemlich sicher, dass im Code in freier Wildbahn eine Variante vorherrscht; ein bisschen so, wie einige Länder auf der rechten Seite fahren und die anderen in die falsche Richtung, wie z. B. das Vereinigte Königreich. ;-)

0 Stimmen

Bei meinen Abenteuern in der freien Wildbahn sehe ich leider viele von beiden Stilen. In meinem Team verwenden wir jetzt clang-format mit einem Stil, auf den wir uns geeinigt haben. Das bedeutet zumindest, dass der gesamte Code, den unser Team produziert, denselben Stil für die Platzierung von Leerzeichen hat.

35voto

Michael Burr Punkte 320591

Verwenden Sie die "Die Regel der Spirale im Uhrzeigersinn" um die Analyse von C/C++-Deklarationen zu erleichtern;

Es sind drei einfache Schritte zu befolgen:

  1. Beginnen Sie mit dem unbekannten Element und bewegen Sie sich spiralförmig/im Uhrzeigersinn Richtung; wenn Sie auf die folgenden Elemente stoßen, ersetzen Sie sie durch die entsprechenden englischen Aussagen:

    [X] o [] : Array X size of... oder Array undefined size of...

    (type1, type2) Funktion, die Typ1 und Typ2 übergibt und zurückgibt...

    * Hinweis(e) auf...

  2. Dies wird spiralförmig im Uhrzeigersinn fortgesetzt, bis alle Spielsteine abgedeckt sind.

  3. Lösen Sie alles, was in Klammern steht, immer zuerst auf!

Außerdem sollten Deklarationen nach Möglichkeit in separaten Anweisungen stehen (was in den allermeisten Fällen der Fall ist).

10 Stimmen

Das sieht entmutigend und ziemlich schrecklich aus, so leid es mir tut.

7 Stimmen

Aber es scheint eine recht gute Erklärung für einige der komplizierteren Konstruktionen zu sein

1 Stimmen

@d03boy: Keine Frage - C/C++-Deklarationen können ein Albtraum sein.

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