在C语言中如何按空格分隔字符串

68

我想用C语言编写一个程序,将一整个句子(作为输入)中的每个单词显示在不同的行上。这是我到目前为止所做的:


void manipulate(char *buffer);
int get_words(char *buffer);

int main(){
    char buff[100];

    printf("sizeof %d\nstrlen %d\n", sizeof(buff), strlen(buff));   // Debugging reasons

    bzero(buff, sizeof(buff));

    printf("Give me the text:\n");
    fgets(buff, sizeof(buff), stdin);

    manipulate(buff);
    return 0;
}

int get_words(char *buffer){                                        // Function that gets the word count, by counting the spaces.
    int count;
    int wordcount = 0;
    char ch;

    for (count = 0; count < strlen(buffer); count ++){
        ch = buffer[count];
        if((isblank(ch)) || (buffer[count] == '\0')){                   // if the character is blank, or null byte add 1 to the wordcounter
            wordcount += 1;
        }
    }
    printf("%d\n\n", wordcount);
    return wordcount;
}

void manipulate(char *buffer){
    int words = get_words(buffer);
    char *newbuff[words];
    char *ptr;
    int count = 0;
    int count2 = 0;
    char ch = '\n';
    
    ptr = buffer;
    bzero(newbuff, sizeof(newbuff));

    for (count = 0; count < 100; count ++){
        ch = buffer[count];
        if (isblank(ch) || buffer[count] == '\0'){
            buffer[count] = '\0';
            if((newbuff[count2] = (char *)malloc(strlen(buffer))) == NULL) {
                printf("MALLOC ERROR!\n");
                exit(-1);
            }
            strcpy(newbuff[count2], ptr);
            printf("\n%s\n",newbuff[count2]);
            ptr = &buffer[count + 1];
            count2 ++;
        }
    }
}

虽然输出结果是我想要的,但最后显示的单词后面有很多黑色空格,而且 malloc() 返回了 NULL,所以最后会显示“MALLOC ERROR!”

我知道我的 malloc() 实现中有错误,但是我不知道错误在哪里。

是否有另一种更优雅或通常更好的方法来解决这个问题?


68
哎呀,每次我看到一个关于 C 语言字符串处理的问题时,我都感谢上帝我不必为此使用 C。 - user395760
5
你是否了解标准库函数strtok(或更安全的扩展strtok_r)? - ephemient
谢谢提供的信息,我之前不知道这个函数。问题是我不确定是否需要使用它,因为我想从文件中获取输入,在屏幕和其他文件上打印单词并删除重复单词等。手册并没有让我对这个特定的函数有更深入的了解。此外,我想先手动完成它,以便更好地掌握C语言。 - redsolja
1
@delnan - 如果你做得对,那就不会太糟糕。而@redsolja没有做对。在C语言中,字符串操作可以非常优雅。 - asveikau
1
@delnan:鉴于意大利面代码(spaghetti code)和编程问题之间的关系,感谢飞行意大利面怪物(Flying Spaghetti Monster)更有意义。你会注意到FSM也代表有限状态机(Finite State Machine)。 - Dave Jarvis
@delnan和C语言是Unix的语言..一个“一切皆为文本”的操作系统.. - xealits
9个回答

112

http://www.cplusplus.com/reference/clibrary/cstring/strtok/

使用空格字符作为分隔符,查看此链接。如果需要更多提示,请告诉我。

引用网站内容:

char * strtok ( char * str, const char * delimiters );

在第一次调用时,函数期望一个C字符串作为str参数,该字符串的第一个字符被用作扫描标记的起始位置。在后续调用中,函数期望一个空指针,并将上一个标记结束位置的下一个位置作为新的起始位置进行扫描。

一旦在调用strtok时找到了str的终止空字符,所有后续调用此函数(使用空指针作为第一个参数)都会返回空指针。

参数

  • str
    • C字符串来截断。
    • 请注意,通过将其分成较小的字符串(标记),修改了此字符串。 或者,可以指定null指针,在这种情况下,函数将继续扫描上一个成功调用该函数的位置。
  • delimiters
    • C字符串,包含分隔符字符。
    • 这些可能因每次调用而异。

返回值

指向字符串中最后一个标记的指针。 如果没有剩余的标记可获取,则返回空指针。

示例

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}

1
这个链接比strtok手册更有帮助,非常感谢,我会看一下的。 - redsolja
12
请注意,strtok() 直接操作其输入参数 str。如果您不希望更改其值,请在调用 strtok() 之前首先将其复制到另一个C字符串中。 - Kevin
1
这在第pch = strtok (NULL, " ,.-");行给我一个分段错误。在Ubuntu 14.04上运行,使用GCC 4.8.4(这是一个在线环境)。 - Noein
大多数情况下不要使用strtok(),它不是线程安全的,当你在某个地方使用它并从那里调用一个子程序时会出现问题。 - 12431234123412341234123
@Kevin 我很好奇 - strtok 如何以及为什么会修改它的 str 参数? - user426

4

为了好玩,这里提供一种基于回调方法的实现:

const char* find(const char* s,
                 const char* e,
                 int (*pred)(char))
{
    while( s != e && !pred(*s) ) ++s;
    return s;
}

void split_on_ws(const char* s,
                 const char* e,
                 void (*callback)(const char*, const char*))
{
    const char* p = s;
    while( s != e ) {
        s = find(s, e, isspace);
        callback(p, s);
        p = s = find(s, e, isnotspace);
    }
}

void handle_word(const char* s, const char* e)
{
    // handle the word that starts at s and ends at e
}

int main()
{
    split_on_ws(some_str, some_str + strlen(some_str), handle_word);
}

1
考虑使用像 strtok_r 这样的函数,正如其他人所建议的,或者类似以下的东西:
void printWords(const char *string) {
    // Make a local copy of the string that we can manipulate.
    char * const copy = strdup(string);
    char *space = copy;
    // Find the next space in the string, and replace it with a newline.
    while (space = strchr(space,' ')) *space = '\n';
    // There are no more spaces in the string; print out our modified copy.
    printf("%s\n", copy);
    // Free our local copy
    free(copy);
}

1

malloc(0) 可能(可选地)返回 NULL,这取决于实现。你知道为什么可能会调用 malloc(0) 吗?或者更准确地说,你看到了吗,你正在读写超出数组大小的位置吗?


我正在调用malloc(),因为我需要为指针数组newbuff安排空间。我对超出数组大小的r和w非常困惑。我唯一注意到的是“ptr =&buffer [count + 1];”,这使得ptr指针从数组中向后指向一个字节。我知道这很重要,我可以改变它,但是...还有什么?非常感谢您的回复。 - redsolja
@redsolja:你在newbuff中只有words个位置,但是count从0到99。你的malloc字符串没有包括结尾的NUL空间。你没有处理连续的空格。你从未计算句子中的最后一个单词。等等... - ephemient

0

出现问题的是get_words()函数总是返回比实际单词数少一个,因此最终你会尝试:

char *newbuff[words]; /* Words is one less than the actual number,
so this is declared to be too small. */

newbuff[count2] = (char *)malloc(strlen(buffer))

count2最终总是比你为newbuff[]声明的元素数量多一个。但是,我不知道为什么malloc()没有返回有效的指针。


关于get_words(),这就是为什么我打印结果并且它可以正确工作(我想)。:sizeof 100 strlen 0 给我这个文本: wordone wordtwo 1 ...其中wordone是第0个单词,而wordtwo是第1个单词。 - redsolja
它可能会工作,但你正在访问未声明的内存,这是危险的。count2 最终变得大于(words - 1),因此 malloc() 返回的值被写入到无效的内存位置。此外,在那个点上,你正在获取一个空字符串的长度,所以 malloc(0) 失败了。 - Doddy

0

以下是一个使用不同风格的C字符串操作示例,它不修改源字符串,也不使用malloc。我使用libc函数strpbrk来查找空格。

int print_words(const char *string, FILE *f)
{
   static const char space_characters[] = " \t";
   const char *next_space;

   // Find the next space in the string
   //
   while ((next_space = strpbrk(string, space_characters)))
   {
      const char *p;

      // If there are non-space characters between what we found
      // and what we started from, print them.
      //
      if (next_space != string)
      {
         for (p=string; p<next_space; p++)
         {
            if(fputc(*p, f) == EOF)
            {
               return -1;
            }
         }

         // Print a newline
         //
         if (fputc('\n', f) == EOF)
         {
            return -1;
         }
      }

      // Advance next_space until we hit a non-space character
      //
      while (*next_space && strchr(space_characters, *next_space))
      {
         next_space++;
      }

      // Advance the string
      //
      string = next_space;
   }

   // Handle the case where there are no spaces left in the string
   //
   if (*string)
   {
      if (fprintf(f, "%s\n", string) < 0)
      {
         return -1;
      }
   }

   return 0;
}

0
你应该使用malloc来分配字符串长度为strlen(ptr),而不是strlen(buf)。此外,你的count2应该限制在单词数量范围内。当你到达字符串结尾时,你仍然会继续扫描缓冲区中的零,并将零大小的字符串添加到数组中。

0
你可以扫描字符数组寻找标记,如果找到了就打印新行,否则打印字符
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    int main()
    {
        char *s;
        s = malloc(1024 * sizeof(char));
        scanf("%[^\n]", s);
        s = realloc(s, strlen(s) + 1);
        int len = strlen(s);
        char delim =' ';
        for(int i = 0; i < len; i++) {
            if(s[i] == delim) {
                printf("\n");
            }
            else {
                printf("%c", s[i]);
            }
        }
        free(s);
        return 0;
    }

-1
char arr[50];
gets(arr);
int c=0,i,l;
l=strlen(arr);

    for(i=0;i<l;i++){
        if(arr[i]==32){
            printf("\n");
        }
        else
        printf("%c",arr[i]);
    }

3
请在您的回答中添加一些描述。 - Mohit Jain

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