从C字符串中删除额外的空格?

4
我已经将几行文本读入一个C字符串数组中。这些行具有任意数量的制表符或空格分隔的列,我正在尝试弄清楚如何删除它们之间所有额外的空白。最终目标是使用strtok来分解列。这是一组很好的列:
Cartwright Wendy 93 Williamson Mark 81 Thompson Mark 100 Anderson John 76 Turner Dennis 56
我该如何消除列之间除一个之外的所有空格或制表符,以便输出看起来像这样?
Cartwright Wendy 93
或者,我可以用不同的字符替换列之间的所有空格,以便使用strtok吗?类似于这样?
Cartwright#Wendy#93
编辑:有多个很好的答案,但只能选择一个。感谢大家的帮助。
9个回答

11
如果我可以提出“你做错了”的意见,为什么不在读取时消除空格呢?使用fscanf("%s", string);来读取一个“单词”(非空白字符),然后读取空格。如果是空格或制表符,则继续读取到一行数据。如果是换行符,则开始一个新条目。在C语言中,尽快将数据转换为您可以处理的格式可能是最容易的,而不是试图进行重度文本操作。

那是我没有考虑过的另一件事。嗯。 - Jamison Dance
我总是可以多被警告几次。C语言不是我的强项。我是一个懦弱的脚本语言网页程序员。 - Jamison Dance
1
脚本语言并没有错。我的母语是Perl,而我目前也在学习Python,不过我同样也喜欢C语言。学习C很好,因为你可以用新的方式来做事情。这甚至使得我的Perl变得难以理解,因为我想要一个字符一个字符地处理文本,使用getc()花了差不多一个小时,直到我记起了按照Perl想要的方式去做会更容易。 - Chris Lutz
1
使用 fscanf 时要小心缓冲区溢出。 - Robert Groves
2
回应 @Robert,使用 fgetssscanf 结合使用。 - Sinan Ünür
显示剩余2条评论

5

为什么不直接使用strtok()呢?无需修改输入。

你需要做的就是重复使用strtok(),直到获得3个非空格标记,然后你就完成了!


发生在我们最好的人身上,伙计 ;) 看看我在Alex Martelli的回答中的评论,我说了完全相同的话哈哈 - hhafez

2

编辑: 我最初使用了一个malloced工作空间,我认为这可能更清晰。然而,不使用额外的内存也是几乎同样简单的,我在评论和个人即时消息中被推向这个方向,因此,接下来就是...:-)

void squeezespaces(char* row, char separator) {
  char *current = row;
  int spacing = 0;
  int i;

  for(i=0; row[i]; ++i) {
    if(row[i]==' ') {
      if (!spacing) {
        /* start of a run of spaces -> separator */
        *current++ = separator
        spacing = 1;
      }
    } else {
      *current++ = row[i];
      spacing = 0;
  }
  *current = 0;    
}

做一个 malloc 似乎有些过重了! - Mitch Wheat
你会如何分配内存? - hhafez
确实如此 :) 没想到那个 - hhafez
可以直接覆盖现有行(只需确保附加关闭的 '\0'!-),我只是想让事情对 OP 更简单。如果 OP 评论和/或编辑(最好:both;-) 要求“不分配辅助内存”,我将相应地编辑答案。 - Alex Martelli
好吧,我放弃了(你知道你是谁,停止在IRC和IM上找我),这里有一个无需额外内存的版本——希望同样简单;-)。现在开心了吗?-) - Alex Martelli

2
以下代码会直接修改字符串;如果你不想破坏原始输入,可以传递第二个缓冲区来接收修改后的字符串。这应该是相当容易理解的:
#include <stdio.h>
#include <string.h>

char *squeeze(char *str)
{
  int r; /* next character to be read */
  int w; /* next character to be written */

  r=w=0;
  while (str[r])
  {
    if (isspace(str[r]) || iscntrl(str[r]))
    {
      if (w > 0 && !isspace(str[w-1]))
        str[w++] = ' ';
    }
    else
      str[w++] = str[r];
    r++;
  }
  str[w] = 0;
  return str;
}

int main(void)
{
  char test[] = "\t\nThis\nis\ta\b     test.";
  printf("test = %s\n", test);
  printf("squeeze(test) = %s\n", squeeze(test));
  return 0;
}

1
char* trimwhitespace(char *str_base) {
    char* buffer = str_base;
    while((buffer = strchr(str_base, ' '))) {
        strcpy(buffer, buffer+1);
    }

    return str_base;
}

0
以下代码简单地逐个字符输入,然后检查每个字符是否有超过一个空格,如果是,则跳过它,否则打印该字符。同样的逻辑也可以用于制表符。希望这能帮助解决您的问题。如果这段代码有任何问题,请告诉我。
    int c, count = 0;
    printf ("Please enter your sentence\n");
    while ( ( c = getchar() ) != EOF )  {
        if ( c != ' ' )  {
            putchar ( c );
            count = 0;
        }
        else  {
            count ++;
            if ( count > 1 )
                ;    /* Empty if body */
            else
                putchar ( c );
         }
     }
}

1
请在您的答案中给出一些解释,而不仅仅是放置一堆代码。 - StephenTG

0
你可以读取一行,然后扫描它以找到每个列的开始。然后按照自己的需求使用列数据。
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_COL 3
#define MAX_REC 512

int main (void)
{
    FILE *input;
    char record[MAX_REC + 1];
    char *scan;
    const char *recEnd;
    char *columns[MAX_COL] = { 0 };
    int colCnt;

    input = fopen("input.txt", "r");

    while (fgets(record, sizeof(record), input) != NULL)
    {
        memset(columns, 0, sizeof(columns));  // reset column start pointers

        scan = record;
        recEnd = record + strlen(record);

        for (colCnt = 0; colCnt < MAX_COL; colCnt++ )
        {
          while (scan < recEnd && isspace(*scan)) { scan++; }  // bypass whitespace
          if (scan == recEnd) { break; }
          columns[colCnt] = scan;  // save column start
          while (scan < recEnd && !isspace(*scan)) { scan++; }  // bypass column word
          *scan++ = '\0';
        }

        if (colCnt > 0)
        {
            printf("%s", columns[0]);
            for (int i = 1; i < colCnt; i++)
            {
             printf("#%s", columns[i]);
            }
            printf("\n");
        }
    }

    fclose(input);
}

请注意,代码仍然需要一些健壮性:使用ferror检查文件错误;确保使用feof命中eof;确保处理了整个记录(所有列数据)。通过使用链接列表而不是固定数组,可以使其更加灵活,并且可以修改为不假设每个列仅包含单个单词(只要列由特定字符分隔即可)。

0

这里有一个替代函数,它可以挤出由<ctype.h>中的isspace()定义的重复空格字符。它返回“挤压”后字符串的长度。

#include <ctype.h>

size_t squidge(char *str)
{
    char *dst = str;
    char *src = str;
    char  c;
    while ((c = *src++) != '\0')
    {
        if (isspace(c))
        {
            *dst++ = ' ';
            while ((c = *src++) != '\0' && isspace(c))
                ;
            if (c == '\0')
                break;
        }
        *dst++ = c;
    }
    *dst = '\0';
    return(dst - str);
}

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

int main(void)
{
    char buffer[256];
    while (fgets(buffer, sizeof(buffer), stdin) != 0)
    {
        size_t len = strlen(buffer);
        if (len > 0)
            buffer[--len] = '\0';
        printf("Before: %zd <<%s>>\n", len, buffer);
        len = squidge(buffer);
        printf("After:  %zd <<%s>>\n", len, buffer);
    }
    return(0);
}

0

我对John Bode的小改进进行了一些改进,以去除尾随空格:

#include <ctype.h>

char *squeeze(char *str)
{
  char* r; /* next character to be read */
  char* w; /* next character to be written */
  char c;
  int sp, sp_old = 0;

  r=w=str;

  do {
    c=*r;
    sp = isspace(c);
    if (!sp) {
      if (sp_old && c) {
        // don't add a space at end of string
        *w++ = ' ';
      }
      *w++ = c;
    }
    if (str < w) {
      // don't add space at start of line
      sp_old = sp;
    }
    r++;
  }
  while (c);

  return str;
}

#include <stdio.h>

int main(void)
{
  char test[] = "\t\nThis\nis\ta\f     test.\n\t\n";
  //printf("test = %s\n", test);
  printf("squeeze(test) = '%s'\n", squeeze(test));
  return 0;
}

换行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接