Einen String in C umkehren

Lesezeit: 12 Minuten

Benutzeravatar von ant2009
Ameise2009

Ich habe ein Reverse-String-Programm entwickelt. Ich frage mich, ob es einen besseren Weg gibt, dies zu tun, und ob mein Code potenzielle Probleme hat. Ich möchte einige fortgeschrittene Funktionen von C üben.

char* reverse_string(char *str)
{
    char temp;
    size_t len = strlen(str) - 1;
    size_t i;
    size_t k = len;

    for(i = 0; i < len; i++)
    {
        temp = str[k];
        str[k] = str[i];
        str[i] = temp;
        k--;

        /* As 2 characters are changing place for each cycle of the loop
           only traverse half the array of characters */
        if(k == (len / 2))
        {
            break;
        }
    }
}

  • Diese Frage ist ähnlich stackoverflow.com/questions/219420/…

    – Phaidros

    24. April 2009 um 3:51 Uhr

  • strlen(str)-1 läuft unter, wenn der String leer ist

    Benutzer25148

    24. April 2009 um 5:42 Uhr

  • Wir können i < len/2 innerhalb der for-Schleife verwenden `` for(i = 0; i < len/2; i++) { temp = str[k]; Str[k] = Str[i]; Str[i] = Temperatur; k--; } ``

    – Das Biest

    7. Mai 2017 um 23:41 Uhr


Benutzeravatar von rampion
Rapunzel

Wenn Sie erweiterte Funktionen von C üben möchten, wie wäre es mit Zeigern? Wir können auch zum Spaß Makros und Xor-Swaps einwerfen!

#include <string.h> // for strlen()

// reverse the given null-terminated string in place
void inplace_reverse(char * str)
{
  if (str)
  {
    char * end = str + strlen(str) - 1;

    // swap the values in the two given variables
    // XXX: fails when a and b refer to same memory location
#   define XOR_SWAP(a,b) do\
    {\
      a ^= b;\
      b ^= a;\
      a ^= b;\
    } while (0)

    // walk inwards from both ends of the string, 
    // swapping until we get to the middle
    while (str < end)
    {
      XOR_SWAP(*str, *end);
      str++;
      end--;
    }
#   undef XOR_SWAP
  }
}

EIN Zeiger (z.B char *von rechts nach links gelesen als a Zeiger auf a char) ist ein Datentyp in C, der verwendet wird, um auf den Speicherort eines anderen Werts zu verweisen. In diesem Fall ist der Ort, an dem a char wird gelagert. Wir können Dereferenzierung
Zeiger, indem Sie ihnen ein voranstellen *, was uns den an diesem Ort gespeicherten Wert gibt. Also der gespeicherte Wert bei str ist *str.

Wir können einfache Arithmetik mit Zeigern machen. Wenn wir einen Zeiger inkrementieren (oder dekrementieren), verschieben wir ihn einfach, um auf die nächste (oder vorherige) Speicherstelle für diesen Werttyp zu verweisen. Das Erhöhen von Zeigern unterschiedlicher Typen kann den Zeiger um eine unterschiedliche Anzahl von Bytes bewegen, da unterschiedliche Werte in C unterschiedliche Bytegrößen haben.

Hier verwenden wir einen Zeiger, um auf den ersten unverarbeiteten zu verweisen
char der Saite (str) und eine andere, um auf die letzte (end). Wir tauschen ihre Werte (*str und *end) und bewegen Sie die Zeiger nach innen in die Mitte der Zeichenfolge. Einmal str >= endentweder zeigen beide auf dasselbe charwas bedeutet, dass unsere ursprüngliche Zeichenfolge eine ungerade Länge hatte (und die mittlere char muss nicht rückgängig gemacht werden) oder wir haben alles bearbeitet.

Um das Austauschen durchzuführen, habe ich a definiert Makro. Makros sind Textersetzungen, die vom C-Präprozessor durchgeführt werden. Sie unterscheiden sich stark von Funktionen, und es ist wichtig, den Unterschied zu kennen. Wenn Sie eine Funktion aufrufen, arbeitet die Funktion mit einer Kopie der Werte, die Sie ihr geben. Wenn Sie ein Makro aufrufen, führt es einfach eine textuelle Ersetzung durch – die Argumente, die Sie ihm geben, werden also direkt verwendet.

Da ich nur die benutzt habe XOR_SWAP Makro einmal, war es wahrscheinlich übertrieben, es zu definieren, aber es machte deutlicher, was ich tat. Nachdem der C-Präprozessor das Makro erweitert hat, sieht die While-Schleife so aus:

    while (str < end)
    {
      do { *str ^= *end; *end ^= *str; *str ^= *end; } while (0);
      str++;
      end--;
    }

Beachten Sie, dass die Makroargumente einmal für jede Verwendung in der Makrodefinition angezeigt werden. Das kann sehr nützlich sein – kann aber bei falscher Anwendung auch Ihren Code brechen. Wenn ich zum Beispiel die Inkrement-/Dekrement-Anweisungen und den Makroaufruf in eine einzige Zeile komprimiert hätte, wie z

      XOR_SWAP(*str++, *end--);

Dann würde sich dies erweitern

      do { *str++ ^= *end--; *end-- ^= *str++; *str++ ^= *end--; } while (0);

Was hat verdreifachen die Inkrement/Dekrement-Operationen und führt nicht wirklich den Austausch durch, den es tun soll.

Wo wir gerade beim Thema sind, Sie sollten wissen was xoder (^) meint. Es ist eine grundlegende arithmetische Operation – wie Addition, Subtraktion, Multiplikation, Division, außer dass es normalerweise nicht in der Grundschule gelehrt wird. Es kombiniert zwei ganze Zahlen Stück für Stück – wie eine Addition, aber wir kümmern uns nicht um die Überträge. 1^1 = 0, 1^0 = 1,
0^1 = 1, 0^0 = 0.

Ein bekannter Trick besteht darin, xor zu verwenden, um zwei Werte zu vertauschen. Dies funktioniert aufgrund von drei grundlegenden Eigenschaften von xor: x ^ 0 = x, x ^ x = 0 und x ^ y = y ^ x für alle Werte x und y. Angenommen, wir haben zwei Variablen a und b die zunächst zwei Werte speichern
va und vb.

  // initially:
  // a == va
  // b == vb
  a ^= b;
  // now: a == va ^ vb
  b ^= a;
  // now: b == vb ^ (va ^ vb)
  //        == va ^ (vb ^ vb)
  //        == va ^ 0
  //        == va
  a ^= b;
  // now: a == (va ^ vb) ^ va
  //        == (va ^ va) ^ vb
  //        == 0 ^ vb
  //        == vb

Die Werte werden also vertauscht. Dies hat einen Fehler – wann a und b sind die gleiche Variable:

  // initially:
  // a == va
  a ^= a;
  // now: a == va ^ va
  //        == 0
  a ^= a;
  // now: a == 0 ^ 0
  //        == 0
  a ^= a;
  // now: a == 0 ^ 0
  //        == 0

Seit wir str < enddas passiert im obigen Code nie, also sind wir in Ordnung.

Während wir um die Korrektheit besorgt sind, sollten wir unsere Grenzfälle überprüfen. Das if (str) Linie sollte sicherstellen, dass uns keine gegeben wurde NULL Zeiger für Zeichenfolge. Was ist mit der leeren Zeichenfolge ""? Brunnen strlen("") == 0also werden wir initialisieren end wie str - 1was bedeutet, dass die while (str < end) Bedingung ist nie wahr, also tun wir nichts. Welches ist richtig.

Es gibt eine Menge C zu entdecken. Viel Spass damit!

Aktualisieren: mmw spricht einen guten Punkt an, nämlich dass Sie etwas vorsichtig sein müssen, wie Sie dies aufrufen, da es an Ort und Stelle funktioniert.

 char stack_string[] = "This string is copied onto the stack.";
 inplace_reverse(stack_string);

Das funktioniert gut, da stack_string ist ein Array, dessen Inhalt mit der gegebenen String-Konstante initialisiert wird. Jedoch

 char * string_literal = "This string is part of the executable.";
 inplace_reverse(string_literal);

Wird dazu führen, dass Ihr Code zur Laufzeit flammt und stirbt. Das ist, weil string_literal zeigt lediglich auf die Zeichenfolge, die als Teil Ihrer ausführbaren Datei gespeichert ist – was normalerweise Speicher ist, den Sie vom Betriebssystem nicht bearbeiten dürfen. In einer glücklicheren Welt würde Ihr Compiler dies wissen und beim Kompilieren einen Fehler ausgeben, der Ihnen das mitteilt string_literal muss vom Typ sein char const * da Sie den Inhalt nicht ändern können. Dies ist jedoch nicht die Welt, in der mein Compiler lebt.

Es gibt einige Hacks, die Sie ausprobieren könnten, um sicherzustellen, dass sich etwas Speicher auf dem Stack oder im Heap befindet (und daher bearbeitet werden kann), aber sie sind nicht unbedingt portabel, und es könnte ziemlich hässlich sein. Ich bin jedoch mehr als glücklich, die Verantwortung dafür dem Funktionsaufrufer zu überlassen. Ich habe ihnen gesagt, dass diese Funktion an Ort und Stelle Speicher manipuliert, es liegt in ihrer Verantwortung, mir ein Argument zu liefern, das dies zulässt.

  • Nun, ich überprüfe das mit der if(str)-Zeile – und da ich auf der Stelle umkehre, muss ich mich nur um die Eingabe kümmern.

    – Rapunzel

    24. April 2009 um 4:47 Uhr

  • Ah. Ich verstehe jetzt, was du meinst. Du meinst, ich muss sicherstellen, dass das Argument nicht für a ist char const * das ist Teil der Binärdatei. Dies gilt für jede Inplace-Stornierung und sollte beachtet werden. Wenn du gesagt hättest char s[] = "Dante Alighieri est né";, dann wäre das in Ordnung, da es auf dem Stapel wäre. Leider glaube ich nicht, dass es dafür einen tragbaren Scheck gibt. In einer idealen Welt hätte der Compiler Ihr Beispiel verlangt char const * s = "..." da die RHS nicht modifizierbar ist.

    – Rapunzel

    24. April 2009 um 5:07 Uhr

  • Vielleicht möchten Sie uns aufklären, warum xor swap eigentlich eine gute Idee ist? Es ist im Allgemeinen langsamer als die “einfache” Version, es ist schwieriger zu lesen und es scheitert spektakulär, wenn Sie versuchen, eine Adresse mit sich selbst zu tauschen.

    – jalf

    24. April 2009 um 8:22 Uhr

  • Beide genaue Punkte; Mein Ziel war hier jedoch nicht, den effizientesten Code zu schreiben, sondern nur einen Vorwand zu haben, um über verschiedene C-Funktionen zu schreiben, und Cisms Xor Swap ist gut zu kennen, wenn auch nur, um es zu erkennen.

    – Rapunzel

    24. April 2009 um 11:31 Uhr

Benutzeravatar von GManNickG
GManNickG

Nur eine Neuordnung und Sicherheitsüberprüfung. Ich habe auch Ihren nicht verwendeten Rückgabetyp entfernt. Ich denke, das ist sicher und sauber, wie es wird:

#include <stdio.h>
#include <string.h>

void reverse_string(char *str)
{
    /* skip null */
    if (str == 0)
    {
        return;
    }

    /* skip empty string */
    if (*str == 0)
    {
        return;
    }

    /* get range */
    char *start = str;
    char *end = start + strlen(str) - 1; /* -1 for \0 */
    char temp;

    /* reverse */
    while (end > start)
    {
        /* swap */
        temp = *start;
        *start = *end;
        *end = temp;

        /* move */
        ++start;
        --end;
    }
}


int main(void)
{
    char s1[] = "Reverse me!";
    char s2[] = "abc";
    char s3[] = "ab";
    char s4[] = "a";
    char s5[] = "";

    reverse_string(0);

    reverse_string(s1);
    reverse_string(s2);
    reverse_string(s3);
    reverse_string(s4);
    reverse_string(s5);

    printf("%s\n", s1);
    printf("%s\n", s2);
    printf("%s\n", s3);
    printf("%s\n", s4);
    printf("%s\n", s5);

    return 0;
}

Bearbeitet, sodass end nicht auf einen möglicherweise fehlerhaften Speicherort zeigt, wenn strlen 0 ist.

  • Ich werde hier ein pedantischer Idiot sein. Diese Funktion enthält etwas Unsicheres: Die Zeile “char* end = start + strlen(str) – 1;” führt zu undefiniertem Verhalten, wenn strlen(str)==0.

    – Michael Burr

    24. April 2009 um 4:34 Uhr

  • Heh, daran habe ich gedacht, nachdem ich gepostet hatte, aber ich wollte nur hoffen, dass es niemand gesehen hat :> Obwohl das nicht undefiniert ist, oder? Es wird auf einen schlechten Speicherort hinweisen, was schlecht ist, aber immer noch gut definiert ist, ja? Auf jeden Fall habe ich meinen Beitrag für mehr Sicherheit aktualisiert. Sag mir weiter, wenn es mehr gibt 🙂 Ich bemühe mich um Korrektheit.

    – GManNickG

    24. April 2009 um 4:38 Uhr

  • Obwohl dies in den meisten Umgebungen wahrscheinlich kein tatsächliches Problem darstellt, handelt es sich um ein vom Standard nicht definiertes Verhalten. Der Standard erlaubt Zeigerarithmetik, um Zeiger innerhalb des Arrays oder auf eins nach dem letzten Element des Arrays zu ergeben (in diesem letzten Fall darf der Zeiger natürlich nicht dereferenziert werden).

    – Michael Burr

    24. April 2009 um 4:56 Uhr

  • Ah, ok, gut zu wissen, danke. Aber jetzt hast du meinen Code hässlicher gemacht 😛

    – GManNickG

    24. April 2009 um 5:00 Uhr

  • Tut mir leid, dass ich den Code hässlich gemacht habe … da die Routine strrev () so oft in Interviews verwendet wird, ist dieses kleine bisschen etwas, wonach ich immer suche. Ich nehme es niemandem übel (wenn das das schlimmste Problem ist, das die Funktion hatte, gibt es meiner Meinung nach keinen Grund zur Sorge), aber darauf hinzuweisen, kann weitere Gespräche anregen, die zusätzliche Einblicke geben.

    – Michael Burr

    24. April 2009 um 5:11 Uhr

Benutzeravatar von Michael Burr
Michael Burr

Sie können Ihre setzen (len/2) Test in der for-Schleife:

for(i = 0,k=len-1 ; i < (len/2); i++,k--)
{
        temp = str[k];
        str[k] = str[i];
        str[i] = temp;

}

  • Schön +1. Könnte auch ein SWAP-Makro schreiben?

    – Tom

    25. April 2009 um 4:47 Uhr

  • @CoderGuy: k stammt aus dem Code in der Frage – es ist ein Index, der beim letzten Zeichen der Zeichenfolge beginnt und sich zum Anfang der Zeichenfolge bewegt. Es gibt immer noch einen Fehler, der nicht behandelt wird – wenn die Länge der Zeichenfolge ist 0 Die Reverse-Funktion wird falsch ausgeführt. Diese Antwort behandelt dieses Szenario nicht richtig. Ich würde wahrscheinlich so etwas wie das Äquivalent von hinzufügen if (strlen(str) == 0) return; am Anfang der Funktion.

    – Michael Burr

    5. März 2015 um 21:05 Uhr

  • @MichaelBurr, also muss ich die Länge der Zeichenfolge durchgehen

    – BRHSM

    6. März 2015 um 8:13 Uhr

Benutzeravatar von paxdiablo
paxdiablo

Dieses komplette Programm zeigt, wie ich es machen würde. Denken Sie daran, dass ich C geschrieben habe, als die meisten von Ihnen Whippersnappers ein Glitzern in den Augen Ihrer Mutter waren, also ist es altmodisch, macht den Job, lange Var-Namen sind für Weicheier. Korrigieren Sie das, wenn Sie möchten, ich bin mehr an der Korrektheit des Codes interessiert.

Es verarbeitet NULLen, leere Zeichenfolgen und alle Zeichenfolgengrößen. Ich habe es nicht mit Strings der maximalen Größe (max(size_t)) getestet, aber es sollte funktionieren, und wenn Sie mit so großen Strings umgehen, sind Sie sowieso verrückt 🙂

#include <stdio.h>
#include <string.h>

char *revStr (char *str) {
    char tmp, *src, *dst;
    size_t len;
    if (str != NULL)
    {
        len = strlen (str);
        if (len > 1) {
            src = str;
            dst = src + len - 1;
            while (src < dst) {
                tmp = *src;
                *src++ = *dst;
                *dst-- = tmp;
            }
        }
    }
    return str;
}

char *str[] = {"", "a", "ab", "abc", "abcd", "abcde"};

int main(int argc, char *argv[]) {
    int i;
    char s[10000];
    for (i=0; i < sizeof(str)/sizeof(str[0]); i++) {
        strcpy (s, str[i]);
        printf ("'%s' -> '%s'\n", str[i], revStr(s));
    }
    return 0;
}

Die Ausgabe davon ist:

'' -> ''
'a' -> 'a'
'ab' -> 'ba'
'abc' -> 'cba'
'abcd' -> 'dcba'
'abcde' -> 'edcba'

Versuche dies:

reverse_string(NULL);
reverse_string("");

  • Alle POSIX-Funktionen sind für NULL, strlen, strcat usw. undefiniert. Es sollte in der Verantwortung des Aufrufers liegen, auf NULL zu prüfen, nicht die Funktion. Müll rein Müll raus. Oder in diesem Fall Müll in dann segfault.

    – Traumlax

    24. April 2009 um 3:47 Uhr

  • “” ist [const char*]nicht [char*] => Sie können keine Konstanten an Ort und Stelle umkehren.

    – Avp

    24. April 2009 um 8:37 Uhr

Benutzeravatar von Ben Straub
Ben Straub

Sie könnten Ihre for-Schleife-Deklaration ändern, um den Code kürzer zu machen:

char* reverse_string(char *str)
{
    char temp;
    size_t len = strlen(str) - 1;
    size_t stop = len/2;
    size_t i,k;

    for(i = 0, k = len; i < stop; i++, k--)
    {
        temp = str[k];
        str[k] = str[i];
        str[i] = temp;
    }
    return str;
}

  • Alle POSIX-Funktionen sind für NULL, strlen, strcat usw. undefiniert. Es sollte in der Verantwortung des Aufrufers liegen, auf NULL zu prüfen, nicht die Funktion. Müll rein Müll raus. Oder in diesem Fall Müll in dann segfault.

    – Traumlax

    24. April 2009 um 3:47 Uhr

  • “” ist [const char*]nicht [char*] => Sie können keine Konstanten an Ort und Stelle umkehren.

    – Avp

    24. April 2009 um 8:37 Uhr

Benutzeravatar von Sanjaya R
Sanjaya R

Benutzt niemand mehr Pointer?

void inplace_rev( char * s ) {
  char t, *e = s + strlen(s);
  while ( --e > s ) { t = *s;*s++=*e;*e=t; }
}

EDIT: Entschuldigung, habe gerade das obige XOR-Beispiel bemerkt …

  • Was tun, wenn Sie const char* haben?

    – Fermi-4

    12. Januar um 14:53 Uhr

1394510cookie-checkEinen String in C umkehren

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

Privacy policy