Vor kurzem bin ich auf dieses Problem gestoßen, das ich selbst nicht verstehen kann.
Was bedeuten diese drei Ausdrücke WIRKLICH ?
*ptr++
*++ptr
++*ptr
Ich habe Ritchie ausprobiert. Aber leider konnte er nicht folgen, was er über diese 3 Operationen erzählte.
Ich weiß, dass sie alle ausgeführt werden, um den Zeiger / den Wert, auf den gezeigt wird, zu erhöhen. Ich kann auch vermuten, dass es eine Menge Dinge über Vorrang und Reihenfolge der Bewertung gibt. Wie man den Zeiger zuerst inkrementiert, dann den Inhalt dieses Zeigers abruft, man einfach den Inhalt abruft und dann den Zeiger usw. inkrementiert usw. Wie Sie sehen können, habe ich kein klares Verständnis über ihre tatsächlichen Operationen, die ich gerne hätte so schnell wie möglich klar. Aber ich bin wirklich verloren, wenn ich die Möglichkeit bekomme, sie in Programmen anzuwenden. Beispielsweise:
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
gibt mir diese Ausgabe:
ello
Aber ich hatte erwartet, dass es gedruckt wird Hello
. Eine letzte Anfrage - Bitte geben Sie mir Beispiele dafür, wie jeder Ausdruck in einem bestimmten Codeausschnitt funktioniert. Da die meiste Zeit nur ein Absatz der Theorie über meinen Kopf geflogen wird.
(*ptr)++
(Klammern müssen*ptr++
char* p
, der auf eine gültige terminierte Zeichenfolge eindeutiger Zeichen zeigt. Haben Sie dann eine Funktionfn(char ch)
, die sowohl dench
Parameter als auch das aktuelle Zeichen druckt, auf das von gezeigt wirdp
. Rufen Sie nunfn(*p++);
F auf: Drucktfn
dasselbe Zeichen zweimal ? Sie werden erstaunt sein, wie viele Professoren diese Frage falsch verstehen.const char* p = "Hello";
Antworten:
Hier ist eine detaillierte Erklärung, von der ich hoffe, dass sie hilfreich ist. Beginnen wir mit Ihrem Programm, da es am einfachsten zu erklären ist.
Die erste Aussage:
deklariert
p
als Zeiger aufchar
.char
Was bedeutet das, wenn wir "Zeiger auf a " sagen ? Dies bedeutet, dass der Wert vonp
die Adresse von a istchar
;p
sagt uns, wo im Speicher etwas Platz für a istchar
.Die Anweisung wird auch initialisiert
p
, um auf das erste Zeichen im Zeichenfolgenliteral zu verweisen"Hello"
. Für diese Übung ist es wichtig zu verstehenp
, dass nicht auf die gesamte Zeichenfolge, sondern nur auf das erste Zeichen verwiesen wird'H'
. Immerhinp
ist ein Zeiger auf einschar
, nicht auf die gesamte Zeichenfolge. Der Wertp
ist die Adresse des'H'
in"Hello"
.Dann richten Sie eine Schleife ein:
Was bedeutet die Schleifenbedingung
*p++
? Drei Dinge wirken hier rätselhaft (zumindest bis Vertrautheit einsetzt):++
und Indirektion*
1. Vorrang . Ein kurzer Blick auf die Prioritätstabelle für Operatoren zeigt, dass das Postfix-Inkrement eine höhere Priorität hat (16) als die Dereferenzierung / Indirektion (15). Dies bedeutet, dass der komplexe Ausdruck wie
*p++
folgt gruppiert wird :*(p++)
. Das heißt, das*
Teil wird auf den Wert desp++
Teils angewendet . Nehmen wir alsop++
zuerst den Teil.2. Postfix-Ausdruckswert . Der Wert von
p++
ist der Wertp
vor dem Inkrement . Wenn Sie haben:Die Ausgabe lautet:
weil
i++
bisi
vor dem Inkrement ausgewertet . Ebensop++
wird auf den aktuellen Wert von ausgewertetp
. Wie wir wissen, ist der aktuelle Wert vonp
die Adresse von'H'
.Nun wurde der
p++
Teil von*p++
bewertet; Es ist der aktuelle Wert vonp
. Dann*
passiert der Teil.*(current value of p)
bedeutet: Zugriff auf den Wert unter der Adresse vonp
. Wir wissen, dass der Wert an dieser Adresse ist'H'
. Der Ausdruck*p++
ergibt also'H'
.Moment mal, sagst du. Wenn dies
*p++
ausgewertet wird'H'
, warum wird das nicht'H'
im obigen Code gedruckt? Hier kommen Nebenwirkungen ins Spiel.3. Nebenwirkungen des Postfix-Ausdrucks . Das Postfix
++
hat den Wert des aktuellen Operanden, hat jedoch den Nebeneffekt, dass dieser Operand inkrementiert wird. Huh? Schauen Sie sich diesenint
Code noch einmal an:Wie bereits erwähnt, lautet die Ausgabe wie folgt:
Wenn
i++
es im erstenprintf()
bewertet wird, wird es mit 7 bewertet. Der C-Standard garantiert jedoch, dass irgendwann vorprintf()
Beginn der Ausführung des zweiten die Nebenwirkung des++
Bedieners aufgetreten ist. Das heißt, bevor das zweiteprintf()
passiert, wurdei
es aufgrund des++
Operators im ersten erhöhtprintf()
. Dies ist übrigens eine der wenigen Garantien, die der Standard für das Timing von Nebenwirkungen gibt.Wenn der Ausdruck in Ihrem Code
*p++
ausgewertet wird, wird er ausgewertet'H'
. Aber wenn Sie dazu kommen:diese lästige Nebenwirkung ist aufgetreten.
p
wurde erhöht. Whoa! Es zeigt nicht mehr auf'H'
, sondern auf eine vergangene Figur'H'
: mit'e'
anderen Worten auf die. Das erklärt Ihre übermütige Leistung:Daher der Chor hilfreicher (und genauer) Vorschläge in den anderen Antworten: Um die empfangene Aussprache
"Hello"
und nicht ihr Cockney-Gegenstück zu drucken , benötigen Sie so etwas wieSo viel dazu. Was ist mit dem Rest? Sie fragen nach den Bedeutungen dieser:
Wir haben gerade über das erste gesprochen, also schauen wir uns das zweite an :
*++ptr
.Wir haben in unserer früheren Erklärung gesehen, dass das Postfix-Inkrement
p++
eine bestimmte Priorität , einen Wert und einen Nebeneffekt hat . Das Präfixinkrement++p
hat den gleichen Nebeneffekt wie sein Postfix-Gegenstück: Es erhöht seinen Operanden um 1. Es hat jedoch eine andere Priorität und einen anderen Wert .Das Präfixinkrement hat eine niedrigere Priorität als das Postfix. es hat Vorrang 15. Mit anderen Worten, es hat den gleichen Vorrang wie der Dereferenzierungs- / Indirektionsoperator
*
. In einem Ausdruck wieWas zählt, ist nicht Vorrang: Die beiden Operatoren haben Vorrang. Die Assoziativität setzt also ein. Das Präfixinkrement und der Indirektionsoperator haben eine Assoziativität von rechts nach links. Aufgrund dieser Assoziativität wird der Operand
ptr
mit dem Operator ganz rechts++
vor dem Operator weiter links gruppiert*
. Mit anderen Worten, der Ausdruck wird gruppiert*(++ptr)
. Wie bei,*ptr++
aber aus einem anderen Grund, wird das*
Teil auch hier auf den Wert des++ptr
Teils angewendet .Was ist dieser Wert? Der Wert des Präfix-Inkrement-Ausdrucks ist der Wert des Operanden nach dem Inkrement . Dies macht es zu einem ganz anderen Tier als der Postfix-Inkrement-Operator. Angenommen, Sie haben:
Die Ausgabe wird sein:
... anders als beim Postfix-Operator. Ebenso, wenn Sie haben:
Die Ausgabe lautet:
Verstehst du warum?
Nun kommen wir zu dem dritten Ausdruck, nach dem Sie gefragt haben
++*ptr
. Das ist eigentlich das Schwierigste. Beide Operatoren haben die gleiche Priorität und Rechts-Links-Assoziativität. Dies bedeutet, dass der Ausdruck gruppiert wird++(*ptr)
. Das++
Teil wird auf den Wert des*ptr
Teils angewendet .Also wenn wir haben:
Die überraschend egoistische Ausgabe wird sein:
Was?! Okay, das
*p
Teil wird also ausgewertet'H'
. Dann++
kommt das ins Spiel, an welchem Punkt es'H'
auf den Zeiger angewendet wird , überhaupt nicht auf den Zeiger! Was passiert, wenn Sie 1 hinzufügen'H'
? Sie erhalten 1 plus den ASCII-Wert von'H'
72; Sie erhalten 73. Stellen Sie das als darchar
, und Sie erhalten daschar
mit dem ASCII-Wert von 73 :'I'
.Das kümmert sich um die drei Ausdrücke, nach denen Sie in Ihrer Frage gefragt haben. Hier ist eine andere, die im ersten Kommentar zu Ihrer Frage erwähnt wurde:
Das ist auch interessant. Wenn Sie haben:
es wird Ihnen diese begeisterte Ausgabe geben:
Was ist los? Auch hier geht es um Vorrang , Ausdruckswert und Nebenwirkungen . Aufgrund der Klammern wird der
*p
Teil als primärer Ausdruck behandelt. Primäre Ausdrücke übertreffen alles andere; Sie werden zuerst ausgewertet. Und*p
, wie Sie wissen, bewertet zu'H'
. Der Rest des Ausdrucks, der++
Teil, wird auf diesen Wert angewendet. Also, in diesem Fall(*p)++
wird'H'++
.Was ist der Wert von
'H'++
? Wenn Sie gesagt haben'I'
, haben Sie (bereits!) Unsere Diskussion über Wert vs. Nebenwirkung mit Postfix-Inkrement vergessen. Denken Sie daran, ergibt'H'++
den aktuellen Wert von'H'
. Das wird also zuerstprintf()
gedruckt'H'
. Dann wurde als Nebeneffekt , dass'H'
geht werden erhöht'I'
. Der zweiteprintf()
druckt das'I'
. Und du hast deinen fröhlichen Gruß.In Ordnung, aber in den letzten beiden Fällen, warum brauche ich
Warum kann ich nicht einfach so etwas haben?
Weil
"Hello"
ist ein String-Literal. Wenn Sie es versuchen++*p
, versuchen Sie,'H'
die Zeichenfolge in zu ändern'I'
und die gesamte Zeichenfolge zu erstellen"Iello"
. In C sind String-Literale schreibgeschützt. Der Versuch, sie zu ändern, ruft undefiniertes Verhalten hervor."Iello"
ist auch auf Englisch undefiniert, aber das ist nur Zufall.Umgekehrt kann man nicht haben
Warum nicht? Denn in diesem Fall
p
handelt es sich um ein Array. Ein Array ist kein modifizierbarer l-Wert. Sie können nicht ändern, wop
Punkte vor oder nach dem Inkrementieren oder Dekrementieren liegen, da der Name des Arrays so funktioniert, als wäre es ein konstanter Zeiger. (Das ist nicht das, was es eigentlich ist. Das ist nur eine bequeme Art, es zu betrachten.)Zusammenfassend sind hier die drei Dinge, nach denen Sie gefragt haben:
Und hier ist ein vierter, der genauso viel Spaß macht wie die anderen drei:
Der erste und der zweite stürzen ab, wenn
ptr
es sich tatsächlich um eine Array-ID handelt. Der dritte und vierte stürzen ab, wennptr
auf ein Zeichenfolgenliteral verwiesen wird.Hier hast du es. Ich hoffe es ist jetzt alles Kristall. Sie waren ein großartiges Publikum und ich werde die ganze Woche hier sein.
quelle
Angenommen,
ptr
Punkte zeigen auf das i-te Element des Arraysarr
.*ptr++
wertet ausarr[i]
und setztptr
auf das (i + 1) -te Element vonarr
. Es ist äquivalent zu*(ptr++)
.*++ptr
setztptr
auf das (i + 1) -te Element vonarr
und wertet auf ausarr[i+1]
. Es ist äquivalent zu*(++ptr)
.++*ptr
erhöht sicharr[i]
um eins und bewertet seinen erhöhten Wert; Der Zeigerptr
bleibt unberührt. Es ist äquivalent zu++(*ptr)
.Es gibt noch eine weitere, aber Sie benötigen Klammern, um sie zu schreiben:
(*ptr)++
erhöht sicharr[i]
um eins und bewertet seinen Wert, bevor er erhöht wird; Der Zeigerptr
bleibt wieder unberührt.Den Rest können Sie selbst herausfinden; es wurde auch von @Jaguar beantwortet.
quelle
*ptr++ : post increment a pointer ptr
*++ptr : Pre Increment a pointer ptr
++*ptr : preincrement the value at ptr location
Lesen Sie hier mehr über Operatoren vor und nach dem Inkrementieren
Dies wird
Hello
als Ausgabe gebenquelle
Hello
Der Zustand in Ihrer Schleife ist schlecht:
Ist das gleiche wie
Und das ist falsch, das sollte sein:
*ptr++
ist das gleiche wie*(ptr++)
, was ist:*++ptr
ist das gleiche wie*(++ptr)
, was ist:++*ptr
ist das gleiche wie++(*ptr)
, was ist:quelle
Wenn Sie Recht haben, beachten Sie, dass das
*
Vorrang vor dem Präfixinkrement hat, jedoch nicht vor dem Postfixinkrement. Hier ist, wie diese Aufschlüsselung:*ptr++
- von links nach rechts gehen, den Zeiger dereferenzieren und dann den Zeigerwert erhöhen (nicht das, worauf er zeigt, da Postfix Vorrang vor der Dereferenzierung hat)*++ptr
- Inkrementieren Sie den Zeiger und dereferenzieren Sie ihn. Dies liegt daran, dass Präfix und Dereferenzierung dieselbe Priorität haben und daher in der Reihenfolge von rechts nach links ausgewertet werden++*ptr
- Ähnlich wie oben in Bezug auf die Priorität, wieder von rechts nach links, um den Zeiger zu dereferenzieren und dann zu erhöhen, auf was der Zeiger zeigt. Bitte beachten Sie, dass dies in Ihrem Fall zu undefiniertem Verhalten führt, da Sie versuchen, eine schreibgeschützte Variable (char* p = "Hello";
) zu ändern .quelle
Ich werde meine Einstellung hinzufügen, denn während die anderen Antworten richtig sind, denke ich, dass ihnen etwas fehlt.
meint
Wohingegen
meint
Es ist wichtig zu verstehen, dass Post-Inkrement (und Post-Dekrement) bedeuten
Warum spielt es eine Rolle? Nun, in C ist das nicht so wichtig. In C ++ kann
ptr
es sich jedoch um einen komplexen Typ wie einen Iterator handeln. BeispielsweiseIn diesem Fall kann
it
ein komplexer Typit++
aufgrund dertemp
Erstellung möglicherweise Nebenwirkungen haben . Wenn Sie Glück haben, wird der Compiler natürlich versuchen, nicht benötigten Code wegzuwerfen, aber wenn der Konstruktor oder Destruktor des Iterators irgendetwas tutit++
, werden diese Effekte beim Erstellen angezeigttemp
.Das kurze von dem, was ich zu sagen versuche, ist Schreiben, was du meinst . Wenn Sie ptr inkrementieren, schreiben Sie
++ptr
nichtptr++
. Wenn du meinst,temp = ptr, ptr += 1, temp
dann schreibeptr++
quelle
Dies ist das gleiche wie:
Der Wert des Objekts, auf das von zeigt,
ptr
wird abgerufen und dannptr
erhöht.Dies ist das gleiche wie:
Der Zeiger
ptr
wird also inkrementiert, und das Objekt, auf das von zeigt,ptr
wird gelesen.Dies ist das gleiche wie:
Das Objekt, auf das von zeigt,
ptr
wird also inkrementiert.ptr
selbst ist unverändert.quelle
Postfix und Präfix haben also eine höhere Priorität als Dereferenzierung
* ptr ++ hier inkrementiere ptr und zeige dann auf den neuen Wert von ptr
* ++ ptr hier Inkrementiere die Faust und zeige dann auf den neuen Wert von ptr
++ * ptr Hier erhalten Sie zuerst den Wert von ptr, der auf diesen Wert zeigt, und erhöhen ihn
quelle
Zeigerausdrücke: * ptr ++, * ++ ptr und ++ * ptr:
Hinweis : Zeiger müssen initialisiert sein und eine gültige Adresse haben. Weil im RAM außer unserem Programm (a.out) viel mehr Programme gleichzeitig ausgeführt werden, dh wenn Sie versuchen, auf einen Speicher zuzugreifen, der nicht für Ihr Betriebssystem reserviert war, wird dies durch einen Segmentierungsfehler verursacht.
Bevor wir dies erklären, betrachten wir ein einfaches Beispiel.
Analysieren Sie die Ausgabe des obigen Codes. Ich hoffe, Sie haben die Ausgabe des obigen Codes erhalten. Aus dem obigen Code geht hervor, dass der Zeigername ( ptr ) bedeutet, dass wir über die Adresse sprechen , und * ptr bedeutet, dass wir über Wert / Daten sprechen .
FALL 1 : * ptr ++, * ++ ptr, * (ptr ++) und * (++ ptr):
oben erwähnt sind alle 4 Syntaxen ähnlich,
address gets incremented
aber wie die Adresse erhöht wird, ist unterschiedlich.Hinweis : Um einen Ausdruck zu lösen, ermitteln Sie, wie viele Operatoren im Ausdruck vorhanden sind, und ermitteln Sie dann die Prioritäten des Operators. Wenn mehrere Operatoren dieselbe Priorität haben, überprüfen Sie die Reihenfolge der Evolution oder Assoziativität , die von rechts (R) nach links (L) oder von links nach rechts erfolgen kann.
* ptr ++ : Hier gibt es 2 Operatoren, nämlich De-Reference (*) und ++ (Inkrement). Beide haben die gleiche Priorität und überprüfen dann die Assoziativität von R zu L. Beginnen Sie also mit der Lösung von rechts nach links, unabhängig davon, welche Operatoren zuerst kommen.
* ptr ++ : Das erste ++ kam beim Lösen von R nach L, daher wird die Adresse erhöht, aber das Post-Inkrement.
* ++ ptr : Wie die erste hier wird auch die Adresse inkrementiert, aber ihre Vorinkrementierung.
* (ptr ++) : Hier gibt es 3 Operatoren, darunter grouping () mit der höchsten Priorität. Also wird zuerst ptr ++ gelöst, dh die Adresse wird erhöht, aber veröffentlicht.
* (++ ptr) : Wie im obigen Fall wird auch hier die Adresse inkrementiert, jedoch vor dem Inkrementieren.
FALL 2 : ++ * ptr, ++ (* ptr), (* ptr) ++:
oben erwähnt sind alle 4 Syntax ähnlich, in allen Wert / Daten werden erhöht, aber wie sich der Wert ändert, ist unterschiedlich.
++ * ptr : first * kam beim Lösen von R nach L, daher wird der Wert geändert, aber sein Vorinkrement.
++ (* ptr) : Wie im obigen Fall wird der Wert geändert.
(* ptr) ++ : Hier gibt es 3 Operatoren, darunter grouping () mit der höchsten Priorität. Inside () * ptr ist da. Also wird zuerst * ptr gelöst, dh der Wert wird erhöht, aber post.
Hinweis : ++ * ptr und * ptr = * ptr + 1 sind beide gleich, in beiden Fällen wird der Wert geändert. ++ * ptr: Es wird nur 1 Befehl (INC) verwendet, der direkte Wert wird in einem einzigen Schuss geändert. * ptr = * ptr + 1: Hier wird der erste Wert erhöht (INC) und dann zugewiesen (MOV).
Um alle oben genannten unterschiedlichen Syntaxen des Inkrements auf dem Zeiger zu verstehen, betrachten wir einfachen Code:
Versuchen Sie im obigen Code, Kommentare zu kommentieren / zu entfernen und die Ergebnisse zu analysieren.
Zeiger als Konstante : Es gibt keine Möglichkeit, Zeiger als Konstante zu setzen, nur wenige erwähne ich hier.
1) const int * p ODER int const * p : Hier
value
ist konstant , Adresse ist nicht konstant, dh wohin zeigt p? Eine Adresse? Was ist an dieser Adresse der Wert? Ein Wert, oder? Dieser Wert ist konstant. Sie können diesen Wert nicht ändern, aber wohin zeigt der Zeiger? Eine Adresse richtig? Es kann auch auf eine andere Adresse verweisen.Um dies zu verstehen, betrachten wir den folgenden Code:
Versuchen Sie, die Ausgabe des obigen Codes zu analysieren
2) int const * p : es heißt '
**constant pointe**r
' dhaddress is constant but value is not constant
. Hier dürfen Sie die Adresse nicht ändern, aber Sie können den Wert ändern.Hinweis : Der konstante Zeiger (siehe oben) muss initialisiert werden, während er sich selbst deklariert.
Um dies zu verstehen, überprüfen wir einfachen Code.
Wenn Sie im obigen Code feststellen, dass es kein ++ * p oder * p ++ gibt, haben Sie vielleicht gedacht, dass dies ein einfacher Fall ist, da wir die Adresse oder den Wert nicht ändern, dies jedoch zu Fehlern führen wird. Warum ? Grund erwähne ich in Kommentaren.
Was ist die Lösung dieses Problems?
Weitere Informationen zu diesem Fall finden Sie im folgenden Beispiel.
3) const int * const p : Hier sind sowohl Adresse als auch Wert konstant .
Um dies zu verstehen, überprüfen Sie den folgenden Code
quelle
++*p
bedeutet , dass Sie versuchen , den ASCII - Wert zu erhöhen ,*p
welcheSie können den Wert nicht erhöhen, da es sich um eine Konstante handelt, sodass ein Fehler angezeigt wird
Bei Ihrer while-Schleife wird die Schleife bis
*p++
zum Ende der Zeichenfolge ausgeführt, an der sich ein'\0'
(NULL-) Zeichen befindet.Da
*p++
Sie nun das erste Zeichen überspringen, erhalten Sie Ihre Ausgabe erst ab dem zweiten Zeichen.Der folgende Code gibt nichts aus, da while-Schleife hat
'\0'
Der folgende Code gibt Ihnen die gleiche Ausgabe wie der nächste Code, dh ello.
...................................
quelle