puts()、gets()、getchar()、putchar()函数在程序中同时使用

问题描述

我对在代码中同时使用 puts()gets()putchar()getchar() 感到困惑。

当我运行以下代码时,它正在执行所有步骤: 取输入,打印输出,再次取输入,打印输出。

#include <stdio.h>

int main() {
    char ch[34];
    gets(ch);
    puts(ch);

    char g;
    g = getchar();
    putchar(g);
}

输出:

Priyanka
Priyanka
J
J

但是,当我使用此代码时: 它只做两步: 获取输入,打印输入,然后一行空格。我不明白为什么它会这样。

代码:

#include <stdio.h>

int main() {
    char g;
    g = getchar();
    putchar(g);   
 
    char ch[34];
    gets(ch);
    puts(ch);
    getch();
}

输出:

P
P

解决方法

代码中存在一些问题,输入机制比您推断的要复杂:

  • 你不应该用 gets() 读取输入:这个函数不能安全使用,因为它不接收有关目标数组大小的信息,所以任何足够长的输入行都会导致缓冲区溢出。它已从 C 标准中删除。您应该改用 fgets() 并处理缓冲区末尾的换行符。
  • g 应该具有类型 int 以容纳 getc() 返回的所有值,即 unsigned char 类型的所有值(在大多数当前系统中 0255) 和特殊的负值 EOF(通常是 -1)。

这是修改后的版本:

#include <stdio.h>

int main() {
    char ch[34];
    if (fgets(ch,sizeof ch,stdin))
        fputs(ch,stdout);

    int g = getchar();
    if (g != EOF)
        putchar(g);
    return 0;
}

输出:

Priyanka
Priyanka
J
J

关于控制台响应程序输入请求的行为,它是实现定义的,但通常涉及 2 层缓冲:

  • FILE 流包实现了一种缓冲方案,其中数据以块的形式从系统读取或写入系统。这种缓冲可以用 setvbuf() 控制。有 3 种设置可用:无缓冲(stderr 的默认设置)、行缓冲(通常是连接到字符设备时 stdinstdout 的默认设置)和完全缓冲可自定义的块大小(常见大小为 512 和 4096)。
  • 当您调用 getchar() 或更一般的 getc(stream) 时,如果流缓冲区中有可用字节,则返回该字节并增加流位置,否则向系统发出请求以填充缓冲区。
  • 如果流附加到文件,填充缓冲区会执行 read 系统调用或等效的,除非在文件末尾或读取错误时成功。
  • 如果流附加到字符设备,例如终端或图形显示上的终端窗口之类的虚拟 tty,则涉及另一层缓冲,其中设备驱动程序从输入设备读取输入并处理一些键以特殊方式,例如 Backspace 擦除前一个字符,光标移动键在输入行内移动,Ctrl-D (unix) 或 Ctrl-Z (windows) 表示文件结束。这层缓冲可以通过 tcsetattr() 系统调用或其他系统特定的 API 进行控制。文本编辑器等交互式应用程序通常会禁用此功能并直接从输入设备检索原始输入。
  • 用户输入的键由终端处理形成输入行,当用户输入Enter时发送回C流API(翻译为系统特定的行尾)序列),流函数执行另一组转换(即:在遗留系统上将 CR/LF 转换为 '\n')并且字节行存储在流缓冲区中。当 getc() 终于有机会返回第一个可用字节时,整行已被用户键入并输入,并在流或设备缓冲区中等待。

在这两个程序中,getchar() 不会返回从 stdin 读取的下一个字节,直到从终端读取整行并将其存储在流缓冲区中。在第一个程序中,该行的其余部分在程序退出时被忽略,但在第二个程序中,该行的其余部分可供后续 gets() 读取。如果您输入 JEnter,则读取的行是 J\n 并且 getchar() 返回 'J',留下换行符 [end in输入流,然后 gets() 将读取换行符并返回一个空行。

,

在语句 putchar()gets() 我不推荐使用的语句之间,丢弃输入流直到 EOF 或直到出现换行符解决了问题:

.
.
int c;
while ((c = getchar()) != EOF && c != '\n')
  ;
.
.

我建议使用 fgets(3),它使用起来非常安全,例如:

char str[1024];
if (fgets(str,sizeof str,stdin) == NULL) {
  // Some problem,handle the error...
}

// or,Input is okay...
,

好吧,你这里有问题。您在第二个示例代码中使用了一个不属于 stdio 包的函数。

您调用了 getch(),它不是 stdio 函数。它是 ncurses 库的一部分,如果您在编译时未指定将使用它,那么您将无法获得可执行程序。所以这让我觉得你没有说实话。

只需使用程序的 getch() 函数即可获得完整行

Priyanka

输出,程序终止。我猜你用 getch() 来停止输出,直到你按下一个字符。但是由于curses 库要求您在调用任何其他curses 库函数之前先调用initscr(),因此它没有正确初始化,因此您得到的输出可能是错误的。

关于 gets() 的使用,我不会重复其他人已经告诉您的内容,它仍在标准库中,并且知道您做什么,您仍然可以在适当控制的环境中使用它。尽管如此,其他人给你的建议在这里并不适用,因为你没有溢出你使用的短缓冲区(只有 34 个字符,太短,太容易挂起程序或崩溃)

stdio 中的函数使用缓冲区,unix tty 驱动程序也干扰了这里。在您按下 <ENTER> 键之前,您的终端不会使您输入到程序中的任何字符可用,然后程序将所有这些字符读入缓冲区。它们从缓冲区中被消耗,直到它为空,所以如果你一个一个地读取它们(使用 fgetch(),或者一次读取它们(使用 fgets() ---我会从现在开始使用这个更安全的功能)只要您按下 <ENTER> 键,一切都会发生。

fgetch() 只需要一个字符,所以如果有多个可用字符,则只从缓冲区中取出一个字符,其余的等待轮到他们。但是 fgets() 读取所有(并填充缓冲区)直到读取 \n(这就是为什么 gets() 如此危险,因为它不知道缓冲区的大小/它不知道'没有指示缓冲区大小的参数,因为 fgets() 具有/并且无法控制读取在溢出之前停止)

因此,在您的情况下,当您按下一系列字符,然后按回车键时,第一个样本读取完整字符串,然后第二个 getchar() 取第二行的第一行(但您需要在该点输入两行完整的行)第二个示例在调用 getchar() 时读取第一个字符,在调用 gets() 时读取该行的其余部分。

要一次读取一个字符,而无需等待输入整行,终端驱动程序必须编程为以原始模式读取字符。 unix 使用 Cookied 模式(默认)读取完整行,这允许您编辑、删除行上的字符,并且只有在您准备好并按下 <ENTER> 键时才输入它。

如果您有兴趣从终端一个一个地读取字符,请阅读手册页 termios(4),其中解释了 tty 设备的接口和 iocontrols。 curses 库进行必要的内务处理以将终端置于原始模式,以允许 vi(1) 之类的程序按字符读取输入字符,但您不需要直接使用 stdio,因为它的缓冲系统会吃掉您的字符试着用诅咒来吃饭。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...