Ich hatte kürzlich das Vergnügen, einem C-Programmier-Anfänger Hinweise zu erklären, und bin auf die folgende Schwierigkeit gestoßen. Es scheint vielleicht überhaupt kein Problem zu sein, wenn Sie bereits wissen, wie man Zeiger verwendet, aber versuchen Sie, das folgende Beispiel mit klarem Verstand zu betrachten:
int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);
Für den absoluten Anfänger könnte die Ausgabe überraschend sein. In Zeile 2 hatte er / sie gerade * bar als & foo deklariert, aber in Zeile 4 stellte sich heraus, dass * bar tatsächlich foo statt & foo ist!
Die Verwirrung, könnte man sagen, ergibt sich aus der Mehrdeutigkeit des Symbols *: In Zeile 2 wird ein Zeiger deklariert. In Zeile 4 wird es als unärer Operator verwendet, der den Wert abruft, auf den der Zeiger zeigt. Zwei verschiedene Dinge, richtig?
Diese "Erklärung" hilft einem Anfänger jedoch überhaupt nicht. Es wird ein neues Konzept eingeführt, indem auf eine subtile Diskrepanz hingewiesen wird. Dies kann nicht der richtige Weg sein, es zu lehren.
Wie haben Kernighan und Ritchie das erklärt?
Der unäre Operator * ist der Indirektions- oder Dereferenzierungsoperator. Wenn es auf einen Zeiger angewendet wird, greift es auf das Objekt zu, auf das der Zeiger zeigt. […]
Die Deklaration des Zeigers ip
int *ip
ist als Mnemonik gedacht; es heißt, dass der Ausdruck*ip
ein int ist. Die Syntax der Deklaration für eine Variable ahmt die Syntax der Ausdrücke nach, in denen die Variable möglicherweise erscheint .
int *ip
sollte gelesen werden wie " *ip
wird ein int
" zurückgeben? Aber warum folgt dann die Zuordnung nach der Deklaration nicht diesem Muster? Was ist, wenn ein Anfänger die Variable initialisieren möchte? int *ip = 1
(read: *ip
gibt ein int
und das int
is zurück 1
) funktioniert nicht wie erwartet. Das konzeptionelle Modell scheint einfach nicht kohärent zu sein. Vermisse ich hier etwas?
Bearbeiten: Es wurde versucht, die Antworten hier zusammenzufassen .
*
eine Deklaration ein Token ist, das "einen Zeiger deklarieren" bedeutet, in Ausdrücken der Dereferenzierungsoperator ist und dass diese beiden verschiedene Dinge darstellen, die zufällig dasselbe Symbol haben (wie der Multiplikationsoperator - gleiches Symbol, andere Bedeutung). Es ist verwirrend, aber alles andere als der tatsächliche Zustand wird noch schlimmer sein.int* bar
macht es das Schreiben so, dass es offensichtlicher wird, dass der Stern tatsächlich Teil des Typs ist, nicht Teil des Bezeichners. Natürlich stößt dies auf verschiedene Probleme mit unintuitiven Dingen wieint* a, b
.*
kann je nach Kontext zwei verschiedene Bedeutungen haben. Genauso wie derselbe Buchstabe je nach Wort unterschiedlich ausgesprochen werden kann, was es schwierig macht, das Sprechen vieler Sprachen zu lernen. Wenn jedes einzelne Konzept / jede Operation ein eigenes Symbol hätte, würden wir viel größere Tastaturen benötigen, sodass Symbole recycelt werden, wenn dies sinnvoll ist.int* p
), während Sie Ihren Schüler davor warnen , mehrere Deklarationen in derselben Zeile zu verwenden, wenn Zeiger beteiligt sind. Wenn der Schüler das Konzept der Zeiger vollständig verstanden hat, erklären Sie dem Schüler, dass dieint *p
Syntax is äquivalent ist, und erklären Sie dann das Problem mit mehreren Deklarationen.Antworten:
Damit Ihr Schüler die Bedeutung des
*
Symbols in verschiedenen Kontexten verstehen kann, muss er zunächst verstehen, dass die Kontexte tatsächlich unterschiedlich sind. Sobald sie verstehen, dass die Kontexte unterschiedlich sind (dh der Unterschied zwischen der linken Seite einer Aufgabe und einem allgemeinen Ausdruck), ist es kein allzu großer kognitiver Sprung, um die Unterschiede zu verstehen.Erklären Sie zunächst, dass die Deklaration einer Variablen keine Operatoren enthalten darf (zeigen Sie dies, indem Sie zeigen, dass das Einfügen eines
-
oder eines+
Symbols in eine Variablendeklaration lediglich einen Fehler verursacht). Zeigen Sie dann weiter, dass ein Ausdruck (dh auf der rechten Seite einer Zuweisung) Operatoren enthalten kann. Stellen Sie sicher, dass der Schüler versteht, dass ein Ausdruck und eine Variablendeklaration zwei völlig unterschiedliche Kontexte sind.Wenn sie verstehen, dass die Kontexte unterschiedlich sind, können Sie weiter erklären, dass das
*
Symbol in einer Variablendeklaration vor dem Variablenbezeichner "diese Variable als Zeiger deklarieren" bedeutet. Dann können Sie erklären, dass das*
Symbol bei Verwendung in einem Ausdruck (als unärer Operator) der "Dereferenzierungsoperator" ist und "der Wert an der Adresse von" bedeutet und nicht seine frühere Bedeutung.Um Ihren Schüler wirklich zu überzeugen, erklären Sie, dass die Ersteller von C jedes Symbol verwendet haben könnten, um den Dereferenzierungsoperator zu bezeichnen (dh sie hätten
@
stattdessen verwenden können), aber aus welchem Grund auch immer sie die Entwurfsentscheidung getroffen haben*
.Alles in allem führt kein Weg daran vorbei zu erklären, dass die Kontexte unterschiedlich sind. Wenn der Schüler nicht versteht, dass die Kontexte unterschiedlich sind, kann er nicht verstehen, warum das
*
Symbol unterschiedliche Bedeutungen haben kann.quelle
Der Grund, warum die Kurzschrift:
In Ihrem Beispiel kann es verwirrend sein, dass es leicht falsch interpretiert werden kann als:
wenn es tatsächlich bedeutet:
So geschrieben, mit getrennter Variablendeklaration und Zuordnung, besteht kein derartiges Verwirrungspotential, und die in Ihrem K & R-Zitat beschriebene Parallelität der Deklaration der Verwendung funktioniert perfekt:
Die erste Zeile deklariert eine Variable
bar
, also*bar
eineint
.Die zweite Zeile weist die Adresse von
foo
zubar
, wodurch*bar
(einint
) ein Alias fürfoo
(auch einint
) wird.Bei der Einführung der C-Zeigersyntax für Anfänger kann es hilfreich sein, zunächst an dieser Art der Trennung von Zeigerdeklarationen von Zuweisungen festzuhalten und die kombinierte Kurzschrift-Syntax (mit entsprechenden Warnungen vor Verwechslungsgefahr) erst dann einzuführen, wenn die grundlegenden Konzepte des Zeigers verwendet werden C wurden angemessen verinnerlicht.
quelle
typedef
.typedef int *p_int;
bedeutet, dass eine Variable vom Typp_int
die Eigenschaft hat,*p_int
die ein istint
. Dann haben wirp_int bar = &foo;
. Es scheint ... eine schlechte Idee zu sein, jemanden zu ermutigen, nicht initialisierte Daten zu erstellen und sie später als Standardgewohnheit zuzuweisen.int a[2] = {47,11};
, dass dies keine Initialisierung des (nicht existierenden) Elementsa[2]
ist.*
sollte es Teil des Typs sein, nicht an die Variable gebunden, und dann können Sie schreibenint* foo_ptr, bar_ptr
, um zwei Zeiger zu deklarieren. Aber es deklariert tatsächlich einen Zeiger und eine ganze Zahl.Kurze Erklärungen
Es ist schön, den Unterschied zwischen Deklaration und Initialisierung zu kennen. Wir deklarieren Variablen als Typen und initialisieren sie mit Werten. Wenn wir beides gleichzeitig tun, nennen wir es oft eine Definition.
1.
int a; a = 42;
Wir erklären ein
int
benanntes a . Dann initialisieren wir es, indem wir ihm einen Wert geben42
.2.
int a = 42;
Wir erklären und
int
benennen a und geben ihm den Wert 42. Es wird mit initialisiert42
. Eine Definition.3.
a = 43;
Wenn wir die Variablen verwenden, sagen wir , dass wir sie bearbeiten .
a = 43
ist eine Zuweisungsoperation. Wir weisen der Variablen a die Nummer 43 zu.Indem ich sage
Wir deklarieren bar als Zeiger auf ein int. Indem ich sage
wir erklären bar und initialisieren es mit der adresse von foo .
Nachdem wir initialisiert haben Balken haben, können wir denselben Operator, das Sternchen, verwenden, um auf den Wert von foo zuzugreifen und ihn zu bearbeiten . Ohne den Operator greifen wir auf die Adresse zu und bearbeiten sie, auf die der Zeiger zeigt.
Außerdem habe ich das Bild sprechen lassen.
Was
Eine vereinfachte ASCIIMATION darüber, was los ist. (Und hier eine Player-Version, wenn Sie pausieren möchten usw.)
quelle
Die 2. Aussage
int *bar = &foo;
kann bildlich im Gedächtnis betrachtet werden als,Jetzt
bar
ist ein Zeiger vom Typint
mit der Adresse&
vonfoo
. Mit dem unären Operator wird*
der in 'foo' enthaltene Wert mithilfe des Zeigers abgerufenbar
.EDIT : Mein Ansatz mit Anfängern ist es, die
memory address
einer Variablen zu erklären , dhMemory Address:
Jeder Variablen ist eine vom Betriebssystem bereitgestellte Adresse zugeordnet. Inint a;
,&a
ist die Adresse der Variablena
.Fahren Sie mit der Erläuterung grundlegender Variablentypen in
C
as,Types of variables:
Variablen können Werte des jeweiligen Typs enthalten, jedoch keine Adressen.Introducing pointers:
Wie oben erwähnt, zum Beispiel VariablenEs ist möglich,
b = a
aber nicht zuzuweisenb = &a
, da die Variableb
den Wert, aber nicht die Adresse enthalten kann. Daher benötigen wir Zeiger .Pointer or Pointer variables :
Wenn eine Variable eine Adresse enthält, wird sie als Zeigervariable bezeichnet. Verwenden Sie*
in der Deklaration, um anzuzeigen, dass es sich um einen Zeiger handelt.quelle
int *ip
als "ip ist ein Zeiger (*) vom Typ int" Probleme beim Lesen von so etwas auftretenx = (int) *ip
.x = (int) *ip;
erhalten Sie den Wert, indem Sie den Zeiger dereferenzieren,ip
und wandeln Sie den Wert in einenint
beliebigen Typip
um.int* bar = &foo;
macht das Unterrichten viel mehr Sinn. Ja, ich weiß, dass es Probleme verursacht, wenn Sie mehrere Zeiger in einer einzelnen Deklaration deklarieren. Nein, ich denke, das ist überhaupt nicht wichtig.Wenn man sich die Antworten und Kommentare hier ansieht, scheint es eine allgemeine Übereinstimmung zu geben, dass die fragliche Syntax für einen Anfänger verwirrend sein kann. Die meisten von ihnen schlagen etwas in diese Richtung vor:
Sie können schreiben,
int* bar
anstattint *bar
den Unterschied hervorzuheben. Dies bedeutet, dass Sie nicht dem K & R-Ansatz "Declaration Mimics Use" folgen, sondern dem Stroustrup C ++ - Ansatz :Wir erklären nicht
*bar
eine ganze Zahl zu sein. Wir erklären unsbar
zu einemint*
. Wenn wir eine neu erstellte Variable in derselben Zeile initialisieren möchten, ist klar, dass es sichbar
nicht um eine Variable handelt*bar
.int* bar = &foo;
Die Nachteile:
int* foo, bar
vsint *foo, *bar
) warnen .Bearbeiten: Ein anderer Ansatz , der vorgeschlagen wurde, besteht darin, den K & R-Weg "nachzuahmen", jedoch ohne die "Kurzschrift" -Syntax (siehe hier) ). Sobald Sie eine Deklaration und eine Zuordnung in derselben Zeile unterlassen , sieht alles viel kohärenter aus.
Früher oder später muss sich der Schüler jedoch mit Zeigern als Funktionsargumenten befassen. Und Zeiger als Rückgabetypen. Und Zeiger auf Funktionen. Sie müssen den Unterschied zwischen
int *func();
und erklärenint (*func)();
. Ich denke, früher oder später werden die Dinge auseinanderfallen. Und vielleicht ist früher besser als später.quelle
Es gibt einen Grund, warum K & R-Stil
int *p
und Stroustrup-Stil bevorzugt werdenint* p
. beide sind in jeder Sprache gültig (und bedeuten dasselbe), aber wie Stroustrup es ausdrückte:Nun, da Sie versuchen, C hier zu unterrichten, würde dies bedeuten, dass Sie Ausdrücke stärker hervorheben sollten als Typen, aber einige Leute können eine Betonung schneller als die andere verstehen, und das betrifft sie und nicht die Sprache.
Daher fällt es einigen Menschen leichter, mit der Idee zu beginnen, dass ein
int*
etwas anderes ist als ein,int
und von dort fortzufahren.Wenn jemand schnell die Art und Weise des Betrachtens es nicht grok , dass Anwendungen
int* bar
zu habenbar
als ein Ding , das nicht ein int ist, sondern ein Zeiger aufint
, dann werden sie schnell sehen , dass*bar
ist etwas zu tun , zubar
, und der Rest wird folgen. Sobald Sie dies getan haben, können Sie später erklären, warum C-Codierer dies bevorzugenint *bar
.Oder nicht. Wenn es einen Weg gegeben hätte, auf dem jeder das Konzept zuerst verstanden hätte, hätten Sie überhaupt keine Probleme gehabt, und der beste Weg, es einer Person zu erklären, ist nicht unbedingt der beste Weg, es einer anderen Person zu erklären.
quelle
int* p = &a
dann können wir es tunint* r = *p
. Ich bin mir ziemlich sicher, dass er es in Das Design und die Evolution von C ++ behandelt hat , aber es ist lange her, dass ich das gelesen habe, und ich habe meine Kopie törichterweise an jemanden gelehnt.int& r = *p
. Und ich wette, der Kreditnehmer versucht immer noch, das Buch zu verdauen.Var A, B: ^Integer;
deutlich macht , dass der Typ „Zeiger auf integer“ gilt für beideA
undB
. Die Verwendung einesK&R
Stilsint *a, *b
ist ebenfalls praktikabel. aber eine Erklärung wieint* a,b;
sieht jedoch so ausa
undb
wird beide als deklariertint*
, aber in Wirklichkeit deklariert siea
alsint*
undb
alsint
.tl; dr:
A: Nicht. Erklären Sie dem Anfänger Zeiger und zeigen Sie ihm, wie er seine Zeigerkonzepte anschließend in der C-Syntax darstellt.
IMO ist die C-Syntax nicht schrecklich, aber auch nicht wunderbar: Es ist weder ein großes Hindernis, wenn Sie bereits Zeiger verstehen, noch eine Hilfe beim Erlernen dieser.
Deshalb: Erklären Sie zunächst die Zeiger und stellen Sie sicher, dass sie sie wirklich verstehen:
Erklären Sie sie mit Box-and-Arrow-Diagrammen. Sie können dies ohne Hex-Adressen tun. Wenn diese nicht relevant sind, zeigen Sie einfach die Pfeile an, die entweder auf ein anderes Feld oder auf ein Nullsymbol zeigen.
Erklären Sie mit Pseudocode: Schreiben Sie einfach die Adresse von foo und den in bar gespeicherten Wert .
Dann, wenn Ihr Anfänger versteht, was Zeiger sind und warum und wie man sie verwendet; Zeigen Sie dann die Zuordnung zur C-Syntax.
Ich vermute, der Grund, warum der K & R-Text kein konzeptionelles Modell liefert, ist der folgende sie bereits Hinweise verstanden haben und wahrscheinlich davon ausgegangen sind, dass dies auch jeder andere kompetente Programmierer zu dieser Zeit getan hat. Die Mnemonik ist nur eine Erinnerung an die Zuordnung vom gut verstandenen Konzept zur Syntax.
quelle
Dieses Problem ist etwas verwirrend, wenn Sie anfangen, C zu lernen.
Hier sind die Grundprinzipien, die Ihnen beim Einstieg helfen können:
In C gibt es nur wenige Grundtypen:
char
: Ein ganzzahliger Wert mit der Größe von 1 Byte.short
: ein ganzzahliger Wert mit der Größe von 2 Bytes.long
: ein ganzzahliger Wert mit der Größe von 4 Bytes.long long
: ein ganzzahliger Wert mit der Größe von 8 Bytes.float
: Ein nicht ganzzahliger Wert mit einer Größe von 4 Bytes.double
: Ein nicht ganzzahliger Wert mit einer Größe von 8 Bytes.Beachten Sie, dass die Größe jedes Typs im Allgemeinen vom Compiler und nicht vom Standard festgelegt wird.
Die Integer - Typen
short
,long
undlong long
sind in der Regel gefolgt vonint
.Es ist jedoch kein Muss, und Sie können sie ohne die verwenden
int
.Alternativ können Sie einfach angeben
int
, dies kann jedoch von verschiedenen Compilern unterschiedlich interpretiert werden.Um dies zusammenzufassen:
short
ist das gleiche wie,short int
aber nicht unbedingt das gleiche wieint
.long
ist das gleiche wie,long int
aber nicht unbedingt das gleiche wieint
.long long
ist das gleiche wie,long long int
aber nicht unbedingt das gleiche wieint
.Auf einem bestimmten Compiler
int
ist entwedershort int
oderlong int
oderlong long int
.Wenn Sie eine Variable eines Typs deklarieren, können Sie auch eine andere Variable deklarieren, die darauf verweist.
Beispielsweise:
int a;
int* b = &a;
Im Wesentlichen haben wir also für jeden Basistyp auch einen entsprechenden Zeigertyp.
Zum Beispiel:
short
undshort*
.Es gibt zwei Möglichkeiten, Variablen zu "betrachten"
b
(das ist wahrscheinlich das, was die meisten Anfänger verwirrt) :Sie können
b
als Variable vom Typ betrachtenint*
.Sie können
*b
als Variable vom Typ betrachtenint
.Daher würden einige Leute erklären
int* b
, während andere erklären würdenint *b
.Tatsache ist jedoch, dass diese beiden Erklärungen identisch sind (die Leerzeichen sind bedeutungslos).
Sie können entweder
b
als Zeiger auf einen ganzzahligen Wert oder verwenden*b
als tatsächlichen spitzen ganzzahligen Wert verwenden.Sie können den angegebenen Wert erhalten (lesen):
int c = *b
.Und Sie können den spitzen Wert setzen (schreiben) :
*b = 5
.Ein Zeiger kann auf eine beliebige Speicheradresse verweisen und nicht nur auf die Adresse einer zuvor deklarierten Variablen. Sie müssen jedoch vorsichtig sein, wenn Sie Zeiger verwenden, um den Wert abzurufen oder festzulegen, der sich an der spitzen Speicheradresse befindet.
Beispielsweise:
int* a = (int*)0x8000000;
Hier haben wir eine Variable,
a
die auf die Speicheradresse 0x8000000 zeigt.Wenn diese Speicheradresse nicht im Speicher Ihres Programms zugeordnet ist, führt ein Lese- oder Schreibvorgang mit
*a
höchstwahrscheinlich dazu, dass Ihr Programm aufgrund einer Speicherzugriffsverletzung abstürzt.Sie können den Wert von sicher ändern
a
, aber Sie sollten sehr vorsichtig sein, wenn Sie den Wert von ändern*a
.Der Typ
void*
ist insofern außergewöhnlich, als er keinen entsprechenden "Werttyp" hat, der verwendet werden kann (dh Sie können ihn nicht deklarierenvoid a
). Dieser Typ wird nur als allgemeiner Zeiger auf eine Speicheradresse verwendet, ohne den Datentyp anzugeben, der sich in dieser Adresse befindet.quelle
Vielleicht macht es ein bisschen mehr, wenn man es ein wenig durchgeht:
Lassen Sie sich von ihnen sagen, was sie von der Ausgabe in jeder Zeile erwarten, und lassen Sie sie dann das Programm ausführen und sehen, was auftaucht. Erklären Sie ihre Fragen (die nackte Version dort wird sicherlich einige dazu veranlassen - aber Sie können sich später über Stil, Strenge und Portabilität Gedanken machen). Schreiben Sie dann eine Funktion, die einen Wert annimmt, und dieselbe, die einen Zeiger nimmt, bevor sich ihr Geist vom Überdenken zu Brei verwandelt oder sie zu einem Zombie nach dem Mittagessen werden.
Nach meiner Erfahrung geht es darum, "warum druckt das so?" Buckel, und dann sofort zu zeigen, warum dies in Funktionsparametern nützlich ist, indem man praktisch spielt (als Auftakt zu einigen grundlegenden K & R-Materialien wie String-Parsing / Array-Verarbeitung), wodurch die Lektion nicht nur Sinn macht, sondern bleibt.
Der nächste Schritt besteht darin , sie zu bekommen , um zu erklären Sie , wie
i[0]
bezieht sich auf&i
. Wenn sie das können, werden sie es nicht vergessen und Sie können anfangen, über Strukturen zu sprechen, sogar ein wenig im Voraus, nur damit es einsinkt.Die obigen Empfehlungen zu Kästchen und Pfeilen sind ebenfalls gut, können aber auch zu einer ausführlichen Diskussion über die Funktionsweise des Gedächtnisses führen. Dies ist ein Gespräch, das irgendwann stattfinden muss, aber von dem unmittelbar ablenkenden Punkt ablenken kann : wie man die Zeigernotation in C interpretiert.
quelle
int foo = 1;
. Nun ist das in Ordnung :int *bar; *bar = foo;
. Dies ist nicht in Ordnung:int *bar = foo;
Der Typ des Ausdrucks
*bar
istint
; Somit wird der Typ der variablen (und Expression)bar
istint *
. Da die Variable einen Zeigertyp hat, muss ihr Initialisierer auch einen Zeigertyp haben.Es besteht eine Inkonsistenz zwischen der Initialisierung und Zuweisung von Zeigervariablen. Das ist nur etwas, was man auf die harte Tour lernen muss.
quelle
Ich würde es lieber lesen, als das erste
*
gilt fürint
mehr alsbar
.quelle
int* a, b
man nicht das tut, was man denkt.int* a,b
das überhaupt verwendet werden sollte. Zur besseren Lesbarkeit, Aktualisierung usw. sollte es nur eine Variablendeklaration pro Zeile geben und niemals mehr. Dies ist auch Anfängern zu erklären, selbst wenn der Compiler damit umgehen kann.*
als Teil des Typs vorzustellen und einfach zu entmutigenint* a, b
. Es sei denn, Sie sagen lieber, dass dies eher ein*a
Typ istint
alsa
einint
int *a, b;
sollte nicht verwendet werden. Das Deklarieren von zwei Variablen mit unterschiedlichen Typen in derselben Anweisung ist eine ziemlich schlechte Praxis und ein starker Kandidat für Wartungsprobleme auf der ganzen Linie. Vielleicht ist es anders für diejenigen von uns, die im eingebetteten Bereich arbeiten, wo einint*
und einint
oft unterschiedlich groß sind und manchmal an völlig unterschiedlichen Speicherorten gespeichert werden. Es ist einer der vielen Aspekte der C-Sprache, die am besten als "es ist erlaubt, aber tu es nicht" gelehrt werden.Question 1
: Was istbar
?Ans
: Es ist eine Zeigervariable (zu tippenint
). Ein Zeiger sollte auf einen gültigen Speicherort zeigen und später mit einem unären Operator dereferenziert werden (* bar)*
, um den an diesem Speicherort gespeicherten Wert zu lesen.Question 2
: Was ist&foo
?Ans
: foo ist eine Variable vom Typ.,int
die an einem gültigen Speicherort gespeichert ist, und an diesem Speicherort erhalten wir sie vom Operator.&
Jetzt haben wir also einen gültigen Speicherort&foo
.Also beide zusammen, dh was der Zeiger brauchte, war ein gültiger Speicherort und das wird dadurch erreicht,
&foo
dass die Initialisierung gut ist.Jetzt zeigt der Zeiger
bar
auf einen gültigen Speicherort, und der darin gespeicherte Wert kann abgerufen werden, dh*bar
quelle
Sie sollten einen Anfänger darauf hinweisen, dass * in der Deklaration und im Ausdruck unterschiedliche Bedeutungen hat. Wie Sie wissen, ist * im Ausdruck ein unärer Operator und * in der Deklaration kein Operator und nur eine Art Syntax, die mit type kombiniert wird, um dem Compiler mitzuteilen, dass es sich um einen Zeigertyp handelt. Es ist besser, einem Anfänger zu sagen: "* hat eine andere Bedeutung. Um die Bedeutung von * zu verstehen, sollten Sie herausfinden, wo * verwendet wird."
quelle
Ich denke der Teufel ist im Raum.
Ich würde schreiben (nicht nur für den Anfänger, sondern auch für mich selbst): int * bar = & foo; anstelle von int * bar = & foo;
Dies sollte deutlich machen, in welchem Verhältnis Syntax und Semantik stehen
quelle
Es wurde bereits festgestellt, dass * mehrere Rollen hat.
Es gibt eine andere einfache Idee, die einem Anfänger helfen kann, Dinge zu erfassen:
Denken Sie, dass "=" auch mehrere Rollen hat.
Wenn die Zuweisung in derselben Zeile wie die Deklaration verwendet wird, stellen Sie sich dies als Konstruktoraufruf vor, nicht als willkürliche Zuweisung.
Wenn du siehst:
Denken Sie, dass es fast gleichbedeutend ist mit:
Klammern haben Vorrang vor Sternchen, daher wird "& foo" viel einfacher intuitiv "bar" als "* bar" zugeordnet.
quelle
Ich habe diese Frage vor ein paar Tagen gesehen und dann zufällig die Erklärung von Go's Typdeklaration im Go Blog gelesen . Zunächst wird ein Bericht über Deklarationen vom Typ C erstellt, der als nützliche Ressource zum Hinzufügen zu diesem Thread erscheint, obwohl ich denke, dass bereits vollständigere Antworten gegeben wurden.
(Es wird weiter beschrieben, wie dieses Verständnis auf Funktionszeiger usw. erweitert werden kann.)
Dies ist ein Weg, über den ich vorher noch nicht nachgedacht habe, aber es scheint ein ziemlich einfacher Weg zu sein, um die Überlastung der Syntax zu erklären.
quelle
Wenn das Problem die Syntax ist, kann es hilfreich sein, äquivalenten Code mit template / using anzuzeigen.
Dies kann dann als verwendet werden
Vergleichen Sie danach die normale / C-Syntax mit diesem C ++ - Ansatz. Dies ist auch nützlich, um const-Zeiger zu erklären.
quelle
Die Quelle der Verwirrung ergibt sich aus der Tatsache, dass das
*
Symbol in C unterschiedliche Bedeutungen haben kann, abhängig von der Tatsache, in der es verwendet wird. Um einem Anfänger den Zeiger zu erklären, sollte die Bedeutung des*
Symbols in einem anderen Kontext erklärt werden.In der Erklärung
Das
*
Symbol ist nicht der Indirektionsoperator . Stattdessen hilft es, den Typ von anzugebenbar
Information des Compilersbar
ein Zeiger auf eine istint
. Wenn es andererseits in einer Anweisung erscheint, führt das*
Symbol (wenn es als unärer Operator verwendet wird ) eine Indirektion durch. Daher die Aussagewäre falsch, da es die Adresse von zuweist
foo
dem Objektbar
auf sichbar
selbst zeigt, nicht auf sich selbst.quelle
"Vielleicht macht das Schreiben als int * bar deutlicher, dass der Stern tatsächlich Teil des Typs ist, nicht Teil des Bezeichners." So ich mache. Und ich sage, dass es so etwas wie Type ist, aber nur für einen Zeigernamen.
"Natürlich stößt man dabei auf verschiedene Probleme mit nicht intuitiven Dingen wie int * a, b."
quelle
Hier muss man die Compiler-Logik verwenden, verstehen und erklären, nicht die menschliche Logik (ich weiß, Sie sind ein Mensch, aber hier müssen Sie den Computer nachahmen ...).
Wenn du schreibst
Der Compiler gruppiert das als
Das heißt: Hier ist eine neue Variable, ihr Name ist
bar
, ihr Typ ist Zeiger auf int und ihr Anfangswert ist&foo
.Und Sie müssen hinzufügen: Das
=
Obige bezeichnet eine Initialisierung, keine Beeinflussung, während dies in den folgenden Ausdrücken der*bar = 2;
Fall ist eine AffektiertheitPer Kommentar bearbeiten:
Achtung: Bei Mehrfachdeklaration
*
bezieht sich das nur auf die folgende Variable:bar ist ein Zeiger auf int, der durch die Adresse von foo initialisiert wurde, b ist ein int, das auf 2 und in initialisiert wurde
Balken im Standbildzeiger auf int und p ist ein Zeiger auf einen Zeiger auf ein int, das auf die Adresse oder den Balken initialisiert wurde.
quelle
int* a, b;
deklariert a als Zeiger auf aint
, aber b als anint
. Das*
Symbol hat nur zwei unterschiedliche Bedeutungen: In einer Deklaration gibt es einen Zeigertyp an, und in einem Ausdruck ist es der unäre Dereferenzierungsoperator.*
mit dem Typ verknüpft ist, so dass der Zeiger initialisiert wird, während bei einer Affektaktion der spitze Wert betroffen ist. Aber wenigstens hast du mir einen schönen Hut gegeben :-)Grundsätzlich ist der Zeiger keine Array-Anzeige. Anfänger denken leicht, dass Zeiger wie Array aussieht. Die meisten Zeichenfolgenbeispiele verwenden die
"char * pstr" sieht ähnlich aus
"char str [80]"
Wichtig ist jedoch, dass Pointer in der unteren Ebene des Compilers nur als Ganzzahl behandelt wird.
Schauen wir uns Beispiele an ::
Die Ergebnisse werden dies mögen 0x2a6b7ed0 ist die Adresse von str []
Denken Sie also grundsätzlich daran, dass der Zeiger eine Art Ganzzahl ist. Präsentation der Adresse.
quelle
Ich würde erklären, dass Ints Objekte sind, ebenso wie Floats usw. Ein Zeiger ist ein Objekttyp, dessen Wert eine Adresse im Speicher darstellt (daher ist ein Zeiger standardmäßig NULL).
Wenn Sie zum ersten Mal einen Zeiger deklarieren, verwenden Sie die Typ-Zeiger-Name-Syntax. Es wird als "Ganzzahlzeiger namens Name, der auf die Adresse eines beliebigen Ganzzahlobjekts verweisen kann" gelesen. Wir verwenden diese Syntax nur während der Dekleration, ähnlich wie wir ein int als 'int num1' deklarieren, aber wir verwenden 'num1' nur, wenn wir diese Variable verwenden möchten, nicht 'int num1'.
int x = 5; // ein ganzzahliges Objekt mit einem Wert von 5
int * ptr; // standardmäßig eine Ganzzahl mit dem Wert NULL
Um einen Zeiger auf eine Adresse eines Objekts zu zeigen, verwenden wir das Symbol '&', das als "Adresse von" gelesen werden kann.
ptr = & x; // Jetzt ist Wert die Adresse von 'x'
Da der Zeiger nur die Adresse des Objekts ist, müssen wir das Symbol '*' verwenden, um den tatsächlichen Wert an dieser Adresse zu erhalten. Wenn er vor einem Zeiger verwendet wird, bedeutet dies "den Wert an der Adresse, auf die durch gezeigt wird".
std :: cout << * ptr; // drucke den Wert an der Adresse aus
Sie können kurz erklären, dass ' ' ein 'Operator' ist, der unterschiedliche Ergebnisse mit unterschiedlichen Objekttypen zurückgibt. Bei Verwendung mit einem Zeiger bedeutet der Operator ' ' nicht mehr "multipliziert mit".
Es ist hilfreich, ein Diagramm zu zeichnen, das zeigt, wie eine Variable einen Namen und einen Wert hat und ein Zeiger eine Adresse (den Namen) und einen Wert hat, und zu zeigen, dass der Wert des Zeigers die Adresse des int ist.
quelle
Ein Zeiger ist nur eine Variable zum Speichern von Adressen.
Der Speicher in einem Computer besteht aus Bytes (ein Byte besteht aus 8 Bits), die sequentiell angeordnet sind. Jedem Byte ist eine Nummer zugeordnet, genau wie Index oder Index in einem Array, die als Adresse des Bytes bezeichnet wird. Die Adresse des Bytes beginnt bei 0 bis eins weniger als die Speichergröße. Zum Beispiel gibt es in einem 64 MB RAM 64 * 2 ^ 20 = 67108864 Bytes. Daher beginnt die Adresse dieser Bytes von 0 bis 67108863.
Mal sehen, was passiert, wenn Sie eine Variable deklarieren.
int Marken;
Wie wir wissen, belegt ein int 4 Datenbytes (vorausgesetzt, wir verwenden einen 32-Bit-Compiler), reserviert der Compiler 4 aufeinanderfolgende Bytes aus dem Speicher, um einen ganzzahligen Wert zu speichern. Die Adresse des ersten Bytes der 4 zugewiesenen Bytes wird als Adresse der Variablenmarkierungen bezeichnet. Angenommen, die Adresse von 4 aufeinanderfolgenden Bytes lautet 5004, 5005, 5006 und 5007, dann lautet die Adresse der variablen Markierungen 5004.
Zeigervariablen deklarieren
Wie bereits erwähnt, ist ein Zeiger eine Variable, die eine Speicheradresse speichert. Wie bei allen anderen Variablen müssen Sie zuerst eine Zeigervariable deklarieren, bevor Sie sie verwenden können. So können Sie eine Zeigervariable deklarieren.
Syntax:
data_type *pointer_name;
Datentyp ist der Typ des Zeigers (auch als Basistyp des Zeigers bekannt). Zeigername ist der Name der Variablen, die eine beliebige gültige C-Kennung sein kann.
Nehmen wir einige Beispiele:
int * ip bedeutet, dass ip eine Zeigervariable ist, die auf Variablen vom Typ int zeigen kann. Mit anderen Worten, eine Zeigervariable ip kann nur die Adresse von Variablen vom Typ int speichern. Ebenso kann die Zeigervariable fp nur die Adresse einer Variablen vom Typ float speichern. Der Variablentyp (auch als Basistyp bezeichnet) ip ist ein Zeiger auf int und der Typ fp ist ein Zeiger auf float. Eine Zeigervariable vom Typ Zeiger auf int kann symbolisch als (int *) dargestellt werden. In ähnlicher Weise kann eine Zeigervariable vom Typ Zeiger auf float als (float *) dargestellt werden.
Nach dem Deklarieren einer Zeigervariablen besteht der nächste Schritt darin, ihr eine gültige Speicheradresse zuzuweisen. Sie sollten niemals eine Zeigervariable verwenden, ohne ihr eine gültige Speicheradresse zuzuweisen, da sie unmittelbar nach der Deklaration einen Müllwert enthält und möglicherweise auf eine beliebige Stelle im Speicher verweist. Die Verwendung eines nicht zugewiesenen Zeigers kann zu einem unvorhersehbaren Ergebnis führen. Es kann sogar zum Absturz des Programms führen.
Quelle: thecguru ist bei weitem die einfachste und doch detaillierteste Erklärung, die ich je gefunden habe.
quelle