Wie schneide ich führende / nachfolgende Leerzeichen auf normale Weise?

177

Gibt es eine saubere, vorzugsweise Standardmethode zum Trimmen von führenden und nachfolgenden Leerzeichen aus einer Zeichenfolge in C? Ich würde meine eigenen rollen, aber ich würde denken, dass dies ein häufiges Problem mit einer ebenso häufigen Lösung ist.

Coledot
quelle

Antworten:

164

Wenn Sie die Zeichenfolge ändern können:

// Note: This function returns a pointer to a substring of the original string.
// If the given string was allocated dynamically, the caller must not overwrite
// that pointer with the returned value, since the original pointer must be
// deallocated using the same allocator with which it was allocated.  The return
// value must NOT be deallocated using free() etc.
char *trimwhitespace(char *str)
{
  char *end;

  // Trim leading space
  while(isspace((unsigned char)*str)) str++;

  if(*str == 0)  // All spaces?
    return str;

  // Trim trailing space
  end = str + strlen(str) - 1;
  while(end > str && isspace((unsigned char)*end)) end--;

  // Write new null terminator character
  end[1] = '\0';

  return str;
}

Wenn Sie die Zeichenfolge nicht ändern können, können Sie grundsätzlich dieselbe Methode verwenden:

// Stores the trimmed input string into the given output buffer, which must be
// large enough to store the result.  If it is too small, the output is
// truncated.
size_t trimwhitespace(char *out, size_t len, const char *str)
{
  if(len == 0)
    return 0;

  const char *end;
  size_t out_size;

  // Trim leading space
  while(isspace((unsigned char)*str)) str++;

  if(*str == 0)  // All spaces?
  {
    *out = 0;
    return 1;
  }

  // Trim trailing space
  end = str + strlen(str) - 1;
  while(end > str && isspace((unsigned char)*end)) end--;
  end++;

  // Set output size to minimum of trimmed string length and buffer size minus 1
  out_size = (end - str) < len-1 ? (end - str) : len-1;

  // Copy trimmed string and add null terminator
  memcpy(out, str, out_size);
  out[out_size] = 0;

  return out_size;
}
Adam Rosenfield
quelle
6
Entschuldigung, die erste Antwort ist überhaupt nicht gut, es sei denn, Sie kümmern sich nicht um Speicherlecks. Sie haben jetzt zwei überlappende Zeichenfolgen (die ursprüngliche, deren nachgestellte Leerzeichen abgeschnitten sind, und die neue). Es kann nur die ursprüngliche Zeichenfolge freigegeben werden. Wenn Sie dies jedoch tun, zeigt die zweite auf freigegebenen Speicher.
David Nehme
7
@nvl: Es wird kein Speicher zugewiesen, daher muss kein Speicher freigegeben werden.
Adam Rosenfield
15
@nvl: Nein strist eine lokale Variable, und durch Ändern wird der ursprüngliche Zeiger, der übergeben wird, nicht geändert. Funktionsaufrufe in C werden immer als Wert übergeben, niemals als Referenz.
Adam Rosenfield
11
@Raj: Es ist von Natur aus nichts Falsches daran, eine andere Adresse als die übergebene zurückzugeben. Hier ist es nicht erforderlich, dass der zurückgegebene Wert ein gültiges Argument der free()Funktion ist. Im Gegenteil - ich habe dies so konzipiert, dass aus Effizienzgründen keine Speicherzuweisung erforderlich ist. Wenn die übergebene Adresse dynamisch zugewiesen wurde, ist der Anrufer weiterhin für die Freigabe dieses Speichers verantwortlich, und der Anrufer muss sicherstellen, dass dieser Wert nicht mit dem hier zurückgegebenen Wert überschrieben wird.
Adam Rosenfield
3
Sie müssen das Argument für isspaceto unsigned charumwandeln, sonst rufen Sie undefiniertes Verhalten auf.
Roland Illig
37

Hier ist eine, die die Zeichenfolge an die erste Position Ihres Puffers verschiebt. Möglicherweise möchten Sie dieses Verhalten, damit Sie die Zeichenfolge, wenn Sie sie dynamisch zugewiesen haben, weiterhin auf demselben Zeiger freigeben können, den trim () zurückgibt:

char *trim(char *str)
{
    size_t len = 0;
    char *frontp = str;
    char *endp = NULL;

    if( str == NULL ) { return NULL; }
    if( str[0] == '\0' ) { return str; }

    len = strlen(str);
    endp = str + len;

    /* Move the front and back pointers to address the first non-whitespace
     * characters from each end.
     */
    while( isspace((unsigned char) *frontp) ) { ++frontp; }
    if( endp != frontp )
    {
        while( isspace((unsigned char) *(--endp)) && endp != frontp ) {}
    }

    if( frontp != str && endp == frontp )
            *str = '\0';
    else if( str + len - 1 != endp )
            *(endp + 1) = '\0';

    /* Shift the string so that it starts at str so that if it's dynamically
     * allocated, we can still free it on the returned pointer.  Note the reuse
     * of endp to mean the front of the string buffer now.
     */
    endp = str;
    if( frontp != str )
    {
            while( *frontp ) { *endp++ = *frontp++; }
            *endp = '\0';
    }

    return str;
}

Auf Richtigkeit prüfen:

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

/* Paste function from above here. */

int main()
{
    /* The test prints the following:
    [nothing to trim] -> [nothing to trim]
    [    trim the front] -> [trim the front]
    [trim the back     ] -> [trim the back]
    [    trim front and back     ] -> [trim front and back]
    [ trim one char front and back ] -> [trim one char front and back]
    [ trim one char front] -> [trim one char front]
    [trim one char back ] -> [trim one char back]
    [                   ] -> []
    [ ] -> []
    [a] -> [a]
    [] -> []
    */

    char *sample_strings[] =
    {
            "nothing to trim",
            "    trim the front",
            "trim the back     ",
            "    trim front and back     ",
            " trim one char front and back ",
            " trim one char front",
            "trim one char back ",
            "                   ",
            " ",
            "a",
            "",
            NULL
    };
    char test_buffer[64];
    char comparison_buffer[64];
    size_t index, compare_pos;

    for( index = 0; sample_strings[index] != NULL; ++index )
    {
        // Fill buffer with known value to verify we do not write past the end of the string.
        memset( test_buffer, 0xCC, sizeof(test_buffer) );
        strcpy( test_buffer, sample_strings[index] );
        memcpy( comparison_buffer, test_buffer, sizeof(comparison_buffer));

        printf("[%s] -> [%s]\n", sample_strings[index],
                                 trim(test_buffer));

        for( compare_pos = strlen(comparison_buffer);
             compare_pos < sizeof(comparison_buffer);
             ++compare_pos )
        {
            if( test_buffer[compare_pos] != comparison_buffer[compare_pos] )
            {
                printf("Unexpected change to buffer @ index %u: %02x (expected %02x)\n",
                    compare_pos, (unsigned char) test_buffer[compare_pos], (unsigned char) comparison_buffer[compare_pos]);
            }
        }
    }

    return 0;
}

Die Quelldatei war trim.c. Kompiliert mit 'cc -Wall trim.c -o trim'.

indiv
quelle
1
Sie müssen das Argument für isspaceto unsigned charumwandeln, sonst rufen Sie undefiniertes Verhalten auf.
Roland Illig
@ RolandIllig: Danke, ich habe nie bemerkt, dass das notwendig ist. Behoben.
Indiv
@ Simas: Warum sagst du das? Die Funktion ruft isspace()also auf, warum sollte es einen Unterschied zwischen " "und geben "\n"? Ich habe Unit-Tests für Zeilenumbrüche hinzugefügt und es sieht für mich in Ordnung aus
indiv
1
@indiv Es greift auf einen ungültigen Speicherblock zu, wenn es manuell zugewiesen wird. Nämlich diese Zeile : *(endp + 1) = '\0';. Der Beispieltest für die Antwort verwendet einen Puffer von 64, wodurch dieses Problem vermieden wird.
Simas
1
@ Nolandda: Danke für das Detail. Ich habe es behoben und den Test aktualisiert, um den Pufferüberlauf zu erkennen, da ich momentan keinen Zugriff auf valgrind habe.
indiv
23

Meine Lösung. String muss änderbar sein. Der Vorteil gegenüber einigen anderen Lösungen besteht darin, dass der Nicht-Leerzeichen-Teil an den Anfang verschoben wird, sodass Sie den alten Zeiger weiterhin verwenden können, falls Sie ihn später freigeben müssen ().

void trim(char * s) {
    char * p = s;
    int l = strlen(p);

    while(isspace(p[l - 1])) p[--l] = 0;
    while(* p && isspace(* p)) ++p, --l;

    memmove(s, p, l + 1);
}   

Diese Version erstellt eine Kopie der Zeichenfolge mit strndup (), anstatt sie an Ort und Stelle zu bearbeiten. strndup () erfordert _GNU_SOURCE, daher müssen Sie möglicherweise Ihr eigenes strndup () mit malloc () und strncpy () erstellen.

char * trim(char * s) {
    int l = strlen(s);

    while(isspace(s[l - 1])) --l;
    while(* s && isspace(* s)) ++s, --l;

    return strndup(s, l);
}
jkramer
quelle
4
trim()Invokes UB wenn sist ""als der erste isspace()Anruf würde isspace(p[-1])und p[-1]verweist nicht unbedingt eine rechtliche Lage.
chux - Monica
1
Sie müssen das Argument für isspaceto unsigned charumwandeln, sonst rufen Sie undefiniertes Verhalten auf.
Roland Illig
1
sollte hinzufügen if(l==0)return;, um
stromlose
11

Hier ist meine C-Minibibliothek zum Trimmen von links, rechts, beiden, an Ort und Stelle und getrennt sowie zum Trimmen einer Reihe angegebener Zeichen (oder standardmäßig Leerzeichen).

Inhalt von strlib.h:

#ifndef STRLIB_H_
#define STRLIB_H_ 1
enum strtrim_mode_t {
    STRLIB_MODE_ALL       = 0, 
    STRLIB_MODE_RIGHT     = 0x01, 
    STRLIB_MODE_LEFT      = 0x02, 
    STRLIB_MODE_BOTH      = 0x03
};

char *strcpytrim(char *d, // destination
                 char *s, // source
                 int mode,
                 char *delim
                 );

char *strtriml(char *d, char *s);
char *strtrimr(char *d, char *s);
char *strtrim(char *d, char *s); 
char *strkill(char *d, char *s);

char *triml(char *s);
char *trimr(char *s);
char *trim(char *s);
char *kill(char *s);
#endif

Inhalt von strlib.c:

#include <strlib.h>

char *strcpytrim(char *d, // destination
                 char *s, // source
                 int mode,
                 char *delim
                 ) {
    char *o = d; // save orig
    char *e = 0; // end space ptr.
    char dtab[256] = {0};
    if (!s || !d) return 0;

    if (!delim) delim = " \t\n\f";
    while (*delim) 
        dtab[*delim++] = 1;

    while ( (*d = *s++) != 0 ) { 
        if (!dtab[0xFF & (unsigned int)*d]) { // Not a match char
            e = 0;       // Reset end pointer
        } else {
            if (!e) e = d;  // Found first match.

            if ( mode == STRLIB_MODE_ALL || ((mode != STRLIB_MODE_RIGHT) && (d == o)) ) 
                continue;
        }
        d++;
    }
    if (mode != STRLIB_MODE_LEFT && e) { // for everything but trim_left, delete trailing matches.
        *e = 0;
    }
    return o;
}

// perhaps these could be inlined in strlib.h
char *strtriml(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_LEFT, 0); }
char *strtrimr(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_RIGHT, 0); }
char *strtrim(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_BOTH, 0); }
char *strkill(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_ALL, 0); }

char *triml(char *s) { return strcpytrim(s, s, STRLIB_MODE_LEFT, 0); }
char *trimr(char *s) { return strcpytrim(s, s, STRLIB_MODE_RIGHT, 0); }
char *trim(char *s) { return strcpytrim(s, s, STRLIB_MODE_BOTH, 0); }
char *kill(char *s) { return strcpytrim(s, s, STRLIB_MODE_ALL, 0); }

Die eine Hauptroutine macht alles. Es wird an Ort und Stelle abgeschnitten , wenn src == dst , andernfalls funktioniert es wie die strcpyRoutinen. Es schneidet eine Reihe von Zeichen ab, die in der Zeichenfolgenbegrenzung angegeben sindoder Leerzeichen, wenn null. Es schneidet links, rechts, beide und alle (wie tr). Es steckt nicht viel dahinter und es wird nur einmal über die Zeichenfolge iteriert. Einige Leute könnten sich beschweren, dass das Trimmen rechts links beginnt, es ist jedoch keine Strlen erforderlich, die sowieso links beginnt. (Auf die eine oder andere Weise muss man das Ende der Zeichenfolge erreichen, um die richtigen Zuschnitte zu erzielen, damit Sie die Arbeit genauso gut erledigen können, wie Sie möchten.) Es kann Argumente für Pipelining- und Cache-Größen und dergleichen geben - wer weiß . Da die Lösung von links nach rechts funktioniert und nur einmal iteriert, kann sie auch für Streams erweitert werden. Einschränkungen: Es funktioniert nicht mit Unicode- Zeichenfolgen.

Schießt den Mond
quelle
2
Ich habe dies positiv bewertet und ich weiß, dass es alt ist, aber ich denke, es gibt einen Fehler. dtab[*d]wird nicht umgewandelt *d, unsigned intbevor es als Array-Index verwendet wird. Auf einem System mit signiertem Zeichen wird dies gelesen, dtab[-127]was zu Fehlern und möglicherweise zum Absturz führen wird.
Zan Lynx
2
Mögliches undefiniertes Verhalten, dtab[*delim++]da charIndexwerte umgewandelt werden müssen unsigned char. Der Code geht von 8 Bit aus char. delimsollte als deklariert werden const char *. dtab[0xFF & (unsigned int)*d]würde klarer als dtab[(unsigned char)*d]. Der Code funktioniert mit UTF-8-codierten Zeichenfolgen, entfernt jedoch keine Nicht-ASCII-Abstandssequenzen.
Chqrlie
@ Michael-Plainer, das sieht interessant aus. Warum testest du es nicht und stellst es auf GitHub?
Daisuke Aramaki
8

Hier ist mein Versuch einer einfachen, aber korrekten Trimmfunktion an Ort und Stelle.

void trim(char *str)
{
    int i;
    int begin = 0;
    int end = strlen(str) - 1;

    while (isspace((unsigned char) str[begin]))
        begin++;

    while ((end >= begin) && isspace((unsigned char) str[end]))
        end--;

    // Shift all characters back to the start of the string array.
    for (i = begin; i <= end; i++)
        str[i - begin] = str[i];

    str[i - begin] = '\0'; // Null terminate string.
}
schweizerisch
quelle
2
Schlagen Sie vor, zu ändern, while ((end >= begin) && isspace(str[end]))um UB zu verhindern, wenn str is "" . Prevents str [-1] `.
chux - Monica
Übrigens muss ich dies in str [i - begin + 1] ändern, um zu funktionieren
truongnm
1
Sie müssen das Argument für isspaceto unsigned charumwandeln, sonst rufen Sie undefiniertes Verhalten auf.
Roland Illig
@ RolandIllig, warum sollte es undefiniertes Verhalten sein? Die Funktion soll mit Zeichen arbeiten.
Wovano
@wovano Nein, ist es nicht. Die Funktionen von <ctype.h>sollen mit Ints arbeiten, die entweder unsigned charoder den speziellen Wert darstellen EOF. Siehe stackoverflow.com/q/7131026/225757 .
Roland Illig
8

Spät zur Trimmparty

Funktionen:
1. Schneiden Sie den Anfang schnell ab, wie bei einer Reihe anderer Antworten.
2. Trimmen Sie nach dem Ende mit nur 1 Test pro Schleife nach rechts. Wie @ jfm3, funktioniert jedoch für eine reine Leerzeichenfolge.
3. Um undefiniertes Verhalten zu vermeiden, wenn chares sich um eine Signatur handelt char, setzen Sie *sauf unsigned char.

Zeichenbehandlung "In allen Fällen ist das Argument ein int, dessen Wert als unsigned charoder darstellbar sein soll oder dem Wert des Makros entspricht EOF. Wenn das Argument einen anderen Wert hat, ist das Verhalten undefiniert." C11 §7.4 1

#include <ctype.h>

// Return a pointer to the trimmed string
char *string_trim_inplace(char *s) {
  while (isspace((unsigned char) *s)) s++;
  if (*s) {
    char *p = s;
    while (*p) p++;
    while (isspace((unsigned char) *(--p)));
    p[1] = '\0';
  }

  // If desired, shift the trimmed string

  return s;
}

@chqrlie kommentierte, dass das oben Gesagte die zugeschnittene Zeichenfolge nicht verschiebt. Um das zu tun ....

// Return a pointer to the (shifted) trimmed string
char *string_trim_inplace(char *s) {
  char *original = s;
  size_t len = 0;

  while (isspace((unsigned char) *s)) {
    s++;
  } 
  if (*s) {
    char *p = s;
    while (*p) p++;
    while (isspace((unsigned char) *(--p)));
    p[1] = '\0';
    // len = (size_t) (p - s);   // older errant code
    len = (size_t) (p - s + 1);  // Thanks to @theriver
  }

  return (s == original) ? s : memmove(original, s, len + 1);
}
chux - Monica wieder einsetzen
quelle
2
Yay, endlich jemand, der über das undefinierte Verhalten von ctype Bescheid weiß.
Roland Illig
1
@chux Ich denke es sollte len = (size_t) (ps) +1 sein; Andernfalls überschneidet sich der letzte Buchstabe.
Theriver
4

Hier ist eine Lösung ähnlich der direkten Änderungsroutine von @ adam-rosenfields, ohne jedoch unnötig auf strlen () zurückzugreifen. Wie bei @jkramer wird die Zeichenfolge im Puffer nach links angepasst, sodass Sie denselben Zeiger freigeben können. Nicht optimal für große Zeichenfolgen, da memmove nicht verwendet wird. Enthält die ++ / - Operatoren, die @ jfm3 erwähnt. FCTX- basierte Unit-Tests enthalten.

#include <ctype.h>

void trim(char * const a)
{
    char *p = a, *q = a;
    while (isspace(*q))            ++q;
    while (*q)                     *p++ = *q++;
    *p = '\0';
    while (p > a && isspace(*--p)) *p = '\0';
}

/* See http://fctx.wildbearsoftware.com/ */
#include "fct.h"

FCT_BGN()
{
    FCT_QTEST_BGN(trim)
    {
        { char s[] = "";      trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "   ";   trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "\t";    trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "a";     trim(s); fct_chk_eq_str("a",   s); } // NOP
        { char s[] = "abc";   trim(s); fct_chk_eq_str("abc", s); } // NOP
        { char s[] = "  a";   trim(s); fct_chk_eq_str("a",   s); } // Leading
        { char s[] = "  a c"; trim(s); fct_chk_eq_str("a c", s); } // Leading
        { char s[] = "a  ";   trim(s); fct_chk_eq_str("a",   s); } // Trailing
        { char s[] = "a c  "; trim(s); fct_chk_eq_str("a c", s); } // Trailing
        { char s[] = " a ";   trim(s); fct_chk_eq_str("a",   s); } // Both
        { char s[] = " a c "; trim(s); fct_chk_eq_str("a c", s); } // Both

        // Villemoes pointed out an edge case that corrupted memory.  Thank you.
        // http://stackoverflow.com/questions/122616/#comment23332594_4505533
        {
          char s[] = "a     ";       // Buffer with whitespace before s + 2
          trim(s + 2);               // Trim "    " containing only whitespace
          fct_chk_eq_str("", s + 2); // Ensure correct result from the trim
          fct_chk_eq_str("a ", s);   // Ensure preceding buffer not mutated
        }

        // doukremt suggested I investigate this test case but
        // did not indicate the specific behavior that was objectionable.
        // http://stackoverflow.com/posts/comments/33571430
        {
          char s[] = "         foobar";  // Shifted across whitespace
          trim(s);                       // Trim
          fct_chk_eq_str("foobar", s);   // Leading string is correct

          // Here is what the algorithm produces:
          char r[16] = { 'f', 'o', 'o', 'b', 'a', 'r', '\0', ' ',                     
                         ' ', 'f', 'o', 'o', 'b', 'a', 'r', '\0'};
          fct_chk_eq_int(0, memcmp(s, r, sizeof(s)));
        }
    }
    FCT_QTEST_END();
}
FCT_END();
Rhys Ulerich
quelle
Diese Lösung ist geradezu gefährlich! Wenn die ursprüngliche Zeichenfolge keine Nicht-Leerzeichen enthält, überschreibt die letzte Trimmzeile glücklicherweise alles, was vor a steht, wenn diese Bytes zufällig Leerzeichen enthalten. Kompilieren Sie dies ohne Optimierungen und sehen Sie, was mit y passiert: unsigned x = 0x20202020; char s [4] = ""; vorzeichenloses y = 0x20202020; printf ("& x, & s, & y =% p,% p,% p \ n", & x, & s, & y); printf ("x, [s], y =% 08x, [% s],% 08x \ n", x, s, y); trim_whitespace (s); printf ("x, [s], y =% 08x, [% s],% 08x \ n", x, s, y);
Villemoes
@Villemoes, danke für den Fehlerbericht. Ich habe die Logik aktualisiert, um zu vermeiden, dass die linke Seite des Puffers verlassen wird, wenn die Zeichenfolge nur Leerzeichen enthält. Behebt diese neue Version Ihre Bedenken?
Rhys Ulerich
Sprachanwälte würden Sie wahrscheinlich für den bloßen Gedanken anschreien, darüber zu spekulieren, einen Zeiger auf das Zeichen vor dem Zeichen zu erstellen, auf das 'a' zeigt (was Ihr '--p' tun wird). In der realen Welt geht es dir wahrscheinlich gut. Sie können aber auch einfach '> =' in '>' ändern und das Dekrement von p in 'isspace (* - p)' verschieben.
Villemoes
Ich denke, die Anwälte wären in Ordnung, da sie nur eine Adresse vergleichen, ohne sie zu berühren, aber ich mag auch Ihren Vorschlag zur Dekrementierung. Ich habe es entsprechend aktualisiert. Vielen Dank.
Rhys Ulerich
1
Doukremt, ist Ihre Sorge, dass der gesamte Puffer nach Foobar nicht mit Nullen gefüllt ist? Wenn ja, wäre es viel hilfreicher, wenn Sie dies explizit sagen würden, anstatt vage Steine ​​zu werfen.
Rhys Ulerich
3

Eine andere, bei der eine Zeile den eigentlichen Job macht:

#include <stdio.h>

int main()
{
   const char *target = "   haha   ";
   char buf[256];
   sscanf(target, "%s", buf); // Trimming on both sides occurs here
   printf("<%s>\n", buf);
}
Daniel
quelle
1
Gute Idee, scanf zu verwenden; aber sein Wille funktioniert nur mit einem einzelnen Wort, das möglicherweise nicht dem entspricht, was das OP wollte (dh das Trimmen von "abc" sollte wahrscheinlich zu "ab c" führen, während Ihr einzelnes scanf nur zu "a" führt). Wir brauchen also eine Schleife und einen Zähler für die übersprungenen Zeichen mit dem %nKonvertierungsspezifizierer, und am Ende ist es leider einfacher, dies von Hand zu tun.
Peter - Monica am
Sehr nützlich, wenn Sie möchten, dass das erste Wort der Zeichenfolge keine Anfangszeichen enthält.
J ... S
3

Die meisten dieser Antworten haben mir nicht gefallen, weil sie eine oder mehrere der folgenden Antworten gegeben haben ...

  1. Es wurde ein anderer Zeiger in der Zeichenfolge des ursprünglichen Zeigers zurückgegeben (eine Art Schmerz, zwei verschiedene Zeiger auf dasselbe zu jonglieren).
  2. Verwenden Sie unentgeltlich Dinge wie strlen () , die den gesamten String vorab iterieren.
  3. Verwendete nicht portable betriebssystemspezifische lib-Funktionen.
  4. Zurückgescannt.
  5. Verwendeter Vergleich mit '' anstelle von isspace (), damit TAB / CR / LF erhalten bleibt.
  6. Verschwendeter Speicher mit großen statischen Puffern.
  7. Verschwendete Zyklen mit kostenintensiven Funktionen wie sscanf / sprintf .

Hier ist meine Version:

void fnStrTrimInPlace(char *szWrite) {

    const char *szWriteOrig = szWrite;
    char       *szLastSpace = szWrite, *szRead = szWrite;
    int        bNotSpace;

    // SHIFT STRING, STARTING AT FIRST NON-SPACE CHAR, LEFTMOST
    while( *szRead != '\0' ) {

        bNotSpace = !isspace((unsigned char)(*szRead));

        if( (szWrite != szWriteOrig) || bNotSpace ) {

            *szWrite = *szRead;
            szWrite++;

            // TRACK POINTER TO LAST NON-SPACE
            if( bNotSpace )
                szLastSpace = szWrite;
        }

        szRead++;
    }

    // TERMINATE AFTER LAST NON-SPACE (OR BEGINNING IF THERE WAS NO NON-SPACE)
    *szLastSpace = '\0';
}
Jason Stewart
quelle
2
Sie müssen das Argument für isspaceto unsigned charumwandeln, sonst rufen Sie undefiniertes Verhalten auf.
Roland Illig
Da es sich bei dieser Antwort um "Verschwendete Zyklen" handelt, beachten Sie, dass Code unnötigerweise den gesamten Stich kopiert, wenn kein Platz vorhanden ist. Eine Führung while (isspace((unsigned char) *szWrite)) szWrite++;würde das verhindern. Der Code kopiert auch den gesamten nachgestellten Leerraum.
chux
@chux Diese Implementierung mutiert direkt mit separaten Lese- und Schreibzeigern (im Gegensatz zur Rückgabe eines neuen Zeigers an einer anderen Position), sodass der Vorschlag, szWrite zum ersten Nicht-Leerzeichen in Zeile 1 zu springen, den führenden Leerzeichen in belassen würde die ursprüngliche Zeichenfolge.
Jason Stewart
@chux, Sie haben Recht, dass es nachgestellte Leerzeichen kopiert (bevor nach dem letzten Nicht-Leerzeichen eine Null hinzugefügt wird), aber das ist der Preis, den ich gezahlt habe, um ein Vorab-Scannen der Zeichenfolge zu vermeiden. Bei bescheidenen Mengen an nachfolgendem WS ist es billiger, nur die Bytes zu kopieren, als die gesamte Zeichenfolge vorab nach dem letzten Nicht-WS-Zeichen zu durchsuchen. Bei großen Mengen an nachfolgendem WS wäre das Vorscannen wahrscheinlich die Reduzierung der Schreibvorgänge wert.
Jason Stewart
@chux, für die Situation "Kopien, wenn kein Leerzeichen vorhanden ist" würde eine Ausführung nur dann ausgeführt, *szWrite = *szReadwenn die Zeiger nicht gleich sind, die Schreibvorgänge in diesem Fall überspringen, aber dann haben wir einen weiteren Vergleich / Zweig hinzugefügt. Mit moderner CPU / MMU / BP habe ich keine Ahnung, ob diese Prüfung ein Verlust oder ein Gewinn wäre. Mit einfacheren Prozessoren und Speicherarchitekturen ist es billiger, nur die Kopie zu erstellen und den Vergleich zu überspringen.
Jason Stewart
2

Sehr spät zur Party ...

Single-Pass-Forward-Scan-Lösung ohne Backtracking. Jedes Zeichen in der Quellzeichenfolge wird genau einmal zweimal getestet . (Es sollte also schneller sein als die meisten anderen Lösungen hier, insbesondere wenn die Quellzeichenfolge viele nachgestellte Leerzeichen enthält.)

Dies umfasst zwei Lösungen, eine zum Kopieren und Trimmen einer Quellzeichenfolge in eine andere Zielzeichenfolge und die andere zum Trimmen der Quellzeichenfolge an Ort und Stelle. Beide Funktionen verwenden denselben Code.

Die (veränderbare) Zeichenfolge wird an Ort und Stelle verschoben, sodass der ursprüngliche Zeiger darauf unverändert bleibt.

#include <stddef.h>
#include <ctype.h>

char * trim2(char *d, const char *s)
{
    // Sanity checks
    if (s == NULL  ||  d == NULL)
        return NULL;

    // Skip leading spaces        
    const unsigned char * p = (const unsigned char *)s;
    while (isspace(*p))
        p++;

    // Copy the string
    unsigned char * dst = (unsigned char *)d;   // d and s can be the same
    unsigned char * end = dst;
    while (*p != '\0')
    {
        if (!isspace(*dst++ = *p++))
            end = dst;
    }

    // Truncate trailing spaces
    *end = '\0';
    return d;
}

char * trim(char *s)
{
    return trim2(s, s);
}
David R Tribble
quelle
1
Jedes Zeichen in der Quellzeichenfolge wird genau einmal getestet : Nicht wirklich, die meisten Zeichen in der Quellzeichenfolge werden zweimal getestet: im Vergleich zu '\0'und dann mit isspace(). Es scheint verschwenderisch, alle Charaktere mit zu testen isspace(). Das Zurückverfolgen vom Ende der Zeichenfolge sollte für nicht pathologische Fälle effizienter sein.
Chqrlie
@chqrlie - Ja, jedes Zeichen wird zweimal getestet. Ich würde gerne sehen, dass dieser Code tatsächlich getestet wird, insbesondere bei Zeichenfolgen mit vielen nachgestellten Leerzeichen im Vergleich zu anderen Algorithmen hier.
David R Tribble
trim()OK. Eckfall: trim2(char *d, const char *s)hat Probleme bei d,sÜberlappung und s < d.
chux
@chux - Wie soll man trim()sich in diesem Eckfall verhalten? Sie möchten eine Zeichenfolge zuschneiden und in den Speicher kopieren, der von der Zeichenfolge selbst belegt wird. Im Gegensatz dazu memmove()muss hierfür die Länge der Quellzeichenfolge bestimmt werden, bevor der Schnitt selbst ausgeführt wird. Dazu muss die gesamte Zeichenfolge ein zusätzliches Mal gescannt werden. Es ist besser, eine andere rtrim2()Funktion zu schreiben , die die Quelle rückwärts in das Ziel kopiert und wahrscheinlich ein zusätzliches Argument für die Länge der Quellzeichenfolge verwendet.
David R Tribble
1

Ich bin mir nicht sicher, was Sie für "schmerzlos" halten.

C-Saiten sind ziemlich schmerzhaft. Wir können die erste Nicht-Leerzeichen-Zeichenposition trivial finden:

while (isspace (* p)) p ++;

Wir können die letzte Nicht-Leerzeichen-Charakterposition mit zwei ähnlichen trivialen Zügen finden:

während (* q) q ++;
do {q--; } while (isspace (* q));

(Ich habe Ihnen den Schmerz erspart, die Operatoren *und ++gleichzeitig zu verwenden.)

Die Frage ist nun, was machst du damit? Der vorliegende Datentyp ist nicht wirklich eine große, robuste Zusammenfassung String, über die man leicht nachdenken kann, sondern kaum mehr als ein Array von Speicherbytes. Ohne einen robusten Datentyp ist es unmöglich, eine Funktion zu schreiben, die die gleiche chompFunktion wie PHperytonby hat . Was würde eine solche Funktion in C zurückgeben?

jfm3
quelle
Dies funktioniert gut, es sei denn, die Zeichenfolge besteht aus allen Leerzeichen. Benötigen Sie eine einmalige Überprüfung vor, um do { q--; } ...zu wissen *q != 0.
chux
1

Verwenden Sie eine Zeichenfolgenbibliothek , zum Beispiel:

Ustr *s1 = USTR1(\7, " 12345 ");

ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));

... wie Sie sagen, dies ist ein "häufiges" Problem, ja, Sie müssen ein #include oder so einfügen und es ist nicht in libc enthalten, aber erfinden Sie nicht Ihren eigenen Hack-Job, indem Sie zufällige Zeiger speichern und size_t's nur dazu führen Puffer läuft über.

James Antill
quelle
1

Um dieses Wachstum aufrechtzuerhalten, gibt es noch eine Option mit einer modifizierbaren Zeichenfolge:

void trimString(char *string)
{
    size_t i = 0, j = strlen(string);
    while (j > 0 && isspace((unsigned char)string[j - 1])) string[--j] = '\0';
    while (isspace((unsigned char)string[i])) i++;
    if (i > 0) memmove(string, string + i, j - i + 1);
}
wallek876
quelle
1
strlen()Gibt a zurück size_t, das den Bereich von überschreiten kann int. Leerzeichen sind nicht auf das Leerzeichen beschränkt. Schließlich, aber am wichtigsten: Undefiniertes Verhalten, strcpy(string, string + i * sizeof(char));da sich Quell- und Zielarrays überlappen. Verwenden Sie memmove()anstelle von strcpy().
Chqrlie
@chqrlie Sie haben Recht, nur Ihre Vorschläge enthalten. Ich verstehe, dass das Kopieren, wenn sich Quelle und Ziel überlappen, undefiniertes Verhalten verursachen kann, möchte aber nur darauf hinweisen, dass dies in diesem speziellen Fall kein Problem verursachen sollte, da wir immer von einer späteren Speicherposition zum Anfang kopieren werden. Danke für die Rückmeldung.
Wallek876
1
Es spielt keine Rolle, wie sich die Quell- und Ziel-Arrays überlappen, es handelt sich um undefiniertes Verhalten. Verlassen Sie sich nicht auf die Annahme, dass das Kopieren byteweise an zunehmenden Adressen erfolgen kann. Außerdem habe ich vergessen zu erwähnen, dass while (isspace((int)string[i])) string[i--] = '\0';die Schleife möglicherweise über den Anfang der Zeichenfolge hinausgeht. Sie sollten diese Schleife mit den vorherigen und folgenden Zeilen kombinieren und schreibenwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
chqrlie
@chqrlie guter Punkt, eine Zeichenfolge mit allen Leerzeichen hätte dazu geführt, dass eine Schleife über den Anfang hinausging, daran dachte ich nicht.
Wallek876
Eigentlich war mein Vorschlag falsch, da er endnicht auf das nachfolgende Null-Byte zeigte und Sie end = ++i;immer noch ein Problem mit Zeichenfolgen hatten, die alle Leerzeichen enthielten. Ich habe gerade den Code repariert.
Chqrlie
1

Ich weiß, dass es viele Antworten gibt, aber ich poste meine Antwort hier, um zu sehen, ob meine Lösung gut genug ist.

// Trims leading whitespace chars in left `str`, then copy at almost `n - 1` chars
// into the `out` buffer in which copying might stop when the first '\0' occurs, 
// and finally append '\0' to the position of the last non-trailing whitespace char.
// Reture the length the trimed string which '\0' is not count in like strlen().
size_t trim(char *out, size_t n, const char *str)
{
    // do nothing
    if(n == 0) return 0;    

    // ptr stop at the first non-leading space char
    while(isspace(*str)) str++;    

    if(*str == '\0') {
        out[0] = '\0';
        return 0;
    }    

    size_t i = 0;    

    // copy char to out until '\0' or i == n - 1
    for(i = 0; i < n - 1 && *str != '\0'; i++){
        out[i] = *str++;
    }    

    // deal with the trailing space
    while(isspace(out[--i]));    

    out[++i] = '\0';
    return i;
}
Ekeyme Mo.
quelle
1
Hinweis: isspace(*str)UB wann *str < 0.
chux - Wiedereinstellung Monica
1
Die Verwendung von size_t nist gut, aber die Schnittstelle informiert den Anrufer in keiner Weise, wenn ner zu klein für eine vollständig zugeschnittene Zeichenfolge ist. Betrachten Sietrim(out, 12, "delete data not")
chux
1

Der einfachste Weg, führende Leerzeichen in einer Zeichenfolge zu überspringen, ist, imho,

#include <stdio.h>

int main()
{
char *foo="     teststring      ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
    return 0;
}
Zibri
quelle
1
Dies funktioniert nicht für Zeichenfolgen mit Leerzeichen in der Mitte, z " foo bar ".
David R Tribble
1

Ok, das ist meine Sicht auf die Frage. Ich glaube, es ist die prägnanteste Lösung, die den vorhandenen String modifiziert ( freefunktioniert) und UB vermeidet. Für kleine Zeichenfolgen ist es wahrscheinlich schneller als eine Lösung mit memmove.

void stripWS_LT(char *str)
{
    char *a = str, *b = str;
    while (isspace((unsigned char)*a)) a++;
    while (*b = *a++)  b++;
    while (b > str && isspace((unsigned char)*--b)) *b = 0;
}
poby
quelle
Der b > strTest wird nur einmal benötigt. *b = 0;nur einmal benötigt.
chux - Wiedereinstellung Monica
1
#include <ctype.h>
#include <string.h>

char *trim_space(char *in)
{
    char *out = NULL;
    int len;
    if (in) {
        len = strlen(in);
        while(len && isspace(in[len - 1])) --len;
        while(len && *in && isspace(*in)) ++in, --len;
        if (len) {
            out = strndup(in, len);
        }
    }
    return out;
}

isspace hilft, alle Leerzeichen zu kürzen.

  • Führen Sie eine erste Schleife aus, um vom letzten Byte nach Leerzeichen zu suchen und die Längenvariable zu reduzieren
  • Führen Sie eine zweite Schleife aus, um vom ersten Byte auf Leerzeichen zu prüfen und die Längenvariable zu reduzieren und den Zeichenzeiger zu erhöhen.
  • Wenn die Längenvariable größer als 0 ist, strnduperstellen Sie schließlich einen neuen Zeichenfolgenpuffer, indem Sie Leerzeichen ausschließen.
Rashok
quelle
Nur ein kleiner Trottel, strndup()ist nicht Teil des C-Standards, sondern nur Posix. Da es jedoch recht einfach zu implementieren ist, ist es keine große Sache.
Patrick Schlüter
trim_space("")kehrt zurück NULL. Ich würde einen Zeiger auf erwarten "". int len;sollte sein size_t len;. isspace(in[len - 1])UB wann in[len - 1] < 0.
chux
Ein erstes while (isspace((unsigned char) *in) in++;vorher len = strlen(in);wäre effizienter als das späterewhile(len && *in && isspace(*in)) ++in, --len;
chux - Reinstate Monica
0

Persönlich würde ich meine eigenen rollen. Sie können strtok verwenden, müssen jedoch darauf achten (insbesondere, wenn Sie führende Zeichen entfernen), dass Sie wissen, welcher Speicher was ist.

Das Entfernen von nachgestellten Leerzeichen ist einfach und ziemlich sicher, da Sie einfach eine 0 über das letzte Leerzeichen setzen und vom Ende zurückzählen können. Führende Räume loszuwerden bedeutet, Dinge zu bewegen. Wenn Sie es an Ort und Stelle tun möchten (wahrscheinlich sinnvoll), können Sie einfach alles um einen Charakter zurückschieben, bis kein führendes Leerzeichen mehr vorhanden ist. Um effizienter zu sein, können Sie auch den Index des ersten Nicht-Leerzeichens finden und alles um diese Zahl zurückschieben. Oder Sie können einfach einen Zeiger auf das erste Nicht-Leerzeichen verwenden (aber dann müssen Sie genauso vorsichtig sein wie bei strtok).

Ben
quelle
4
strtok ist im Allgemeinen kein sehr gutes Werkzeug - nicht zuletzt, weil es nicht wieder eingeführt wird. Wenn Sie in einer einzelnen Funktion bleiben, kann diese sicher verwendet werden. Wenn jedoch die Möglichkeit besteht, dass Threads oder andere Funktionen aufgerufen werden, die möglicherweise selbst strtok verwenden, sind Sie in Schwierigkeiten.
Jonathan Leffler
0
#include "stdafx.h"
#include "malloc.h"
#include "string.h"

int main(int argc, char* argv[])
{

  char *ptr = (char*)malloc(sizeof(char)*30);
  strcpy(ptr,"            Hel  lo    wo           rl   d G    eo rocks!!!    by shahil    sucks b i          g       tim           e");

  int i = 0, j = 0;

  while(ptr[j]!='\0')
  {

      if(ptr[j] == ' ' )
      {
          j++;
          ptr[i] = ptr[j];
      }
      else
      {
          i++;
          j++;
          ptr[i] = ptr[j];
      }
  }


  printf("\noutput-%s\n",ptr);
        return 0;
}
Balkrishna Talele
quelle
3
Das brachte mich zum Lachen, weil ich dachte, Dreamlax hätte die Testzeichenfolge so bearbeitet, dass sie "sucks big time" enthält. Nee. Der ursprüngliche Autor ist nur ehrlich.
James Morris
1
Verwenden Sie diesen Code nicht. Es entsteht ein Pufferüberlauf.
Roland Illig
0

Ein bisschen spät zum Spiel, aber ich werde meine Routinen in den Kampf werfen. Sie sind wahrscheinlich nicht die absolut effizientesten, aber ich glaube, sie sind korrekt und einfach (mit rtrim()Druck auf die Komplexität):

#include <ctype.h>
#include <string.h>

/*
    Public domain implementations of in-place string trim functions

    Michael Burr
    [email protected]
    2010
*/

char* ltrim(char* s) 
{
    char* newstart = s;

    while (isspace( *newstart)) {
        ++newstart;
    }

    // newstart points to first non-whitespace char (which might be '\0')
    memmove( s, newstart, strlen( newstart) + 1); // don't forget to move the '\0' terminator

    return s;
}


char* rtrim( char* s)
{
    char* end = s + strlen( s);

    // find the last non-whitespace character
    while ((end != s) && isspace( *(end-1))) {
            --end;
    }

    // at this point either (end == s) and s is either empty or all whitespace
    //      so it needs to be made empty, or
    //      end points just past the last non-whitespace character (it might point
    //      at the '\0' terminator, in which case there's no problem writing
    //      another there).    
    *end = '\0';

    return s;
}

char*  trim( char* s)
{
    return rtrim( ltrim( s));
}
Michael Burr
quelle
1
Sie sollten das charArgument in isspace()to (unsigned char)umwandeln, um undefiniertes Verhalten bei potenziell negativen Werten zu vermeiden. Vermeiden Sie es auch, die Zeichenfolge zu verschieben, ltrim()wenn dies nicht erforderlich ist.
Chqrlie
0

Die meisten der bisherigen Antworten lauten wie folgt:

  1. Zurückverfolgen am Ende der Zeichenfolge (dh das Ende der Zeichenfolge suchen und dann rückwärts suchen, bis ein Nicht-Leerzeichen gefunden wird) oder
  2. Rufen Sie strlen()zuerst an und machen Sie einen zweiten Durchgang durch die gesamte Zeichenfolge.

Diese Version macht nur einen Durchgang und geht nicht zurück. Daher kann es eine bessere Leistung als die anderen erzielen, allerdings nur dann, wenn es üblich ist, Hunderte von nachgestellten Leerzeichen zu haben (was bei der Ausgabe einer SQL-Abfrage nicht ungewöhnlich ist).

static char const WHITESPACE[] = " \t\n\r";

static void get_trim_bounds(char  const *s,
                            char const **firstWord,
                            char const **trailingSpace)
{
    char const *lastWord;
    *firstWord = lastWord = s + strspn(s, WHITESPACE);
    do
    {
        *trailingSpace = lastWord + strcspn(lastWord, WHITESPACE);
        lastWord = *trailingSpace + strspn(*trailingSpace, WHITESPACE);
    }
    while (*lastWord != '\0');
}

char *copy_trim(char const *s)
{
    char const *firstWord, *trailingSpace;
    char *result;
    size_t newLength;

    get_trim_bounds(s, &firstWord, &trailingSpace);
    newLength = trailingSpace - firstWord;

    result = malloc(newLength + 1);
    memcpy(result, firstWord, newLength);
    result[newLength] = '\0';
    return result;
}

void inplace_trim(char *s)
{
    char const *firstWord, *trailingSpace;
    size_t newLength;

    get_trim_bounds(s, &firstWord, &trailingSpace);
    newLength = trailingSpace - firstWord;

    memmove(s, firstWord, newLength);
    s[newLength] = '\0';
}
finnw
quelle
1
Wenn Sie mit der Leistung befasst sind, verwenden Sie nicht strspn()und strcspn()in einer engen Schleife. Dies ist sehr ineffizient und der Overhead wird den unbewiesenen Vorteil des einzelnen Vorwärtsdurchlaufs in den Schatten stellen. strlen()wird normalerweise inline mit sehr effizientem Code erweitert, was kein wirkliches Problem darstellt. Das Trimmen des Anfangs und des Endes der Zeichenfolge ist viel schneller als das Testen jedes Zeichens in der Zeichenfolge auf Weißheit, selbst im speziellen Fall von Zeichenfolgen mit sehr wenigen oder keinen nicht weißen Zeichen.
Chqrlie
0

Dies ist die kürzest mögliche Implementierung, die ich mir vorstellen kann:

static const char *WhiteSpace=" \n\r\t";
char* trim(char *t)
{
    char *e=t+(t!=NULL?strlen(t):0);               // *e initially points to end of string
    if (t==NULL) return;
    do --e; while (strchr(WhiteSpace, *e) && e>=t);  // Find last char that is not \r\n\t
    *(++e)=0;                                      // Null-terminate
    e=t+strspn (t,WhiteSpace);                           // Find first char that is not \t
    return e>t?memmove(t,e,strlen(e)+1):t;                  // memmove string contents and terminator
}
Michał Gawlas
quelle
1
Wie wäre es damit:char *trim(char *s) { char *p = s, *e = s + strlen(s); while (e > s && isspace((unsigned char)e[-1])) { *--e = '\0'; } while (isspace((unsigned char)*p)) { p++; } if (p > s) { memmove(s, p, e + 1 - p); } return s; }
Chqrlie
0

Diese Funktionen ändern den ursprünglichen Puffer. Wenn er dynamisch zugewiesen wird, kann der ursprüngliche Zeiger freigegeben werden.

#include <string.h>

void rstrip(char *string)
{
  int l;
  if (!string)
    return;
  l = strlen(string) - 1;
  while (isspace(string[l]) && l >= 0)
    string[l--] = 0;
}

void lstrip(char *string)
{
  int i, l;
  if (!string)
    return;
  l = strlen(string);
  while (isspace(string[(i = 0)]))
    while(i++ < l)
      string[i-1] = string[i];
}

void strip(char *string)
{
  lstrip(string);
  rstrip(string);
}
Telc
quelle
rstrip()Ruft undefiniertes Verhalten für die leere Zeichenfolge auf. lstrip()ist bei Zeichenfolgen mit einem langen Anfangsanteil an Leerzeichen unnötig langsam. isspace()sollte kein charArgument übergeben werden, da es undefiniertes Verhalten bei negativen Werten aufruft, die sich von unterscheiden EOF.
Chqrlie
0

Um meine Saiten von beiden Seiten zu trimmen, benutze ich den Oldie, aber den Gooody.

char *trimAll(char *strData)
{
  unsigned int L = strlen(strData);
  if(L > 0){ L--; }else{ return strData; }
  size_t S = 0, E = L;
  while((!(strData[S] > ' ') || !(strData[E] > ' ')) && (S >= 0) && (S <= L) && (E >= 0) && (E <= L))
  {
    if(strData[S] <= ' '){ S++; }
    if(strData[E] <= ' '){ E--; }
  }
  if(S == 0 && E == L){ return strData; } // Nothing to be done
  if((S >= 0) && (S <= L) && (E >= 0) && (E <= L)){
    L = E - S + 1;
    memmove(strData,&strData[S],L); strData[L] = '\0';
  }else{ strData[0] = '\0'; }
  return strData;
}
Деян Добромиров
quelle
Sie sollten size_tanstelle von verwenden unsigned int. Der Code hat viele redundante Tests und ruft undefiniertes Verhalten auf, strncpy(strData,&strData[S],L)da sich die Quell- und Ziel-Arrays überlappen. Verwenden Sie memmove()anstelle von strncpy().
Chqrlie
In diesem Fall ist es in Ordnung, da die Zieladresse immer einen kleineren Index als die Quelle hat, aber ja, memmove ist in der Tat besser.
11еян Добромиров
Nein, es ist nicht in Ordnung. Es spielt keine Rolle, wie sich die Quell- und Ziel-Arrays überlappen, es ruft undefiniertes Verhalten auf, da Sie nicht sicher Annahmen über die Implementierung der Bibliotheksfunktionen treffen können, die über ihre Standardspezifikation hinausgehen. Moderne Compiler neigen dazu, Situationen mit potenziell undefiniertem Verhalten unfair auszunutzen, auf Nummer sicher zu gehen und sich von UB fernzuhalten, und lassen Neulinge keine unsicheren Annahmen treffen.
Chqrlie
0

Ich füge nur Code hinzu, weil der bisher veröffentlichte Code nicht optimal erscheint (und ich noch keinen Repräsentanten habe, der einen Kommentar abgeben kann.)

void inplace_trim(char* s)
{
    int start, end = strlen(s);
    for (start = 0; isspace(s[start]); ++start) {}
    if (s[start]) {
        while (end > 0 && isspace(s[end-1]))
            --end;
        memmove(s, &s[start], end - start);
    }
    s[end - start] = '\0';
}

char* copy_trim(const char* s)
{
    int start, end;
    for (start = 0; isspace(s[start]); ++start) {}
    for (end = strlen(s); end > 0 && isspace(s[end-1]); --end) {}
    return strndup(s + start, end - start);
}

strndup()ist eine GNU-Erweiterung. Wenn Sie es nicht haben oder etwas Äquivalentes, rollen Sie Ihr eigenes. Beispielsweise:

r = strdup(s + start);
r[end-start] = '\0';
sfink
quelle
1
isspace(0)Wird als falsch definiert, können Sie beide Funktionen vereinfachen. Bewegen Sie auch das memmove()Innere des ifBlocks.
Chqrlie
0

Hier verwende ich die dynamische Speicherzuordnung, um die Eingabezeichenfolge auf die Funktion trimStr zu trimmen. Zunächst ermitteln wir, wie viele nicht leere Zeichen in der Eingabezeichenfolge vorhanden sind. Dann weisen wir ein Zeichenarray mit dieser Größe zu und kümmern uns um das nullterminierte Zeichen. Wenn wir diese Funktion verwenden, müssen wir den Speicher innerhalb der Hauptfunktion freigeben.

#include<stdio.h>
#include<stdlib.h>

char *trimStr(char *str){
char *tmp = str;
printf("input string %s\n",str);
int nc = 0;

while(*tmp!='\0'){
  if (*tmp != ' '){
  nc++;
 }
 tmp++;
}
printf("total nonempty characters are %d\n",nc);
char *trim = NULL;

trim = malloc(sizeof(char)*(nc+1));
if (trim == NULL) return NULL;
tmp = str;
int ne = 0;

while(*tmp!='\0'){
  if (*tmp != ' '){
     trim[ne] = *tmp;
   ne++;
 }
 tmp++;
}
trim[nc] = '\0';

printf("trimmed string is %s\n",trim);

return trim; 
 }


int main(void){

char str[] = " s ta ck ove r fl o w  ";

char *trim = trimStr(str);

if (trim != NULL )free(trim);

return 0;
}
saeed_falahat
quelle
0

Hier ist, wie ich es mache. Die Zeichenfolge wird an Ort und Stelle gekürzt, sodass Sie sich keine Sorgen mehr machen müssen, wenn Sie die Rückgabe einer zurückgegebenen Zeichenfolge aufheben oder den Zeiger auf eine zugewiesene Zeichenfolge verlieren. Es ist vielleicht nicht die kürzest mögliche Antwort, aber es sollte den meisten Lesern klar sein.

#include <ctype.h>
#include <string.h>
void trim_str(char *s)
{
    const size_t s_len = strlen(s);

    int i;
    for (i = 0; i < s_len; i++)
    {
        if (!isspace( (unsigned char) s[i] )) break;
    }

    if (i == s_len)
    {
        // s is an empty string or contains only space characters

        s[0] = '\0';
    }
    else
    {
        // s contains non-space characters

        const char *non_space_beginning = s + i;

        char *non_space_ending = s + s_len - 1;
        while ( isspace( (unsigned char) *non_space_ending ) ) non_space_ending--;

        size_t trimmed_s_len = non_space_ending - non_space_beginning + 1;

        if (s != non_space_beginning)
        {
            // Non-space characters exist in the beginning of s

            memmove(s, non_space_beginning, trimmed_s_len);
        }

        s[trimmed_s_len] = '\0';
    }
}
Isaac To
quelle
Absolut klar für die Leser, aber strlen führt eine weitere Schleife durch .. :)
ingconti
0
char* strtrim(char* const str)
{
    if (str != nullptr)
    {
        char const* begin{ str };
        while (std::isspace(*begin))
        {
            ++begin;
        }

        auto end{ begin };
        auto scout{ begin };
        while (*scout != '\0')
        {
            if (!std::isspace(*scout++))
            {
                end = scout;
            }
        }

        auto /* std::ptrdiff_t */ const length{ end - begin };
        if (begin != str)
        {
            std::memmove(str, begin, length);
        }

        str[length] = '\0';
    }

    return str;
}
Mitch Laber
quelle
2
Während dieser Code die Frage möglicherweise beantwortet, würde die Bereitstellung eines zusätzlichen Kontexts darüber, wie und / oder warum das Problem gelöst wird, den langfristigen Wert der Antwort verbessern.
Nic3500