Jak w standardowy sposób przyciąć początkowe / końcowe białe znaki?

178

Czy istnieje czysta, najlepiej standardowa metoda przycinania wiodących i końcowych białych znaków z ciągu w C? Zrobiłbym własne, ale wydaje mi się, że jest to częsty problem z równie powszechnym rozwiązaniem.

coledot
źródło

Odpowiedzi:

164

Jeśli możesz zmodyfikować ciąg:

// 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;
}

Jeśli nie możesz zmodyfikować ciągu, możesz użyć zasadniczo tej samej metody:

// 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
źródło
6
Przepraszamy, pierwsza odpowiedź wcale nie jest dobra, chyba że nie przejmujesz się wyciekami pamięci. Masz teraz dwa zachodzące na siebie ciągi (oryginalny, z przyciętymi końcowymi spacjami i nowy). Tylko oryginalny ciąg może zostać zwolniony, ale jeśli to zrobisz, drugi wskazuje na zwolnioną pamięć.
David Nehme
7
@nvl: Nie ma przydzielonej pamięci, więc nie ma pamięci do zwolnienia.
Adam Rosenfield
15
@nvl: No. strjest zmienną lokalną, a zmiana jej nie zmienia przekazywanego pierwotnego wskaźnika. Wywołania funkcji w języku C są zawsze przekazywane przez wartość, nigdy nie są przekazywane przez odniesienie.
Adam Rosenfield
11
@Raj: Nie ma nic złego w zwracaniu adresu innego niż ten, który został przekazany. Nie ma tutaj wymogu, aby zwracana wartość była prawidłowym argumentem free()funkcji. Wręcz przeciwnie - zaprojektowałem to, aby uniknąć konieczności przydzielania pamięci dla wydajności. Jeśli przekazany adres został przydzielony dynamicznie, to wywołujący nadal jest odpowiedzialny za zwolnienie tej pamięci, a wywołujący musi upewnić się, że nie nadpisze tej wartości wartością zwróconą tutaj.
Adam Rosenfield
3
Musisz rzucić argument na isspaceto unsigned char, w przeciwnym razie wywołasz niezdefiniowane zachowanie.
Roland Illig,
37

Oto taki, który przesuwa ciąg na pierwszą pozycję twojego bufora. Możesz chcieć tego zachowania, aby po dynamicznym przydzieleniu ciągu nadal można było zwolnić go na tym samym wskaźniku, który zwraca funkcja trim ():

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;
}

Sprawdź poprawność:

#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;
}

Plik źródłowy to trim.c. Skompilowane za pomocą „cc -Wall trim.c -o trim”.

indyw
źródło
2
Musisz rzucić argument na isspaceto unsigned char, w przeciwnym razie wywołasz niezdefiniowane zachowanie.
Roland Illig
@RolandIllig: Dzięki, nigdy nie zdawałem sobie sprawy, że to konieczne. Naprawione.
indyw
@Simas: Dlaczego tak mówisz? Funkcja wywołuje, isspace()więc dlaczego miałaby istnieć różnica między " "i "\n"? Dodałem testy jednostkowe dla nowych linii i wydaje mi się to w porządku ... ideone.com/bbVmqo
indyw
1
@indiv uzyska dostęp do nieprawidłowego bloku pamięci po ręcznym przydzieleniu. Mianowicie ta linia: *(endp + 1) = '\0';. Przykładowy test odpowiedzi używa bufora 64, co pozwala uniknąć tego problemu.
Simas
1
@nolandda: Dzięki za szczegóły. Naprawiłem to i zaktualizowałem test, aby wykryć przepełnienie bufora, ponieważ w tej chwili nie mam dostępu do valgrind.
indyw
23

Moje rozwiązanie. Ciąg musi być zmienny. Zaletą nad niektórymi innymi rozwiązaniami jest to, że przesuwa część nieprzestrzenną na początek, dzięki czemu można nadal używać starego wskaźnika, na wypadek gdybyś musiał go później zwolnić ().

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);
}   

Ta wersja tworzy kopię ciągu za pomocą strndup () zamiast edytować go w miejscu. strndup () wymaga _GNU_SOURCE, więc być może będziesz musiał stworzyć własną strndup () za pomocą malloc () i strncpy ().

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
źródło
4
trim()wywołuje UB, jeśli sjest ""tak, jak isspace()byłoby to pierwsze wywołanie isspace(p[-1])i p[-1]niekoniecznie odnosi się do legalnej lokalizacji.
chux - Przywróć Monikę
1
Musisz rzucić argument na isspaceto unsigned char, w przeciwnym razie wywołasz niezdefiniowane zachowanie.
Roland Illig
1
należy dodać, if(l==0)return;aby uniknąć zerowej długości str
ch271828n
11

Oto moja mini biblioteka C do przycinania lewej, prawej, wszystkich, na miejscu i osobno oraz przycinania zestawu określonych znaków (lub domyślnie białych znaków).

zawartość 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

zawartość 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); }

Jedna główna rutyna to wszystko. Obcina w miejscu, jeśli src == dst , w przeciwnym razie działa jak strcpyprocedury. To przycina zestawu znaków określonego w ciągu delimlub spacje, jeśli null. Przycina w lewo, w prawo, oba i wszystkie (jak tr). Nie ma w tym wiele, a iteruje po ciągu tylko raz. Niektórzy ludzie mogą narzekać, że trymowanie po prawej zaczyna się po lewej stronie, jednak nie jest potrzebny strlen, który i tak zaczyna się po lewej stronie. (Tak czy inaczej, aby uzyskać prawidłowe przycinanie, musisz dotrzeć do końca łańcucha, więc równie dobrze możesz wykonać pracę na bieżąco). Mogą istnieć argumenty dotyczące przetwarzania potoków i rozmiarów pamięci podręcznej i tym podobne - kto wie . Ponieważ rozwiązanie działa od lewej do prawej i wykonuje iterację tylko raz, można je rozszerzyć, aby działało również na strumieniach. Ograniczenia: nie działa na ciągach znaków Unicode .

Strzela do Księżyca
źródło
2
Głosowałem za tym i wiem, że jest stary, ale myślę, że jest błąd. dtab[*d]nie rzutuje *dna unsigned intprzed użyciem go jako indeksu tablicy. W systemie z podpisanym char, spowoduje to odczytanie, do dtab[-127]którego spowoduje błędy i prawdopodobnie awarię.
Zan Lynx,
2
Potencjalne niezdefiniowane zachowanie jest włączone, dtab[*delim++]ponieważ charwartości indeksu muszą być rzutowane na unsigned char. Kod zakłada 8-bitowe char. delimnależy zadeklarować jako const char *. dtab[0xFF & (unsigned int)*d]będzie jaśniejsze jak dtab[(unsigned char)*d]. Kod działa na łańcuchach zakodowanych w UTF-8, ale nie usuwa sekwencji odstępów innych niż ASCII.
chqrlie
@ michael-plainer, wygląda to interesująco. Dlaczego go nie przetestujesz i nie umieścisz na GitHubie?
Daisuke Aramaki
9

Oto moja próba prostej, ale poprawnej funkcji przycinania na miejscu.

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.
}
szwajcarski
źródło
2
Zaproponuj zmianę na, while ((end >= begin) && isspace(str[end]))aby uniemożliwić UB, gdy str is "" . Prevents str [-1] ".
chux - Przywróć Monikę
Btw, muszę to zmienić na str [i - begin + 1], aby
działało
1
Musisz rzucić argument na isspaceto unsigned char, w przeciwnym razie wywołasz niezdefiniowane zachowanie.
Roland Illig,
@RolandIllig, dlaczego miałoby to być niezdefiniowane zachowanie? Funkcja przeznaczona jest do pracy ze znakami.
wovano
@wovano Nie, nie jest. Funkcje z <ctype.h>są przeznaczone do pracy z wartościami ints, które reprezentują unsigned charwartość specjalną lub jedną z nich EOF. Zobacz stackoverflow.com/q/7131026/225757 .
Roland Illig
8

Spóźniony na przyjęcie wykończeniowe

Cechy:
1. Szybko przytnij początek, tak jak w wielu innych odpowiedziach.
2. Po przejściu do końca, przycinanie prawej strony z tylko 1 testem na pętlę. Podobnie jak @ jfm3, ale działa dla całego ciągu znaków spacji)
3. Aby uniknąć niezdefiniowanego zachowania, gdy charjest znakiem char, rzutuj *sna unsigned char.

Obsługa znaków „We wszystkich przypadkach argumentem jest an int, którego wartość powinna być reprezentowana jako unsigned charwartość makra lub powinna być równa wartości makra EOF. Jeśli argument ma inną wartość, zachowanie jest nieokreślone”. 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 skomentował, że powyższe nie zmienia przyciętego ciągu. Aby to zrobić ...

// 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 - Przywróć Monikę
źródło
3
Tak, w końcu ktoś, kto wie o niezdefiniowanym typie zachowania.
Roland Illig
2
@chux Myślę, że powinno być len = (size_t) (ps) +1; w przeciwnym razie ostatnia litera zachodzi na siebie.
teriver
4

Oto rozwiązanie podobne do procedury modyfikacji w miejscu @ adam-rosenfields, ale bez niepotrzebnego uciekania się do strlen (). Podobnie jak @jkramer, ciąg jest korygowany w lewo w buforze, dzięki czemu można zwolnić ten sam wskaźnik. Nie jest optymalny dla dużych strun, ponieważ nie używa memmove. Obejmuje operatory ++ / - wymienione w @ jfm3. Zawiera testy jednostkowe oparte na FCTX .

#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
źródło
To rozwiązanie jest wręcz niebezpieczne! Jeśli oryginalny łańcuch nie zawiera żadnych znaków niebędących białymi znakami, ostatnia linia trim szczęśliwie nadpisuje wszystko, co poprzedza a, jeśli te bajty zawierają bajty „białych spacji”. Skompiluj to bez optymalizacji i zobacz, co stanie się z y: unsigned x = 0x20202020; char s [4] = ""; unsigned 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, dziękuję za zgłoszenie błędu. Zaktualizowałem logikę, aby uniknąć wychodzenia z lewej strony bufora, gdy ciąg zawiera tylko białe znaki. Czy ta nowa wersja rozwiązuje Twoje obawy?
Rhys Ulerich
Prawnicy językowi prawdopodobnie krzyczeliby na ciebie za samą myśl o spekulowaniu na temat stworzenia wskaźnika do znaku poprzedzającego znak „a”, na który wskazuje (co zrobi twoje „--p”). W prawdziwym świecie prawdopodobnie wszystko w porządku. Ale możesz też po prostu zmienić '> =' na '>' i przesunąć ułamek p do 'isspace (* - p)'.
Villemoes
Myślę, że prawnicy byliby w porządku, ponieważ to po prostu porównanie adresu bez jego dotykania, ale podoba mi się również twoja sugestia dotycząca dekrementu. Zaktualizowałem go odpowiednio. Dzięki.
Rhys Ulerich
1
doukremt, czy obawiasz się, że cały bufor po foobar nie jest wypełniony zerami? Jeśli tak, byłoby o wiele bardziej pomocne, gdybyś powiedział to wyraźnie, zamiast rzucać niejasnymi kamieniami.
Rhys Ulerich
3

Kolejny, z jedną linią wykonującą prawdziwą robotę:

#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
źródło
1
Dobry pomysł na użycie scanf; ale jego zadziała tylko z jednym słowem, które może nie być tym, czego chciał OP (tj. przycięcie „abc” powinno prawdopodobnie dać w wyniku „ab c”, podczas gdy pojedynczy scanf daje tylko „a”). Potrzebujemy więc pętli i licznika pominiętych znaków ze specyfikatorem %nkonwersji, a obawiam się, że na końcu łatwiej jest to zrobić ręcznie.
Peter - Przywróć Monikę
Bardzo przydatne, gdy chcesz, aby pierwsze słowo łańcucha pomijało wszelkie początkowe spacje.
J ... S
3

Nie podobały mi się te odpowiedzi, ponieważ wykonały co najmniej jedną z następujących czynności ...

  1. Zwrócono inny wskaźnik w ciągu oryginalnego wskaźnika (trochę uciążliwe, aby połączyć dwa różne wskaźniki do tej samej rzeczy).
  2. Nieuzasadnione użycie rzeczy takich jak strlen (), które wstępnie iterują cały ciąg.
  3. Używane nieprzenośne funkcje lib specyficzne dla systemu operacyjnego.
  4. Wstecznie zeskanowano.
  5. Użyto porównania do „” zamiast isspace (), aby zachować TAB / CR / LF.
  6. Zmarnowana pamięć z dużymi buforami statycznymi.
  7. Zmarnowane cykle z kosztownymi funkcjami, takimi jak sscanf / sprintf .

Oto moja wersja:

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
źródło
2
Musisz rzucić argument na isspaceto unsigned char, w przeciwnym razie wywołasz niezdefiniowane zachowanie.
Roland Illig
Ponieważ ta odpowiedź dotyczy „zmarnowanych cykli”, zwróć uwagę, że kod niepotrzebnie kopiuje całe żądło, gdy nie ma miejsca. Prowadzenie while (isspace((unsigned char) *szWrite)) szWrite++;by temu zapobiegło. Kod kopiuje również wszystkie końcowe białe znaki.
chux - Przywróć Monikę
@chux ta implementacja mutuje w miejscu za pomocą oddzielnych wskaźników do odczytu i zapisu (w przeciwieństwie do zwracania nowego wskaźnika w innej lokalizacji), więc sugestia przeskoczenia szWrite do pierwszej spacji w line-one pozostawiłaby wiodącą spację w oryginalny ciąg.
Jason Stewart
@chux, masz rację, że kopiuje końcowe białe znaki (przed dodaniem null po ostatnim znaku innym niż spacja), ale to cena, którą wybrałem, aby zapłacić, aby uniknąć wstępnego skanowania ciągu. W przypadku niewielkich ilości końcowych znaków WS, taniej jest po prostu skopiować bajty, zamiast wstępnie przeskanować cały ciąg w poszukiwaniu ostatniego znaku innego niż WS. W przypadku dużej liczby końcowych WS wstępne skanowanie byłoby prawdopodobnie warte zmniejszenia liczby zapisów.
Jason Stewart
@chux, dla sytuacji "kopiuje, gdy nie ma miejsca", wykonanie tylko *szWrite = *szReadwtedy, gdy wskaźniki nie są równe, pomija zapisy w tym przypadku, ale dodaliśmy kolejne porównanie / gałąź. Przy nowoczesnym CPU / MMU / BP nie mam pojęcia, czy ta kontrola byłaby stratą, czy zyskiem. Przy prostszych procesorach i architekturach pamięci tańsze jest po prostu skopiowanie i pominięcie porównania.
Jason Stewart
2

Bardzo późno na imprezę ...

Rozwiązanie do jednoprzebiegowego skanowania do przodu bez cofania. Każdy znak w łańcuchu źródłowym jest testowany dokładnie raz dwa razy. (Powinien więc być szybszy niż większość innych rozwiązań tutaj, zwłaszcza jeśli ciąg źródłowy ma dużo spacji na końcu).

Obejmuje to dwa rozwiązania, jedno do kopiowania i przycinania ciągu źródłowego do innego ciągu docelowego, a drugie do przycinania ciągu źródłowego w miejscu. Obie funkcje używają tego samego kodu.

Ciąg (modyfikowalny) jest przenoszony w miejscu, więc oryginalny wskaźnik do niego pozostaje niezmieniony.

#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
źródło
1
Każdy znak w łańcuchu źródłowym jest testowany dokładnie raz : nie do końca, większość znaków w łańcuchu źródłowym jest testowana dwukrotnie: porównywana z, '\0'a następnie testowana z isspace(). Testowanie wszystkich postaci przy użyciu programu wydaje się marnotrawstwem isspace(). Wycofywanie się od końca struny powinno być skuteczniejsze w przypadkach niepatologicznych.
chqrlie
@chqrlie - Tak, każda postać jest testowana dwukrotnie. Chciałbym zobaczyć ten kod faktycznie przetestowany, szczególnie biorąc pod uwagę ciągi z dużą ilością spacji końcowych, w porównaniu z innymi algorytmami tutaj.
David R Tribble
trim()DOBRZE. Obudowa narożna: trim2(char *d, const char *s)ma problemy z d,snakładaniem się i s < d.
chux - Przywróć Monikę
@chux - Jak powinno się trim()zachować w tym narożnym przypadku ? Prosisz o przycięcie i skopiowanie ciągu do pamięci zajmowanej przez sam ciąg. W przeciwieństwie do memmove()tego wymaga to określenia długości łańcucha źródłowego przed wykonaniem samego przycinania, co wymaga dodatkowego przeskanowania całego ciągu. Lepiej napisać inną rtrim2()funkcję, która wie, że kopiuje źródło do miejsca docelowego wstecz i prawdopodobnie pobiera dodatkowy argument długości ciągu źródłowego.
David R Tribble,
1

Nie jestem pewien, co uważasz za „bezbolesne”.

Struny C są dość bolesne. Możemy znaleźć pierwszą pozycję znaku niebędącego białymi znakami w trywialny sposób:

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

Możemy znaleźć ostatnią pozycję znaku niebędącego białymi znakami z dwoma podobnymi trywialnymi ruchami:

while (* q) q ++;
zrobić {q--; } while (isspace (* q));

(Oszczędziłem ci bólu używania operatorów *i ++w tym samym czasie.)

Pytanie brzmi, co z tym zrobisz? Typ danych, o którym mowa, nie jest tak naprawdę dużą, solidną abstrakcją, o Stringktórej łatwo jest pomyśleć, ale zamiast tego tak naprawdę niewiele więcej niż tablica bajtów pamięci. Brak solidnego typu danych uniemożliwia napisanie funkcji, która będzie działać tak samo jak chompfunkcja PHperytonby . Jaka byłaby taka funkcja w C?

jfm3
źródło
Działa to dobrze, chyba że ciąg składa się ze wszystkich odstępów. Potrzebujesz jednorazowego sprawdzenia, zanim do { q--; } ...się dowiesz *q != 0.
chux - Przywróć Monikę
1

Użyj biblioteki ciągów , na przykład:

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

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

... jak mówisz, że jest to "powszechny" problem, tak, musisz dołączyć #include lub coś takiego i nie jest to zawarte w libc, ale nie wymyślaj własnego zadania hakerskiego, przechowującego losowe wskaźniki i size_t w ten sposób prowadzi tylko do przepełnienia bufora.

James Antill
źródło
1

Żeby to rosło, jeszcze jedna opcja z modyfikowalnym ciągiem znaków:

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
źródło
1
strlen()zwraca wartość, size_tktóra może przekroczyć zakres int. spacja nie jest ograniczona do znaku spacji. Wreszcie, ale najważniejsze: niezdefiniowane zachowanie włączone, strcpy(string, string + i * sizeof(char));ponieważ tablice źródłowe i docelowe nakładają się. Użyj memmove()zamiast strcpy().
chqrlie
@chqrlie masz rację, po prostu dołącz swoje sugestie. Rozumiem, że kopiowanie, gdy źródło i miejsce docelowe nachodzą na siebie, może powodować nieokreślone zachowanie, ale chcę tylko zaznaczyć, że w tym konkretnym przypadku nie powinno to powodować żadnego problemu, ponieważ zawsze będziemy kopiować z późniejszej pozycji pamięci na początek, Dziękujemy za opinię.
wallek876
1
nie ma znaczenia, w jaki sposób tablice źródłowe i docelowe nakładają się, jest to niezdefiniowane zachowanie. Nie należy zakładać, że kopiowanie może odbywać się po jednym bajcie w czasie wzdłuż rosnących adresów. Zapomniałem też wspomnieć, że while (isspace((int)string[i])) string[i--] = '\0';może zapętlić się poza początkiem ciągu. Powinieneś połączyć tę pętlę z poprzednimi i następnymi liniami i napisaćwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
chqrlie
@chqrlie dobra uwaga, ciąg ze wszystkimi białymi spacjami spowodowałby zapętlenie poza początek, nie pomyślał o tym.
wallek876
Właściwie moja sugestia była niepoprawna, ponieważ endnie wskazywała na końcowy bajt zerowy, a end = ++i;nadal miałeś problem z łańcuchami zawierającymi wszystkie białe znaki. Właśnie naprawiłem kod.
chqrlie
1

Wiem, że jest wiele odpowiedzi, ale zamieszczam odpowiedź tutaj, aby sprawdzić, czy moje rozwiązanie jest wystarczająco dobre.

// 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
źródło
2
Uwaga: isspace(*str)UB kiedy *str < 0.
chux - Przywróć Monikę
1
Użycie size_t njest dobre, ale interfejs w żaden sposób nie informuje wywołującego o ntym, że jest zbyt mały dla pełnego przyciętego ciągu. Rozważtrim(out, 12, "delete data not")
chux - Przywróć Monikę
1

Najłatwiejszym sposobem na pominięcie początkowych spacji w ciągu jest imho,

#include <stdio.h>

int main()
{
char *foo="     teststring      ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
    return 0;
}
Zibri
źródło
1
Nie zadziała to w przypadku ciągów znaków ze spacjami w środku, takich jak " foo bar ".
David R Tribble,
1

Ok, to moje podejście do pytania. Uważam, że jest to najbardziej zwięzłe rozwiązanie, które modyfikuje ciąg w miejscu ( freezadziała) i pozwala uniknąć jakiegokolwiek UB. W przypadku małych strun jest to prawdopodobnie szybsze niż rozwiązanie obejmujące 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
źródło
b > strBadanie jest potrzebne tylko raz. *b = 0;potrzebne tylko raz.
chux - Przywróć Monikę
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 pomaga przyciąć wszystkie białe przestrzenie.

  • Uruchom pierwszą pętlę, aby sprawdzić od ostatniego bajtu znak spacji i zmniejszyć zmienną długości
  • Uruchom drugą pętlę, aby sprawdzić od pierwszego bajtu znak spacji i zmniejszyć zmienną długości oraz wskaźnik przyrostu znaku.
  • Wreszcie, jeśli zmienna length jest większa niż 0, użyj, strndupaby utworzyć nowy bufor ciągu, wykluczając spacje.
rashok
źródło
Tylko mały chwytak, strndup()nie jest częścią standardu C, ale tylko Posix. Ale ponieważ jest to dość łatwe do wdrożenia, nie jest to wielka sprawa.
Patrick Schlüter
trim_space("")zwraca NULL. Spodziewałbym się wskaźnika "". int len;powinno być size_t len;. isspace(in[len - 1])UB kiedy in[len - 1] < 0.
chux - Przywróć Monikę
Inicjał while (isspace((unsigned char) *in) in++;wcześniejlen = strlen(in); byłaby bardziej wydajna niż późniejszawhile(len && *in && isspace(*in)) ++in, --len;
chux - Przywróć Monikę
0

Osobiście zrobiłbym własny. Możesz użyć strtok, ale musisz uważać, robiąc to (szczególnie jeśli usuwasz wiodące znaki), aby wiedzieć, czym jest pamięć.

Pozbycie się końcowych spacji jest łatwe i całkiem bezpieczne, ponieważ możesz po prostu wstawić 0 ponad ostatnią spacją, licząc od końca. Pozbycie się wiodących spacji oznacza przenoszenie rzeczy. Jeśli chcesz to zrobić w miejscu (prawdopodobnie rozsądne), możesz po prostu przesuwać wszystko do tyłu o jeden znak, aż nie będzie spacji wiodącej. Lub, aby być bardziej wydajnym, możesz znaleźć indeks pierwszego znaku innego niż spacja i cofnąć wszystko o tę liczbę. Lub możesz po prostu użyć wskaźnika do pierwszego znaku innego niż spacja (ale wtedy musisz być ostrożny w taki sam sposób, jak w przypadku strtok).

Ben
źródło
4
strtok nie jest generalnie dobrym narzędziem w użyciu - nie tylko dlatego, że nie jest ponownie wprowadzany. Jeśli pozostaniesz wewnątrz jednej funkcji, możesz jej bezpiecznie używać, ale jeśli istnieje jakakolwiek możliwość wątków lub wywoływania innych funkcji, które same mogą używać strtok, masz kłopoty.
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
źródło
3
Rozśmieszyło mnie to, ponieważ pomyślałem, że dreamlax zredagował testowy ciąg znaków, aby zawierał „jest do bani”. Nie. Oryginalny autor jest po prostu szczery.
James Morris
1
Nie używaj tego kodu. Powoduje przepełnienie bufora.
Roland Illig
0

Trochę za późno do gry, ale wrzucę swoje procedury do walki. Prawdopodobnie nie są najbardziej wydajne, ale uważam, że są poprawne i proste (z rtrim()przesuwaniem obwiedni złożoności):

#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
źródło
1
należy rzutować charargument na isspace()to, (unsigned char)aby uniknąć niezdefiniowanego zachowania na potencjalnie ujemnych wartościach. Unikaj również przesuwania struny, ltrim()jeśli nie jest to konieczne.
chqrlie
0

Większość dotychczasowych odpowiedzi dotyczy jednej z następujących czynności:

  1. Cofnij się na końcu ciągu (tj. Znajdź koniec ciągu, a następnie przeszukaj wstecz, aż zostanie znaleziony znak inny niż spacja) lub
  2. Zadzwoń strlen()najpierw, wykonując drugie przejście przez cały ciąg.

Ta wersja wykonuje tylko jedno przejście i nie cofa się. Dlatego może działać lepiej niż inne, chociaż tylko wtedy, gdy często występują setki spacji końcowych (co nie jest niczym niezwykłym, gdy mamy do czynienia z wynikiem zapytania SQL).

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
źródło
1
Jeśli zależy ci na wydajności, nie używaj strspn()i strcspn()w ciasnej pętli. Jest to bardzo nieefektywne, a narzut przyćmiewa niesprawdzoną przewagę pojedynczego podania w przód. strlen()jest zwykle rozszerzany w tekście za pomocą bardzo wydajnego kodu, nie jest to prawdziwy problem. Przycinanie początku i końca łańcucha będzie znacznie szybsze niż testowanie białości każdego znaku w ciągu, nawet w specjalnym przypadku ciągów z bardzo małą liczbą znaków innych niż białe lub bez nich.
chqrlie
0

To najkrótsza możliwa implementacja, o jakiej mogę pomyśleć:

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
źródło
1
Co powiesz na to: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

Te funkcje zmodyfikują oryginalny bufor, więc jeśli zostanie przydzielony dynamicznie, oryginalny wskaźnik może zostać zwolniony.

#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
źródło
rstrip()wywołuje niezdefiniowane zachowanie na pustym łańcuchu. lstrip()jest niepotrzebnie powolny na łańcuchu z długą początkową częścią białych znaków. isspace()nie należy przekazywać charargumentu, ponieważ wywołuje niezdefiniowane zachowanie na wartościach ujemnych innych niż EOF.
chqrlie
0

Aby przyciąć struny z obu stron, używam starego, ale gooody;) Może przyciąć wszystko z ascii mniej niż spacją, co oznacza, że ​​znaki kontrolne również zostaną przycięte!

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;
}
Деян Добромиров
źródło
Powinieneś użyć size_tzamiast unsigned int. Kod zawiera wiele redundantnych testów i wywołuje niezdefiniowane zachowanie, strncpy(strData,&strData[S],L)ponieważ tablice źródłowa i docelowa nakładają się. Użyj memmove()zamiast strncpy().
chqrlie
W tym przypadku jest to w porządku, ponieważ adres docelowy ma zawsze mniejszy indeks niż źródło, ale tak, memmove będzie rzeczywiście lepsze.
Деян Добромиров
nie, nie jest OK. nie ma znaczenia, w jaki sposób tablice źródłowe i docelowe nakładają się na siebie, wywołuje niezdefiniowane zachowanie, ponieważ nie można bezpiecznie przyjąć założeń dotyczących implementacji funkcji bibliotecznych poza ich standardową specyfikacją. Współczesne kompilatory mają tendencję do nieuczciwego wykorzystywania sytuacji z potencjalnym niezdefiniowanym zachowaniem, zachowują się bezpiecznie i trzymają się z dala od UB i nie pozwalają nowicjuszom na dokonywanie niebezpiecznych założeń.
chqrlie
0

Uwzględniam tylko kod, ponieważ kod opublikowany do tej pory wydaje się nieoptymalny (i nie mam jeszcze przedstawiciela do komentowania).

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()jest rozszerzeniem GNU. Jeśli nie masz tego lub czegoś równoważnego, wyrzuć własne. Na przykład:

r = strdup(s + start);
r[end-start] = '\0';
sfink
źródło
1
isspace(0)jest zdefiniowana jako fałsz, możesz uprościć obie funkcje. Przesuń również memmove()wnętrze ifbloku.
chqrlie
0

Tutaj używam dynamicznej alokacji pamięci, aby przyciąć ciąg wejściowy do funkcji trimStr. Najpierw sprawdzamy, ile niepustych znaków znajduje się w ciągu wejściowym. Następnie przydzielamy tablicę znaków o tym rozmiarze i dbamy o znak zakończony znakiem null. Kiedy używamy tej funkcji, musimy zwolnić pamięć wewnątrz funkcji głównej.

#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
źródło
0

Oto jak to robię. Obcina ciąg w miejscu, więc nie martw się o cofnięcie przydziału zwracanego ciągu lub utratę wskaźnika do przydzielonego ciągu. Może nie jest to najkrótsza możliwa odpowiedź, ale powinna być zrozumiała dla większości czytelników.

#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
źródło
absolutnie jasne dla czytelników, ale strlen wykonuje kolejną pętlę .. :)
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
źródło
2
Chociaż ten kod może odpowiedzieć na pytanie, dostarczenie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi.
Nic3500,