繁体   English   中英

接受C中的单个字符?

[英]Accepting a single character in C?

我打算做的是输入字符并将其用作模式。 我已经尝试过使用getchar(),但是它不起作用。 我听说过使用scanf,但是每当我按下键盘上的特殊字符“ shift”时,它就会跳过并停止。

int i, j, n;
char c;
c = getchar();
printf("Enter value of n: ");
scanf("%d", &n);

printf("Enter a Character: ");
getchar();

for(i=1; i<=n; i++)
{
    for(j=1; j<=i; j++)
    {
        printf("%c", c);
    }
    printf("\n");
}

您需要将getchar返回的值分配给变量c ,并且对getchar进行了冗余调用,这就是它跳过读取所需输入的原因:

int i, j, n;
char c;
printf("Enter value of n: ");
scanf("%d", &n);

printf("Enter a Character: ");
scanf(" %c", &c);

for(i=1; i<=n; i++)
{
    for(j=1; j<=i; j++)
    {
        printf("%c", c);
    }
    printf("\n");
}

您可以将%cscanf

scanf("%d %c", &n, %c);

这消除了对两个getchar调用的需要。

需要空间; 它告诉scanf跳过空格。

您遇到的问题是您对getchar(3)假设不正确。 您认为getchar()将返回输入流中按下的下一个键,但是您错误地认为它将在没有缓冲或系统处理的情况下完成(终端驱动程序为程序提供了完整的行,或者更糟糕的是,从文件中读取,必须缓冲完整的缓冲块,因此您不会从输入流中丢失任何字符)

您错误地假设为输入到程序而需要按的行尾不计入输入流。

实际发生的是:

  • 您输入了完整的一行(因为内核驱动程序以这种方式工作),所以您按字符,然后什么也没看到,而不是按return键之后。
  • 一旦按下它,您将有一个以上的字符(取决于按下返回键之前按下的字符数)保留在缓冲区中,直到被程序占用为止。 通常,当您执行更多的getchar()scanf()语句时,会发生这种情况。

这种缓冲机制的思想是允许程序员通过字符处理大量文本来处理字符 ,而不会因每次字符读取而进行系统调用(这是一项昂贵的操作)的开销,因此请考虑将getchar()视为示例函数让新用户了解编程领域,但这是对经验丰富的程序员的有效提示,而无需考虑缓冲大量文本。

使用stdio软件包,每个字符都很重要,因此在将输入提供给getchar(3)时,您必须缓慢而谨慎地思考。

下一个问题是: 对,那么我如何解决并停止我的程序,直到按下某个键? 使用此处公开的工具集的第一个答案是,请小心输入的内容 ,而不是要求任何键,而要用户按回车键,然后执行类似的操作:

printf("Hit <ENTER> to continue"); fflush(stdout); /* so we get the line out, bypassing the buffering mechanism */
int c;
while ((c = getchar()) != EOF && c != '\n') {
    /* just ignore the character we have received */
}
/* c == '\n' || c == EOF, so we can continue */

或者,如果你愿意,你可以写一个函数只是要做到这一点(因为可能有实现这么多的标准,无人包含在标准C库这样的功能,我该道歉。 ;)

void wait_for_enter()
{
    /* I use stderr, for two reasons:
     *   o stderr is normally unbuffered, so there's no need to fflush()
     *   o stdout can be redirected, so the prompt will not be visible in
     *     case you want to save the output of your program.
     */
    fprintf(stderr, "Hit <ENTER> to continue"); 
    int c;
    while ((c = getchar()) != EOF && c != '\n') {
        /* just ignore the character we have received 
         * until we get the end of file (ctrl-d at the terminal)
         * or a new line */
    }
    /* c == '\n' || c == EOF, so we can continue */
    /* it's assumed that the user pressed the enter key, so the echoed
     * enter already did a newline, no need to do it here */
} /* wait_for_enter */

为了在原始模式下等待任何字符 ,首先需要确保输入来自终端(您不能在普通文件上执行以下操作),然后必须将终端驱动程序切换到原始模式,因此每个字符立即将其提供给程序,并且不执行任何行编辑处理,然后将stdin描述符设置为完全没有缓冲。 只有这样,您才能通过键入键入getchar(3)来接收单个字符。我认为这超出了此问题的范围,因为执行此操作的代码比上面的复杂得多。

编辑

以下是该程序的完整示例,该程序使用原始输入来处理键入的字符。

/* pru.c -- program to show raw input from the terminal.
 * Author: Luis Colorado <luiscoloradourcola@gmail.com>
 * Date: Fri Sep 20 08:46:06 EEST 2019
 * Copyright: (C) 2019 Luis Colorado.  All rights reserved.
 * License: BSD.
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>  /* see termios(3) for a description on terminal conf */

#define F(_fmt) __FILE__":%d:%s: " _fmt, __LINE__, __func__

/* this function switches the terminal into raw mode and returns a malloc(3)ed
 * terminal configuration, so it can be later restored. BEWARE that the returned
 * configuration info must be deallocated by free(3) once it's not needed anymore.
 * In case of failure of any system call, the function returns NULL, and errno is
 * set to the failing cause.  */
struct termios *set_raw(int fd)
{
    struct termios *ret = malloc(sizeof *ret), cfg;
    if (!ret) return NULL;

    int res = tcgetattr(fd, &cfg);
    if (res < 0) goto error;

    *ret = cfg; /* save it for return */

    cfmakeraw(&cfg);

    /* set it after all buffered characters in the driver have drained out */
    res = tcsetattr(fd, TCSADRAIN, &cfg);
    if (res < 0) goto error;

    return ret;

error:
    free(ret);
    return NULL;
} /* set_raw */

/* restores the configuration back to the associated file descriptor */
int restore_cfg(int fd, struct termios *cf)
{
    /* set it after all buffered characters in the driver have drained out */
    return tcsetattr(fd, TCSADRAIN, cf);
} /* restore_cfg */ 

int main()
{
    struct termios *cfg = set_raw(fileno(stdin));
    if (!cfg) {
        fprintf(stderr, F("stdin: %s\n"),
                strerror(errno));
    }

    setbuf(stdin, NULL); /* stdin unbuffered */
    setbuf(stdout, NULL); /* stdout unbuffered */

    /* BEWARE that raw mode doesn't process any characters, so no Ctrl-C(interrupt), Ctrl-D(EOF), etc.
     * will be available, only if you read from a file, you'll get EOF, but you'll not be able to produce
     * that on the terminal, you'll need to swith to another console and kill the process. */
    int c;
    while ((c = getchar()) != EOF && c != '\033') {  /* ESCAPE key('\033') is a safeguard to end input */
        /* print the input char as an hex number */
        printf("[%02x]", c);
    }

    if (cfg) { /* if we were able to set the terminal to raw mode */
        /* restore config */
        restore_cfg(fileno(stdin), cfg);

        /* and free it */
        free(cfg);
    }

    exit(EXIT_SUCCESS);
} /* main */

完整的源代码也可以从这里下载。 您可以使用该程序查看输入键如何映射为字符,因为您会注意到,当您按enter键时,原始输入为[0d] (aschari char 13,CARRY RETURN),而在常规行模式下,您将获得'\\n'表示为[0a]或ASCII LINE FEED(如果您从pru.c文本文件中重定向输入,则可以进行检查)。 另外,您还将看到无法使用Ctrl-D从终端驱动程序中指定EOF ,并且Ctrl-C不能提供帮助。 好吧,我已经包含了一项保护措施,可以在您按ESC键时结束程序,该程序会生成ASCII ESCAPE字符( \\033 )。 在源代码中也对此进行了注释。

所有这些处理都是由内核驱动程序完成的,因此所有的UNIX实现都获得相同的行尾字符或以相同方式解释控制字符。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM