如何以标准方式修剪前导/后缀空白?

时间:2020-03-06 14:36:53  来源:igfitidea点击:

是否有一种干净的,最好是标准的方法来修剪C中字符串的开头和结尾空格?我会自己动手,但我认为这是一个同样常见的解决方案,这是一个普遍的问题。

解决方案

如果可以修改字符串:

// 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] = '
// 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;
}
'; return str; }

如果我们不能修改字符串,则可以使用基本相同的方法:

void str_trim(char *output, const char *text, int32 max_len)
{
    int32 i, j, length;
    length = strlen(text);

    if (max_len < 0) {
        max_len = length + 1;
    }

    for (i=0; i<length; i++) {
        if ( (text[i] != ' ') && (text[i] != '\t') && (text[i] != '\n') && (text[i] != '\r')) {
            break;
        }
    }

    if (i == length) {
        // handle lines that are all whitespace
        output[0] = 0;
        return;
    }

    for (j=length-1; j>=0; j--) {
        if ( (text[j] != ' ') && (text[j] != '\t') && (text[j] != '\n') && (text[j] != '\r')) {
            break;
        }
    }

    length = j + 1 - i;
    strncpy(output, text + i, length);
    output[length] = 0;
}

就个人而言,我会自己动手。我们可以使用strtok,但是这样做时要特别小心(尤其是要删除前导字符时),以知道什么是内存。

摆脱尾随的空格很容易,而且很安全,因为我们可以在最后一个空格的顶部放一个0,从末尾开始倒数。摆脱领先空间意味着要四处走动。如果我们想就地执行(可能明智的话),我们可以继续将所有内容移回一个字符,直到没有前导空格为止。或者,为了提高效率,我们可以找到第一个非空格字符的索引,然后将所有内容都移回该数字。或者,我们可以仅使用指向第一个非空格字符的指针(但随后我们需要以与strtok相同的方式小心)。

这是一个执行我们想要的功能。它应该处理字符串全为空格的简并情况。我们必须传入输出缓冲区和缓冲区的长度,这意味着我们必须传入分配的缓冲区。

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

循环中的if语句可能可以替换为isspace(text [i])或者isspace(text [j]),以使各行更易于阅读。我认为我是用这种方式设置它们的,因为有些字符我不想测试,但是看起来我现在涵盖了所有空白:-)

我不确定我们认为"无痛"。

C字符串非常痛苦。我们可以很容易地找到第一个非空白字符位置:

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

我们可以通过两个类似的琐碎动作找到最后一个非空白字符位置:

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

    if( str == NULL ) { return NULL; }
    if( str[0] == '
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;
}
' ) { 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) = '
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] = '
r = strdup(s + start);
r[end-start] = '
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);
}
';
'; } 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); }
'; else if( frontp != str && endp == frontp ) *str = '
char * trim(char * s) {
    int l = strlen(s);

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

    return strndup(s, l);
}
'; /* 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 = '
Ustr *s1 = USTR1(, " 12345 ");

ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));
'; } return str; }

(我免除了我们同时使用*++运算符的痛苦。)

现在的问题是我们如何处理?眼前的数据类型并不是一个容易想到的强大的抽象字符串,而是一个存储字节数组。缺乏健壮的数据类型,不可能编写出与PHperytonby的chomp函数相同的函数。这样的函数在C语言中会返回什么?

这是将字符串移到缓冲区的第一个位置的字符串。我们可能想要这种行为,以便如果我们动态分配了字符串,仍然可以在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]!='
#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 '
#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
') memmove( s, newstart, strlen( newstart) + 1); // don't forget to move the '
#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); }
' 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 '
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] = '
#include <ctype.h>

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

/* 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', '##代码##', ' ',                     
                         ' ', 'f', 'o', 'o', 'b', 'a', 'r', '##代码##'};
          fct_chk_eq_int(0, memcmp(s, r, sizeof(s)));
        }
    }
    FCT_QTEST_END();
}
FCT_END();
'; // Null terminate string. }
' terminator, in which case there's no problem writing // another there). *end = '##代码##'; return s; } char* trim( char* s) { return rtrim( ltrim( s)); }
') { if(ptr[j] == ' ' ) { j++; ptr[i] = ptr[j]; } else { i++; j++; ptr[i] = ptr[j]; } } printf("\noutput-%s\n",ptr); return 0; }

测试正确性:

##代码##

源文件是trim.c。与'cc trim.c -o trim'一起编译。

我只包含代码是因为到目前为止发布的代码似乎不是最佳选择(并且我还没有代表要评论)。

##代码##

strndup()是GNU扩展。如果我们没有它或者类似的东西,请自己动手。例如:

##代码##

我的解决方案。字符串必须是可变的。与其他解决方案相比,它的优点是将非空格部分移到开头,这样我们就可以继续使用旧的指针,以防日后需要free()它。

##代码##

此版本使用strndup()创建字符串的副本,而不是在原位对其进行编辑。 strndup()需要_GNU_SOURCE,因此也许我们需要使用malloc()和strncpy()来制作自己的strndup()。

##代码##

使用字符串库,例如:

##代码##

...正如我们所说的,这是一个"常见"问题,是的,我们需要包含一个#include左右,并且它不包含在libc中,但是不要去发明我们自己的存储随机指针的Hack作业,而size_t只会导致缓冲区溢出。

##代码##

比赛有点晚了,但我会把日常工作弄得一团糟。它们可能不是最绝对有效的,但我相信它们是正确的并且很简单(使用rtrim()推动了复杂性包络):

##代码##

这是我的C mini库,用于在适当位置并单独修剪左右,全部修剪,并修剪一组指定的字符(默认情况下为空白)。

strlib.h的内容:

##代码##

strlib.c的内容:

##代码##

一个主要的例程可以完成所有操作。
如果src == dst,它会修剪到位,否则,
它的工作方式类似于strcpy例程。
它修剪字符串delim中指定的一组字符,如果为null,则修剪空白。
它修剪左,右,两者和全部(如tr)。
它没有太多内容,并且仅在字符串上迭代一次。有些人可能会抱怨修剪右部从左边开始,但是,不需要从左边开始的任何努力。 (必须以一种或者另一种方式到达字符串的末尾以进行正确的修整,因此我们不妨随心所欲地完成工作。)关于流水线化和缓存大小,可能会有一些争论,这些人都知道。由于该解决方案从左到右工作并且仅迭代一次,因此它也可以扩展为在流上工作。局限性:它不适用于unicode字符串。

这是我尝试的一种简单而正确的就地修剪功能。

##代码##

这是一种类似于@ adam-rosenfields就地修改例程的解决方案,但无需使用strlen()。像@jkramer一样,字符串在缓冲区内向左调整,因此我们可以释放相同的指针。由于它不使用记忆,因此不适用于大型字符串。包括@ jfm3提及的++ /-运算符。包括基于FCTX的单元测试。

##代码##