Warum benennen die meisten C-Programmierer Variablen wie folgt:
int *myVariable;
anstatt so:
int* myVariable;
Beide sind gültig. Es scheint mir, dass das Sternchen ein Teil des Typs ist, nicht ein Teil des Variablennamens. Kann jemand diese Logik erklären?
c
pointers
variables
naming-conventions
WBlasko
quelle
quelle
typedefs
, aber das wird meiner Meinung nach unnötige Komplexität hinzufügen.Antworten:
Sie sind genau gleichwertig. In
Es scheint offensichtlich, dass myVariable den Typ int * hat , während myVariable2 den Typ int hat . Im
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.
quelle
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 Typint
, was Sinn macht.quelle
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 daspointer
selbst.Weil das * in dieser Zeile enger an die Variable als an den Typ gebunden ist:
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:
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.quelle
int *const a, b;
. Wo bindet das *? Der Typ vona
istint* 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.
Die Zeile oben bedeutet nicht , ich zuweisen möchten
10
zua
, bedeutet dies , ich zugewiesen werden soll10
, was auch immer Speicherplatza
auf Punkte an . Und ich habe noch nie jemanden schreiben sehenhast 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:
Hier
*e
wäre irreführend, nicht wahr?Okay, was bedeutet nun die folgende Zeile eigentlich:
Die meisten Leute würden sagen:
Dies bedeutet, dass dies
a
ein Zeiger auf einenint
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 Typint
.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.quelle
int a, *b, (*c)();
so etwas wie "deklariere die folgenden Objekte alsint
: das Objekta
, das Objekt, auf das von gezeigt wirdb
, und das Objekt, das von der Funktion zurückgegeben wird, auf die von gezeigt wirdc
".*
inint *a;
ist kein Operator und es ist keine Dereferenzierunga
(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 vona
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 vonint *a;
.a
existiert noch nicht einmal zum Zeitpunkt der*
Verarbeitung. Denken Sie, dass diesint *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.
quelle
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
quelle
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:
nicht so wie:
Wenn Sie es sich zur Gewohnheit machen, den Stern neben der Variablen zu platzieren, werden Ihre Deklarationen leichter lesbar. Es vermeidet auch Schandflecken wie:
- 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 zuint
, in sehr die Art und Weise , dass in dem Ausdruck4 + 3 * 7
3
bindet stärker an ,7
als es zu4
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:Wie deutlich gezeigt wird,
*
bindet es enger ana
den gemeinsamen VorfahrenDeclarator
, während Sie den ganzen Weg den Baum hinaufgehen müssenDeclaration
, um einen gemeinsamen Vorfahren zu finden, an dem der beteiligt istint
.quelle
(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, umtypedef int *iptr;
iptr a, b;
einige Zeiger erstellen zu können. Durch eine typedef Sie können die Bindung*
an dieint
.Declaration-Specifier
mit den "Dekorationen" in derDeclarator
, um den endgültigen Typ für jede Variable zu erhalten. Allerdings spielt es keine „Bewegung“ , die Dekorationen auf die Erklärung Spezifizierer sonstint a[10], b;
würde produzieren völlig lächerlich Ergebnisse, Es analysiert ,int *a, b[10];
wieint
*a
,
b[10]
;
. Es gibt keine andere Möglichkeit, es sinnvoll zu beschreiben.int*a
am Ende vom Compiler als "a
has typeint*
" gelesen wird . Das meine ich mit meinem ursprünglichen Kommentar.Weil es sinnvoller ist, wenn Sie Erklärungen haben wie:
quelle
int* a, b;
ausb
einem Zeiger aufint
. Aber sie haben es nicht getan. Und das aus gutem Grund. Was ist unter Ihrem vorgeschlagenen System die Artb
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.quelle
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:
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 einint
und ein Array von Zeigern aufchar
ein nimmt und ein zurückgibtint
." 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)
meansfn
ist ein Zeiger auf eine Funktion, die ein nimmtint
und nichts zurückgibt.int a[10]
deklariert 'a' als Array von 10int
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:quelle
(*fn)
den Zeiger beibehaltenfn
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 .
quelle
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:
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:
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
Oderint*const x
?Überlegen Sie
int *const a, b;
, welche Arten vona
undb
? Nicht so offensichtlich, dass "der Stern zur Variablen gehört". Vielmehr würde man anfangen zu überlegen, wo das hingehörtconst
.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 verwendentypedef
(was wir nicht sollten, sehr schlechte Praxis!) Und denken, "der Stern gehört zum Variablennamen", neigen dazu, diesen sehr subtilen Fehler zu schreiben: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ächlichint*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 alsconst (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:
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:Ein anderes stichhaltiges, aber subjektives Argument wäre, dass der gegebene
int* a
Typa
ohne Frage istint*
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 .
quelle
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Wie der große Prophet Dan Saks lehrt: "Wenn Sie immerconst
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 Ihreconst
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
Dies bedeutet nicht
Es übersetzt tatsächlich zu
Dies macht die
int *x
Notation etwas inkonsistent.quelle
new
ist ein C ++ - Operator. Seine Frage ist mit "C" und nicht mit "C ++" gekennzeichnet.