Take a look a this piece of code, which is supposed to get the date of birth of a user, in the form of three variables belonging to a struct.
printf("Insert date of birth in this format: dd/mm/yyyy\n");
scanf("%d/%d/%d", &user.bDay, &user.bMonth, &user.bYear);
while(isalpha(user.bDay)==1||isalpha(user.bMonth)==1||isalpha(user.bYear)==1){
puts("Invalid input");
scanf("%d/%d/%d", &user.bDay, &user.bMonth, &user.bYear);
}
What if the user types characters? Something like 1q/02/199i? I wanted to make the program print an error message and simply asking the user to insert his date of birth again.
I tried using isalpha function, as you can see in the code, but it doesn't work, the program crashes and prints "Invalis input" infinite times. How can I make the user insert only integers?
scanf is scanning the input string for integers. Considering that, I suppose that your struct is a struct of int, or uint16_t, or something of the sort.
In any case, 'checking' these integers with isalpha, as if they were characters, is not going to help you. Just a small example: if the user had written '9' (which is 57 in ASCII), scanf will convert this to 9. 9 ASCII is not a digit, nor an alphanumeric character.
Also, to confirm that you have a digit, you want to check it with isdigit. Using isalpha to see that it is not a digit, is insufficient; the user might have entered a symbol of some kind, which is neither an alphabetic character nor a numeric digit.
However, these checks must be done on the character the user entered. They can not be done on the integer variables filled in by scanf.
If you read the user input as a string, you can parse it yourself and do whatever validation you would like (for example, use strtok to split it at the slashes, and then use isdigit on each character in each token).
Alternately, you can use scanf, and then check your three integers, that they are each in the expected range (1-31 for day, 1-12 for month, etc.).
Or, you can do away with the formatted string, and separately ask the user for day, month, and year, validating each one separately.
How to stop user from inserting characters instead of integers
Read the line of user input into a string with fgets().
scanf() is difficult to use with good error handling.
char buffer[100];
if (fgets(buffer, sizeof buffer, stdin)) {
Then test the string's validity:
if (sscanf(buffer, "%d /%d /%d", &user.bDay, &user.bMonth, &user.bYear) == 3) {
Success();
}
else {
puts("Invalid input");
}
}
Add additional tests as desired like range limits and a test for trailing junk.
char junk;
if (sscanf(buffer, "%2d /%2d /%4d %c", &user.bDay, &user.bMonth, &user.bYear, &junk) == 3
&& user.bDay >= 1 && user.bMonth >= 1 && user.bMonth <= 12 /* ... */) {
Related
I came across this problem when I want to check what I input is number. The scanf function will return 1 if I successfully input a number. So here is what I wrote:
int argu;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
I looked it up online and found that it had something to do with the cache and I need to clean it up so scanf can get new data. So I tried fflush but it didn't work.
Then I saw this:
int argu,j;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
while((j=getchar())!='\n' && j != '\n');
}
Then when I input things like 'abcd' it worked well and it prompted for my input. But when I input things like '12ab', it wouldn't work again.
So is there a way I can check the input for scanf("%d", &argu) is actually a number and prompt for another input if it isn't?
EDIT:
I saw the answers and solved my problem by using while(*eptr != '\n').
Notice that the fgets function actually reads '\n' into the array and gets doesn't. So be careful.
It's better to read a full line, using fgets(), and then inspecting it, rather than trying to parse "on the fly" from the input stream.
It's easier to ignore non-valid input, that way.
Use fgets() and then just strtol() to convert to a number, it will make it easy to see if there is trailing data after the number.
For instance:
char line[128];
while(fgets(line, sizeof line, stdin) != NULL)
{
char *eptr = NULL;
long v = strtol(line, &eptr, 10);
if(eptr == NULL || !isspace(*eptr))
{
printf("Invalid input: %s", line);
continue;
}
/* Put desired processing code here. */
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
That's because if scanf encounters a character that does not match the conversion specifier, it leaves it in the input stream. Basically, what's happening is that scanf reads the character a from the input stream, determines that it's not a valid match for the %d conversion specifier, and then pushes it back onto the input stream. The next time through the loop it does the same thing. And again. And again. And again.
fflush is not a good solution, because it isn't defined to work on input streams.
For the input "12ab", scanf will read and convert "12", leaving "ab" in the input stream.
The best solution is to read all your input as text, then convert to numeric types using strtol (for integral values) and strtod (for real values). For example:
char input[SIZE]; // assume SIZE is big enough for whatever input we get
int value;
if (fgets(input, sizeof input, stdin) != NULL)
{
char *chk;
int tmp = (int) strtol(input, &chk, 10);
if (isspace(*chk) || *chk == 0)
value = tmp;
else
printf("%s is not a valid integer string\n", input);
}
chk points to the first character in the input stream that isn't a decimal digit. If this character is not whitespace or the 0 terminator, then the input string wasn't a valid integer. This will detect and reject inputs like "12ab" as well as "abcd".
scanf is a good solution if you know your input is always going to be properly formed and well-behaved. If there's a chance that your input isn't well-behaved, use fgets and convert as needed.
I will suggest to get input as a string and check for non-numeric characters in it. If input is valid convert string to int by sscanf(str,"%d",&i); or else diplay error.
Just call scanf("%*[^\n]\n") inside the loop, and it will discard the "cache".
Call scanf("%*[^\n]\n") inside the loop. This should be enough to discard anything associated with the cache.
I'm trying to use fgets in a function to obtain input in C. My code displays properly, however it does not follow my parameters for the do/while loop (if I enter a number outside of the range, it proceeds with the program, rather than repeating the loop until the input is proper). I have tried tinkering with it, and I can't figure out what my issue is. I think it might have something to do with it taking the input as a character and not as an integer. But I tried casting my entry variable into an int, and that was no help. Any assistance is greatly appreciated!
{
char line[MAX_LINE];
int entry;
do
{
printf("Please enter %s(%d-%d) ", descriptionCPtr, low, high);
fgets(line, MAX_LINE, stdin);
entry = (int)line[0];
}
while(entry < low || entry > high);
return(entry);
}
Your intuition is correct. You are not properly converting user input to a numeric value. fgets reads user input and stores it (along with \n) into line variable; an array of characters. Let's assume the user is an old man entering his age 99, so line will be an array of 9, 9, \n and \0 characters. In the next line however, you are casting line[0] which is character 9 (with ASCII code 57) into an integer and assigning 57 to entry. Well, this probably is not what you intend to achieve here.
My recommended solution for this case is to use scanf instead of fgets.
int age;
scanf("%d", &age);
if (age < min_age || age > max_age)
printf("age not valid\n");
In case you want to stick with fgets, you can remove the new line character and replace casting with calling functions like atoi (which is not really safe by the way) to convert line into integer.
I am writing a super simple command line based program in C. It's just a small test and the code is very simple. So what it is meant to do is to ask the user for their name, maths grade, english grade, computing grade. Then it figures out their average grade and also tells them the name they entered. Yes I know this is an extremely simple program, but I'm still doing something wrong.
The problem is, one part of my code will run first telling the user to enter their name and then once they do this and press enter the rest of my code will run all at once and then stop working. It's weird I just don't understand what is wrong.
#include <stdio.h>
int main(int argc, const char * argv[])
{
char chr;
char firstname;
int mathsmark, englishmark, computingmark, averagemark;
printf("What is your name?\n");
scanf("%c", &firstname);
printf("\n");
printf("What is your maths mark?\n");
scanf("%d", &mathsmark);
printf("\n");
printf("What is your english mark?\n");
scanf("%d", &englishmark);
printf("\n");
printf("What is your computing mark?\n");
scanf("%d", &computingmark);
printf("\n");
printf("Your name is: %c", firstname);
printf("\n");
averagemark = (mathsmark + englishmark + computingmark) / 3;
printf("%d", averagemark);
printf("\n");
chr = '\0';
while (chr != '\n') {
chr = getchar ();
}
return 0;
}
One major problem is that you've declared firstname to be a single character long, and when you try to read the name from the console, you're using the %c conversion specifier, which reads the next single character from the input stream and stores it to firstname. The remainder of the name is left in the input stream to foul up the remaining scanf calls.
For example, if you type "Jacob" as a first name, then the first scanf call assigns J to firstname, leaving "acob\n" in the input stream.
The next scanf call attempts to convert "acob\n" to an integer value and save it to mathsmark, which fails ("acob\n" is not a valid integer string). Same thing happens for the next two scanf calls.
The last loop
while (chr != '\n')
{
chr = getchar();
}
finally consumes the rest of "acob\n", which contains the newline character (because you hit Enter after typing the name), causing the loop and program to exit.
How do you fix this?
First, you need to declare firstname as an array of char:
char firstname[SOME_SIZE] = {0};
where SOME_SIZE is large enough to handle all your cases. The you need to change scanf call to
scanf("%s", firstname);
This tells scanf to read characters from the input stream up to the next whitespace character and store the results to the firstname array. Note that you don't need to use the & operator here; under most circumstances, an expression of array type will be converted ("decay") to an expression of pointer type, and the value of the expression will be the address of the first element in the array.
Note that scanf is not very safe, and it's not very robust. If you enter more characters than your buffer is sized to hold, scanf will happily store those extra characters to memory following the array, potentially clobbering something important. You can guard against this by using an explicit field width in the conversion specifier, like
scanf(*%29s", firstname);
but in general it's a pain.
scanf is also not very good at detecting bad input. If you enter "12er" as one of your marks, scanf will convert and assign the "12", leaving the "er" in the stream to foul up the next read.
scanf returns the number of successful assignments, so one way to guard against bad input is to check the return value, like so:
if (scanf("%d", &mathmarks) != 1)
{
printf("Bad input detected for math marks\n");
}
Unfortunately, scanf won't remove bad characters from the stream; you'll have to do that yourself using getchar or similar.
This is a common mistake amongst newer C/C++ developers. The scanf function detects you hitting the ENTER/RETURN key to signal the end of input, but it also catches the \n character as well at the end of the input string, so you essentially get two RETURNS being detected.
Please read up on an example of using fgets and sscanf here:
http://www.linuxforums.org/forum/programming-scripting/67560-problem-scanf.html
It will resolve this issue very quickly for you. In the meantime, I strongly urge you to check out this book:
http://www.amazon.com/Primer-Plus-5th-Stephen-Prata/dp/0672326965
It is the most commonly used C programming book in high school and colleges in North America, and has TONS of examples for you to work through, including this specific program you demonstrated above. The print version has more examples than the e-book, so I would just cough up the $30.00 for the printed version.
Good luck!
You might want to look at a few tutorials. Maybe one on Format specifiers and one on strings in C
scanf() reads data from stdin and stores them as specified by the format specifiers. In this case:
char firstname;
scanf("%c", &firstname);
Read 1 character from stdin and store it to firstname:
>> What is your first name?
Mike
Now firstname == 'M' because scanf() read 1 character as we requested.
What you wanted to do was read a string (a bunch of characters):
char firstname[5]; // an array of characters
scanf("%s", firstname); // store as a string
firstname[4] = '\0'; // Truncate the result with a NULL to insure no overflow
>> What is your first name?
Mike
Now firstname is [M][i][k][e][\0] because scanf() read 1 string, as we requested.
Note the same holds true for printf(), a printf with a %c will give you one character where as a printf() with a %s will give you all the characters until the NULL terminator.
You have (at least) two choices.
char firstname[number_big_enough_to_hold_long_name];
/*or */
char *firstname = malloc(sizeof(char) * number_big_enough_to_hold_long_name);
/* ... code ... */
free(firstname);
Further it would be best to limit width of read. scanf() does not know the size (available space) of firstname.
scanf("%number_big_enough_to_hold_long_names", ...
/* i.e. */
char firstname[32];
if(scanf("%31s", firstname) == EOF) {
perror("bad");
return 1;
}
Further you should check if there is anything left before trying next read. I.e. If someone enters "My Name" then only "My" will end up in firstname and "Name" will be left in input stream.
And getchar() returns an int not a char.
getchar
scanf
And search "ansi c char arrays tutorial" or similar.
I have an assignment where the user must enter four inputs, one after another. They are:
character, float, float, int.
The main issue is how to check for errors and make sure the used entered valid input?
I have finished the character section but for the floats and ints, how can I check that only numbers are entered and print an error message if letters or symbols are entered?
Thought maybe isdigit() or isaplha() but unsure how to implement their use.
NOTE I have already used scanf() for the input but not sure how to check if input is valid?
If the user is required to enter a string, two floating point numbers and an integer, use
char s[1024];
float f1, f2;
int i;
if (sscanf (buff, "%s %f %f %d", s, &f1, &f2, &i) == 4) {
/* Could scan values as expected. */
} else {
/* Input not as expected. */
}
since sscanf returns the number of successfully scanned values. For the details, see the sscanf manual page. Note that scanning an unbounded string with %s has its problems with large inputs. This may not be an issue for homework assignments, but is definitely something to be aware of in production software.
With sscanf(), you can try to parse the content of a string as some data type, like an integer (with the %d format specifier) or floating point number (with %g).
The return value of sscanf() tells you if it was successful in interpreting the text as the desired data.
You can also use %n to learn how many characters sscanf() looked at, which is handy when you want to analyze in multiple steps.
I don't know how you're getting your values right now other than you're using scanf() as you mentioned in your post. So lets say you're doing something like this:
char buf[100];
scanf("%s", buf);
to get the float/int values. If you want to use isdigit() to verify they are all digit values you can loop as such:
int i = 0;
//need to check for a . for floats
//need to check for a - for negative numbers
while(isdigit(buf[i]) || buf[i] == '.' || buf[i] == '-')
i++;
if(i == strlen(buf)) // if we made it to the end of the string
//we have all digits, do all digit code
else
//there are numbers or symbols, ask for the number again, or terminate, or whatever
I came across this problem when I want to check what I input is number. The scanf function will return 1 if I successfully input a number. So here is what I wrote:
int argu;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
I looked it up online and found that it had something to do with the cache and I need to clean it up so scanf can get new data. So I tried fflush but it didn't work.
Then I saw this:
int argu,j;
while(scanf("%d",&argu)!=1){
printf("Please input a number!\n");
while((j=getchar())!='\n' && j != '\n');
}
Then when I input things like 'abcd' it worked well and it prompted for my input. But when I input things like '12ab', it wouldn't work again.
So is there a way I can check the input for scanf("%d", &argu) is actually a number and prompt for another input if it isn't?
EDIT:
I saw the answers and solved my problem by using while(*eptr != '\n').
Notice that the fgets function actually reads '\n' into the array and gets doesn't. So be careful.
It's better to read a full line, using fgets(), and then inspecting it, rather than trying to parse "on the fly" from the input stream.
It's easier to ignore non-valid input, that way.
Use fgets() and then just strtol() to convert to a number, it will make it easy to see if there is trailing data after the number.
For instance:
char line[128];
while(fgets(line, sizeof line, stdin) != NULL)
{
char *eptr = NULL;
long v = strtol(line, &eptr, 10);
if(eptr == NULL || !isspace(*eptr))
{
printf("Invalid input: %s", line);
continue;
}
/* Put desired processing code here. */
}
But when I input things like abcd to it, the loop would go forever and not stop for prompt.
That's because if scanf encounters a character that does not match the conversion specifier, it leaves it in the input stream. Basically, what's happening is that scanf reads the character a from the input stream, determines that it's not a valid match for the %d conversion specifier, and then pushes it back onto the input stream. The next time through the loop it does the same thing. And again. And again. And again.
fflush is not a good solution, because it isn't defined to work on input streams.
For the input "12ab", scanf will read and convert "12", leaving "ab" in the input stream.
The best solution is to read all your input as text, then convert to numeric types using strtol (for integral values) and strtod (for real values). For example:
char input[SIZE]; // assume SIZE is big enough for whatever input we get
int value;
if (fgets(input, sizeof input, stdin) != NULL)
{
char *chk;
int tmp = (int) strtol(input, &chk, 10);
if (isspace(*chk) || *chk == 0)
value = tmp;
else
printf("%s is not a valid integer string\n", input);
}
chk points to the first character in the input stream that isn't a decimal digit. If this character is not whitespace or the 0 terminator, then the input string wasn't a valid integer. This will detect and reject inputs like "12ab" as well as "abcd".
scanf is a good solution if you know your input is always going to be properly formed and well-behaved. If there's a chance that your input isn't well-behaved, use fgets and convert as needed.
I will suggest to get input as a string and check for non-numeric characters in it. If input is valid convert string to int by sscanf(str,"%d",&i); or else diplay error.
Just call scanf("%*[^\n]\n") inside the loop, and it will discard the "cache".
Call scanf("%*[^\n]\n") inside the loop. This should be enough to discard anything associated with the cache.