Antworten:
Sie sind genau gleichwertig. In
int *myVariable, myVariable2;
Es scheint offensichtlich, dass myVariable den Typ int * hat , während myVariable2 den Typ int hat . Im
int* myVariable, myVariable2;
es mag offensichtlich erscheinen, dass beide vom Typ int * sind , aber das ist nicht korrekt, wie myVariable2
der Typ int .
Daher ist der erste Programmierstil intuitiver.
int* someVar
persönlichen Projekten. Es macht mehr Sinn.
int[10] x
. Dies ist einfach nicht die Syntax von C. Die Grammatik analysiert explizit als: int (*x)
und nicht als (int *) x
, daher ist das Platzieren des Sterns auf der linken Seite einfach irreführend und basiert auf einem Missverständnis der C-Deklarationssyntax.
int* myVariable; int myVariable2;
stattdessen tun .
Wenn Sie es anders betrachten, *myVariable
ist es von Typ int
, was Sinn macht.
myVariable
kann NULL sein. In diesem Fall *myVariable
wird ein Segmentierungsfehler verursacht, es gibt jedoch keinen Typ NULL.
int x = 5; int *pointer = &x;
weil er darauf hindeutet, dass wir das int *pointer
auf einen bestimmten Wert setzen, nicht auf das pointer
selbst.
Weil das * in dieser Zeile enger an die Variable als an den Typ gebunden ist:
int* varA, varB; // This is misleading
Wie @Lundin weiter unten ausführt, fügt const noch mehr Feinheiten hinzu, über die man nachdenken muss. Sie können dies vollständig umgehen, indem Sie eine Variable pro Zeile deklarieren, was niemals mehrdeutig ist:
int* varA;
int varB;
Das Gleichgewicht zwischen klarem Code und prägnantem Code ist schwer zu finden - ein Dutzend redundanter Zeilen von int a;
ist auch nicht gut. Trotzdem verwende ich standardmäßig eine Deklaration pro Zeile und mache mir Sorgen, dass der Code später kombiniert wird.
int *const a, b;
. Wo bindet das *? Der Typ von a
ist int* const
, wie können Sie also sagen, dass das * zur Variablen gehört, wenn es Teil des Typs selbst ist?
Bisher hat hier niemand erwähnt, dass dieses Sternchen tatsächlich der " Dereferenzierungsoperator " in C ist.
*a = 10;
Die Zeile oben bedeutet nicht , ich zuweisen möchten 10
zu a
, bedeutet dies , ich zugewiesen werden soll 10
, was auch immer Speicherplatz a
auf Punkte an . Und ich habe noch nie jemanden schreiben sehen
* a = 10;
hast du? Der Dereferenzierungsoperator wird also so gut wie immer ohne Leerzeichen geschrieben. Dies ist wahrscheinlich, um es von einer Multiplikation zu unterscheiden, die über mehrere Zeilen verteilt ist:
x = a * b * c * d
* e * f * g;
Hier *e
wäre irreführend, nicht wahr?
Okay, was bedeutet nun die folgende Zeile eigentlich:
int *a;
Die meisten Leute würden sagen:
Dies bedeutet, dass dies a
ein Zeiger auf einen int
Wert ist.
Dies ist technisch korrekt, die meisten Leute sehen / lesen es gerne so und so würden es moderne C-Standards definieren (beachten Sie, dass Sprache C selbst älter ist als alle ANSI- und ISO-Standards). Aber es ist nicht die einzige Möglichkeit, es zu betrachten. Sie können diese Zeile auch wie folgt lesen:
Der dereferenzierte Wert von a
ist vom Typ int
.
Tatsächlich kann das Sternchen in dieser Deklaration auch als Dereferenzierungsoperator angesehen werden, was auch seine Platzierung erklärt. Und das a
ist ein Zeiger, der überhaupt nicht wirklich deklariert ist. Dies impliziert, dass das einzige, was Sie tatsächlich dereferenzieren können, ein Zeiger ist.
Der C-Standard definiert für den *
Bediener nur zwei Bedeutungen :
Und Indirektion ist nur eine einzige Bedeutung, es gibt keine zusätzliche Bedeutung für das Deklarieren eines Zeigers, es gibt nur Indirektion, was die Dereferenzierungsoperation tut, sie führt einen indirekten Zugriff durch, also ist auch innerhalb einer Anweisung wie int *a;
dieser ein indirekter Zugriff ( *
Mittel) indirekter Zugang) und damit ist die zweite Aussage viel näher am Standard als die erste.
int a, *b, (*c)();
so etwas wie "deklariere die folgenden Objekte als int
: das Objekt a
, das Objekt, auf das von gezeigt wird b
, und das Objekt, das von der Funktion zurückgegeben wird, auf die von gezeigt wird c
".
*
in int *a;
ist kein Operator und es ist keine Dereferenzierung a
(die noch nicht einmal definiert ist)
*
(es gibt nur eine Bedeutung für den Gesamtausdruck, nicht für das *
innerhalb des Ausdrucks!). Es heißt, dass "int a;" deklariert einen Zeiger, was er tut, behauptet nie etwas anderes, aber ohne eine Bedeutung, die ihm gegeben *
wird. Das Lesen als * der dereferenzierte Wert von a
ist ein int ist immer noch völlig gültig, da dies dieselbe sachliche Bedeutung hat. Nichts, wirklich nichts, was in 6.7.6.1 geschrieben wurde, würde dieser Aussage widersprechen.
int *a;
ist eine Erklärung, kein Ausdruck. a
wird nicht dereferenziert von int *a;
. a
existiert noch nicht einmal zum Zeitpunkt der *
Verarbeitung. Denken Sie, dass dies int *a = NULL;
ein Fehler ist, weil er einen Nullzeiger dereferenziert?
Ich werde hier auf die Nerven gehen und sagen, dass es eine klare Antwort auf diese Frage gibt , sowohl für Variablendeklarationen als auch für Parameter- und Rückgabetypen, dh , das Sternchen sollte neben dem Namen stehen : int *myVariable;
. Um zu verstehen, warum, sehen Sie sich an, wie Sie andere Symboltypen in C deklarieren:
int my_function(int arg);
für eine Funktion;
float my_array[3]
für ein Array.
Das allgemeine Muster, das als Deklaration nach Verwendung bezeichnet wird , besteht darin, dass der Typ eines Symbols in den Teil vor dem Namen und die Teile um den Namen aufgeteilt wird und diese Teile um den Namen die Syntax nachahmen, die Sie zum Abrufen von a verwenden würden Wert des Typs links:
int a_return_value = my_function(729);
float an_element = my_array[2];
und : int copy_of_value = *myVariable;
.
C ++ wirft einen Schlüssel in die Arbeit mit Referenzen, da die Syntax an der Stelle, an der Sie Referenzen verwenden, mit der von Werttypen identisch ist. Sie könnten also argumentieren, dass C ++ einen anderen Ansatz für C verfolgt. Andererseits behält C ++ dieselbe bei Verhalten von C im Fall von Zeigern, daher sind Referenzen in dieser Hinsicht wirklich ungerade.
Das ist nur eine Frage der Präferenz.
Wenn Sie den Code lesen, ist die Unterscheidung zwischen Variablen und Zeigern im zweiten Fall einfacher. Dies kann jedoch zu Verwirrung führen, wenn Sie sowohl Variablen als auch Zeiger eines gemeinsamen Typs in eine einzelne Zeile setzen (was von Projektrichtlinien häufig nicht empfohlen wird.) weil die Lesbarkeit abnimmt).
Ich ziehe es vor, Zeiger mit dem entsprechenden Vorzeichen neben dem Typnamen zu deklarieren, z
int* pMyPointer;
Ein großer Guru sagte einmal: "Lies es wie der Compiler, du musst."
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
Zugegeben, dies war das Thema Const Placement, aber hier gilt die gleiche Regel.
Der Compiler liest es wie folgt:
int (*a);
nicht so wie:
(int*) a;
Wenn Sie es sich zur Gewohnheit machen, den Stern neben der Variablen zu platzieren, werden Ihre Deklarationen leichter lesbar. Es vermeidet auch Schandflecken wie:
int* a[10];
- Bearbeiten -
Um zu erklären , genau das, was ich meine , wenn ich sage , es als geparst int (*a)
, das bedeutet , dass *
bindet stärker an , a
als es zu int
, in sehr die Art und Weise , dass in dem Ausdruck 4 + 3 * 7
3
bindet stärker an , 7
als es zu 4
dem höheren Vorrang fälligen *
.
Mit Entschuldigungen für die ASCII-Kunst int *a
sieht eine Zusammenfassung des AST für das Parsen ungefähr so aus:
Declaration
/ \
/ \
Declaration- Init-
Secifiers Declarator-
| List
| |
| ...
"int" |
Declarator
/ \
/ ...
Pointer \
| Identifier
| |
"*" |
"a"
Wie deutlich gezeigt wird, *
bindet es enger an a
den gemeinsamen Vorfahren Declarator
, während Sie den ganzen Weg den Baum hinaufgehen müssen Declaration
, um einen gemeinsamen Vorfahren zu finden, an dem der beteiligt ist int
.
(int*) a
.
int
in diesem Fall. Schritt 2. Lesen Sie eine Erklärung, einschließlich aller Arten von Dekorationen. *a
in diesem Fall. Schritt 3 Lesen Sie das nächste Zeichen. Wenn Komma, verbrauchen Sie es und fahren Sie mit Schritt 2 fort. Wenn das Semikolon stoppt. Wenn irgendetwas anderes einen Syntaxfehler auslöst. ...
int* a, b;
und ein Paar Zeiger erhalten. Der Punkt, den ich mache, ist, dass das *
an die Variable bindet und mit ihr analysiert wird, nicht mit dem Typ, der den "Basistyp" der Deklaration bildet. Dies ist auch ein Grund dafür, dass typedefs eingeführt wurden, um typedef int *iptr;
iptr a, b;
einige Zeiger erstellen zu können. Durch eine typedef Sie können die Bindung *
an die int
.
Declaration-Specifier
mit den "Dekorationen" in der Declarator
, um den endgültigen Typ für jede Variable zu erhalten. Allerdings spielt es keine „Bewegung“ , die Dekorationen auf die Erklärung Spezifizierer sonst int a[10], b;
würde produzieren völlig lächerlich Ergebnisse, Es analysiert , int *a, b[10];
wie int
*a
,
b[10]
;
. Es gibt keine andere Möglichkeit, es sinnvoll zu beschreiben.
int*a
am Ende vom Compiler als " a
has type int*
" gelesen wird . Das meine ich mit meinem ursprünglichen Kommentar.
Weil es sinnvoller ist, wenn Sie Erklärungen haben wie:
int *a, *b;
int* a, b;
aus b
einem Zeiger auf int
. Aber sie haben es nicht getan. Und das aus gutem Grund. Was ist unter Ihrem vorgeschlagenen System die Art b
der folgenden Erklärung : int* a[10], b;
?
Um mehrere Zeiger in einer Zeile zu deklarieren, bevorzuge ich, int* a, * b;
dass "a" intuitiver als Zeiger auf eine Ganzzahl deklariert wird und Stile nicht gemischt werden, wenn ebenfalls "b" deklariert wird. Wie jemand sagte, würde ich sowieso nicht zwei verschiedene Typen in derselben Aussage deklarieren.
Leute, die es vorziehen, int* x;
versuchen, ihren Code in eine fiktive Welt zu zwingen, in der der Typ links und die Kennung (Name) rechts ist.
Ich sage "fiktiv", weil:
In C und C ++ ist der deklarierte Bezeichner im allgemeinen Fall von den Typinformationen umgeben.
Das mag verrückt klingen, aber Sie wissen, dass es wahr ist. Hier sind einige Beispiele:
int main(int argc, char *argv[])
bedeutet " main
ist eine Funktion, die ein int
und ein Array von Zeigern auf char
ein nimmt und ein zurückgibt int
." Mit anderen Worten, die meisten Typinformationen befinden sich rechts. Einige Leute denken, dass Funktionsdeklarationen nicht zählen, weil sie irgendwie "besonders" sind. OK, versuchen wir es mit einer Variablen.
void (*fn)(int)
means fn
ist ein Zeiger auf eine Funktion, die ein nimmt int
und nichts zurückgibt.
int a[10]
deklariert 'a' als Array von 10 int
s.
pixel bitmap[height][width]
.
Klar, ich habe Beispiele ausgewählt, die rechts viele Typinformationen enthalten, um meinen Standpunkt zu verdeutlichen. Es gibt viele Deklarationen, bei denen die meisten - wenn nicht alle - des Typs links stehen, wie z struct { int x; int y; } center
.
Diese Deklarationssyntax entstand aus dem Wunsch von K & R, dass Deklarationen die Verwendung widerspiegeln. Das Lesen einfacher Deklarationen ist intuitiv und das Lesen komplexerer Deklarationen kann durch Erlernen der Rechts-Links-Rechts-Regel (manchmal auch als Spiralregel oder nur als Rechts-Links-Regel bezeichnet) beherrscht werden.
C ist einfach genug, dass viele C-Programmierer diesen Stil annehmen und einfache Deklarationen schreiben als int *p
.
In C ++ wurde die Syntax etwas komplexer (mit Klassen, Referenzen, Vorlagen, Aufzählungsklassen), und als Reaktion auf diese Komplexität werden Sie in vielen Deklarationen mehr Aufwand beim Trennen des Typs vom Bezeichner sehen. Mit anderen Worten, int* p
wenn Sie einen großen Teil des C ++ - Codes auschecken , werden möglicherweise mehr Deklarationen im Stil angezeigt.
In beiden Sprachen, Sie können immer den Typen haben auf der linken Seite der variablen Erklärungen (1) nie mehrere Variablen in derselben Anweisung, und (2) die Nutzung von erklärt typedef
s (oder Alias - Deklarationen, die ironischerweise den Alias setzen Bezeichner links von den Typen). Beispielsweise:
typedef int array_of_10_ints[10];
array_of_10_ints a;
(*fn)
den Zeiger beibehalten fn
und nicht den Rückgabetyp.
Ich habe bereits auf eine ähnliche Frage in CP geantwortet, und da dies niemand erwähnt hat, muss ich auch hier darauf hinweisen, dass C eine freie Formatsprache ist , unabhängig davon, welchen Stil Sie wählen, ist es in Ordnung, während der Parser jedes Token unterscheiden kann. Diese Besonderheit von C führte zu einer ganz besonderen Art von Wettbewerb, der als C-Verschleierungswettbewerb bezeichnet wird .
Viele der Argumente in diesem Thema sind einfach subjektiv und das Argument über "Der Stern bindet an den Variablennamen" ist naiv. Hier sind einige Argumente, die nicht nur Meinungen sind:
Die vergessenen Zeigertyp-Qualifizierer
Formal gehört der "Stern" weder zum Typ noch zum Variablennamen, er ist Teil eines eigenen grammatikalischen Elements namens Zeiger . Die formale C-Syntax (ISO 9899: 2018) lautet:
(6.7) Deklaration:
Deklarationsspezifizierer Init-Deklarator-Liste opt;
Wobei Deklarationsspezifizierer den Typ (und den Speicher) enthalten und die Init-Deklaratorliste den Zeiger und den Variablennamen enthält. Was wir sehen, wenn wir diese Deklaratorlistensyntax weiter analysieren:
(6.7.6) Deklarator:
Zeiger Opt - Direktdeklarator
...
(6.7.6) Zeiger:
*
Typ-Qualifizierer-Liste Opt
*
Typ-Qualifizierer-Liste Opt- Zeiger
Wenn ein Deklarator die gesamte Deklaration ist, ist ein Direktdeklarator der Bezeichner (Variablenname), und ein Zeiger ist der Stern, gefolgt von einer optionalen Typqualifizierungsliste, die zum Zeiger selbst gehört.
Was die verschiedenen Stilargumente zu "Der Stern gehört zur Variablen" inkonsistent macht, ist, dass sie diese Zeigertypqualifizierer vergessen haben. int* const x
, int *const x
Oder int*const x
?
Überlegen Sie int *const a, b;
, welche Arten von a
und b
? Nicht so offensichtlich, dass "der Stern zur Variablen gehört". Vielmehr würde man anfangen zu überlegen, wo das hingehört const
.
Sie können definitiv ein stichhaltiges Argument dafür vorbringen, dass der Stern zum Qualifikator für den Zeigertyp gehört, aber nicht viel darüber hinaus.
Die Typqualifizierungsliste für den Zeiger kann Probleme für diejenigen verursachen, die den int *a
Stil verwenden. Diejenigen, die Zeiger in einem verwenden typedef
(was wir nicht sollten, sehr schlechte Praxis!) Und denken, "der Stern gehört zum Variablennamen", neigen dazu, diesen sehr subtilen Fehler zu schreiben:
/*** bad code, don't do this ***/
typedef int *bad_idea_t;
...
void func (const bad_idea_t *foo);
Dies kompiliert sauber. Jetzt könnten Sie denken, dass der Code const korrekt gemacht wurde. Nicht so! Dieser Code ist aus Versehen eine gefälschte Konstantenkorrektheit.
Die Art von foo
ist tatsächlich int*const*
- der äußerste Zeiger wurde schreibgeschützt gemacht, nicht der auf Daten gerichtete. Innerhalb dieser Funktion können wir also **foo = n;
den Variablenwert im Aufrufer ändern.
Dies liegt daran, dass im Ausdruck const bad_idea_t *foo
das *
hier nicht zum Variablennamen gehört! Im Pseudocode ist diese Parameterdeklaration als const (bad_idea_t *) foo
und nicht als zu lesen (const bad_idea_t) *foo
. Der Stern gehört in diesem Fall zum versteckten Zeigertyp - der Typ ist ein Zeiger und ein const-qualifizierter Zeiger wird als geschrieben *const
.
Aber dann liegt die Wurzel des Problems im obigen Beispiel in der Praxis, Zeiger hinter a typedef
und nicht hinter dem *
Stil zu verstecken .
In Bezug auf die Deklaration mehrerer Variablen in einer einzelnen Zeile
Das Deklarieren mehrerer Variablen in einer einzelnen Zeile wird allgemein als schlechte Praxis anerkannt 1) . CERT-C fasst es gut zusammen als:
DCL04-C. Deklarieren Sie nicht mehr als eine Variable pro Deklaration
Nur die englische lesen, dann den gesunden Menschenverstand stimmt zu, dass eine Erklärung sein sollte , eine Erklärung.
Und es spielt keine Rolle, ob die Variablen Zeiger sind oder nicht. Wenn Sie jede Variable in einer einzelnen Zeile deklarieren, wird der Code in fast allen Fällen klarer.
Das Argument, dass der Programmierer verwirrt int* a, b
ist, ist also schlecht. Die Wurzel des Problems ist die Verwendung mehrerer Deklaratoren, nicht die Platzierung der *
. Unabhängig vom Stil sollten Sie stattdessen Folgendes schreiben:
int* a; // or int *a
int b;
Ein anderes stichhaltiges, aber subjektives Argument wäre, dass der gegebene int* a
Typ a
ohne Frage ist int*
und der Stern daher zum Typqualifizierer gehört.
Aber im Grunde ist meine Schlussfolgerung, dass viele der hier veröffentlichten Argumente nur subjektiv und naiv sind. Sie können für keinen der beiden Stile wirklich ein gültiges Argument vorbringen - es ist wirklich eine Frage der subjektiven persönlichen Präferenz.
1) CERT-C DCL04-C .
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Wie der große Prophet Dan Saks lehrt: "Wenn Sie immer const
so weit rechts wie möglich platzieren, ohne die semantische Bedeutung zu ändern", hört dies vollständig auf ein Problem sein. Es macht auch Ihre const
Erklärungen konsistenter zu lesen. "Alles rechts vom Wort const ist das, was const ist, alles links vom Typ ist sein Typ." Dies gilt für alle Konstanten in einer Deklaration. Versuchen Sie es mitint const * * const x;
Erwägen
int *x = new int();
Dies bedeutet nicht
int *x;
*x = new int();
Es übersetzt tatsächlich zu
int *x;
x = new int();
Dies macht die int *x
Notation etwas inkonsistent.
new
ist ein C ++ - Operator. Seine Frage ist mit "C" und nicht mit "C ++" gekennzeichnet.
typedefs
, aber das wird meiner Meinung nach unnötige Komplexität hinzufügen.