简体   繁体   中英

Accepting a single character in C?

What I intend to do is to get the character entered and used it as a pattern. I've tried using getchar() but it won't work. I've hear of using scanf but it skips and stops whenever I press "shift" for the special characters on my keyboard.

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");
}

You need to assign the value returned by getchar to the variable c , and you had a redundent call to getchar that's why it skips reading the desired input:

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");
}

You can use %c with scanf :

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

This eliminates the need for the two getchar calls.

The space is required ; it tells scanf to skip whitespace.

The problem you have is that your assumptions on getchar(3) are incorrect. You think getchar() is going to return the next key pressed in the input stream, but you are incorrectly assuming that it will be done without buffering or system processing (the terminal driver gives the program complete lines, or even worse, if you are reading from a file, complete buffer blocks, that have to be buffered so you miss no characters from the input stream)

You are assuming incorrectly that the end of line you need to press for the input to be feeded to the program does not count in the input stream.

What actually happens is:

  • you feed a complete line (because the kernel driver works that way) so you press your character, and then you see nothing, not after you have pressed the return key.
  • once you press it, you have more than one character (depending on how many you pressed before hitting the return key) that will stay in the buffer, until they are so consumed by the program. Normally this happens when you have executed more getchar() or scanf() statements.

The idea of this buffering mechanism is to allow a programmer to process character by charcacter large amounts of text, without the overhead of making a system call per character reading (this is a costly operation) so think of getchar() not as a sample function to get new users introduced to the world of programming, but as a hint to experienced programmers to use efficiently without having to think on buffering large amounts of text.

With stdio package, every character counts, so you have to think slowly and minuciously when you feed input to getchar(3) .

The next question is: Right, then how can I solve and stop my program until I press some key? The first answer, with the set of tools you have exposed here is, be careful on what you input , instead of asking for any key, ask the user to press the return key, and then, do something like:

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 */

or, if you prefer, you can write a function just to do this (as there can be so many criteria to implement it, nobody included such a function in the standard C library, my apologies for that. ;) )

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 */

In order to wait for any character and in raw mode, you need first to ensure your input comes from a terminal (you cannot do the following on a normal file), then you have to switch the terminal driver to raw mode, so each character is given immediately to the program and no line editing processing is done, and then set the stdin descriptor to no buffering at all. Only then, you can receive individual characters with getchar(3) , one by one, as they are keyed in. I think this is far out of the scope of this question, as the code to do that is far more complex than the above.

EDIT

Following is a complete sample of a program that uses raw input to process characters as they are keyed in.

/* 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 */

The full source code can be also downloaded from here . You can use this program to see how input keys get mapped into characters, as you'll note that when you press the enter key, the raw input is [0d] (ascii char 13, CARRY RETURN) while in normal line mode you get '\\n' which is [0a] or ASCII LINE FEED, instead (you can check this if you redirect input from the pru.c text file). Also you'll see that you are unable to specify EOF from the terminal driver with Ctrl-D and that Ctrl-C does not come to help. Well, I have included a safeguard, by ending the program in case you press the ESC key, which generates an ASCII ESCAPE character ( \\033 ). This is also commented in the source code.

All of this processing is done by the kernel driver, so all unix implementations get the same line end characters or interpret the control characters the same way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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