84

在接下来的代码中:

#include <stdio.h>

int main(void) {   
  int c;   
  while ((c=getchar())!= EOF)      
    putchar(c); 
  return 0;
}

我必须按Enter打印我输入的所有字母getchar,但我不想这样做,我想做的是按下字母并立即看到我介绍的字母重复而不按Enter。例如,如果我按下字母“a”,我想在它旁边看到另一个“a”,依此类推:

aabbccddeeff.....

但是当我按'a'时什么也没有发生,我可以写其他字母并且副本仅在我按时出现Enter

abcdef
abcdef

我怎样才能做到这一点?

我正在使用cc -o example example.cUbuntu 下的命令进行编译。

4

13 回答 13

104

这取决于您的操作系统,如果您在类似 UNIX 的环境中,默认情况下启用 ICANON 标志,因此输入被缓冲直到下一个'\n'or EOF。通过禁用规范模式,您将立即获得字符。这在其他平台上也是可能的,但没有直接的跨平台解决方案。

编辑:我看到你指定你使用 Ubuntu。我昨天刚刚发布了类似的内容,但请注意,这将禁用终端的许多默认行为。

#include<stdio.h>
#include <termios.h>            //termios, TCSANOW, ECHO, ICANON
#include <unistd.h>     //STDIN_FILENO


int main(void){   
    int c;   
    static struct termios oldt, newt;

    /*tcgetattr gets the parameters of the current terminal
    STDIN_FILENO will tell tcgetattr that it should write the settings
    of stdin to oldt*/
    tcgetattr( STDIN_FILENO, &oldt);
    /*now the settings will be copied*/
    newt = oldt;

    /*ICANON normally takes care that one line at a time will be processed
    that means it will return if it sees a "\n" or an EOF or an EOL*/
    newt.c_lflag &= ~(ICANON);          

    /*Those new settings will be set to STDIN
    TCSANOW tells tcsetattr to change attributes immediately. */
    tcsetattr( STDIN_FILENO, TCSANOW, &newt);

    /*This is your part:
    I choose 'e' to end input. Notice that EOF is also turned off
    in the non-canonical mode*/
    while((c=getchar())!= 'e')      
        putchar(c);                 

    /*restore the old settings*/
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt);


    return 0;
}

您会注意到,每个字符出现两次。这是因为输入会立即回显到终端,然后您的程序putchar()也会将其放回。如果你想解除输入和输出的关联,你还必须打开 ECHO 标志。您可以通过简单地将适当的行更改为:

newt.c_lflag &= ~(ICANON | ECHO); 
于 2009-11-25T18:03:26.680 回答
74

在 linux 系统上,您可以使用stty命令修改终端行为。默认情况下,终端将缓冲所有信息,直到Enter被按下,甚至在将其发送到 C 程序之前。

一个快速、肮脏且不是特别便携的示例,用于从程序本身内部更改行为:

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

int main(void){
  int c;
  /* use system call to make terminal send all keystrokes directly to stdin */
  system ("/bin/stty raw");
  while((c=getchar())!= '.') {
    /* type a period to break out of the loop, since CTRL-D won't work raw */
    putchar(c);
  }
  /* use system call to set terminal behaviour to more normal behaviour */
  system ("/bin/stty cooked");
  return 0;
}

请注意,这并不是真正的最佳选择,因为它只是假设这stty cooked是您在程序退出时想要的行为,而不是检查原始终端设置是什么。此外,由于在原始模式下会跳过所有特殊处理,因此如果没有在程序中显式处理它们,许多键序列(例如CTRL-CCTRL-D)实际上不会像您期望的那样工作。

您可以man stty更好地控制终端行为,具体取决于您想要实现的目标。

于 2009-11-25T18:31:10.960 回答
13

getchar() 是一个标准函数,在许多平台上需要您按 ENTER 才能获取输入,因为平台会缓冲输入,直到按下该键。许多编译器/平台支持不关心 ENTER 的非标准 getch() (绕过平台缓冲,将 ENTER 视为另一个键)。

于 2009-11-25T17:23:08.340 回答
6

I/O 是一种操作系统功能。在许多情况下,操作系统不会将键入的字符传递给程序,直到按下 ENTER。这允许用户在将输入发送到程序之前修改输入(例如退格和重新输入)。对于大多数目的,这很有效,向用户提供了一致的界面,并使程序不必处理这个问题。在某些情况下,程序希望在按键被按下时从按键中获取字符。

C 库本身处理文件,而不关心数据如何进入输入文件。因此,语言本身无法在按键时获取按键;相反,这是特定于平台的。由于您没有指定操作系统或编译器,我们无法为您查找。

此外,标准输出通常被缓冲以提高效率。这是由 C 库完成的,因此有一个 C 解决方案,即fflush(stdout);在写入每个字符之后。之后,字符是否立即显示取决于操作系统,但我熟悉的所有操作系统都会立即显示输出,所以这通常不是问题。

于 2009-11-25T17:32:56.117 回答
6

我喜欢卢卡斯的回答,但我想详细说明一下。termios.h在namedcfmakeraw()中有一个内置函数, man描述为:

cfmakeraw() sets the terminal to something like the "raw" mode of the
old Version 7 terminal driver: input is available character by
character, echoing is disabled, and all special processing of
terminal input and output characters is disabled. [...]

这与 Lucas 建议的基本相同,您可以在手册页中看到它设置的确切标志:termios(3)

用例

int c = 0;
static struct termios oldTermios, newTermios;

tcgetattr(STDIN_FILENO, &oldTermios);
newTermios = oldTermios;

cfmakeraw(&newTermios);

tcsetattr(STDIN_FILENO, TCSANOW, &newTermios);
c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldTermios);

switch (c) {
    case 113: // q
        printf("\n\n");
        exit(0);
        break;
    case 105: // i
        printf("insert\n");
        break;
    default:
        break;
于 2016-07-11T21:15:55.250 回答
4

由于您正在使用 Unix 衍生产品(Ubuntu),因此这是一种方法 - 不推荐,但它会起作用(只要您可以准确键入命令):

echo "stty -g $(stty -g)" > restore-sanity
stty cbreak
./your_program

当你对它感到厌烦时,使用中断来停止程序。

sh restore-sanity
  • 'echo' 行将当前终端设置保存为将恢复它们的 shell 脚本。
  • 'stty' 行关闭大部分特殊处理(Control-D例如,因此没有效果),并在字符可用时立即将它们发送到程序。这意味着你不能再编辑你的打字了。
  • 'sh' 行恢复了您原来的终端设置。

如果 'stty sane' 为您的目的足够准确地恢复您的设置,您可以节省开支。'-g' 的格式不能在 'stty' 的版本之间移植(因此在 Solaris 10 上生成的内容不能在 Linux 上运行,反之亦然),但这个概念无处不在。'stty sane' 选项并非普遍可用,AFAIK(但在 Linux 上)。

于 2009-11-25T18:23:11.897 回答
2

您可以包含“ncurses”库,并使用getch()而不是getchar().

于 2009-11-25T19:20:33.000 回答
2

如何避免Entergetchar()压着

首先,终端输入通常是线路或完全缓冲的。这意味着操作系统将来自终端的实际输入存储到缓冲区中。\n通常,当 fe在 中发出信号/提供时,此缓冲区会刷新到程序中stdin。这是 fe 制作的Enter

getchar()只是在链的末端。它无法实际影响缓冲过程。


我怎么能这样?

首先getchar()放弃,如果您不想使用特定的系统调用来明确更改终端的行为,就像其他答案中很好解释的那样。

不幸的是,没有标准库函数,也没有可移植的方法来在单个字符输入时刷新缓冲区。但是,也有基于实现的和不可移植的解决方案。


在 Windows/MS-DOS 中,头文件中有getch()getche()函数conio.h,它们完全符合您的要求 - 读取单个字符而无需等待换行符刷新缓冲区。

getch()和之间的主要区别在于getche()getch()不会立即在控制台中输出实际的输入字符,而getche()会。附加"e"代表echo

例子:

#include <stdio.h>
#include <conio.h>   

int main (void) 
{
    int c;

    while ((c = getche()) != EOF)
    {
        if (c == '\n')
        {
            break;
        }

        printf("\n");
    }

    return 0;
}

在Linux 中,获得直接字符处理和输出的一种方法是使用ncurses 库中的cbreak()andecho()选项和getch()andrefresh()例程。

请注意,您需要使用 初始化所谓的标准屏幕,并使用例程initscr()关闭它。endwin()

例子:

#include <stdio.h>
#include <ncurses.h>   

int main (void) 
{
    int c;

    cbreak(); 
    echo();

    initscr();

    while ((c = getch()) != ERR)
    {
        if (c == '\n')
        {
            break;
        }

        printf("\n");
        refresh();
    }

    endwin();

    return 0;
}

注意:您需要使用该-lncurses选项调用编译器,以便链接器可以搜索并找到 ncurses-library。

于 2020-05-29T15:35:06.600 回答
1

我在我目前正在处理的任务中遇到了这个问题/问题。它还取决于您从哪个输入中获取。我在用

/dev/tty

在程序运行时获取输入,因此需要是与命令关联的文件流。

在我必须测试/定位的 ubuntu 机器上,它需要的不仅仅是

system( "stty -raw" );

或者

system( "stty -icanon" );

我必须添加 --file 标志以及命令的路径,如下所示:

system( "/bin/stty --file=/dev/tty -icanon" );

现在一切都好起来了。

于 2012-02-14T17:39:34.063 回答
1

是的,您也可以在 Windows 上执行此操作,这是下面的代码,使用 conio.h 库

#include <iostream> //basic input/output
#include <conio.h>  //provides non standard getch() function
using namespace std;

int main()
{  
  cout << "Password: ";  
  string pass;
  while(true)
  {
             char ch = getch();    

             if(ch=='\r'){  //when a carriage return is found [enter] key
             cout << endl << "Your password is: " << pass <<endl; 
             break;
             }

             pass+=ch;             
             cout << "*";
             }
  getch();
  return 0;
}
于 2011-06-10T15:36:18.417 回答
0

可以创建一个新函数来检查Enter

#include <stdio.h>

char getChar()
{
    printf("Please enter a char:\n");

    char c = getchar();
    if (c == '\n')
    {
        c = getchar();
    }

    return c;
}

int main(int argc, char *argv[])
{
    char ch;
    while ((ch = getChar()) != '.')
    {
        printf("Your char: %c\n", ch);
    }

    return 0;
}
于 2021-02-02T16:11:49.993 回答
0

这段代码对我有用。注意:这不是标准库的一部分,即使大多数编译器(我使用 GCC)都支持它。

#include <stdio.h>
#include <conio.h>
int main(int argc, char const *argv[]) {
    char a = getch();
    printf("You typed a char with an ASCII value of %d, printable as '%c'\n", a, a);
    return 0;
}

此代码检测第一次按键。

于 2019-12-07T20:58:49.377 回答
-1

默认情况下,C 库会缓冲输出,直到看到返回为止。要立即打印出结果,请使用fflush

while((c=getchar())!= EOF)      
{
    putchar(c);
    fflush(stdout);
}
于 2009-11-25T17:22:50.650 回答