使用fgets()函数如何从stdin读取输入?

49

我写了下面这段代码来从终端窗口读取一行,但问题是代码陷入了无限循环。由于这一行/句子的长度未定义,因此我计划将其分成几部分读入缓冲区,然后将其连接到另一个可以通过realloc扩展的字符串中。请问是否有人能够发现我的错误或提出更好的实现方法?

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

#define BUFFERSIZE 10

int main (int argc, char *argv[])
{
    char buffer[BUFFERSIZE];
    printf("Enter a message: \n");
    while(fgets(buffer, BUFFERSIZE , stdin) != NULL)
    {
        printf("%s\n", buffer);
    }
    return 0;
}

看起来还不错,你想什么时候结束循环?现在的话,在*nix上可以通过按下ctrl+d或在Windows上按下ctrl+z来结束它。 - nos
1
我没有看到代码中明显的错误 - 当你说“陷入无限循环”时,你具体是什么意思? - Paul R
6个回答

36

这里是一个字符串连接的解决方案:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFERSIZE 10

int main() {
  char *text = calloc(1,1), buffer[BUFFERSIZE];
  printf("Enter a message: \n");
  while( fgets(buffer, BUFFERSIZE , stdin) ) /* break with ^D or ^Z */
  {
    text = realloc( text, strlen(text)+1+strlen(buffer) );
    if( !text ) ... /* error handling */
    strcat( text, buffer ); /* note a '\n' is appended here everytime */
    printf("%s\n", buffer);
  }
  printf("\ntext:\n%s",text);
  return 0;
}

1
为什么在这一行上要加 +1text = realloc( text, strlen(text)+1+strlen(buffer) ); - gonidelis
1
@johngonidelis 字符串被存储为字符的ASCII值序列,最后一个字符的二进制值为'0'。strlen()仅计算实际字母数,但当您为自己的字符串分配空间时,需要再包括一个单独的空间来存储空字节。此行已经包含了文本中的字符数量,加上缓冲区中的字符数量,再加上一个单独的空字节来表示字符串的结尾所需的空间。 - William

7
您对fgets返回值的理解有误。请查看以下链接:http://www.cplusplus.com/reference/clibrary/cstdio/fgets/。当fgets遇到EOF字符时,它会返回null。如果您运行上述程序并按下CTRL+D(或任何组合键作为EOF字符),循环将成功退出。
您想如何检测输入的结束?是换行符还是句号(您说过“句子”)?

输入的结尾应该是一个换行符。 - robdavies35
扫描您的缓冲区以查找换行符,然后 :) - salezica

4

如果该行为空,则退出循环(改进代码)。

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

// The value BUFFERSIZE can be changed to customer's taste . Changes the
// size of the base array (string buffer )    
#define BUFFERSIZE 10

int main(void)
{
    char buffer[BUFFERSIZE];
    char cChar;
    printf("Enter a message: \n");
    while(*(fgets(buffer, BUFFERSIZE, stdin)) != '\n')
    {
        // For concatenation
        // fgets reads and adds '\n' in the string , replace '\n' by '\0' to 
        // remove the line break .
/*      if(buffer[strlen(buffer) - 1] == '\n')
            buffer[strlen(buffer) - 1] = '\0'; */
        printf("%s", buffer);
        // Corrects the error mentioned by Alain BECKER.       
        // Checks if the string buffer is full to check and prevent the 
        // next character read by fgets is '\n' .
        if(strlen(buffer) == (BUFFERSIZE - 1) && (buffer[strlen(buffer) - 1] != '\n'))
        {
            // Prevents end of the line '\n' to be read in the first 
            // character (Loop Exit) in the next loop. Reads
            // the next char in stdin buffer , if '\n' is read and removed, if
            // different is returned to stdin 
            cChar = fgetc(stdin);
            if(cChar != '\n')
                ungetc(cChar, stdin);
            // To print correctly if '\n' is removed.
            else
                printf("\n");
        }
    }
    return 0;
}

按下Enter键时退出。

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

#define BUFFERSIZE 16

int main(void)
{
    char buffer[BUFFERSIZE];
    printf("Enter a message: \n");
    while(true)
    {
        assert(fgets(buffer, BUFFERSIZE, stdin) != NULL);
        // Verifies that the previous character to the last character in the
        // buffer array is '\n' (The last character is '\0') if the
        // character is '\n' leaves loop.
        if(buffer[strlen(buffer) - 1] == '\n')
        {
            // fgets reads and adds '\n' in the string, replace '\n' by '\0' to 
            // remove the line break .
            buffer[strlen(buffer) - 1] = '\0';
            printf("%s", buffer);
            break;
        }
        printf("%s", buffer);   
    }
    return 0;
}

将拼接和动态分配(链表)转换为单个字符串。

/* Autor : Tiago Portela
   Email : sapitando@gmail.com
   Sobre : Compilado com TDM-GCC 5.10 64-bit e LCC-Win32 64-bit;
   Obs : Apenas tentando aprender algoritimos, sozinho, por hobby. */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>

#define BUFFERSIZE 8

typedef struct _Node {
    char *lpBuffer;
    struct _Node *LpProxNode;
} Node_t, *LpNode_t;

int main(void)
{
    char acBuffer[BUFFERSIZE] = {0};
    LpNode_t lpNode = (LpNode_t)malloc(sizeof(Node_t));
    assert(lpNode!=NULL);
    LpNode_t lpHeadNode = lpNode;
    char* lpBuffer = (char*)calloc(1,sizeof(char));
    assert(lpBuffer!=NULL);
    char cChar;


    printf("Enter a message: \n");
    // Exit when Enter is pressed
/*  while(true)
    {
        assert(fgets(acBuffer, BUFFERSIZE, stdin)!=NULL);
        lpNode->lpBuffer = (char*)malloc((strlen(acBuffer) + 1) * sizeof(char));
        assert(lpNode->lpBuffer!=NULL);
        strcpy(lpNode->lpBuffer, acBuffer);
        if(lpNode->lpBuffer[strlen(acBuffer) - 1] == '\n')
        {
            lpNode->lpBuffer[strlen(acBuffer) - 1] = '\0';
            lpNode->LpProxNode = NULL;
            break;
        }
        lpNode->LpProxNode = (LpNode_t)malloc(sizeof(Node_t));
        lpNode = lpNode->LpProxNode;
        assert(lpNode!=NULL);
    }*/

    // Exits the loop if the line is empty(Improving code).
    while(true)
    {
        assert(fgets(acBuffer, BUFFERSIZE, stdin)!=NULL);
        lpNode->lpBuffer = (char*)malloc((strlen(acBuffer) + 1) * sizeof(char));
        assert(lpNode->lpBuffer!=NULL);
        strcpy(lpNode->lpBuffer, acBuffer);
        if(acBuffer[strlen(acBuffer) - 1] == '\n')
            lpNode->lpBuffer[strlen(acBuffer) - 1] = '\0';
        if(strlen(acBuffer) == (BUFFERSIZE - 1) && (acBuffer[strlen(acBuffer) - 1] != '\n'))
        {
            cChar = fgetc(stdin);
            if(cChar != '\n')
                ungetc(cChar, stdin);
        }
        if(acBuffer[0] == '\n')
        {
            lpNode->LpProxNode = NULL;
            break;
        }
        lpNode->LpProxNode = (LpNode_t)malloc(sizeof(Node_t));
        lpNode = lpNode->LpProxNode;
        assert(lpNode!=NULL);
    }


    printf("\nPseudo String :\n");
    lpNode = lpHeadNode;
    while(lpNode != NULL)
    {
        printf("%s", lpNode->lpBuffer);
        lpNode = lpNode->LpProxNode;
    }


    printf("\n\nMemory blocks:\n");
    lpNode = lpHeadNode;
    while(lpNode != NULL)
    {
        printf("Block \"%7s\" size = %lu\n", lpNode->lpBuffer, (long unsigned)(strlen(lpNode->lpBuffer) + 1));
        lpNode = lpNode->LpProxNode;
    }


    printf("\nConcatenated string:\n");
    lpNode = lpHeadNode;
    while(lpNode != NULL)
    {
        lpBuffer = (char*)realloc(lpBuffer, (strlen(lpBuffer) + strlen(lpNode->lpBuffer)) + 1);
        strcat(lpBuffer, lpNode->lpBuffer);
        lpNode = lpNode->LpProxNode;
    }
    printf("%s", lpBuffer);
    printf("\n\n");

    // Deallocate memory
    lpNode = lpHeadNode;
    while(lpNode != NULL)
    {
        lpHeadNode = lpNode->LpProxNode;
        free(lpNode->lpBuffer);
        free(lpNode);
        lpNode = lpHeadNode;
    }
    lpBuffer = (char*)realloc(lpBuffer, 0);
    lpBuffer = NULL;
    if((lpNode == NULL) && (lpBuffer == NULL))
    {

        printf("Deallocate memory = %s", (char*)lpNode);
    }
    printf("\n\n");

    return 0;
}

我错了还是只有当空行恰好在 BUFFERSIZE 边界之后出现时,循环才会退出? - Alain BECKER
@Alain BECKER 我改进了代码(我猜),在这个事件中真的不在循环中,但现在只有在行为空时才离开(按下回车键而没有输入任何内容),我用TDM-GCC和LCC进行了测试。 - sapitando
在一个已经有好的答案的6年老问题上,没有解释地发布一堆代码真的没有什么意义。 - Paul R
@ Paul R 如果你运行这个代码,你会发现它非常容易理解,没有什么响应是令人满意的(通过 Ctrl + Z 退出循环或者其他方式),我是一个新手,用这个问题来练习。 - sapitando

3
假设您只想读取单行,则使用在<limits.h>中定义的LINE_MAX
#include <stdio.h>
#include <limits.h>
...
char line[LINE_MAX];
...
if (fgets(line, LINE_MAX, stdin) != NULL) {
...
}
...

3
LINE_MAX 不符合 C89 或 C99 标准,它只是特定编译器的设定。 - user411313

0

检查每次读入缓冲区的字符串长度,并使用它来跳出循环。

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

#define LEN 10

int main()
{
        char buffer[LEN];
        char *str = calloc(LEN,sizeof(char));
        short flag = 0;
        while(!flag && fgets(buffer, LEN, stdin))       
        {
                str = realloc(str, strlen(str)+strlen(buffer)+1);
                if(!str) exit(1);
                if(strlen(buffer) != LEN-1)
                        flag = 1;
                strcat(str,buffer);
        }
        printf("%s",str);
        return 0;
}

0

如果您想要连接输入的内容,那么将printf("%s\n", buffer);替换为strcat(big_buffer, buffer);。同时在开始时创建并初始化大缓冲区:char *big_buffer = new char[BIG_BUFFERSIZE];big_buffer[0] = '\0';。您还应该通过验证当前缓冲区长度加上新缓冲区长度不超过限制来防止缓冲区溢出:if ((strlen(big_buffer) + strlen(buffer)) < BIG_BUFFERSIZE)。修改后的程序如下:

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

#define BUFFERSIZE 10
#define BIG_BUFFERSIZE 1024

int main (int argc, char *argv[])
{
    char buffer[BUFFERSIZE];
    char *big_buffer = new char[BIG_BUFFERSIZE];
    big_buffer[0] = '\0';
    printf("Enter a message: \n");
    while(fgets(buffer, BUFFERSIZE , stdin) != NULL)
    {
        if ((strlen(big_buffer) + strlen(buffer)) < BIG_BUFFERSIZE)
        {
            strcat(big_buffer, buffer);
        }
    }
    return 0;
}

1
“new” 是 C++ 的关键字,但问题是关于 C 语言的。 - Spikatrix

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