Sunday, January 13, 2013

Reading from Console in ANSI C

The ANSI C standard I/O library (stdio.h) provides us with 3 powerful functions for reading data from STDIN (the stream used for the console):
-Reading a character: getchar
-Reading formatted input: scanf
-Reading an entire line: gets

Here's a short example on how you can use any of these functions to read a string:
#include<stdio.h>

int main(void)
{
    const int LENGTH = 100;

    //The variable where the string will be stored
    char myString[LENGTH];
    //Iterator for reading with getchar
    int i;

    //Reading with getchar
    puts("Reading the string using getchar: ");
    for(i=0; i<(LENGTH-1); i++)
    {
        myString[i] = getchar();
        //Stops reading when the user presses ENTER
        if(myString[i]=='\n')
        {
            //Adds the null terminator
            myString[i+1]='\0';
            break;
        }
    }
    puts(myString);

    //Reading with gets
    puts("Reading the string using gets: ");
    gets(myString);
    puts(myString);

    //Reading with scanf
    puts("Reading the string using scanf: ");
    scanf("%s",myString);
    puts(myString);

    return 0;
}
If you don't know what puts does, you may like to read this first.

When to use getchar?
getchar is a great function to be used when you want to limit the user input or you want to validate it (or you just want to process the input character by character). Here are some examples:
-You built a console menu, just like the one in here, and you want to read only one character
-You want to stop the user input if a certain character is encountered
#include<stdio.h>

int main(void)
{
    const int LENGTH = 200;

    char myText[LENGTH];
    int i = 0;
    //The characters are put into the string until . in encountered
    do
    {
        myText[i] = getchar();
        i++;
    }while(myText[i-1]!='.');
    //Adding the null terminator
    myText[i+1] = '\0';
    puts(myText);
    return 0;
}
-You want to make sure that a buffer overflow doesn't happen :-) . We will speak more about this when we get to gets.
#include<stdio.h>

int main(void)
{
    const int LENGTH = 5;

    char pinNumber[LENGTH];
    int i;
    //Only 4 characters are read
    for(i=0; i<(LENGTH-1); i++)
    {
        pinNumber[i] = getchar();
    }
    //Adding the null terminator
    pinNumber[LENGTH-1] = '\0';
    puts(pinNumber);
    return 0;
}
-You want to extract only certain characters from the user's input:
#include<stdio.h>

int main(void)
{
    const int LENGTH = 5;

    char pinNumber[LENGTH];
    int i = 0;
    char buffer;
    //Only 4 characters are read and they should be digits!
    while( (i<(LENGTH-1)) )
    {
        buffer=getchar();
        if( (buffer>='0') && (buffer<='9') )
        {
            pinNumber[i] = buffer;
            i++;
        }
    }
    //Adding the null terminator
    pinNumber[LENGTH-1] = '\0';
    puts(pinNumber);
    return 0;
}
/*Example Input:
  The b1rd 1s th3 w0rd.
  Output
  1130
*/
TIP : Don't forget to add the null terminator to your string! If you don't know what a null terminator is, read this.

When to use scanf?
scanf should be used when you are dealing with number or otherwise other forms of formatted input. If you do not know exactly what formatted input and formatted strings are, see this. Here are some examples:
#include<stdio.h>

int main(void)
{
   //Integers
   int intData1,intData2;
   //Real
   double realData, realData2;
   //Characters
   char charData, charData2;
   //Strings
   char stringData[10], stringData2[10];

   //Read a integer value using the decimal base
   puts("Write a integer: ");
   scanf("%d",&intData1);
   //Read a integer value using the hexadecimal base
   puts("Write a integer in hexadecimal: ");
   scanf("%x",&intData2);
   //Read a real value
   puts("Write a real number: ");
   scanf("%lf",&realData);
   //Discards the last character, before reading a char with scanf
   getchar();
   //Read a character
   puts("Write a character: ");
   scanf("%c",&charData);
   //Read a string. A string is a pointer and you don't need the & operator
   puts("Write a string: ");
   scanf("%s",stringData);
   //Reading multiple values
   puts("Write a string, a real number and a character: ");
   scanf("%s %lf %c",stringData2,&realData2, &charData2);

   printf("Integer data: %d %x\n"
          "Real data: %lf\n"
          "Char data: %c\n"
          "String data: %s\n"
          "Multiple values: %s %lf %c",
          intData1, intData2, realData,charData, stringData,
          stringData2, realData2, charData2);

   return 0;
}
/*Example session:
Write a integer:
12
Write a integer in hexadecimal:
f3
Write a real number:
32.3
Write a character:
x
Write a string:
mystr
Write a string, a real number and a character:
mystr
44.8
c
Integer data: 12 f3
Real data: 32.300000
Char data: x
String data: mystr
Multiple values: mystr2 44.800000 c
*/
There are some certain particularities to scanf:
TIP : Formatted data should be reffered through a pointer (that's why the & operator is used). String are pointers by default and the & operator should not be used on them.
TIP :  You can avoid a buffer overflow, by specifying the maximum size of input when reading a string. In the example below, the limit is 4 characters + the automatically added null terminator:
#include<stdio.h>

int main(void)
{
    char myString[5];
    puts("Input a string: ");
    scanf("%4s", myString);
    puts(myString);
    puts("No buffer overflow");
    return 0;
}
/*Session
Input a string:
ThisIsGroundControlToMajorTom
This
No buffer overflow
*/
TIP :  If you read a character using scanf and before that you read a string or a number, you may observe that the character reading may be "skipped". This happens because the input buffer contains a newline character that remained from the previous reading (if you watch this with the debugger you shall see that your character shall be equal to 10 which is the equivalent of newline). Use getchar calls to discard extra characters from the input buffer.
PITFALL :  If you read a string with scanf and it encounters a whitespace character, the function will consider it as a terminator. Use scanf to read words, not sentences :-)

When to use gets?
The main purpose of gets is to read strings that contain whitespace. Here's an example:
#include<stdio.h>

int main(void)
{
    const int LENGTH = 200;

    char myString1[LENGTH];
    char myString2[LENGTH];

    puts("Input a string: ");
    gets(myString1);

    puts("Input the same string again: ");
    scanf("%199s",myString2);

    puts("String read with gets: ");
    puts(myString1);
    puts("String read with scanf: ");
    puts(myString2);

    return 0;
}
/*Session
Input a string:
The bird is the word
Input the same string again:
The bird is the word
String read with gets:
The bird is the word
String read with scanf:
The
 */
PITFALL:Unfortunately gets has many inherent problems related to buffer overflow, since you cannot specify the maximum number of character to read. This article explains it in more detail.
TIP: Just like in scanf, a null terminator is added at the end of the string replacing the newline character.
TIP: A safer alternative to gets is fgets. Here's how you can replace gets with fgets in the program above:
#include<stdio.h>

int main(void)
{
    const int LENGTH = 200;

    char myString1[LENGTH];
    char myString2[LENGTH];

    puts("Input a string: ");
    fgets(myString1, LENGTH-1, stdin);

    puts("Input the same string again: ");
    scanf("%199s",myString2);

    puts("String read with gets: ");
    puts(myString1);
    puts("String read with scanf: ");
    puts(myString2);

    return 0;
}
Question: In which situations do you use gets, in which situations scanf and in which situations getchar?

References:
http://www.cplusplus.com/reference/cstdio/
http://c-faq.com/stdio/index.html
http://faq.cprogramming.com/cgi-bin/smartfaq.cgi
Related Posts Plugin for WordPress, Blogger...