Как стандартным способом обрезать начальные / конечные пробелы?

Существует ли чистый, желательно стандартный метод обрезания начальных и конечных пробелов из строки в C? Я бы бросил свой, но я бы подумал, что это общая проблема с таким же общим решением.

41 ответ

Решение

Если вы можете изменить строку:

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

Если вы не можете изменить строку, то вы можете использовать в основном тот же метод:

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

Вот тот, который сдвигает строку в первую позицию вашего буфера. Возможно, вам понадобится такое поведение, чтобы при динамическом размещении строки вы могли освобождать ее по тому же указателю, который возвращает 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( str + len - 1 != endp )
            *(endp + 1) = '\0';
    else if( frontp != str &&  endp == frontp )
            *str = '\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;
}

Тест на правильность:

int main(int argc, char *argv[])
{
    char *sample_strings[] =
    {
            "nothing to trim",
            "    trim the front",
            "trim the back     ",
            " trim one char front and back ",
            " trim one char front",
            "trim one char back ",
            "                   ",
            " ",
            "a",
            "",
            NULL
    };
    char test_buffer[64];
    int index;

    for( index = 0; sample_strings[index] != NULL; ++index )
    {
            strcpy( test_buffer, sample_strings[index] );
            printf("[%s] -> [%s]\n", sample_strings[index],
                                     trim(test_buffer));
    }

    /* The test prints the following:
    [nothing to trim] -> [nothing to trim]
    [    trim the front] -> [trim the front]
    [trim the back     ] -> [trim the 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]
    [] -> []
    */

    return 0;
}

Исходный файл был trim.c. Составлено с помощью 'cc trim.c -o trim'.

Мое решение. Строка должна быть изменяемой. Преимущество перед некоторыми другими решениями заключается в том, что он перемещает непробельную часть в начало, так что вы можете продолжать использовать старый указатель на случай, если вам понадобится освободить () его позже.

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

Эта версия создает копию строки с помощью strndup (), а не редактирует ее на месте. strndup () требует _GNU_SOURCE, поэтому, возможно, вам нужно создать свой собственный strndup () с помощью malloc () и 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);
}

Вот моя C мини-библиотека для обрезки левого, правого, обоих, всех, на месте и отдельно, а также обрезки набора указанных символов (или пробелов по умолчанию).

содержимое 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

содержимое 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); }

Одна основная рутина делает все это. Он обрезается, если src == dst, в противном случае он работает как strcpy Подпрограммы. Он обрезает набор символов, указанных в строке delim или пробел, если ноль. Он обрезает левый, правый, оба и все (как tr). В этом нет ничего особенного, и он перебирает строку только один раз. Некоторые люди могут жаловаться, что обрезка справа начинается слева, однако, нет необходимости в strlen, который начинается слева в любом случае. (Так или иначе, вы должны добраться до конца строки для правильной обрезки, так что вы также можете выполнять работу по ходу дела.) Могут быть аргументы о конвейерной обработке и размерах кэша и тому подобное - кто знает, Поскольку решение работает слева направо и повторяется только один раз, его можно расширить и на потоки. Ограничения: он не работает со строками Unicode.

Вот моя попытка простой, но правильной функции обрезки на месте.

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

Поздно на вечеринку

Особенности:
1. Обрежьте начало быстро, как в ряде других ответов.
2. Пройдя до конца, обрежьте вправо всего одним тестом на петлю. Как @jfm3, но работает для всей строки пробела)
3. Чтобы избежать неопределенного поведения, когда char является подписанным char, бросать *s в unsigned char,

Обработка символов "Во всех случаях аргумент является int, значение которого должно быть представлено в виде unsigned char или должен быть равен значению макроса EOF, Если аргумент имеет любое другое значение, поведение не определено." 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';
  }
  return s;
}

Другой, с одной линией, выполняющей настоящую работу:

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

Вот решение, похожее на процедуру модификации на месте @adam-rosenfields, но без необходимости прибегать к strlen(). Как и @jkramer, строка корректируется в буфере влево, чтобы вы могли освободить тот же указатель. Не оптимально для больших строк, так как не использует memmove. Включает операторы ++/-, о которых упоминает @jfm3. Включены юнит-тесты на основе 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://stackru.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://stackru.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();

Если вы используете glibтогда вы можете использовать g_strstrip

Мне не понравились большинство из этих ответов, потому что они сделали одно или несколько из следующих действий...

  1. Возвращает другой указатель внутри строки исходного указателя (боль в том, чтобы совмещать два разных указателя на одну и ту же вещь).
  2. Сделано безвозмездное использование таких вещей, как strlen(), которые предварительно повторяют всю строку.
  3. Используются непереносимые для ОС функции lib.
  4. Backscanned.
  5. Использовано сравнение с ' ' вместо isspace(), чтобы сохранить TAB / CR / LF.
  6. Потраченная впустую память с большими статическими буферами.
  7. Потраченные впустую циклы с дорогими функциями, такими как sscanf / sprintf.

Вот моя версия:

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

Я не уверен, что вы считаете "безболезненным".

Струны C довольно болезненны. Мы можем найти первую позицию непробельного символа тривиально:

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

Мы можем найти последнюю позицию непробельного символа с двумя похожими тривиальными ходами:

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

(Я избавил вас от боли использования * а также ++ операторы одновременно.)

Теперь вопрос в том, что вы делаете с этим? Тип данных под рукой не очень большой надежный String об этом легко думать, но на самом деле это всего лишь массив байтов хранения. Не имея надежного типа данных, невозможно написать функцию, которая будет делать то же, что и PHperytonby. chomp функция. Что бы могла вернуть такая функция в C?

Самый простой способ пропустить ведущие пробелы в строке, imho,

#include <stdio.h>

int main()
{
char *foo="     teststring      ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
    return 0;
}

Очень поздно на вечеринку...

Однопроходное решение для сканирования вперед без возврата. Каждый символ в исходной строке проверяется ровно один раз. (Так что это должно быть быстрее, чем большинство других решений здесь, особенно если в исходной строке много пробелов.)

Это включает в себя два решения: одно для копирования и обрезки исходной строки в другую строку назначения, а другое для обрезки исходной строки на месте. Обе функции используют один и тот же код.

(Изменяемая) строка перемещается на место, поэтому исходный указатель на нее остается неизменным.

#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        
    unsigned const char * p = (unsigned const 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);
}

Немного опоздал к игре, но я переверну рутину. Они, вероятно, не самые эффективные, но я верю, что они правильные и простые rtrim() выдвигая конверт сложности):

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

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

    Michael Burr
    michael.burr@nth-element.com
    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));
}

Если и ТОЛЬКО ЕСЛИ есть только один непрерывный блок текста между пробелами, вы можете использовать один вызов для strtok(3), вот так:

      char *trimmed = strtok(input, "\r\t\n ");

Это работает для строк, подобных следующим:

      "   +1.123.456.7890 "
" 01-01-2020\n"
"\t2.523"

Это не будет работать для строк, содержащих пробелы между блоками, не являющимися пробелами, например " hi there ". Вероятно, лучше избегать этого подхода, но теперь он находится в вашем наборе инструментов, если он вам нужен.

Используйте библиотеку строк, например:

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

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

... как вы говорите, это "общая" проблема, да, вам нужно включить #include или около того, и он не включен в libc, но не изобретайте свою собственную хакерскую работу, хранящую случайные указатели, а size_t таким образом приводит только к переполнение буфера.

Этот короткий и простой, использует циклы for и не перезаписывает границы строки. Вы можете заменить тест наisspace() если нужно.

void trim (char *s)         // trim leading and trailing spaces+tabs
{
 int i,j,k, len;

 j=k=0;
 len = strlen(s);
                    // find start of string
 for (i=0; i<len; i++) if ((s[i]!=32) && (s[i]!=9)) { j=i; break; }
                    // find end of string+1
 for (i=len-1; i>=j; i--) if ((s[i]!=32) && (s[i]!=9)) { k=i+1; break;} 

 if (k<=j) {s[0]=0; return;}        // all whitespace (j==k==0)

 len=k-j;
 for (i=0; i<len; i++) s[i] = s[j++];   // shift result to start of string
 s[i]=0;                // end the string

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

Хорошо, это мой взгляд на вопрос. Я считаю, что это наиболее краткое решение, которое изменяет строку на месте (free будет работать) и избегает любого UB. Для небольших строк это, вероятно, быстрее, чем решение с использованием 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;
}

Я знаю, что есть много ответов, но я публикую свой ответ здесь, чтобы посмотреть, достаточно ли хорошее решение.

// 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;
}
#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 помогает обрезать все пробелы.

  • Запустите первый цикл, чтобы проверить из последнего байта символ пробела и уменьшить переменную длины
  • Запустите второй цикл, чтобы проверить из первого байта символ пробела и уменьшить переменную длины и увеличить указатель на символ.
  • Наконец, если переменная длины больше 0, используйте strndup создать новый строковый буфер, исключив пробелы.

Это было очень полезно, я хотел сказать, что был рад, что этот пост был доступен, и показать, что я смог сделать с примерами. Мне нужно было токенизировать строку большего размера, а затем взять подстроку (ы) и найти последнюю - чтобы я мог удалить новую строку из вызова fgets(), а также удалить пробелы в начале этого токена - чтобы я мог легко сравнить его со статической строкой. Первый пример в посте выше привел меня туда, так что спасибо. Вот как я использовал примеры кода и полученный результат.

int _tmain(int argc, _TCHAR* argv[])
{
   FILE * fp;   // test file
   char currDBSStatstr[100] = {"/0"};
   char *beg;
   char *end;
   char *str1;
   char str[] = "Initializing DBS Configuration";
   fp = fopen("file2-1.txt","r");
   if (fp != NULL)
   {
      printf("File exists.\n");
      fgets(currDBSStatstr, sizeof(currDBSStatstr), fp);
   }
   else
   {
      printf("Error.\n");
      exit(2);
   }  
   //print string
   printf("String: %s\n", currDBSStatstr);
   //extract first string
   str1 = strtok(currDBSStatstr, ":-");
   //print first token
   printf("%s\n", str1);
   //get more tokens in sequence
   while(1)
   {
      //extract more tokens in sequence
      str1 = strtok(NULL, ":-");
      //check to see if done
      if (str1 == NULL)
      {
         printf("Tokenizing Done.\n");
         exit(0);
      }
      //print string after tokenizing Done
      printf("%s\n", str1);
      end = str1 + strlen(str1) - 1;
      while((end > str1) && (*end == '\n'))
      {
         end--;
         *(end+1) = 0;
         beg = str1;
         while(isspace(*str1))
            str1++;
      }
      printf("%s\n", str1);
      if (strcmp(str, str1) == 0)
         printf("Strings are equal.\n");
   }
   return 0;

}

Выход

Файл существует.

Строка: DBS Состояние: запуск DBS - инициализация конфигурации DBS

DBS State

DBS Startup

DBS Startup

Инициализация конфигурации DBS

Инициализация конфигурации DBS

Строки равны.

Токенизация Готово.

Просто чтобы сохранить этот рост, еще один вариант с изменяемой строкой:

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

Поскольку другие ответы, похоже, не изменяют указатель строки напрямую, а скорее полагаются на возвращаемое значение, я подумал, что предоставлю этот метод, который дополнительно не использует никаких библиотек и поэтому подходит для программирования в стиле операционной системы:

// only used for printf in main
#include <stdio.h>

// note the char ** means we can modify the address
char *trimws(char **strp) { 
  char *str;
  // go to the end of the string
  for (str = *strp; *str; str++) 
    ;
  // back up one from the null terminator
  str--; 
  // set trailing ws to null
  for (; *str == ' '; str--) 
    *str = 0;
  // increment past leading ws
  for (str = *strp; *str == ' '; str++) 
    ;
  // set new begin address of string
  *strp = str; 
}

int main(void) {
  char buf[256] = "   whitespace    ";
  // pointer must be modifiable lvalue so we make bufp
  char **bufp = &buf;
  // pass in the address
  trimws(&bufp);
  // prints : XXXwhitespaceXXX
  printf("XXX%sXXX\n", bufp); 
  return 0;
}

Я только включаю код, потому что код, размещенный до сих пор, кажется неоптимальным (и у меня пока нет представителя, чтобы комментировать.)

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() является расширением GNU. Если у вас его нет или что-то подобное, бросьте свой. Например:

r = strdup(s + start);
r[end-start] = '\0';

Это кратчайшая возможная реализация, о которой я могу подумать:

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
}

Большинство ответов до сих пор делают одно из следующего:

  1. Возврат в конце строки (то есть найти конец строки и затем искать назад, пока не будет найден непробельный символ), или
  2. Вызов strlen() во-первых, делая второй проход через всю строку.

Эта версия делает только один проход и не возвращается. Следовательно, он может работать лучше, чем другие, хотя только в том случае, если распространены сотни пробелов (что весьма обычно при работе с выводом запроса 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';
}

Для обрезки моих струн с обеих сторон я использую oldie, но gooody;) Он может обрезать что угодно с помощью ascii меньше пробела, что означает, что управляющие символы также будут обрезаны!

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

Эти функции изменят исходный буфер, поэтому при динамическом размещении исходный указатель может быть освобожден.

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

Что вы думаете об использовании функции StrTrim, определенной в заголовке Shlwapi.h.? Это прямолинейно, скорее, самоопределение.
Подробности можно найти на:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx

Если у вас есть
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
Это даст ausCaptain как "GeorgeBailey" не "GeorgeBailey ",

Другие вопросы по тегам