Wie kopiert “while(*s++ = *t++)” einen String?

Lesezeit: 9 Minuten

Benutzeravatar von Devoted
Hingebungsvoll

Meine Frage ist, was macht dieser Code (von http://www.joelonsoftware.com/articles/CollegeAdvice.html):

while (*s++ = *t++);

Die Website sagt, dass der obige Code eine Zeichenfolge kopiert, aber ich verstehe nicht warum …

hat das was mit zeigern zu tun?

Es ist äquivalent dazu:

while (*t) {
    *s = *t;
    s++;
    t++;
}
*s = *t;

Wenn der Char das t weist darauf hin '\0', wird die While-Schleife beendet. Bis dahin kopiert es das Zeichen that t zeigt auf das Zeichen, dass s zeigt, dann inkrementieren s und t auf das nächste Zeichen in ihren Arrays zeigen.

Benutzeravatar von paxdiablo
paxdiablo

Unter der Decke ist so viel los:

while (*s++ = *t++);

Das s und t Variablen sind Zeiger (mit ziemlicher Sicherheit Zeichen), s Ziel sein. Die folgenden Schritte veranschaulichen, was passiert:

  • der Inhalt von t (*t) werden nach s (*s), ein Zeichen.
  • s und t werden beide erhöht (++).
  • die Zuweisung (Kopie) gibt das kopierte Zeichen zurück (in die while).
  • das while wird fortgesetzt, bis dieses Zeichen Null ist (Ende der Zeichenfolge in C).

Effektiv ist es:

while (*t != 0) {
    *s = *t;
    s++;
    t++;
}
*s = *t;
s++;
t++;

aber viel kompakter geschrieben.

Angenommen s und t sind char *s, die auf Strings zeigen (und davon ausgehen s ist mindestens so groß wie t). In C enden alle Strings auf 0 (ASCII “NUL”), richtig? Also was macht das:

*s++ = *t++;

Erstens tut es das *s = *tkopieren Sie den Wert bei *t zu *s. Dann tut es das s++Also s zeigt nun auf das nächste Zeichen. Und dann tut es das t++Also t zeigt auf das nächste Zeichen. Das hat damit zu tun Operator Vorrang und Präfix vs. Postfix Inkrement/Dekrement.

Die Operatorpriorität ist die Reihenfolge, in der Operatoren aufgelöst werden. Sehen Sie sich für ein einfaches Beispiel Folgendes an:

4 + 2 * 3

Ist das 4 + (2 * 3) oder (4 + 2) * 3? Nun, wir wissen, dass es das erste ist, weil Vorrang – die Binärdatei * (Multiplikationsoperator) hat eine höhere Priorität als die Binärzahl + (Additionsoperator) und wird zuerst aufgelöst.

Im *s++wir haben unär * (Zeiger-Dereferenzierungsoperator) und unär ++ (Postfix-Inkrementoperator). In diesem Fall, ++ hat eine höhere Priorität (auch als “fester binden” bezeichnet) als *. Hätten wir gesagt ++*swürden wir die erhöhen Wert bei *s eher als das Adresse, auf die verwiesen wird s Weil Präfix Inkrement hat niedrigeren Vorrang* als Dereferenzierung, aber wir haben es verwendet postfix Inkrement, das höheren Vorrang hat. Wenn wir das Präfixinkrement hätten verwenden wollen, hätten wir es tun können *(++s)da die Klammer alle niedrigeren Präzedenzfälle überschrieben und erzwungen hätte ++s zuerst kommen, aber dies hätte den unerwünschten Nebeneffekt, dass ein leeres Zeichen am Anfang der Zeichenkette verbleibt.

Beachten Sie, dass es nicht zuerst geschieht, nur weil es eine höhere Priorität hat. Postfix-Inkrement findet speziell statt nach der wert wurde verwendet, was sein warum *s = *t passiert vorher s++.

Jetzt verstehst du es also *s++ = *t++. Aber sie haben es in eine Schleife gesteckt:

while(*s++ = *t++);

Diese Schleife tut nichts – die Aktion ist alles in der Bedingung. Aber sehen Sie sich diese Bedingung an – sie gibt “false” zurück, wenn *s ist immer 0, was bedeutet *t war 0, was bedeutet, dass sie sich am Ende der Zeichenfolge befanden (yay für ASCII “NUL”). Diese Schleife läuft also so lange, wie Zeichen vorhanden sind tund kopiert sie pflichtbewusst in sinkrementieren s und t den ganzen Weg. Wenn diese Schleife beendet wird, s wurde NUL-terminiert und ist eine richtige Zeichenfolge. Das einzige Problem ist, s weist auf das Ende hin. Halten Sie einen weiteren Zeiger bereit, der auf den Anfang von zeigt s (dh s Vor dem while() Schleife) – das wird Ihre kopierte Zeichenfolge sein:

char *s, *string = s;
while(*s++ = *t++);
printf("%s", string); // prints the string that was in *t

Alternativ schau dir das hier an:

size_t i = strlen
while(*s++ = *t++);
s -= i + 1;
printf("%s\n", s); // prints the string that was in *t

Wir haben damit begonnen, die Länge zu ermitteln, und als wir fertig waren, haben wir mehr Zeigerarithmetik zum Setzen durchgeführt s zurück an den Anfang, wo es begann.

Natürlich ignoriert dieses Codefragment (und alle meine Codefragmente) Pufferprobleme der Einfachheit halber. Die bessere Version ist diese:

size_t i = strlen
char *c = malloc(i + 1);
while(*s++ = *t++);
s -= i + 1;
printf("%s\n", s); // prints the string that was in *t
free(c);

Aber das wussten Sie bereits, oder Sie werden bald auf jedermanns Lieblingswebsite eine Frage dazu stellen. 😉

* Eigentlich haben sie dieselbe Priorität, aber das wird durch unterschiedliche Regeln gelöst. Sie haben in dieser Situation effektiv eine geringere Priorität.

  • Kann mir jemand erklären warum char *c = malloc(i + 1); und free(c); ist besser zu ignore buffer issues for simplicity ? Ich verstehe es nicht.

    – 林果皞

    7. November 2016 um 8:22 Uhr


Benutzeravatar von Patrick Schlüter
Patrick Schlüter

while(*s++ = *t++);

Warum denken die Leute, dass es gleichbedeutend ist mit:

while (*t) {
    *s = *t;
    s++;
    t++;
}
*s = *t; /* if *t was 0 at the beginning s and t are not incremented  */

wenn es offensichtlich nicht ist.

char tmp = 0;
do {
   tmp = *t;
   *s = tmp;
   s++;
   t++;
} while(tmp);

ist eher so

BEARBEITEN: Kompilierungsfehler behoben. Das tmp Variable muss außerhalb der Schleife deklariert werden.

Benutzeravatar von AgentLiquid
AgentLiquid

Das Mysteriöse daran ist die Reihenfolge der Operationen. Wenn Sie die C-Sprachspezifikation nachschlagen, heißt es, dass in diesem Zusammenhang die Reihenfolge der Operationen wie folgt ist:

1. * operator
2. = (assignment) operator
3. ++ operator

Die While-Schleife wird dann auf Englisch:

while (some condition):
  Take what is at address "t" and copy it over to location at address "s".
  Increment "s" by one address location.
  Increment "t" by one address location.

Was ist nun “einige Bedingung”? Die Clang-Spezifikation sagt auch, dass der Wert eines Zuweisungsausdrucks der zugewiesene Wert selbst ist, was in diesem Fall der Fall ist *t.

“Einige Bedingung” ist also “t zeigt auf etwas, das nicht Null ist”, oder einfacher ausgedrückt, “während die Daten an Ort und Stelle t ist nicht NULL“.

  • warum die Zuweisung vor dem Post-Inkrement-Operator erfolgt, wenn der Post-Inkrement-Operator Vorrang hat.

    – Warun

    27. November 2017 um 17:49 Uhr

Benutzeravatar von Clint Pachl
Clint Pachl

Die Programmiersprache C (K&R) von Brian W. Kernighan und Dennis M. Ritchie erläutert dies ausführlich.

Zweite Ausgabe, Seite 104:

5.5 Zeichenzeiger und Funktionen

EIN String-Konstantegeschrieben als

"I am a string"

ist ein Array von Zeichen. In der internen Darstellung wird das Array mit dem Nullzeichen abgeschlossen '\0' damit Programme das Ende finden können. Die gespeicherte Länge ist also um eins größer als die Anzahl der Zeichen zwischen den doppelten Anführungszeichen.

Das vielleicht häufigste Vorkommen von String-Konstanten ist als Argument für Funktionen, wie in

printf("hello, world\n");

Wo eine Zeichenkette wie diese in einem Programm erscheint, erfolgt der Zugriff darauf über einen Zeichenzeiger; printf erhält einen Zeiger auf den Anfang des Zeichen-Arrays. Das heißt, auf eine String-Konstante wird durch einen Zeiger auf ihr erstes Element zugegriffen.

Stringkonstanten müssen keine Funktionsargumente sein. Wenn pmessage wird als deklariert

char *pmessage;

dann die aussage

pmessage = "now is the time";

ordnet zu pmessage ein Zeiger auf das Zeichenarray. Das ist nicht eine String-Kopie; es sind nur Zeiger beteiligt. C stellt keine Operatoren zur Verfügung, um eine ganze Zeichenkette als Einheit zu verarbeiten.

Es gibt einen wichtigen Unterschied zwischen diesen Definitionen:

char amessage[] = "now is the time"; /* an array */
char *pmessage = "now is the time"; /* a pointer */

amessage ist ein Array, das gerade groß genug ist, um die Zeichenfolge und aufzunehmen '\0' das initialisiert es. Einzelne Zeichen innerhalb des Arrays können durch geändert werden amessage beziehen sich immer auf denselben Speicher. Auf der anderen Seite, pmessage ist ein Zeiger, der initialisiert wird, um auf eine Zeichenkettenkonstante zu zeigen; Der Zeiger kann anschließend so geändert werden, dass er auf eine andere Stelle zeigt, aber das Ergebnis ist undefiniert, wenn Sie versuchen, den Inhalt der Zeichenfolge zu ändern.

          +---+       +--------------------+
pmessage: | o-------->| now is the time \0 |
          +---+       +--------------------+

          +--------------------+
amessage: | now is the time \0 |
          +--------------------+

Wir werden weitere Aspekte von Zeigern und Arrays veranschaulichen, indem wir Versionen von zwei nützlichen Funktionen studieren, die aus der Standardbibliothek übernommen wurden. Die erste Funktion ist strcpy(s,t)wodurch die Zeichenfolge kopiert wird t zur Saite s. Es wäre schön, es einfach zu sagen s = t aber das kopiert den Zeiger, nicht die Zeichen. Um die Zeichen zu kopieren, brauchen wir eine Schleife. Die Array-Version ist die erste:

/* strcpy: copy t to s; array subscript version */
void strcpy(char *s, char *t)
{
    int i;

    i = 0;
    while((s[i] = t[i]) != '\0')
        i ++;
}

Als Kontrast hier eine Version von strcpy mit Zeigern:

/* strcpy: copy t to s; pointer version 1 */
void strcpy(char *s, char *t)
{
    while((*s = *t) != '\0')
    {
        s ++;
        t ++;
    }
}

Da Argumente als Wert übergeben werden, strcpy kann die Parameter verwenden s und t irgendwie gefällt es. Hier handelt es sich praktischerweise um initialisierte Zeiger, die Zeichen für Zeichen entlang der Arrays bewegt werden, bis die '\0' das endet t kopiert wurde s.

In der Praxis, strcpy nicht so geschrieben werden, wie wir es oben gezeigt haben. Erfahrene C-Programmierer würden es vorziehen

/* strcpy: copy t to s; pointer version 2 */
void strcpy(char *s, char *t)
{
    while((*s++ = *t++) != '\0')
        ;
}

Dies verschiebt das Inkrement von s und t in den Testteil der Schleife. Der Wert von *t++ ist der Charakter, der t vorher hingewiesen t wurde erhöht; das Postfix ++ ändert sich nicht t bis nachdem dieses Zeichen abgerufen wurde. Auf die gleiche Weise wird das Zeichen in das alte gespeichert s Stellung davor s wird erhöht. Dieses Zeichen ist auch der Wert, mit dem verglichen wird '\0' um die Schleife zu kontrollieren. Der Nettoeffekt besteht darin, dass Zeichen kopiert werden t zu sbis einschließlich der Beendigung '\0'.

Beachten Sie als letzte Abkürzung, dass ein Vergleich gegen '\0' ist überflüssig, da die Frage lediglich ist, ob der Ausdruck Null ist. Die Funktion würde also wahrscheinlich geschrieben werden als

/* strcpy: cope t to s; pointer version 3 */
void strcpy(char *s, char *t)
{
    while(*s++ = *t++);
}

Obwohl dies auf den ersten Blick kryptisch erscheinen mag, ist die Notation beträchtlich, und das Idiom sollte beherrscht werden, da Sie es häufig in C-Programmen sehen werden.

Das strcpy in der Standardbibliothek (<string.h>) gibt die Zielzeichenfolge als Funktionswert zurück.

Dies ist das Ende der relevanten Teile dieses Abschnitts.

PS: Wenn Sie dies gerne gelesen haben, sollten Sie eine Kopie von K&R kaufen – es ist nicht teuer.

  • warum die Zuweisung vor dem Post-Inkrement-Operator erfolgt, wenn der Post-Inkrement-Operator Vorrang hat.

    – Warun

    27. November 2017 um 17:49 Uhr

Eddies Benutzeravatar
Eddi

Es funktioniert, indem Zeichen aus der Zeichenfolge kopiert werden, auf die von ‘t‘ in die Zeichenfolge, auf die von ‘s‘. Für jede Zeichenkopie werden beide Zeiger inkrementiert. Die Schleife endet, wenn sie a findet NUL Zeichen (gleich Null, daher der Ausgang).

1414520cookie-checkWie kopiert “while(*s++ = *t++)” einen String?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy